Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
synthesiser.hpp
Go to the documentation of this file.
1#pragma once
2
20
21#include <pulp/midi/buffer.hpp>
22#include <pulp/midi/message.hpp>
24
25#include <algorithm>
26#include <array>
27#include <atomic>
28#include <cstddef>
29#include <cstdint>
30#include <limits>
31#include <type_traits>
32#include <vector>
33
34namespace pulp::midi {
35
39 uint8_t channel = 0;
40 uint8_t note = 0;
41 uint8_t velocity = 0;
42 int8_t priority = 0;
43 uint8_t voice_group = 0;
44 uint32_t note_id = 0;
45 bool active = false;
46 bool releasing = false;
47 bool sustained = false;
48 bool sostenuto = false;
49 bool soft_pedal = false;
50};
51
70
78
89
98public:
99 virtual ~SynthesiserVoice() = default;
100
105 virtual void on_note_on(const SynthesiserNote& note) {
106 note_ = note;
107 active_ = true;
108 releasing_ = false;
109 note_.sustained = false;
110 note_.sostenuto = false;
111 }
112
118 virtual void on_note_off() {
119 releasing_ = true;
120 note_.releasing = true;
121 note_.sustained = false;
122 note_.sostenuto = false;
123 }
124
128 virtual void on_choke(float fade_ms) {
129 (void)fade_ms;
130 on_note_off();
131 }
132
135 virtual void on_pitch_bend(float bend_semitones) { (void)bend_semitones; }
136
138 virtual void on_aftertouch(float pressure) { (void)pressure; }
139
141 virtual void on_cc(uint8_t cc_number, uint8_t value) {
142 (void)cc_number; (void)value;
143 }
144
149 virtual void render(float* out, int num_samples) = 0;
150
155 virtual float peak_level() const { return 0.0f; }
156
159 virtual void reset() {
160 note_ = {};
161 active_ = false;
162 releasing_ = false;
163 }
164
165 bool active() const { return active_; }
166 bool releasing() const { return releasing_; }
167 const SynthesiserNote& note() const { return note_; }
168
173 if (!active_ || releasing_) return;
174 note_.sustained = true;
175 }
176
179 if (!active_ || releasing_ || !note_.sustained) return;
180 note_.sustained = false;
181 if (!note_.sostenuto) on_note_off();
182 }
183
187 if (!active_ || releasing_) return;
188 note_.sostenuto = true;
189 }
190
193 if (!active_ || releasing_ || !note_.sostenuto) return;
194 note_.sostenuto = false;
195 if (!note_.sustained) on_note_off();
196 }
197
200 void set_soft_pedal(bool down) {
201 if (!active_ || releasing_) return;
202 note_.soft_pedal = down;
203 }
204
205protected:
209 void mark_inactive() { active_ = false; releasing_ = false; }
210
212 bool active_ = false;
213 bool releasing_ = false;
214};
215
220 uint8_t voice_group = 0;
221 bool choke_group = false;
222 float choke_fade_ms = 0.0f;
223 int8_t priority = 0;
224};
225
228template <typename Voice>
230 static_assert(std::is_base_of<SynthesiserVoice, Voice>::value,
231 "Voice must derive from SynthesiserVoice");
232
233public:
236 explicit Synthesiser(std::size_t polyphony = 16)
237 : voices_(polyphony) {}
238
239 void set_steal_strategy(VoiceStealStrategy s) { strategy_ = s; }
240 VoiceStealStrategy steal_strategy() const { return strategy_; }
241
247 if (r > 0.0f) bend_range_semi_ = r;
248 }
249 float pitch_bend_range_semitones() const { return bend_range_semi_; }
250
253 void note_on(uint8_t channel, uint8_t note, uint8_t velocity,
254 int8_t priority = 0, uint8_t voice_group = 0,
255 bool choke_group = false, float choke_fade_ms = 0.0f) {
256 if (velocity == 0) { note_off(channel, note); return; }
257 const auto masked_channel = static_cast<uint8_t>(channel & 0x0F);
258 const auto masked_group = static_cast<uint8_t>(voice_group & 0x7F);
259 if (choke_group && masked_group != 0) {
260 choke_voice_group(masked_channel, masked_group, choke_fade_ms);
261 }
262 Voice* v = find_free_voice();
263 if (!v) v = steal_voice();
264 if (!v) return;
266 n.channel = masked_channel;
267 n.note = static_cast<uint8_t>(note & 0x7F);
268 n.velocity = static_cast<uint8_t>(velocity & 0x7F);
269 n.priority = priority;
270 n.voice_group = masked_group;
271 n.note_id = ++next_id_;
272 n.active = true;
273 n.releasing = false;
274 n.soft_pedal = soft_pedal_down_[n.channel];
275 v->on_note_on(n);
276 }
277
278 void note_off(uint8_t channel, uint8_t note) {
279 const auto masked_channel = static_cast<uint8_t>(channel & 0x0F);
280 for (auto& v : voices_) {
281 if (v.active() && !v.releasing()
282 && v.note().channel == masked_channel
283 && v.note().note == (note & 0x7F)) {
284 bool deferred = false;
285 if (sustain_down_[masked_channel]) {
286 v.defer_note_off_for_sustain();
287 deferred = true;
288 }
289 if (v.note().sostenuto) {
290 deferred = true;
291 }
292 if (!deferred) {
293 v.on_note_off();
294 }
295 }
296 }
297 }
298
299 void pitch_bend(uint8_t channel, float bend_semitones) {
300 for (auto& v : voices_) {
301 if (v.active() && v.note().channel == (channel & 0x0F)) {
302 v.on_pitch_bend(bend_semitones);
303 }
304 }
305 }
306
307 void aftertouch(uint8_t channel, float pressure) {
308 for (auto& v : voices_) {
309 if (v.active() && v.note().channel == (channel & 0x0F)) {
310 v.on_aftertouch(pressure);
311 }
312 }
313 }
314
315 void cc(uint8_t channel, uint8_t cc_number, uint8_t value) {
316 const auto masked_channel = static_cast<uint8_t>(channel & 0x0F);
317 for (auto& v : voices_) {
318 if (v.active() && v.note().channel == masked_channel) {
319 v.on_cc(cc_number, value);
320 }
321 }
322 if ((cc_number & 0x7F) == 64) {
323 const bool was_down = sustain_down_[masked_channel];
324 const bool is_down = value >= 64;
325 sustain_down_[masked_channel] = is_down;
326 if (was_down && !is_down) {
327 release_sustained_notes(masked_channel);
328 }
329 } else if ((cc_number & 0x7F) == 66) {
330 const bool was_down = sostenuto_down_[masked_channel];
331 const bool is_down = value >= 64;
332 sostenuto_down_[masked_channel] = is_down;
333 if (!was_down && is_down) {
334 capture_sostenuto_notes(masked_channel);
335 } else if (was_down && !is_down) {
336 release_sostenuto_notes(masked_channel);
337 }
338 } else if ((cc_number & 0x7F) == 67) {
339 const bool is_down = value >= 64;
340 soft_pedal_down_[masked_channel] = is_down;
341 set_soft_pedal_notes(masked_channel, is_down);
342 }
343 }
344
350 void process(const MidiBuffer& events, float* out, int num_samples) {
351 process(events, out, num_samples, {});
352 }
353
356 void process(const MidiBuffer& events, float* out, int num_samples,
357 const SynthesiserNoteOnPolicy& note_on_policy) {
358 auto constant = [&note_on_policy](uint8_t, uint8_t, uint8_t) {
359 return note_on_policy;
360 };
361 render_block(events, out, num_samples, constant);
362 }
363
377 template <typename PolicyFn>
378 requires std::is_invocable_r_v<SynthesiserNoteOnPolicy, PolicyFn&,
379 uint8_t, uint8_t, uint8_t>
380 void process(const MidiBuffer& events, float* out, int num_samples,
381 PolicyFn&& policy_for_note) {
382 render_block(events, out, num_samples, policy_for_note);
383 }
384
385 std::size_t polyphony() const { return voices_.size(); }
386 Voice& voice(std::size_t i) { return voices_[i]; }
387 const Voice& voice(std::size_t i) const { return voices_[i]; }
388
389 std::size_t active_count() const {
390 std::size_t n = 0;
391 for (const auto& v : voices_) if (v.active()) ++n;
392 return n;
393 }
394
395 std::size_t releasing_count() const {
396 std::size_t n = 0;
397 for (const auto& v : voices_) if (v.active() && v.releasing()) ++n;
398 return n;
399 }
400
401 std::uint64_t steal_count() const {
402 return steal_count_.load(std::memory_order_relaxed);
403 }
404
406 steal_count_.store(0, std::memory_order_relaxed);
407 }
408
414 return {
415 .polyphony = polyphony(),
416 .active_voice_count = active_count(),
417 .releasing_voice_count = releasing_count(),
418 .steal_count = steal_count(),
419 .steal_strategy = strategy_,
420 };
421 }
422
423 std::uint64_t estimate_optional_runtime_cost() const {
424 const auto t = telemetry();
425 std::uint64_t cost = 0;
426 cost = saturating_add_u64_(
427 cost, saturating_mul_u64_(
428 static_cast<std::uint64_t>(t.polyphony), 4));
429 cost = saturating_add_u64_(
430 cost, saturating_mul_u64_(
431 static_cast<std::uint64_t>(t.active_voice_count), 64));
432 cost = saturating_add_u64_(
433 cost, saturating_mul_u64_(
434 static_cast<std::uint64_t>(t.releasing_voice_count), 32));
435 return cost;
436 }
437
441 bool required = false) const {
442 const auto t = telemetry();
443 const auto cost = estimate_optional_runtime_cost();
444 const auto decision = frame.evaluate(lane, cost, required);
445 return {
446 .decision = decision,
447 .frame_stats = frame.stats(),
448 .telemetry = t,
449 .estimated_cost = cost,
450 };
451 }
452
457 void panic(bool immediate) {
458 if (immediate) {
459 reset();
460 return;
461 }
462 sustain_down_.fill(false);
463 sostenuto_down_.fill(false);
464 for (auto& v : voices_) {
465 if (v.active() && !v.releasing())
466 v.on_note_off();
467 }
468 }
469
470 void reset() {
471 for (auto& v : voices_) v.reset();
472 sustain_down_.fill(false);
473 sostenuto_down_.fill(false);
474 soft_pedal_down_.fill(false);
475 next_id_ = 0;
476 }
477
478private:
479 static std::uint64_t saturating_add_u64_(std::uint64_t a,
480 std::uint64_t b) {
481 const auto max = std::numeric_limits<std::uint64_t>::max();
482 return b > max - a ? max : a + b;
483 }
484
485 static std::uint64_t saturating_mul_u64_(std::uint64_t a,
486 std::uint64_t b) {
487 const auto max = std::numeric_limits<std::uint64_t>::max();
488 if (a == 0 || b == 0) return 0;
489 return a > max / b ? max : a * b;
490 }
491
492 Voice* find_free_voice() {
493 for (auto& v : voices_) if (!v.active()) return &v;
494 return nullptr;
495 }
496
497 Voice* steal_voice() {
498 if (voices_.empty()) return nullptr;
499 Voice* chosen = nullptr;
500 switch (strategy_) {
502 uint32_t oldest_id = std::numeric_limits<uint32_t>::max();
503 for (auto& v : voices_) {
504 if (v.active() && v.note().note_id < oldest_id) {
505 chosen = &v;
506 oldest_id = v.note().note_id;
507 }
508 }
509 break;
510 }
512 float min_level = std::numeric_limits<float>::infinity();
513 uint32_t tie_id = std::numeric_limits<uint32_t>::max();
514 for (auto& v : voices_) {
515 if (!v.active()) continue;
516 const float lvl = v.peak_level();
517 if (lvl < min_level
518 || (lvl == min_level && v.note().note_id < tie_id)) {
519 chosen = &v;
520 min_level = lvl;
521 tie_id = v.note().note_id;
522 }
523 }
524 break;
525 }
527 uint8_t lowest = 127;
528 uint32_t tie_id = std::numeric_limits<uint32_t>::max();
529 for (auto& v : voices_) {
530 if (!v.active()) continue;
531 const uint8_t n = v.note().note;
532 if (n < lowest || (n == lowest && v.note().note_id < tie_id)) {
533 chosen = &v;
534 lowest = n;
535 tie_id = v.note().note_id;
536 }
537 }
538 break;
539 }
541 int highest = -1;
542 uint32_t tie_id = std::numeric_limits<uint32_t>::max();
543 for (auto& v : voices_) {
544 if (!v.active()) continue;
545 const int n = v.note().note;
546 if (n > highest
547 || (n == highest && v.note().note_id < tie_id)) {
548 chosen = &v;
549 highest = n;
550 tie_id = v.note().note_id;
551 }
552 }
553 break;
554 }
556 int lowest_prio = std::numeric_limits<int>::max();
557 uint32_t tie_id = std::numeric_limits<uint32_t>::max();
558 for (auto& v : voices_) {
559 if (!v.active()) continue;
560 const int p = v.note().priority;
561 if (p < lowest_prio
562 || (p == lowest_prio && v.note().note_id < tie_id)) {
563 chosen = &v;
564 lowest_prio = p;
565 tie_id = v.note().note_id;
566 }
567 }
568 break;
569 }
570 }
571 if (chosen) {
572 steal_count_.fetch_add(1, std::memory_order_relaxed);
573 chosen->reset();
574 }
575 return chosen;
576 }
577
581 template <typename PolicyFn>
582 void render_block(const MidiBuffer& events, float* out, int num_samples,
583 PolicyFn& policy_for_note) {
584 if (num_samples <= 0) return;
585 int sample_index = 0;
586 for (std::size_t i = 0; i < events.size(); ++i) {
587 const auto& e = events[i];
588 int target = static_cast<int>(e.sample_offset);
589 if (target < sample_index) target = sample_index;
590 if (target > num_samples) target = num_samples;
591 if (target > sample_index) {
592 render_active(out + sample_index, target - sample_index);
593 sample_index = target;
594 }
595 dispatch_event(e, policy_for_note);
596 }
597 if (sample_index < num_samples) {
598 render_active(out + sample_index, num_samples - sample_index);
599 }
600 }
601
602 template <typename PolicyFn>
603 void dispatch_event(const MidiEvent& e, PolicyFn& policy_for_note) {
604 const uint8_t status_top = static_cast<uint8_t>(e.data()[0] & 0xF0);
605 const uint8_t channel = e.channel();
606 if (e.is_note_on() && e.velocity() > 0) {
607 // Resolved here rather than per buffer so a kit can give each note
608 // its own choke group, and only for note-ons so a CC never pays for
609 // the lookup.
610 const SynthesiserNoteOnPolicy note_on_policy =
611 policy_for_note(channel, e.note(), e.velocity());
612 note_on(channel, e.note(), e.velocity(), note_on_policy.priority,
613 note_on_policy.voice_group, note_on_policy.choke_group,
614 note_on_policy.choke_fade_ms);
615 return;
616 }
617 if (e.is_note_off() || (e.is_note_on() && e.velocity() == 0)) {
618 note_off(channel, e.note());
619 return;
620 }
621 if (e.is_pitch_bend()) {
622 const uint16_t raw = static_cast<uint16_t>(
623 e.data()[1] | (uint16_t(e.data()[2]) << 7));
624 const float norm = (static_cast<float>(raw) - 8192.0f) / 8192.0f;
625 pitch_bend(channel, norm * bend_range_semi_);
626 return;
627 }
628 if (status_top == 0xD0) {
629 aftertouch(channel, e.data()[1] / 127.0f);
630 return;
631 }
632 if (e.is_cc()) {
633 cc(channel, e.cc_number(), e.cc_value());
634 return;
635 }
636 // PolyAftertouch (0xA0), ProgramChange (0xC0), system messages
637 // are ignored at this layer — subclasses can route them via
638 // direct event taps if needed.
639 }
640
641 void render_active(float* out, int n) {
642 for (auto& v : voices_) {
643 if (v.active()) v.render(out, n);
644 }
645 }
646
647 void release_sustained_notes(uint8_t channel) {
648 for (auto& v : voices_) {
649 if (v.active() && !v.releasing()
650 && v.note().channel == channel
651 && v.note().sustained) {
652 v.release_sustained_note();
653 }
654 }
655 }
656
657 void capture_sostenuto_notes(uint8_t channel) {
658 for (auto& v : voices_) {
659 if (v.active() && !v.releasing() && v.note().channel == channel) {
660 v.capture_for_sostenuto();
661 }
662 }
663 }
664
665 void release_sostenuto_notes(uint8_t channel) {
666 for (auto& v : voices_) {
667 if (v.active() && !v.releasing()
668 && v.note().channel == channel
669 && v.note().sostenuto) {
670 v.release_sostenuto_note();
671 }
672 }
673 }
674
675 void set_soft_pedal_notes(uint8_t channel, bool down) {
676 for (auto& v : voices_) {
677 if (v.active() && !v.releasing() && v.note().channel == channel) {
678 v.set_soft_pedal(down);
679 }
680 }
681 }
682
683 void choke_voice_group(uint8_t channel, uint8_t voice_group,
684 float fade_ms) {
685 for (auto& v : voices_) {
686 if (v.active() && v.note().channel == channel
687 && v.note().voice_group == voice_group) {
688 v.on_choke(fade_ms);
689 }
690 }
691 }
692
693 std::vector<Voice> voices_;
694 std::array<bool, 16> sustain_down_{};
695 std::array<bool, 16> sostenuto_down_{};
696 std::array<bool, 16> soft_pedal_down_{};
698 float bend_range_semi_ = 2.0f;
699 uint32_t next_id_ = 0;
700 std::atomic<std::uint64_t> steal_count_{0};
701};
702
703} // namespace pulp::midi
Small runtime policy helper for budgeted work and graceful degradation.
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
Abstract base for one synth voice.
Definition synthesiser.hpp:97
bool active_
Definition synthesiser.hpp:212
void capture_for_sostenuto()
Capture this voice for sostenuto.
Definition synthesiser.hpp:186
bool active() const
Definition synthesiser.hpp:165
void set_soft_pedal(bool down)
Update note metadata for soft-pedal state.
Definition synthesiser.hpp:200
virtual void render(float *out, int num_samples)=0
Render num_samples of audio into out.
void mark_inactive()
Subclasses call this from render() once their release tail finishes so the synth can reclaim the voic...
Definition synthesiser.hpp:209
virtual float peak_level() const
Reported by the voice for the Quietest steal strategy and for caller-side metering.
Definition synthesiser.hpp:155
virtual void on_note_on(const SynthesiserNote &note)
Activate this voice for note.
Definition synthesiser.hpp:105
bool releasing() const
Definition synthesiser.hpp:166
void release_sustained_note()
Release a note whose note-off was deferred by CC64 sustain.
Definition synthesiser.hpp:178
void release_sostenuto_note()
Release a note captured by CC66 sostenuto if no sustain hold remains.
Definition synthesiser.hpp:192
virtual void on_pitch_bend(float bend_semitones)
Channel-level pitch bend (semitones, already scaled by the configured bend range).
Definition synthesiser.hpp:135
bool releasing_
Definition synthesiser.hpp:213
virtual void on_cc(uint8_t cc_number, uint8_t value)
Channel-level CC (raw 0–127 value). Default no-op.
Definition synthesiser.hpp:141
const SynthesiserNote & note() const
Definition synthesiser.hpp:167
SynthesiserNote note_
Definition synthesiser.hpp:211
virtual void reset()
Hard-reset (clear state, drop note).
Definition synthesiser.hpp:159
void defer_note_off_for_sustain()
Mark this voice as waiting for sustain-pedal release.
Definition synthesiser.hpp:172
virtual ~SynthesiserVoice()=default
virtual void on_note_off()
Begin release.
Definition synthesiser.hpp:118
virtual void on_aftertouch(float pressure)
Channel-level aftertouch (0..1). Default no-op.
Definition synthesiser.hpp:138
virtual void on_choke(float fade_ms)
Choke this voice with the caller's requested fade time.
Definition synthesiser.hpp:128
const Voice & voice(std::size_t i) const
Definition synthesiser.hpp:387
std::uint64_t steal_count() const
Definition synthesiser.hpp:401
void process(const MidiBuffer &events, float *out, int num_samples, const SynthesiserNoteOnPolicy &note_on_policy)
Drive the synth with constant metadata applied to every note-on in the buffer.
Definition synthesiser.hpp:356
void note_off(uint8_t channel, uint8_t note)
Definition synthesiser.hpp:278
void reset()
Definition synthesiser.hpp:470
std::uint64_t estimate_optional_runtime_cost() const
Definition synthesiser.hpp:423
void set_steal_strategy(VoiceStealStrategy s)
Definition synthesiser.hpp:239
void pitch_bend(uint8_t channel, float bend_semitones)
Definition synthesiser.hpp:299
Synthesiser(std::size_t polyphony=16)
Construct with a fixed polyphony.
Definition synthesiser.hpp:236
void aftertouch(uint8_t channel, float pressure)
Definition synthesiser.hpp:307
void process(const MidiBuffer &events, float *out, int num_samples)
Drive the synth across one audio block.
Definition synthesiser.hpp:350
std::size_t releasing_count() const
Definition synthesiser.hpp:395
Voice & voice(std::size_t i)
Definition synthesiser.hpp:386
void cc(uint8_t channel, uint8_t cc_number, uint8_t value)
Definition synthesiser.hpp:315
std::size_t active_count() const
Definition synthesiser.hpp:389
void set_pitch_bend_range_semitones(float r)
Pitch-bend range applied to channel-level pitch-bend messages.
Definition synthesiser.hpp:246
SynthesiserTelemetry telemetry() const
Cheap owner-thread snapshot for voice-count telemetry.
Definition synthesiser.hpp:413
void note_on(uint8_t channel, uint8_t note, uint8_t velocity, int8_t priority=0, uint8_t voice_group=0, bool choke_group=false, float choke_fade_ms=0.0f)
Direct event entry points — usable when the caller wants to build a Synthesiser pipeline without a fu...
Definition synthesiser.hpp:253
void process(const MidiBuffer &events, float *out, int num_samples, PolicyFn &&policy_for_note)
Drive the synth resolving note-on metadata per note rather than once per buffer.
Definition synthesiser.hpp:380
std::size_t polyphony() const
Definition synthesiser.hpp:385
SynthesiserRuntimeBudgetReport evaluate_optional_runtime_budget(runtime::RuntimeBudgetFrame &frame, runtime::RuntimeWorkLane lane=runtime::RuntimeWorkLane::Background, bool required=false) const
Definition synthesiser.hpp:438
void panic(bool immediate)
Silence every voice.
Definition synthesiser.hpp:457
void reset_steal_count()
Definition synthesiser.hpp:405
VoiceStealStrategy steal_strategy() const
Definition synthesiser.hpp:240
float pitch_bend_range_semitones() const
Definition synthesiser.hpp:249
Definition budget_policy.hpp:101
RuntimeBudgetDecision evaluate(RuntimeWorkLane lane, std::uint64_t estimated_cost, bool required=false) noexcept
Definition budget_policy.hpp:112
const RuntimeBudgetFrameStats & stats() const noexcept
Definition budget_policy.hpp:130
@ events
a per-block list of ValueEvent occurrences
Definition value_channel_telemetry.hpp:25
Definition child_process_manager.hpp:18
Voice
The two registered voices.
Definition forge_synthesis_catalog.hpp:152
Definition custom_node_events.hpp:26
@ Oldest
Steal the voice that started first.
Definition mpe_synth_voice.hpp:112
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
@ e
Definition pitch.hpp:24
RuntimeWorkLane
Definition budget_policy.hpp:11
@ Background
Definition budget_policy.hpp:14
@ i
Definition formant_filter_bank.hpp:20
@ n
Definition input_events.hpp:40
@ p
Definition input_events.hpp:40
@ v
Definition input_events.hpp:41
Constant note-on metadata for MIDI-buffer dispatch.
Definition synthesiser.hpp:219
bool choke_group
Definition synthesiser.hpp:221
uint8_t voice_group
Definition synthesiser.hpp:220
int8_t priority
Steal priority for VoiceStealStrategy::Priority.
Definition synthesiser.hpp:223
float choke_fade_ms
Definition synthesiser.hpp:222
Per-note metadata maintained by the Synthesiser when a voice is activated.
Definition synthesiser.hpp:38
uint8_t velocity
Note-on velocity (1–127; 0 → note off).
Definition synthesiser.hpp:41
bool active
Voice is sounding (held or releasing).
Definition synthesiser.hpp:45
bool sostenuto
Note captured by sostenuto pedal (CC66).
Definition synthesiser.hpp:48
bool soft_pedal
Soft pedal (CC67) is active for this note.
Definition synthesiser.hpp:49
bool sustained
Note-off deferred by sustain pedal (CC64).
Definition synthesiser.hpp:47
bool releasing
Note-off received; voice in release tail.
Definition synthesiser.hpp:46
int8_t priority
User-supplied priority for Priority steal.
Definition synthesiser.hpp:42
uint8_t channel
MIDI channel (0–15).
Definition synthesiser.hpp:39
uint32_t note_id
Monotonic id; younger > older.
Definition synthesiser.hpp:44
uint8_t note
MIDI note number (0–127).
Definition synthesiser.hpp:40
uint8_t voice_group
Optional channel-scoped choke group (0 = none).
Definition synthesiser.hpp:43
Definition synthesiser.hpp:79
runtime::RuntimeBudgetFrameStats frame_stats
Definition synthesiser.hpp:81
runtime::RuntimeBudgetDecision decision
Definition synthesiser.hpp:80
SynthesiserTelemetry telemetry
Definition synthesiser.hpp:82
std::uint64_t estimated_cost
Definition synthesiser.hpp:83
bool should_run_optional_work() const noexcept
Definition synthesiser.hpp:85
Definition synthesiser.hpp:71
VoiceStealStrategy steal_strategy
Definition synthesiser.hpp:76
std::size_t active_voice_count
Definition synthesiser.hpp:73
std::size_t releasing_voice_count
Definition synthesiser.hpp:74
std::size_t polyphony
Definition synthesiser.hpp:72
std::uint64_t steal_count
Definition synthesiser.hpp:75
Definition budget_policy.hpp:39
Definition budget_policy.hpp:46