Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
voice_modulation_sources.hpp
Go to the documentation of this file.
1#pragma once
2
33
36#include <pulp/signal/lfo.hpp>
37
38#include <array>
39#include <cmath>
40#include <cstdint>
41#include <span>
42#include <vector>
43
44namespace pulp::audio {
45
47enum class VoiceLfoPhasePolicy : std::uint8_t {
54};
55
57 bool enabled = false;
58 pulp::signal::Lfo::Wave wave = pulp::signal::Lfo::Wave::sine;
61 double rate_hz = 5.0;
63 float depth = 1.0f;
66 bool unison_phase_spread = false;
68 std::uint32_t seed = 1;
69};
70
77
86
93template <std::size_t MaximumVoices>
95 static_assert(MaximumVoices > 0, "VoiceModulationSources needs at least one voice");
96 static_assert(MaximumVoices <= 256, "VoiceModulationSources bounds voice count at 256");
97 static constexpr std::uint32_t kMaxFrames = 1u << 20;
98
99public:
101
106 if (!config_valid(config))
107 return false;
108 try {
109 scratch_.resize(config.max_frames);
110 } catch (...) {
111 return false;
112 }
113 config_ = config;
114 config_.envelope.envelope.sample_rate = config.sample_rate;
115 for (std::size_t i = 0; i < MaximumVoices; ++i) {
116 auto& lfo = lfos_[i];
117 lfo.set_mode(config.lfo.phase_policy == VoiceLfoPhasePolicy::FreeRunning
118 ? pulp::signal::Lfo::Mode::free
119 : pulp::signal::Lfo::Mode::retrig);
120 lfo.set_wave(config.lfo.wave);
121 lfo.set_rate_hz(config.lfo.rate_hz);
122 lfo.set_seed(config.lfo.seed + static_cast<std::uint32_t>(i));
123 lfo.set_phase_offset(config.lfo.unison_phase_spread
124 ? static_cast<double>(i) / static_cast<double>(MaximumVoices)
125 : 0.0);
126 lfo.prepare(config.sample_rate);
127 if (config.envelope.enabled && !envelopes_[i].prepare(config_.envelope.envelope))
128 return false;
129 }
130 prepared_ = true;
131 return true;
132 }
133
135 void release() noexcept {
136 scratch_.clear();
137 scratch_.shrink_to_fit();
138 prepared_ = false;
139 }
140
142 void reset() noexcept {
143 for (std::size_t i = 0; i < MaximumVoices; ++i) {
144 lfos_[i].reset();
145 envelopes_[i].reset();
146 }
147 }
148
149 bool prepared() const noexcept { return prepared_; }
150 const VoiceModulationSourcesConfig& config() const noexcept { return config_; }
151
167 bool set_lfo_rate_hz(double rate_hz) noexcept {
168 if (!prepared() || !std::isfinite(rate_hz) || !(rate_hz > 0.0))
169 return false;
170 config_.lfo.rate_hz = rate_hz;
171 for (auto& lfo : lfos_)
172 lfo.set_rate_hz(rate_hz);
173 return true;
174 }
175
185 bool set_lfo_depth(float depth) noexcept {
186 if (!prepared() || !std::isfinite(depth))
187 return false;
188 config_.lfo.depth = depth;
189 return true;
190 }
191
203 if (!prepared())
204 return;
205 config_.lfo.wave = wave;
206 for (auto& lfo : lfos_)
207 lfo.set_wave(wave);
208 }
209
212 void note_on(std::size_t voice_index) noexcept {
213 if (voice_index >= MaximumVoices)
214 return;
215 lfos_[voice_index].retrigger();
216 envelopes_[voice_index].note_on();
217 }
218
220 void note_off(std::size_t voice_index) noexcept {
221 if (voice_index >= MaximumVoices)
222 return;
223 envelopes_[voice_index].note_off();
224 }
225
229 VoiceModulationResult append_lfo_lane(std::size_t voice_index,
230 VoiceModulationBuffer& destination,
233 std::uint32_t frame_count) noexcept {
234 if (voice_index >= MaximumVoices)
236 if (!prepared_)
238 if (!destination.prepared())
240 if (!block_bounds_valid(destination, frame_count))
242 if (!config_.lfo.enabled)
244 auto& lfo = lfos_[voice_index];
245 const float depth = config_.lfo.depth;
246 if (rate == VoiceModulationRate::AudioRate) {
247 auto reservation = destination.reserve_audio_rate(target);
248 if (!reservation.ok)
249 return {false, reservation.status};
250 for (std::uint32_t frame = 0; frame < frame_count; ++frame)
251 reservation.values[frame] = depth * lfo.next();
252 return {true, VoiceModulationStatus::Ok};
253 }
254 for (std::uint32_t frame = 0; frame < frame_count; ++frame)
255 scratch_[frame] = depth * lfo.next();
256 return destination.add_constant(target, scratch_[0]);
257 }
258
263 VoiceModulationBuffer& destination,
266 std::uint32_t frame_count) noexcept {
267 if (voice_index >= MaximumVoices)
269 if (!prepared_)
271 if (!destination.prepared())
273 if (!block_bounds_valid(destination, frame_count))
275 if (!config_.envelope.enabled)
277 auto& envelope = envelopes_[voice_index];
278 if (rate == VoiceModulationRate::AudioRate) {
279 auto reservation = destination.reserve_audio_rate(target);
280 if (!reservation.ok)
281 return {false, reservation.status};
282 envelope.render(std::span<float>(reservation.values, frame_count));
283 return {true, VoiceModulationStatus::Ok};
284 }
285 envelope.render(std::span<float>(scratch_.data(), frame_count));
286 return destination.add_constant(target, scratch_[0]);
287 }
288
293 VoiceModulationResult write_voice(std::size_t voice_index,
294 VoiceModulationBuffer& destination,
295 VoiceModulationTarget lfo_target,
296 VoiceModulationTarget envelope_target,
298 std::uint32_t frame_count) noexcept {
299 if (voice_index >= MaximumVoices)
301 if (!prepared_)
303 if (!destination.prepared())
305 if (!block_bounds_valid(destination, frame_count))
307 if (!config_.lfo.enabled || !config_.envelope.enabled)
309 if (lfo_target == envelope_target)
311 if (destination.max_lanes() < 2)
313 auto result = destination.begin_block(frame_count);
314 if (!result.ok)
315 return result;
316 result = append_lfo_lane(voice_index, destination, lfo_target, rate, frame_count);
317 if (!result.ok)
318 return result;
319 return append_envelope_lane(voice_index, destination, envelope_target, rate, frame_count);
320 }
321
322private:
323 static bool config_valid(const VoiceModulationSourcesConfig& config) noexcept {
324 if (!(config.sample_rate > 0.0) || !std::isfinite(config.sample_rate))
325 return false;
326 if (config.max_frames == 0 || config.max_frames > kMaxFrames)
327 return false;
328 if (!std::isfinite(config.lfo.rate_hz) || !(config.lfo.rate_hz > 0.0))
329 return false;
330 if (!std::isfinite(config.lfo.depth))
331 return false;
332 return true;
333 }
334
335 static bool block_bounds_valid(const VoiceModulationBuffer& destination,
336 std::uint32_t frame_count) noexcept {
337 return frame_count != 0 && frame_count <= destination.max_frames();
338 }
339
340 std::array<pulp::signal::Lfo, MaximumVoices> lfos_;
341 std::array<AhdsrEnvelope, MaximumVoices> envelopes_;
342 std::vector<float> scratch_;
343 VoiceModulationSourcesConfig config_{};
344 bool prepared_ = false;
345};
346
347} // namespace pulp::audio
Definition voice_modulation_buffer.hpp:78
const VoiceModulationSourcesConfig & config() const noexcept
Definition voice_modulation_sources.hpp:150
VoiceModulationResult write_voice(std::size_t voice_index, VoiceModulationBuffer &destination, VoiceModulationTarget lfo_target, VoiceModulationTarget envelope_target, VoiceModulationRate rate, std::uint32_t frame_count) noexcept
RT-safe after prepare.
Definition voice_modulation_sources.hpp:293
void note_on(std::size_t voice_index) noexcept
RT-safe after prepare.
Definition voice_modulation_sources.hpp:212
void reset() noexcept
RT-safe after prepare. Resets every voice's sources to a fresh start.
Definition voice_modulation_sources.hpp:142
void release() noexcept
Control/background-thread only. Invalidates prepared storage.
Definition voice_modulation_sources.hpp:135
bool set_lfo_rate_hz(double rate_hz) noexcept
RT-safe after prepare.
Definition voice_modulation_sources.hpp:167
bool prepared() const noexcept
Definition voice_modulation_sources.hpp:149
VoiceModulationResult append_lfo_lane(std::size_t voice_index, VoiceModulationBuffer &destination, VoiceModulationTarget target, VoiceModulationRate rate, std::uint32_t frame_count) noexcept
RT-safe after prepare.
Definition voice_modulation_sources.hpp:229
VoiceModulationResult append_envelope_lane(std::size_t voice_index, VoiceModulationBuffer &destination, VoiceModulationTarget target, VoiceModulationRate rate, std::uint32_t frame_count) noexcept
RT-safe after prepare.
Definition voice_modulation_sources.hpp:262
bool prepare(const VoiceModulationSourcesConfig &config) noexcept
Control/background-thread only.
Definition voice_modulation_sources.hpp:105
void note_off(std::size_t voice_index) noexcept
RT-safe after prepare. Releases that voice's envelope.
Definition voice_modulation_sources.hpp:220
void set_lfo_wave(pulp::signal::Lfo::Wave wave) noexcept
RT-safe after prepare.
Definition voice_modulation_sources.hpp:202
bool set_lfo_depth(float depth) noexcept
RT-safe after prepare.
Definition voice_modulation_sources.hpp:185
Wave
Definition lfo.hpp:79
The house low-frequency oscillator: seven waveforms plus a continuous shape morph,...
Definition analyzer_provider.hpp:12
VoiceLfoPhasePolicy
LFO phase policy on note-on.
Definition voice_modulation_sources.hpp:47
@ FreeRunning
The LFO runs from reset() and ignores note-on.
Definition voice_modulation_sources.hpp:50
@ Retrigger
Note-on restarts phase and the lifecycle for that voice.
Definition voice_modulation_sources.hpp:53
VoiceModulationRate
Definition voice_modulation_buffer.hpp:25
@ AudioRate
Definition voice_modulation_buffer.hpp:27
VoiceModulationTarget
Definition voice_modulation_buffer.hpp:9
@ NotPrepared
Definition voice_modulation_buffer.hpp:32
@ InvalidTarget
Definition voice_modulation_buffer.hpp:34
@ LaneOverflow
Definition voice_modulation_buffer.hpp:37
@ InvalidFrameCount
Definition voice_modulation_buffer.hpp:33
@ DuplicateTarget
Definition voice_modulation_buffer.hpp:36
@ Ok
Definition voice_modulation_buffer.hpp:31
Definition instrument_envelope.hpp:17
Definition voice_modulation_sources.hpp:71
bool enabled
Definition voice_modulation_sources.hpp:72
AhdsrEnvelopeConfig envelope
AHDSR shape.
Definition voice_modulation_sources.hpp:75
Definition voice_modulation_sources.hpp:56
double rate_hz
Free-running rate in Hz; clamped into the shipped LFO's legal range.
Definition voice_modulation_sources.hpp:61
std::uint32_t seed
Base seed; voice i uses seed + i.
Definition voice_modulation_sources.hpp:68
float depth
Bipolar output scale. Negative values invert phase. Must be finite.
Definition voice_modulation_sources.hpp:63
bool unison_phase_spread
Spread unison voices evenly around the cycle: voice i starts at phase offset i / MaximumVoices cycles...
Definition voice_modulation_sources.hpp:66
pulp::signal::Lfo::Wave wave
Definition voice_modulation_sources.hpp:58
VoiceLfoPhasePolicy phase_policy
Definition voice_modulation_sources.hpp:59
bool enabled
Definition voice_modulation_sources.hpp:57
Definition voice_modulation_buffer.hpp:40
Definition voice_modulation_sources.hpp:78
VoiceEnvelopeSourceSpec envelope
Definition voice_modulation_sources.hpp:84
double sample_rate
Definition voice_modulation_sources.hpp:79
std::uint32_t max_frames
Per-block frame capacity; bounds the audio-rate lanes this bank can render and its constant-rate scra...
Definition voice_modulation_sources.hpp:82
VoiceLfoSourceSpec lfo
Definition voice_modulation_sources.hpp:83