109 note_.sustained =
false;
110 note_.sostenuto =
false;
120 note_.releasing =
true;
121 note_.sustained =
false;
122 note_.sostenuto =
false;
141 virtual void on_cc(uint8_t cc_number, uint8_t value) {
142 (void)cc_number; (void)value;
149 virtual void render(
float* out,
int num_samples) = 0;
174 note_.sustained =
true;
180 note_.sustained =
false;
188 note_.sostenuto =
true;
194 note_.sostenuto =
false;
202 note_.soft_pedal = down;
228template <
typename Voice>
230 static_assert(std::is_base_of<SynthesiserVoice, Voice>::value,
231 "Voice must derive from SynthesiserVoice");
247 if (r > 0.0f) bend_range_semi_ = r;
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);
262 Voice* v = find_free_voice();
263 if (!v) v = steal_voice();
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_;
274 n.soft_pedal = soft_pedal_down_[n.channel];
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();
289 if (v.note().sostenuto) {
300 for (
auto& v : voices_) {
301 if (v.active() && v.note().channel == (channel & 0x0F)) {
302 v.on_pitch_bend(bend_semitones);
308 for (
auto& v : voices_) {
309 if (v.active() && v.note().channel == (channel & 0x0F)) {
310 v.on_aftertouch(pressure);
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);
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);
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);
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);
358 auto constant = [¬e_on_policy](uint8_t, uint8_t, uint8_t) {
359 return note_on_policy;
361 render_block(
events, out, num_samples, constant);
377 template <
typename PolicyFn>
379 uint8_t, uint8_t, uint8_t>
381 PolicyFn&& policy_for_note) {
382 render_block(
events, out, num_samples, policy_for_note);
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]; }
391 for (
const auto& v : voices_)
if (v.active()) ++n;
397 for (
const auto& v : voices_)
if (v.active() && v.releasing()) ++n;
402 return steal_count_.load(std::memory_order_relaxed);
406 steal_count_.store(0, std::memory_order_relaxed);
419 .steal_strategy = strategy_,
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));
441 bool required =
false)
const {
444 const auto decision = frame.
evaluate(lane, cost, required);
446 .decision = decision,
447 .frame_stats = frame.
stats(),
449 .estimated_cost = cost,
462 sustain_down_.fill(
false);
463 sostenuto_down_.fill(
false);
464 for (
auto& v : voices_) {
465 if (v.active() && !v.releasing())
471 for (
auto& v : voices_) v.reset();
472 sustain_down_.fill(
false);
473 sostenuto_down_.fill(
false);
474 soft_pedal_down_.fill(
false);
479 static std::uint64_t saturating_add_u64_(std::uint64_t a,
481 const auto max = std::numeric_limits<std::uint64_t>::max();
482 return b > max - a ? max : a + b;
485 static std::uint64_t saturating_mul_u64_(std::uint64_t a,
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;
492 Voice* find_free_voice() {
493 for (
auto& v : voices_)
if (!v.active())
return &v;
497 Voice* steal_voice() {
498 if (voices_.empty())
return nullptr;
499 Voice* chosen =
nullptr;
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) {
506 oldest_id =
v.note().note_id;
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();
518 || (lvl == min_level &&
v.note().note_id < tie_id)) {
521 tie_id =
v.note().note_id;
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)) {
535 tie_id =
v.note().note_id;
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;
547 || (n == highest &&
v.note().note_id < tie_id)) {
550 tie_id =
v.note().note_id;
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;
562 || (p == lowest_prio &&
v.note().note_id < tie_id)) {
565 tie_id =
v.note().note_id;
572 steal_count_.fetch_add(1, std::memory_order_relaxed);
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) {
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;
595 dispatch_event(e, policy_for_note);
597 if (sample_index < num_samples) {
598 render_active(out + sample_index, num_samples - sample_index);
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) {
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);
617 if (
e.is_note_off() || (
e.is_note_on() &&
e.velocity() == 0)) {
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;
628 if (status_top == 0xD0) {
633 cc(channel,
e.cc_number(),
e.cc_value());
641 void render_active(
float* out,
int n) {
642 for (
auto& v : voices_) {
643 if (
v.active())
v.render(out, n);
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();
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();
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();
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);
683 void choke_voice_group(uint8_t channel, uint8_t voice_group,
685 for (
auto& v : voices_) {
686 if (
v.active() &&
v.note().channel == channel
687 &&
v.note().voice_group == voice_group) {
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};
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 ¬e)
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 ¬e_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