41 timbre_target_ = note.
timbre;
49 pitch_bend_ = smooth(pitch_bend_, pitch_bend_target_);
50 pressure_ = smooth(pressure_, pressure_target_);
51 timbre_ = smooth(timbre_, timbre_target_);
58 virtual void render(
float* out,
int num_samples) = 0;
64 pitch_bend_ = pitch_bend_target_ = 0.0f;
65 pressure_ = pressure_target_ = 0.0f;
66 timbre_ = timbre_target_ = 0.0f;
77 if (coeff < 0.0f) coeff = 0.0f;
78 if (coeff >= 1.0f) coeff = 0.9999f;
83 bool active()
const {
return active_; }
86 uint8_t
channel()
const {
return note_.channel; }
88 uint8_t
velocity()
const {
return note_.velocity; }
91 float timbre()
const {
return timbre_; }
97 float smooth(
float current,
float target)
const {
98 return smoothing_ * current + (1.0f - smoothing_) * target;
101 MpeNoteState note_{};
102 bool active_ =
false;
103 bool releasing_ =
false;
104 float pitch_bend_ = 0.0f, pitch_bend_target_ = 0.0f;
105 float pressure_ = 0.0f, pressure_target_ = 0.0f;
106 float timbre_ = 0.0f, timbre_target_ = 0.0f;
107 float smoothing_ = 0.99f;
145 const bool glide = channel_refcount_[note.
channel] > 0;
146 ++channel_refcount_[note.
channel];
150 if (channel_refcount_[note.
channel] > 0) --channel_refcount_[note.
channel];
152 void reset() { channel_refcount_.fill(0); }
153 bool channel_held(uint8_t ch)
const {
return channel_refcount_[ch] > 0; }
155 std::array<uint8_t, 16> channel_refcount_{};
160template<
typename Voice>
162 static_assert(std::is_base_of_v<MpeSynthVoice, Voice>,
163 "Voice must derive from MpeSynthVoice");
168 std::size_t
polyphony()
const {
return voices_.size(); }
169 Voice&
voice(std::size_t i) {
return voices_[i]; }
170 const Voice&
voice(std::size_t i)
const {
return voices_[i]; }
179 const bool glide = glide_detector_.observe_note_on(e.state);
180 last_was_glide_ = glide;
181 Voice* v = pick_free_voice();
193 if (v && v->active() && !v->releasing()) {
195 released.
channel = v->channel();
196 released.
note = v->note_number();
197 released.
note_id = v->note_id();
198 glide_detector_.observe_note_off(released);
203 v->on_note_on(e.state);
204 age_[
static_cast<std::size_t
>(v - voices_.data())] = ++age_counter_;
210 glide_detector_.observe_note_off(e.state);
211 last_was_glide_ =
false;
220 if (Voice* v = find_voice_by_id(e.state.note_id)) {
221 glide_detector_.observe_note_off(e.state);
227 if (Voice* v = find_voice_by_id(e.state.note_id))
228 v->set_pitch_bend(e.state.pitch_bend_semitones);
231 if (Voice* v = find_voice_by_id(e.state.note_id))
232 v->set_pressure(e.state.pressure);
235 if (Voice* v = find_voice_by_id(e.state.note_id))
236 v->set_timbre(e.state.timbre);
243 for (
const auto& e : buf)
dispatch(e);
248 for (
const auto& v : voices_)
if (v.active()) ++n;
254 for (
const auto& v : voices_)
if (v.active() && v.releasing()) ++n;
259 return steal_count_.load(std::memory_order_relaxed);
263 steal_count_.store(0, std::memory_order_relaxed);
276 .steal_mode = steal_mode_,
277 .last_was_glide = last_was_glide_,
283 std::uint64_t cost = 0;
284 cost = saturating_add_u64_(
285 cost, saturating_mul_u64_(
286 static_cast<std::uint64_t
>(t.polyphony), 4));
287 cost = saturating_add_u64_(
288 cost, saturating_mul_u64_(
289 static_cast<std::uint64_t
>(t.active_voice_count), 64));
290 cost = saturating_add_u64_(
291 cost, saturating_mul_u64_(
292 static_cast<std::uint64_t
>(t.releasing_voice_count), 32));
299 bool required =
false)
const {
302 const auto decision = frame.
evaluate(lane, cost, required);
304 .decision = decision,
305 .frame_stats = frame.
stats(),
307 .estimated_cost = cost,
314 for (
auto& v : voices_) v.reset();
315 glide_detector_.reset();
316 last_was_glide_ =
false;
321 static std::uint64_t saturating_add_u64_(std::uint64_t a,
323 const auto max = std::numeric_limits<std::uint64_t>::max();
324 return b > max - a ? max : a + b;
327 static std::uint64_t saturating_mul_u64_(std::uint64_t a,
329 const auto max = std::numeric_limits<std::uint64_t>::max();
330 if (a == 0 || b == 0)
return 0;
331 return a > max / b ? max : a * b;
334 Voice* pick_free_voice() {
335 for (
auto& v : voices_)
if (!v.active())
return &v;
339 Voice* steal_voice() {
340 if (voices_.empty())
return nullptr;
341 auto cmp = [
this](
const Voice&
a,
const Voice&
b) ->
bool {
342 switch (steal_mode_) {
344 return age_[&
a - voices_.data()] < age_[&
b - voices_.data()];
346 return a.velocity() <
b.velocity();
348 return a.note_number() <
b.note_number();
350 return a.note_number() >
b.note_number();
354 Voice* target = &voices_[0];
355 for (
auto& v : voices_)
if (cmp(v, *target)) target = &
v;
356 steal_count_.fetch_add(1, std::memory_order_relaxed);
361 for (
auto& v : voices_) {
362 if (
v.active() &&
v.note_id() ==
id)
return &
v;
367 std::vector<Voice> voices_;
368 std::vector<uint64_t> age_ = std::vector<uint64_t>(voices_.size(), 0);
369 uint64_t age_counter_ = 0;
371 MpeGlideDetector glide_detector_;
372 bool last_was_glide_ =
false;
373 std::atomic<std::uint64_t> steal_count_{0};
Small runtime policy helper for budgeted work and graceful degradation.
A time-ordered list of per-note expression events for one block.
Definition mpe_buffer.hpp:51
Glide/legato detector — observes MPE note-on events and reports when a new note on the same channel o...
Definition mpe_synth_voice.hpp:140
void observe_note_off(const MpeNoteState ¬e)
Definition mpe_synth_voice.hpp:149
void reset()
Definition mpe_synth_voice.hpp:152
bool channel_held(uint8_t ch) const
Definition mpe_synth_voice.hpp:153
bool observe_note_on(const MpeNoteState ¬e)
Returns true if note.channel already has an active note at the time of a new note-on,...
Definition mpe_synth_voice.hpp:144
Abstract base for a single MPE voice.
Definition mpe_synth_voice.hpp:30
void set_timbre(float t)
Definition mpe_synth_voice.hpp:73
float pitch_bend() const
Definition mpe_synth_voice.hpp:89
virtual void reset()
Hard-reset the voice (e.g. on allocation steal).
Definition mpe_synth_voice.hpp:61
uint8_t channel() const
Definition mpe_synth_voice.hpp:86
void set_smoothing(float coeff)
Smoothing coefficient in [0, 1). 0 = instant; 0.99 = ~100 sample TC.
Definition mpe_synth_voice.hpp:76
bool releasing() const
Definition mpe_synth_voice.hpp:84
float pressure() const
Definition mpe_synth_voice.hpp:90
void advance_smoothers()
Called once per sample before rendering. Advances smoothers.
Definition mpe_synth_voice.hpp:48
virtual void on_note_off()
Called by the allocator when this voice should release.
Definition mpe_synth_voice.hpp:45
bool active() const
Definition mpe_synth_voice.hpp:83
uint8_t note_number() const
Definition mpe_synth_voice.hpp:87
uint8_t velocity() const
Definition mpe_synth_voice.hpp:88
virtual void render(float *out, int num_samples)=0
Render num_samples of audio into out (additive or replacing — subclass decides).
virtual ~MpeSynthVoice()=default
virtual void on_note_on(const MpeNoteState ¬e)
Called by the allocator when this voice is activated.
Definition mpe_synth_voice.hpp:35
void set_pitch_bend(float semitones)
Definition mpe_synth_voice.hpp:71
float timbre() const
Definition mpe_synth_voice.hpp:91
void finish_release()
Definition mpe_synth_voice.hpp:94
void set_pressure(float p)
Definition mpe_synth_voice.hpp:72
MpeNoteGeneration note_id() const
Definition mpe_synth_voice.hpp:85
Voice & voice(std::size_t i)
Definition mpe_synth_voice.hpp:169
std::size_t active_count() const
Definition mpe_synth_voice.hpp:246
MpeVoiceAllocator(std::size_t polyphony)
Definition mpe_synth_voice.hpp:165
const Voice & voice(std::size_t i) const
Definition mpe_synth_voice.hpp:170
void dispatch_all(const MpeBuffer &buf)
Dispatch all events in a buffer (in the order they appear).
Definition mpe_synth_voice.hpp:242
bool last_was_glide() const
Definition mpe_synth_voice.hpp:311
std::uint64_t estimate_optional_runtime_cost() const
Definition mpe_synth_voice.hpp:281
MpeVoiceAllocatorTelemetry telemetry() const
Cheap owner-thread snapshot for voice-count telemetry.
Definition mpe_synth_voice.hpp:270
void reset_all()
Definition mpe_synth_voice.hpp:313
MpeVoiceAllocatorRuntimeBudgetReport evaluate_optional_runtime_budget(runtime::RuntimeBudgetFrame &frame, runtime::RuntimeWorkLane lane=runtime::RuntimeWorkLane::Background, bool required=false) const
Definition mpe_synth_voice.hpp:296
std::size_t releasing_count() const
Definition mpe_synth_voice.hpp:252
std::uint64_t steal_count() const
Definition mpe_synth_voice.hpp:258
void dispatch(const MpeExpressionEvent &e)
Dispatch a single MPE expression event to the voice pool.
Definition mpe_synth_voice.hpp:176
void reset_steal_count()
Definition mpe_synth_voice.hpp:262
void set_steal_mode(MpeVoiceStealMode m)
Definition mpe_synth_voice.hpp:172
MpeVoiceStealMode steal_mode() const
Definition mpe_synth_voice.hpp:173
std::size_t polyphony() const
Definition mpe_synth_voice.hpp:168
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
Sample-accurate MPE expression event stream.
MPE (MIDI Polyphonic Expression) voice tracker.
Voice
The two registered voices.
Definition forge_synthesis_catalog.hpp:152
Definition custom_node_events.hpp:26
MpeVoiceStealMode
Voice-steal policy when the pool is full.
Definition mpe_synth_voice.hpp:111
@ HighestPitch
Steal the highest-note voice.
Definition mpe_synth_voice.hpp:115
@ Oldest
Steal the voice that started first.
Definition mpe_synth_voice.hpp:112
@ LowestPitch
Steal the lowest-note voice.
Definition mpe_synth_voice.hpp:114
@ LowestVelocity
Steal the quietest voice.
Definition mpe_synth_voice.hpp:113
std::uint64_t MpeNoteGeneration
Stable identity assigned to one accepted MPE note-on generation.
Definition mpe_voice_tracker.hpp:36
@ a
Definition pitch.hpp:29
@ b
Definition pitch.hpp:31
RuntimeWorkLane
Definition budget_policy.hpp:11
@ Background
Definition budget_policy.hpp:14
@ v
Definition input_events.hpp:41
A single per-note expression event.
Definition mpe_buffer.hpp:31
@ NoteOn
Definition mpe_buffer.hpp:33
@ NoteOff
Definition mpe_buffer.hpp:34
@ Pressure
Definition mpe_buffer.hpp:36
@ PitchBend
Definition mpe_buffer.hpp:35
@ Timbre
Definition mpe_buffer.hpp:37
Per-note state maintained by MpeVoiceTracker.
Definition mpe_voice_tracker.hpp:44
float timbre
CC 74 (0..1); same value as slide.
Definition mpe_voice_tracker.hpp:51
float pitch_bend_semitones
Current per-note pitch bend in semitones.
Definition mpe_voice_tracker.hpp:49
float pressure
Channel pressure (0..1).
Definition mpe_voice_tracker.hpp:50
uint8_t channel
MPE member channel the note lives on.
Definition mpe_voice_tracker.hpp:46
MpeNoteGeneration note_id
Monotonic generation assigned on note-on (never 0).
Definition mpe_voice_tracker.hpp:52
uint8_t note
MIDI note number (0-127).
Definition mpe_voice_tracker.hpp:47
Definition mpe_synth_voice.hpp:127
runtime::RuntimeBudgetFrameStats frame_stats
Definition mpe_synth_voice.hpp:129
MpeVoiceAllocatorTelemetry telemetry
Definition mpe_synth_voice.hpp:130
bool should_run_optional_work() const noexcept
Definition mpe_synth_voice.hpp:133
std::uint64_t estimated_cost
Definition mpe_synth_voice.hpp:131
runtime::RuntimeBudgetDecision decision
Definition mpe_synth_voice.hpp:128
Definition mpe_synth_voice.hpp:118
bool last_was_glide
Definition mpe_synth_voice.hpp:124
std::uint64_t steal_count
Definition mpe_synth_voice.hpp:122
std::size_t active_voice_count
Definition mpe_synth_voice.hpp:120
MpeVoiceStealMode steal_mode
Definition mpe_synth_voice.hpp:123
std::size_t releasing_voice_count
Definition mpe_synth_voice.hpp:121
std::size_t polyphony
Definition mpe_synth_voice.hpp:119
Definition budget_policy.hpp:39
Definition budget_policy.hpp:46