Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
voice_runtime_facade.hpp
Go to the documentation of this file.
1#pragma once
2
11
16
17#include <array>
18#include <cmath>
19#include <cstddef>
20#include <cstdint>
21#include <limits>
22#include <span>
23#include <type_traits>
24#include <utility>
25
26namespace pulp::audio {
27
39
45
51 int note = 60;
53 std::uint8_t velocity = 127;
54 bool gate = true;
57 float pressure = 0.0f;
58 float timbre = 0.0f;
59 float expression = 1.0f;
60};
61
74
76 public:
77 static constexpr std::uint32_t lane_count = 6;
78
83 [[nodiscard]] static VoiceNoteModulationResult
84 write(VoiceModulationBuffer& buffer, std::uint32_t frame_count,
85 const VoiceNoteModulationInput& input,
86 const VoiceNoteModulationRouting& routing = {}) noexcept {
87 const auto validation = validate_(buffer, frame_count, input, routing);
88 if (!validation.ok)
89 return validation;
90
91 const double pitch = static_cast<double>(input.note - input.reference_note) * 100.0 +
92 static_cast<double>(input.pitch_bend_normalized) *
93 static_cast<double>(input.bend_range_semitones) * 100.0;
94 const std::array<std::pair<VoiceModulationTarget, float>, lane_count> lanes{{
95 {routing.pitch, static_cast<float>(pitch)},
96 {routing.velocity, static_cast<float>(input.velocity) / 127.0f},
97 {routing.gate, input.gate ? 1.0f : 0.0f},
98 {routing.pressure, input.pressure},
99 {routing.timbre, input.timbre},
100 {routing.expression, input.expression},
101 }};
102
103 auto buffer_result = buffer.begin_block(frame_count);
104 if (!buffer_result.ok)
105 return buffer_failure_(buffer_result.status);
106 for (const auto& [target, value] : lanes) {
107 buffer_result = buffer.add_constant(target, value);
108 if (!buffer_result.ok) {
109 buffer.reset();
110 return buffer_failure_(buffer_result.status);
111 }
112 }
114 }
115
116 private:
117 static bool normalized_(float value) noexcept {
118 return std::isfinite(value) && value >= 0.0f && value <= 1.0f;
119 }
120
121 static bool target_valid_(VoiceModulationTarget target) noexcept {
122 return static_cast<std::uint8_t>(target) <=
123 static_cast<std::uint8_t>(VoiceModulationTarget::Aux7);
124 }
125
126 static VoiceNoteModulationResult fail_(VoiceNoteModulationStatus status) noexcept {
127 return {false, status, VoiceModulationStatus::Ok};
128 }
129
130 static VoiceNoteModulationResult buffer_failure_(VoiceModulationStatus status) noexcept {
131 return {false, VoiceNoteModulationStatus::BufferRejected, status};
132 }
133
134 static VoiceNoteModulationResult validate_(const VoiceModulationBuffer& buffer,
135 std::uint32_t frame_count,
136 const VoiceNoteModulationInput& input,
137 const VoiceNoteModulationRouting& routing) noexcept {
138 if (!buffer.prepared())
140 if (frame_count == 0 || frame_count > buffer.max_frames())
142 if (buffer.max_lanes() < lane_count)
144 if (input.note < 0 || input.note > 127 || input.reference_note < 0 ||
145 input.reference_note > 127 || input.velocity > 127)
147 if (!normalized_(input.pressure) || !normalized_(input.timbre) ||
148 !normalized_(input.expression))
150 if (!std::isfinite(input.pitch_bend_normalized) || input.pitch_bend_normalized < -1.0f ||
151 input.pitch_bend_normalized > 1.0f || !std::isfinite(input.bend_range_semitones) ||
152 input.bend_range_semitones < 0.0f)
154
155 const double pitch = static_cast<double>(input.note - input.reference_note) * 100.0 +
156 static_cast<double>(input.pitch_bend_normalized) *
157 static_cast<double>(input.bend_range_semitones) * 100.0;
158 if (!std::isfinite(pitch) ||
159 pitch < static_cast<double>(std::numeric_limits<float>::lowest()) ||
160 pitch > static_cast<double>(std::numeric_limits<float>::max()))
162
163 const std::array<VoiceModulationTarget, lane_count> targets{{
164 routing.pitch,
165 routing.velocity,
166 routing.gate,
167 routing.pressure,
168 routing.timbre,
169 routing.expression,
170 }};
171 for (std::size_t i = 0; i < targets.size(); ++i) {
172 if (!target_valid_(targets[i]))
174 for (std::size_t j = 0; j < i; ++j) {
175 if (targets[i] == targets[j])
177 }
178 }
180 }
181};
182
190
199
200namespace detail {
201template <typename> inline constexpr bool unsupported_voice_runtime_owner = false;
202}
203
206template <typename Owner> class VoiceRuntimeFacade {
208 "VoiceRuntimeFacade owner must be midi::Synthesiser<Voice> "
209 "or audio::InstrumentVoiceAllocator");
210};
211
216template <typename Voice> class VoiceRuntimeFacade<midi::Synthesiser<Voice>> {
217 public:
219
220 explicit VoiceRuntimeFacade(Owner& owner) noexcept : owner_(&owner) {}
221
222 Owner& owner() noexcept {
223 return *owner_;
224 }
225 const Owner& owner() const noexcept {
226 return *owner_;
227 }
228
229 std::size_t voice_count() const noexcept {
230 return owner_->polyphony();
231 }
232 std::size_t active_voice_count() const noexcept {
233 return owner_->active_count();
234 }
235 std::size_t releasing_voice_count() const noexcept {
236 return owner_->releasing_count();
237 }
239 return owner_->telemetry();
240 }
241
242 [[nodiscard]] bool set_steal_strategy(midi::VoiceStealStrategy strategy) noexcept {
243 if (strategy != midi::VoiceStealStrategy::Oldest &&
248 return false;
249 owner_->set_steal_strategy(strategy);
250 return true;
251 }
253 return owner_->steal_strategy();
254 }
255 [[nodiscard]] bool set_pitch_bend_range_semitones(float semitones) noexcept {
256 if (!std::isfinite(semitones) || semitones <= 0.0f)
257 return false;
258 owner_->set_pitch_bend_range_semitones(semitones);
259 return true;
260 }
261 float pitch_bend_range_semitones() const noexcept {
262 return owner_->pitch_bend_range_semitones();
263 }
264
265 void note_on(std::uint8_t channel, std::uint8_t note, std::uint8_t velocity,
266 int8_t priority = 0, std::uint8_t voice_group = 0, bool choke_group = false,
267 float choke_fade_ms = 0.0f) {
268 owner_->note_on(channel, note, velocity, priority, voice_group, choke_group, choke_fade_ms);
269 }
270 void note_off(std::uint8_t channel, std::uint8_t note) {
271 owner_->note_off(channel, note);
272 }
273 void control_change(std::uint8_t channel, std::uint8_t controller, std::uint8_t value) {
274 owner_->cc(channel, controller, value);
275 }
276 void pitch_bend(std::uint8_t channel, float semitones) {
277 owner_->pitch_bend(channel, semitones);
278 }
279 void aftertouch(std::uint8_t channel, float pressure) {
280 owner_->aftertouch(channel, pressure);
281 }
282
283 void process(const midi::MidiBuffer& events, float* output, int frame_count) {
284 owner_->process(events, output, frame_count);
285 }
286
287 void process(const midi::MidiBuffer& events, float* output, int frame_count,
288 const midi::SynthesiserNoteOnPolicy& note_on_policy) {
289 owner_->process(events, output, frame_count, note_on_policy);
290 }
291
292 template <typename PolicyFn>
293 requires std::is_invocable_r_v<midi::SynthesiserNoteOnPolicy, PolicyFn&, std::uint8_t,
294 std::uint8_t, std::uint8_t>
295 void process(const midi::MidiBuffer& events, float* output, int frame_count,
296 PolicyFn&& policy_for_note) {
297 owner_->process(events, output, frame_count, std::forward<PolicyFn>(policy_for_note));
298 }
299
300 Voice& voice(std::size_t index) noexcept {
301 return owner_->voice(index);
302 }
303 const Voice& voice(std::size_t index) const noexcept {
304 return owner_->voice(index);
305 }
306 void reset() {
307 owner_->reset();
308 }
309
310 private:
311 Owner* owner_;
312};
313
320 public:
322
323 explicit VoiceRuntimeFacade(Owner& owner) noexcept : owner_(&owner) {}
324
325 Owner& owner() noexcept {
326 return *owner_;
327 }
328 const Owner& owner() const noexcept {
329 return *owner_;
330 }
331
332 std::uint32_t voice_count() const noexcept {
333 return owner_->max_voices();
334 }
335 std::uint32_t active_voice_count() const noexcept {
336 return owner_->active_voice_count();
337 }
338 std::uint32_t allocated_voice_count() const noexcept {
339 return owner_->allocated_voice_count();
340 }
341
342 [[nodiscard]] bool set_steal_policy(VoiceStealPolicy policy) noexcept {
343 if (policy != VoiceStealPolicy::Oldest &&
345 return false;
346 owner_->set_steal_policy(policy);
347 return true;
348 }
350 return owner_->steal_policy();
351 }
352 void set_termination_fade_frames(std::uint32_t frames) noexcept {
353 owner_->set_termination_fade_frames(frames);
354 }
355
357 trigger(const InstrumentVoiceTrigger& trigger, std::span<VoiceTermination> terminations,
358 std::span<VoiceModulationBuffer> modulation) noexcept {
359 const auto voices = owner_->max_voices();
360 if (voices == 0)
362 if (terminations.size() != voices)
364 if (modulation.size() != voices)
366 for (const auto& buffer : modulation) {
367 if (!buffer.prepared())
369 }
370
371 const auto allocation = owner_->trigger(trigger, terminations);
372 if (!allocation.allocated)
373 return {VoiceRuntimeFacadeStatus::OwnerRejected, allocation};
374 for (std::uint32_t i = 0; i < allocation.termination_count; ++i)
375 modulation[terminations[i].voice_index].reset();
376 modulation[allocation.voice_index].reset();
377 return {VoiceRuntimeFacadeStatus::Ok, allocation};
378 }
379
380 [[nodiscard]] bool release_voice(std::uint32_t voice_index) noexcept {
381 return owner_->release_voice(voice_index);
382 }
383 [[nodiscard]] std::uint32_t release_note(int note,
384 std::uint32_t sample_id = kInvalidSampleId) noexcept {
385 return owner_->release_note(note, sample_id);
386 }
387 [[nodiscard]] bool finish_voice(std::uint32_t voice_index,
388 std::span<VoiceModulationBuffer> modulation) noexcept {
389 if (!modulation_valid_(modulation) || voice_index >= modulation.size())
390 return false;
391 if (!owner_->finish_voice(voice_index))
392 return false;
393 modulation[voice_index].reset();
394 return true;
395 }
396
397 std::span<const InstrumentVoice> voices() const noexcept {
398 return owner_->voices();
399 }
400
401 VoiceSumResult mix(std::span<const VoiceSumInput> inputs, BufferView<float> destination,
402 std::uint64_t frames, const VoiceSumOptions& options = {}) const noexcept {
403 return VoiceSumMixer::mix(inputs, destination, frames, options);
404 }
405
406 [[nodiscard]] VoiceRuntimeFacadeStatus
407 reset(std::span<VoiceModulationBuffer> modulation) noexcept {
408 if (owner_->max_voices() == 0)
410 if (!modulation_valid_(modulation))
412 owner_->reset();
413 for (auto& buffer : modulation)
414 buffer.reset();
416 }
417
418 private:
419 bool modulation_valid_(std::span<const VoiceModulationBuffer> modulation) const noexcept {
420 if (modulation.size() != owner_->max_voices())
421 return false;
422 for (const auto& buffer : modulation) {
423 if (!buffer.prepared())
424 return false;
425 }
426 return true;
427 }
428
429 Owner* owner_;
430};
431
432template <typename Owner> VoiceRuntimeFacade(Owner&) -> VoiceRuntimeFacade<Owner>;
433
434} // namespace pulp::audio
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Definition instrument_voice_allocator.hpp:68
Definition voice_modulation_buffer.hpp:78
Definition voice_runtime_facade.hpp:75
static constexpr std::uint32_t lane_count
Definition voice_runtime_facade.hpp:77
static VoiceNoteModulationResult write(VoiceModulationBuffer &buffer, std::uint32_t frame_count, const VoiceNoteModulationInput &input, const VoiceNoteModulationRouting &routing={}) noexcept
Replaces the current block with six constant lanes.
Definition voice_runtime_facade.hpp:84
Owner & owner() noexcept
Definition voice_runtime_facade.hpp:325
std::uint32_t voice_count() const noexcept
Definition voice_runtime_facade.hpp:332
std::uint32_t allocated_voice_count() const noexcept
Definition voice_runtime_facade.hpp:338
const Owner & owner() const noexcept
Definition voice_runtime_facade.hpp:328
std::span< const InstrumentVoice > voices() const noexcept
Definition voice_runtime_facade.hpp:397
InstrumentVoiceAllocator Owner
Definition voice_runtime_facade.hpp:321
bool release_voice(std::uint32_t voice_index) noexcept
Definition voice_runtime_facade.hpp:380
std::uint32_t active_voice_count() const noexcept
Definition voice_runtime_facade.hpp:335
AllocatedVoiceRuntimeTriggerResult trigger(const InstrumentVoiceTrigger &trigger, std::span< VoiceTermination > terminations, std::span< VoiceModulationBuffer > modulation) noexcept
Definition voice_runtime_facade.hpp:357
void set_termination_fade_frames(std::uint32_t frames) noexcept
Definition voice_runtime_facade.hpp:352
bool finish_voice(std::uint32_t voice_index, std::span< VoiceModulationBuffer > modulation) noexcept
Definition voice_runtime_facade.hpp:387
VoiceSumResult mix(std::span< const VoiceSumInput > inputs, BufferView< float > destination, std::uint64_t frames, const VoiceSumOptions &options={}) const noexcept
Definition voice_runtime_facade.hpp:401
VoiceStealPolicy steal_policy() const noexcept
Definition voice_runtime_facade.hpp:349
bool set_steal_policy(VoiceStealPolicy policy) noexcept
Definition voice_runtime_facade.hpp:342
VoiceRuntimeFacadeStatus reset(std::span< VoiceModulationBuffer > modulation) noexcept
Definition voice_runtime_facade.hpp:407
std::uint32_t release_note(int note, std::uint32_t sample_id=kInvalidSampleId) noexcept
Definition voice_runtime_facade.hpp:383
VoiceRuntimeFacade(Owner &owner) noexcept
Definition voice_runtime_facade.hpp:323
void pitch_bend(std::uint8_t channel, float semitones)
Definition voice_runtime_facade.hpp:276
float pitch_bend_range_semitones() const noexcept
Definition voice_runtime_facade.hpp:261
midi::Synthesiser< Voice > Owner
Definition voice_runtime_facade.hpp:218
void control_change(std::uint8_t channel, std::uint8_t controller, std::uint8_t value)
Definition voice_runtime_facade.hpp:273
std::size_t voice_count() const noexcept
Definition voice_runtime_facade.hpp:229
Owner & owner() noexcept
Definition voice_runtime_facade.hpp:222
std::size_t releasing_voice_count() const noexcept
Definition voice_runtime_facade.hpp:235
bool set_pitch_bend_range_semitones(float semitones) noexcept
Definition voice_runtime_facade.hpp:255
void process(const midi::MidiBuffer &events, float *output, int frame_count, PolicyFn &&policy_for_note)
Definition voice_runtime_facade.hpp:295
VoiceRuntimeFacade(Owner &owner) noexcept
Definition voice_runtime_facade.hpp:220
void aftertouch(std::uint8_t channel, float pressure)
Definition voice_runtime_facade.hpp:279
const Voice & voice(std::size_t index) const noexcept
Definition voice_runtime_facade.hpp:303
std::size_t active_voice_count() const noexcept
Definition voice_runtime_facade.hpp:232
void process(const midi::MidiBuffer &events, float *output, int frame_count, const midi::SynthesiserNoteOnPolicy &note_on_policy)
Definition voice_runtime_facade.hpp:287
void note_on(std::uint8_t channel, std::uint8_t note, std::uint8_t velocity, int8_t priority=0, std::uint8_t voice_group=0, bool choke_group=false, float choke_fade_ms=0.0f)
Definition voice_runtime_facade.hpp:265
void process(const midi::MidiBuffer &events, float *output, int frame_count)
Definition voice_runtime_facade.hpp:283
midi::VoiceStealStrategy steal_strategy() const noexcept
Definition voice_runtime_facade.hpp:252
void note_off(std::uint8_t channel, std::uint8_t note)
Definition voice_runtime_facade.hpp:270
void reset()
Definition voice_runtime_facade.hpp:306
const Owner & owner() const noexcept
Definition voice_runtime_facade.hpp:225
midi::SynthesiserTelemetry telemetry() const
Definition voice_runtime_facade.hpp:238
Voice & voice(std::size_t index) noexcept
Definition voice_runtime_facade.hpp:300
bool set_steal_strategy(midi::VoiceStealStrategy strategy) noexcept
Definition voice_runtime_facade.hpp:242
Primary template intentionally rejects unsupported owners.
Definition voice_runtime_facade.hpp:206
static VoiceSumResult mix(std::span< const VoiceSumInput > inputs, BufferView< float > destination, std::uint64_t frames, const VoiceSumOptions &options={}) noexcept
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
Polyphonic synth that routes plain-MIDI events into a pool of Voice instances.
Definition synthesiser.hpp:229
constexpr bool unsupported_voice_runtime_owner
Definition voice_runtime_facade.hpp:201
Definition analyzer_provider.hpp:12
VoiceStealPolicy
Definition instrument_voice_allocator.hpp:12
@ Oldest
Definition instrument_voice_allocator.hpp:13
@ PreferSameVoiceGroupOldest
Definition instrument_voice_allocator.hpp:14
VoiceRuntimeFacade(Owner &) -> VoiceRuntimeFacade< Owner >
@ NotPrepared
Definition sample_heritage_bus_dsp.hpp:21
@ Ok
Definition loop_types.hpp:34
VoiceNoteModulationStatus
Definition voice_runtime_facade.hpp:28
@ InvalidNote
Definition voice_runtime_facade.hpp:32
@ NotPrepared
Definition voice_runtime_facade.hpp:30
@ BufferRejected
Definition voice_runtime_facade.hpp:37
@ InvalidRouting
Definition voice_runtime_facade.hpp:35
@ InvalidFrameCount
Definition voice_runtime_facade.hpp:31
@ Ok
Definition voice_runtime_facade.hpp:29
@ InvalidBendRange
Definition voice_runtime_facade.hpp:34
@ InvalidController
Definition voice_runtime_facade.hpp:33
@ InsufficientLanes
Definition voice_runtime_facade.hpp:36
constexpr std::uint32_t kInvalidSampleId
Definition sample_identity.hpp:8
@ Voice
Definition sample_heritage_schema.hpp:35
VoiceModulationTarget
Definition voice_modulation_buffer.hpp:9
@ PitchCents
Definition voice_modulation_buffer.hpp:11
@ Gain
Definition voice_modulation_buffer.hpp:10
@ Aux7
Definition voice_modulation_buffer.hpp:22
@ Aux1
Definition voice_modulation_buffer.hpp:16
@ Pressure
Definition voice_modulation_buffer.hpp:13
@ Aux0
Definition voice_modulation_buffer.hpp:15
@ Timbre
Definition voice_modulation_buffer.hpp:14
VoiceRuntimeFacadeStatus
Definition voice_runtime_facade.hpp:183
@ OwnerRejected
Definition voice_runtime_facade.hpp:188
@ InvalidModulationCapacity
Definition voice_runtime_facade.hpp:186
@ OwnerNotPrepared
Definition voice_runtime_facade.hpp:185
@ Ok
Definition voice_runtime_facade.hpp:184
@ InvalidTerminationCapacity
Definition voice_runtime_facade.hpp:187
VoiceModulationStatus
Definition voice_modulation_buffer.hpp:30
@ NotPrepared
Definition voice_modulation_buffer.hpp:32
@ InvalidFrameCount
Definition voice_modulation_buffer.hpp:33
@ Ok
Definition voice_modulation_buffer.hpp:31
Definition child_process_manager.hpp:18
Definition custom_node_events.hpp:26
VoiceStealStrategy
Voice-stealing strategy applied when note_on fires and no free voice is available.
Definition synthesiser.hpp:54
@ Oldest
Steal the oldest active voice (smallest note_id).
Definition synthesiser.hpp:56
@ Priority
Steal the voice with the lowest priority.
Definition synthesiser.hpp:68
@ Highest
Steal the voice with the highest MIDI note number.
Definition synthesiser.hpp:65
@ Quietest
Steal the voice currently reporting the lowest peak_level().
Definition synthesiser.hpp:61
@ Lowest
Steal the voice with the lowest MIDI note number.
Definition synthesiser.hpp:63
@ buffer
A borrowed static buffer with a traversable playhead.
Definition granular.hpp:110
@ i
Definition formant_filter_bank.hpp:20
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ j
Definition input_events.hpp:40
Definition voice_runtime_facade.hpp:191
VoiceAllocationResult allocation
Definition voice_runtime_facade.hpp:193
VoiceRuntimeFacadeStatus status
Definition voice_runtime_facade.hpp:192
bool ok() const noexcept
Definition voice_runtime_facade.hpp:195
Definition instrument_voice_allocator.hpp:47
Definition instrument_voice_allocator.hpp:56
Note/controller state translated to constant per-voice modulation lanes.
Definition voice_runtime_facade.hpp:50
int reference_note
Definition voice_runtime_facade.hpp:52
float timbre
Definition voice_runtime_facade.hpp:58
std::uint8_t velocity
Definition voice_runtime_facade.hpp:53
float bend_range_semitones
Definition voice_runtime_facade.hpp:56
int note
Definition voice_runtime_facade.hpp:51
bool gate
Definition voice_runtime_facade.hpp:54
float pressure
Definition voice_runtime_facade.hpp:57
float pitch_bend_normalized
Definition voice_runtime_facade.hpp:55
float expression
Definition voice_runtime_facade.hpp:59
Definition voice_runtime_facade.hpp:40
VoiceNoteModulationStatus status
Definition voice_runtime_facade.hpp:42
bool ok
Definition voice_runtime_facade.hpp:41
VoiceModulationStatus buffer_status
Definition voice_runtime_facade.hpp:43
Explicit mapping from note semantics to VoiceModulationBuffer targets.
Definition voice_runtime_facade.hpp:66
VoiceModulationTarget timbre
Definition voice_runtime_facade.hpp:71
VoiceModulationTarget pitch
Definition voice_runtime_facade.hpp:67
VoiceModulationTarget pressure
Definition voice_runtime_facade.hpp:70
VoiceModulationTarget expression
Definition voice_runtime_facade.hpp:72
VoiceModulationTarget gate
Definition voice_runtime_facade.hpp:69
VoiceModulationTarget velocity
Definition voice_runtime_facade.hpp:68
Definition voice_sum_mixer.hpp:16
Definition voice_sum_mixer.hpp:20
Constant note-on metadata for MIDI-buffer dispatch.
Definition synthesiser.hpp:219
Definition synthesiser.hpp:71
Generic MIDI-1 polyphonic synth framework.