Vital
Loading...
Searching...
No Matches
voice_handler.cpp
Go to the documentation of this file.
1
7#include "voice_handler.h"
8
9#include "synth_constants.h"
10#include "utils.h"
11
12namespace vital {
13
14 namespace {
16 constexpr int kParallelVoices = poly_float::kSize / 2;
17 constexpr int kChannelShift = 8;
18 constexpr int kNoteMask = (1 << kChannelShift) - 1;
19
21 force_inline int combineNoteChannel(int note, int channel) {
22 return (channel << kChannelShift) + note;
23 }
24
26 force_inline int getChannel(int value) {
27 return value >> kChannelShift;
28 }
29
31 force_inline int getNote(int value) {
32 return value & kNoteMask;
33 }
34
36 force_inline int voiceCompareNewestFirst(Voice* left, Voice* right) {
37 int left_count = left->state().note_count;
38 int right_count = right->state().note_count;
39 return left_count - right_count;
40 }
41
43 force_inline int voiceCompareLowestFirst(Voice* left, Voice* right) {
44 return right->state().midi_note - left->state().midi_note;
45 }
46
48 force_inline int voiceCompareHighestFirst(Voice* left, Voice* right) {
49 return left->state().midi_note - right->state().midi_note;
50 }
51
53 force_inline int pressedCompareLowestFirst(int left, int right) {
54 return getNote(right) - getNote(left);
55 }
56
58 force_inline int pressedCompareHighestFirst(int left, int right) {
59 return getNote(left) - getNote(right);
60 }
61 } // namespace
62
63 //========================= Voice Implementation =========================//
64
66 : voice_index_(0),
67 voice_mask_(0),
68 event_sample_(-1),
69 aftertouch_sample_(-1),
70 aftertouch_(0.0f),
71 slide_sample_(-1),
72 slide_(0.0f),
73 parent_(parent) {
74 state_.event = kVoiceOff;
75 state_.midi_note = 0;
76 state_.tuned_note = 0;
77 state_.velocity = 0;
78 state_.last_note = 0;
79 state_.note_pressed = 0;
80 state_.channel = 0;
81 key_state_ = kDead;
82 last_key_state_ = kDead;
83 }
84
85 //======================= VoiceHandler Implementation ====================//
86
87 VoiceHandler::VoiceHandler(int num_outputs, int polyphony, bool control_rate) :
88 SynthModule(kNumInputs, num_outputs, control_rate),
89 polyphony_(0),
90 legato_(false),
91 voice_killer_(nullptr),
92 last_num_voices_(0),
93 last_played_note_(-1.0f),
94 sustain_(),
95 sostenuto_(),
96 mod_wheel_values_(),
97 pitch_wheel_values_(),
98 zoned_pitch_wheel_values_(),
99 pressure_values_(),
100 slide_values_(),
101 tuning_(nullptr),
102 voice_priority_(kRoundRobin),
103 voice_override_(kKill),
104 total_notes_(0) {
105 // Reserve space for data structures:
106 pressed_notes_.reserve(kMidiSize);
107 all_voices_.reserve(kMaxPolyphony + kParallelVoices);
108 free_voices_.reserve(kMaxPolyphony + kParallelVoices);
109 active_voices_.reserve(kMaxPolyphony + kParallelVoices);
110 all_aggregate_voices_.reserve(kMaxPolyphony / kParallelVoices + kParallelVoices);
111 active_aggregate_voices_.reserve(kMaxPolyphony / kParallelVoices + kParallelVoices);
112
113 // By default, note_ is used as the main "midi" output for reference.
114 voice_midi_ = &note_;
115
116 // Mark these Outputs as belonging to voice_router_ (the poly router).
117 voice_event_.owner = &voice_router_;
118 retrigger_.owner = &voice_router_;
119 reset_.owner = &voice_router_;
120 note_.owner = &voice_router_;
121 last_note_.owner = &voice_router_;
122 note_pressed_.owner = &voice_router_;
123 note_count_.owner = &voice_router_;
124 note_in_octave_.owner = &voice_router_;
125 channel_.owner = &voice_router_;
126 velocity_.owner = &voice_router_;
127 lift_.owner = &voice_router_;
128 aftertouch_.owner = &voice_router_;
129 slide_.owner = &voice_router_;
130 active_mask_.owner = &voice_router_;
131 mod_wheel_.owner = &voice_router_;
132 pitch_wheel_.owner = &voice_router_;
133 pitch_wheel_percent_.owner = &voice_router_;
134 local_pitch_bend_.owner = &voice_router_;
135
137 voice_router_.router(this);
138 global_router_.router(this);
139 }
140
142
143 void VoiceHandler::prepareVoiceTriggers(AggregateVoice* aggregate_voice, int num_samples) {
144 // Clear triggers for each block.
145 note_.clearTrigger();
146 last_note_.clearTrigger();
147 channel_.clearTrigger();
148 velocity_.clearTrigger();
149 lift_.clearTrigger();
150 voice_event_.clearTrigger();
151 retrigger_.clearTrigger();
152 reset_.clearTrigger();
153 aftertouch_.clearTrigger();
154 slide_.clearTrigger();
155
156 // Iterate each Voice in the group and trigger events if needed.
157 for (Voice* voice : aggregate_voice->voices) {
158 if (voice->hasNewEvent()) {
159 int offset = voice->event_sample() * getOversampleAmount();
160 if (num_samples <= offset)
161 voice->shiftVoiceEvent(num_samples / getOversampleAmount());
162 else {
163 poly_mask mask = voice->voice_mask();
164 voice_event_.trigger(mask, voice->state().event, offset);
165
166 // Trigger note-on
167 if (voice->state().event == kVoiceOn) {
168 note_.trigger(mask, voice->state().tuned_note, offset);
169 last_note_.trigger(mask, voice->state().last_note, offset);
170 velocity_.trigger(mask, voice->state().velocity, offset);
171 channel_.trigger(mask, voice->state().channel, offset);
172
173 // If the voice was previously dead, also send a reset trigger.
174 if (voice->last_key_state() == Voice::kDead)
175 reset_.trigger(mask, kVoiceOn, offset);
176 }
177 // Trigger note-off
178 else if (voice->state().event == kVoiceOff)
179 lift_.trigger(mask, voice->state().lift, offset);
180
181 // Retrigger logic
182 if (!legato_ || voice->last_key_state() != Voice::kHeld || voice->state().event != kVoiceOn)
183 retrigger_.trigger(mask, voice->state().event, offset);
184
185 voice->completeVoiceEvent();
186 }
187
188 // Check aftertouch event
189 if (voice->hasNewAftertouch()) {
190 int aftertouch_sample = voice->aftertouch_sample() * getOversampleAmount();
191 if (num_samples <= aftertouch_sample)
192 voice->shiftAftertouchEvent(num_samples / getOversampleAmount());
193 else {
194 aftertouch_.trigger(voice->voice_mask(), voice->aftertouch(), aftertouch_sample);
195 voice->clearAftertouchEvent();
196 }
197 }
198
199 // Check slide event
200 if (voice->hasNewSlide()) {
201 int slide_sample = voice->slide_sample() * getOversampleAmount();
202 if (num_samples <= slide_sample)
203 voice->shiftSlideEvent(num_samples / getOversampleAmount());
204 else {
205 slide_.trigger(voice->voice_mask(), voice->slide(), slide_sample);
206 voice->clearSlideEvent();
207 }
208 }
209 }
210 }
211 }
212
213 void VoiceHandler::prepareVoiceValues(AggregateVoice* aggregate_voice) {
214 // For each voice, copy or mask-load its state into the relevant outputs.
215 for (Voice* voice : aggregate_voice->voices) {
216 poly_mask mask = voice->voice_mask();
217 int channel = voice->state().channel;
218
219 // Tune, note, channel, velocity, etc.
220 poly_float note_val = utils::maskLoad(note_.trigger_value, voice->state().tuned_note, mask);
221 note_.trigger_value = note_val;
222 last_note_.trigger_value = utils::maskLoad(last_note_.trigger_value, voice->state().last_note, mask);
223
224 note_pressed_.trigger_value = utils::maskLoad(note_pressed_.trigger_value, voice->state().note_pressed, mask);
225 note_count_.trigger_value = utils::maskLoad(note_count_.trigger_value, voice->state().note_count, mask);
226 note_in_octave_.trigger_value = utils::mod(note_val * (1.0f / kNotesPerOctave));
227 channel_.trigger_value = utils::maskLoad(channel_.trigger_value, channel, mask);
228 velocity_.trigger_value = utils::maskLoad(velocity_.trigger_value, voice->state().velocity, mask);
229
230 // Handle lift for released voices
231 mono_float lift_val = (voice->released() ? voice->state().lift : 0.0f);
232 lift_.trigger_value = utils::maskLoad(lift_.trigger_value, lift_val, mask);
233
234 // Aftertouch and slide
235 aftertouch_.trigger_value = utils::maskLoad(aftertouch_.trigger_value, voice->aftertouch(), mask);
236 slide_.trigger_value = utils::maskLoad(slide_.trigger_value, voice->slide(), mask);
237
238 // Mark voice active or not
239 bool dead = (voice->key_state() == Voice::kDead);
240 poly_float active_value = dead ? 0.0f : 1.0f;
241 active_mask_.trigger_value = utils::maskLoad(active_mask_.trigger_value, active_value, mask);
242
243 // Mod and pitch wheels
244 mono_float mod_wheel_val = mod_wheel_values_[channel];
245 mod_wheel_.trigger_value = utils::maskLoad(mod_wheel_.trigger_value, mod_wheel_val, mask);
246
247 mono_float pitch_wheel_val = zoned_pitch_wheel_values_[channel];
248 pitch_wheel_.trigger_value = utils::maskLoad(pitch_wheel_.trigger_value, pitch_wheel_val, mask);
249
250 mono_float pitch_wheel_percent_val = pitch_wheel_val * 0.5f + 0.5f;
251 pitch_wheel_percent_.trigger_value = utils::maskLoad(pitch_wheel_percent_.trigger_value,
252 pitch_wheel_percent_val, mask);
253
254 // Per-voice pitch bend
255 mono_float local_pitch_bend_val = voice->state().local_pitch_bend * kLocalPitchBendRange;
256 local_pitch_bend_.trigger_value = utils::maskLoad(local_pitch_bend_.trigger_value, local_pitch_bend_val, mask);
257 }
258 }
259
260 void VoiceHandler::processVoice(AggregateVoice* aggregate_voice, int num_samples) {
261 // Simply run the shared processor for this set of voices.
262 aggregate_voice->processor->process(num_samples);
263 }
264
265 void VoiceHandler::clearAccumulatedOutputs() {
266 // Zero out all accumulated Output buffers before mixing.
267 for (auto& output : accumulated_outputs_)
269 }
270
271 void VoiceHandler::clearNonaccumulatedOutputs() {
272 // Zero out all non-accumulated Output buffers (single-lane).
273 for (auto& outputs : nonaccumulated_outputs_)
274 utils::zeroBuffer(outputs.second->buffer, outputs.second->buffer_size);
275 }
276
277 void VoiceHandler::accumulateOutputs(int num_samples) {
278 // Sum each voice's output into the accumulated buffers.
279 for (auto& output : accumulated_outputs_) {
280 int buffer_size = std::min(num_samples, output.second->buffer_size);
281 poly_float* dest = output.second->buffer;
282 const poly_float* source = output.first->buffer;
283
284 for (int i = 0; i < buffer_size; ++i)
285 dest[i] += source[i];
286 }
287 }
288
289 void VoiceHandler::combineAccumulatedOutputs(int num_samples) {
290 // For some poly-lane outputs, add a swapped version to handle stereo or voice doubling.
291 for (auto& output : accumulated_outputs_) {
292 int buffer_size = std::min(num_samples, output.second->buffer_size);
293 poly_float* dest = output.second->buffer;
294
295 for (int i = 0; i < buffer_size; ++i)
296 dest[i] += utils::swapVoices(dest[i]);
297 }
298 }
299
300 void VoiceHandler::writeNonaccumulatedOutputs(poly_mask voice_mask, int num_samples) {
301 // Non-accumulated outputs: we only write the last active voice.
302 for (auto& outputs : nonaccumulated_outputs_) {
303 int buffer_size = std::min(num_samples, outputs.second->buffer_size);
304 poly_float* dest = outputs.second->buffer;
305 const poly_float* source = outputs.first->buffer;
306
307 VITAL_ASSERT(buffer_size == 1);
308
309 for (int i = 0; i < buffer_size; ++i) {
310 poly_float masked = source[i] & voice_mask;
311 dest[i] = masked + utils::swapVoices(masked);
312 }
313 }
314 }
315
317 // If output buffer size > 1, or if it's not control-rate, accumulate it.
318 return (output->buffer_size > 1) || (output->owner && !output->owner->isControlRate());
319 }
320
321 void VoiceHandler::process(int num_samples) {
322 // Process the global (mono) router first.
323 global_router_.process(num_samples);
324
325 int num_voices = active_voices_.size();
326 if (num_voices == 0) {
327 // If no voices are active, clear the accumulated buffers if needed.
328 if (last_num_voices_)
329 clearAccumulatedOutputs();
330
331 last_num_voices_ = num_voices;
332 return;
333 }
334
335 // Update polyphony from input signals (if used).
336 int polyphony = static_cast<int>(std::roundf(input(kPolyphony)->at(0)[0]));
338
339 // Update voice priority from input signals.
340 int priority = utils::roundToInt(input(kVoicePriority)->at(0))[0];
341 voice_priority_ = static_cast<VoicePriority>(priority);
342
343 // Update override policy from input signals.
344 int voice_override = utils::roundToInt(input(kVoiceOverride)->at(0))[0];
345 voice_override_ = static_cast<VoiceOverride>(voice_override);
346
347 clearAccumulatedOutputs();
348
349 // Gather all aggregate voices in active_aggregate_voices_.
350 active_aggregate_voices_.clear();
351 AggregateVoice* last_aggregate_voice = nullptr;
352 int last_aggregate_index = 0;
353 for (Voice* active_voice : active_voices_) {
354 if (active_aggregate_voices_.count(active_voice->parent()) == 0)
355 active_aggregate_voices_.push_back(active_voice->parent());
356 last_aggregate_voice = active_voice->parent();
357 last_aggregate_index = active_voice->voice_index();
358 }
359
360 // Move the last used AggregateVoice to the end (maintaining a specific order).
361 if (last_aggregate_voice) {
362 active_aggregate_voices_.remove(last_aggregate_voice);
363 active_aggregate_voices_.push_back(last_aggregate_voice);
364 }
365
366 // Process each aggregate voice
367 for (AggregateVoice* aggregate_voice : active_aggregate_voices_) {
368 prepareVoiceTriggers(aggregate_voice, num_samples);
369 prepareVoiceValues(aggregate_voice);
370 processVoice(aggregate_voice, num_samples);
371 accumulateOutputs(num_samples);
372
373 // Possibly kill voices that are silent if using a voice_killer_.
374 poly_mask alive_mask = constants::kFullMask;
375 if (voice_killer_)
376 alive_mask = ~utils::getSilentMask(voice_killer_->buffer, num_samples);
377
378 // Check if the voice is fully released and silent => remove from active_voices_.
379 for (Voice* single_voice : aggregate_voice->voices) {
380 bool released = (single_voice->state().event == kVoiceOff || single_voice->state().event == kVoiceKill);
381 bool alive = (single_voice->voice_mask() & alive_mask).sum();
382 bool active = active_voices_.count(single_voice);
383 if (released && !alive && active) {
384 active_voices_.remove(single_voice);
385 free_voices_.push_back(single_voice);
386 single_voice->markDead();
387 }
388 }
389 }
390
391 combineAccumulatedOutputs(num_samples);
392
393 // For the last active voice, write non-accumulated outputs.
394 if (active_voices_.size()) {
395 poly_mask voice_mask = constants::kFirstMask;
396 if (last_aggregate_index)
397 voice_mask = ~voice_mask;
398
399 writeNonaccumulatedOutputs(voice_mask, num_samples);
400
401 // Remember the last played note for possible legato transitions.
402 last_played_note_ = voice_midi_->trigger_value & voice_mask;
403 last_played_note_ += utils::swapVoices(last_played_note_);
404 }
405
406 last_num_voices_ = num_voices;
407 }
408
410 // Initialize sub-routers first, then do standard SynthModule init.
411 voice_router_.init();
412 global_router_.init();
414 }
415
416 void VoiceHandler::setSampleRate(int sample_rate) {
417 // Apply to all sub-routers and aggregated voice processors.
419 voice_router_.setSampleRate(sample_rate);
420 global_router_.setSampleRate(sample_rate);
421 for (auto& aggregate_voice : all_aggregate_voices_)
422 aggregate_voice->processor->setSampleRate(sample_rate);
423 }
424
426 return active_voices_.size();
427 }
428
430 for (Voice* voice : active_voices_) {
431 if (voice->state().event != kVoiceKill && voice->state().midi_note == note)
432 return true;
433 }
434 return false;
435 }
436
437 bool VoiceHandler::isNotePlaying(int note, int channel) {
438 for (Voice* voice : active_voices_) {
439 if (voice->state().event != kVoiceKill && voice->state().midi_note == note && voice->state().channel == channel)
440 return true;
441 }
442 return false;
443 }
444
445 void VoiceHandler::sustainOn(int channel) {
446 sustain_[channel] = true;
447 }
448
449 void VoiceHandler::sustainOff(int sample, int channel) {
450 sustain_[channel] = false;
451 for (Voice* voice : active_voices_) {
452 if (voice->sustained() && !voice->sostenuto() && voice->state().channel == channel)
453 voice->deactivate(sample);
454 }
455 }
456
457 void VoiceHandler::sostenutoOn(int channel) {
458 sostenuto_[channel] = true;
459 for (Voice* voice : active_voices_) {
460 if (voice->state().channel == channel)
461 voice->setSostenuto(true);
462 }
463 }
464
465 void VoiceHandler::sostenutoOff(int sample, int channel) {
466 sostenuto_[channel] = false;
467 for (Voice* voice : active_voices_) {
468 if (voice->state().channel == channel) {
469 voice->setSostenuto(false);
470
471 if (voice->sustained() && !sustain_[channel])
472 voice->deactivate(sample);
473 }
474 }
475 }
476
477 void VoiceHandler::sustainOnRange(int from_channel, int to_channel) {
478 for (int i = from_channel; i <= to_channel; ++i)
479 sustain_[i] = true;
480 }
481
482 void VoiceHandler::sustainOffRange(int sample, int from_channel, int to_channel) {
483 for (int i = from_channel; i <= to_channel; ++i)
484 sustain_[i] = false;
485
486 for (Voice* voice : active_voices_) {
487 int channel = voice->state().channel;
488 if (voice->sustained() && !voice->sostenuto() && channel >= from_channel && channel <= to_channel)
489 voice->deactivate(sample);
490 }
491 }
492
493 void VoiceHandler::sostenutoOnRange(int from_channel, int to_channel) {
494 for (int i = from_channel; i <= to_channel; ++i)
495 sostenuto_[i] = true;
496 for (Voice* voice : active_voices_) {
497 int channel = voice->state().channel;
498 if (channel >= from_channel && channel <= to_channel)
499 voice->setSostenuto(true);
500 }
501 }
502
503 void VoiceHandler::sostenutoOffRange(int sample, int from_channel, int to_channel) {
504 for (int i = from_channel; i <= to_channel; ++i)
505 sostenuto_[i] = false;
506
507 for (Voice* voice : active_voices_) {
508 int channel = voice->state().channel;
509 if (channel >= from_channel && channel <= to_channel) {
510 voice->setSostenuto(false);
511 if (voice->sustained() && !sustain_[channel])
512 voice->deactivate(sample);
513 }
514 }
515 }
516
518 // Kills all voices immediately.
519 pressed_notes_.clear();
520
521 for (Voice* voice : active_voices_) {
522 voice->kill(0);
523 voice->markDead();
524 free_voices_.push_back(voice);
525 }
526
527 active_voices_.clear();
528 }
529
530 void VoiceHandler::allNotesOff(int sample) {
531 // Deactivates all voices (letting them release).
532 pressed_notes_.clear();
533 for (Voice* voice : active_voices_)
534 voice->deactivate(sample);
535 }
536
537 void VoiceHandler::allNotesOff(int sample, int channel) {
538 // Deactivates all voices on the specified channel only.
539 pressed_notes_.clear();
540 for (Voice* voice : active_voices_) {
541 if (voice->state().channel == channel)
542 voice->deactivate(sample);
543 }
544 }
545
546 void VoiceHandler::allNotesOffRange(int sample, int from_channel, int to_channel) {
547 // Deactivates all voices in the specified channel range.
548 pressed_notes_.clear();
549 for (Voice* voice : active_voices_) {
550 int channel = voice->state().channel;
551 if (channel >= from_channel && channel <= to_channel)
552 voice->deactivate(sample);
553 }
554 }
555
557 // Returns the mask for the last active voice if any exist.
558 if (active_voices_.size()) {
559 if (active_voices_.back()->voice_index())
560 return ~constants::kFirstMask;
562 }
563 return 0;
564 }
565
566 Voice* VoiceHandler::grabVoice() {
567 // Finds an available voice according to the override policy and state.
568
569 // If we still have capacity or can kill a voice (kKill) without legato:
570 if (active_voices_.size() < polyphony() || (voice_override_ == kKill && !legato_)) {
571 Voice* parallel_voice = grabFreeParallelVoice();
572 if (!parallel_voice)
573 parallel_voice = grabFreeVoice();
574 if (parallel_voice)
575 return parallel_voice;
576 }
577
578 // Attempt to find a voice in kReleased state.
579 Voice* voice = grabVoiceOfType(Voice::kReleased);
580 if (voice)
581 return voice;
582
583 // Attempt to find a voice in kSustained state.
584 voice = grabVoiceOfType(Voice::kSustained);
585 if (voice)
586 return voice;
587
588 // Attempt to find a voice in kHeld state.
589 voice = grabVoiceOfType(Voice::kHeld);
590 if (voice)
591 return voice;
592
593 // Attempt to find a voice in kTriggering state.
594 voice = grabVoiceOfType(Voice::kTriggering);
595 return voice;
596 }
597
598 Voice* VoiceHandler::grabFreeVoice() {
599 // Returns the top of free_voices_ or nullptr if empty.
600 Voice* voice = nullptr;
601 if (free_voices_.size()) {
602 voice = free_voices_.front();
603 free_voices_.pop_front();
604 }
605 return voice;
606 }
607
608 Voice* VoiceHandler::grabFreeParallelVoice() {
609 // Tries to find an AggregateVoice that already has at least one active voice but also a dead (free) voice.
610 for (auto& aggregate_voice : all_aggregate_voices_) {
611 Voice* dead_voice = nullptr;
612 bool has_active_voice = false;
613
614 for (Voice* single_voice : aggregate_voice->voices) {
615 if (single_voice->key_state() == Voice::kDead)
616 dead_voice = single_voice;
617 else
618 has_active_voice = true;
619 }
620
621 // If we found one with an active voice and an unused dead voice, reuse the dead one.
622 if (has_active_voice && dead_voice) {
623 VITAL_ASSERT(free_voices_.count(dead_voice));
624 free_voices_.remove(dead_voice);
625 return dead_voice;
626 }
627 }
628 return nullptr;
629 }
630
631 Voice* VoiceHandler::grabVoiceOfType(Voice::KeyState key_state) {
632 // Search through active_voices_ for a voice in the specified key_state and remove it from active list.
633 for (auto iter = active_voices_.begin(); iter != active_voices_.end(); ++iter) {
634 Voice* voice = *iter;
635 if (voice->key_state() == key_state) {
636 active_voices_.erase(iter);
637 return voice;
638 }
639 }
640 return nullptr;
641 }
642
643 Voice* VoiceHandler::getVoiceToKill(int max_voices) {
644 // Finds a voice that can be forcibly killed to reduce the active voice count.
645 int excess_voices = active_voices_.size() - max_voices;
646 Voice* released = nullptr;
647 Voice* sustained = nullptr;
648 Voice* held = nullptr;
649
650 for (Voice* voice : active_voices_) {
651 if (voice->state().event == kVoiceKill)
652 excess_voices--;
653 else if (!released && voice->key_state() == Voice::kReleased)
654 released = voice;
655 else if (!sustained && voice->key_state() == Voice::kSustained)
656 sustained = voice;
657 else if (!held)
658 held = voice;
659 }
660
661 if (excess_voices <= 0)
662 return nullptr;
663
664 if (released)
665 return released;
666 if (sustained)
667 return sustained;
668 if (held)
669 return held;
670
671 return nullptr;
672 }
673
674 int VoiceHandler::grabNextUnplayedPressedNote() {
675 // Finds the next pressed note that is not currently playing, depending on voice priority.
676
677 auto iter = pressed_notes_.begin();
678
679 if (voice_priority_ == kNewest) {
680 iter = pressed_notes_.end();
681 while (iter != pressed_notes_.begin()) {
682 --iter;
683 if (!isNotePlaying(getNote(*iter), getChannel(*iter)))
684 break;
685 }
686 }
687 else {
688 for (; iter != pressed_notes_.end(); ++iter) {
689 if (!isNotePlaying(getNote(*iter), getChannel(*iter)))
690 break;
691 }
692 }
693
694 int old_note_value = *iter;
695 if (voice_priority_ == kRoundRobin) {
696 pressed_notes_.erase(iter);
697 pressed_notes_.push_back(old_note_value);
698 }
699 return old_note_value;
700 }
701
702 void VoiceHandler::sortVoicePriority() {
703 // Sorts active voices and pressed notes based on the configured priority.
704 switch (voice_priority_) {
705 case kHighest:
706 active_voices_.sort<voiceCompareLowestFirst>();
707 pressed_notes_.sort<pressedCompareHighestFirst>();
708 break;
709 case kLowest:
710 active_voices_.sort<voiceCompareHighestFirst>();
711 pressed_notes_.sort<pressedCompareLowestFirst>();
712 break;
713 case kOldest:
714 active_voices_.sort<voiceCompareNewestFirst>();
715 break;
716 default:
717 break;
718 }
719 }
720
721 void VoiceHandler::noteOn(int note, mono_float velocity, int sample, int channel) {
723
724 Voice* voice = grabVoice();
725 if (!voice)
726 return;
727
728 mono_float tuned_note = note;
729 if (tuning_)
730 tuned_note = tuning_->convertMidiNote(note);
731
732 poly_float last_note_val = tuned_note;
733 if (last_played_note_[0] >= 0.0f)
734 last_note_val = last_played_note_;
735 last_played_note_ = tuned_note;
736
737 int note_value = combineNoteChannel(note, channel);
738 pressed_notes_.remove(note_value);
739 pressed_notes_.push_back(note_value);
740
741 total_notes_++;
742 voice->activate(note, tuned_note, velocity, last_note_val,
743 pressed_notes_.size(), total_notes_, sample, channel);
744 voice->setLocalPitchBend(pitch_wheel_values_[channel]);
745 voice->setAftertouch(pressure_values_[channel]);
746 voice->setSlide(slide_values_[channel]);
747 active_voices_.push_back(voice);
748
749 sortVoicePriority();
750 }
751
752 void VoiceHandler::noteOff(int note, mono_float lift, int sample, int channel) {
753 // Removes any pressing of the note from the pressed_notes_ queue.
754 pressed_notes_.removeAll(combineNoteChannel(note, channel));
755
756 for (Voice* voice : active_voices_) {
757 if (voice->state().midi_note == note && voice->state().channel == channel) {
758 if (sustain_[channel]) {
759 voice->sustain();
760 voice->setLiftVelocity(lift);
761 }
762 else {
763 // If more notes are pressed than we have polyphony, reassign this voice immediately.
764 if (polyphony_ <= pressed_notes_.size() && voice->state().event != kVoiceKill) {
765 Voice* new_voice = voice;
766 if (voice_override_ == kKill) {
767 voice->kill();
768 new_voice = grabVoice();
769 }
770 else
771 active_voices_.remove(voice);
772
773 if (voice_priority_ == kNewest)
774 active_voices_.push_front(new_voice);
775 else
776 active_voices_.push_back(new_voice);
777
778 int old_note_value = grabNextUnplayedPressedNote();
779
780 mono_float old_note = getNote(old_note_value);
781 int old_channel = getChannel(old_note_value);
782 mono_float tuned_note = old_note;
783 if (tuning_)
784 tuned_note = tuning_->convertMidiNote(old_note);
785
786 total_notes_++;
787 new_voice->activate(old_note, tuned_note, voice->state().velocity,
788 last_played_note_, pressed_notes_.size() + 1,
789 total_notes_, sample, old_channel);
790 voice->setLocalPitchBend(pitch_wheel_values_[channel]);
791 voice->setAftertouch(pressure_values_[channel]);
792 voice->setSlide(slide_values_[channel]);
793 }
794 else {
795 voice->deactivate(sample);
796 voice->setLiftVelocity(lift);
797 }
798 }
799 }
800 }
801 sortVoicePriority();
802 }
803
804 void VoiceHandler::setAftertouch(int note, mono_float aftertouch, int sample, int channel) {
805 // Per-note aftertouch.
806 for (Voice* voice : active_voices_) {
807 if (voice->state().midi_note == note && voice->state().channel == channel)
808 voice->setAftertouch(aftertouch, sample);
809 }
810 }
811
812 void VoiceHandler::setChannelAftertouch(int channel, mono_float aftertouch, int sample) {
813 // Applies aftertouch to all held voices in the channel.
814 pressure_values_[channel] = aftertouch;
815 for (Voice* voice : active_voices_) {
816 if (voice->state().channel == channel && voice->held())
817 voice->setAftertouch(aftertouch, sample);
818 }
819 }
820
821 void VoiceHandler::setChannelRangeAftertouch(int from_channel, int to_channel, mono_float aftertouch, int sample) {
822 // Applies aftertouch to all held voices in the range of channels.
823 for (Voice* voice : active_voices_) {
824 int ch = voice->state().channel;
825 if (ch >= from_channel && ch <= to_channel)
826 voice->setAftertouch(aftertouch, sample);
827 }
828 }
829
830 void VoiceHandler::setChannelSlide(int channel, mono_float slide, int sample) {
831 slide_values_[channel] = slide;
832 for (Voice* voice : active_voices_) {
833 if (voice->state().channel == channel && voice->held())
834 voice->setSlide(slide, sample);
835 }
836 }
837
838 void VoiceHandler::setChannelRangeSlide(int from_channel, int to_channel, mono_float slide, int sample) {
839 // Similar logic for MPE "slide" data.
840 for (Voice* voice : active_voices_) {
841 int ch = voice->state().channel;
842 if (ch >= from_channel && ch <= to_channel)
843 voice->setSlide(slide, sample);
844 }
845 }
846
847 void VoiceHandler::setPolyphony(int polyphony) {
848 // If we need more voices, add parallel voice groups until we reach that count.
849 while (all_voices_.size() < static_cast<size_t>(polyphony))
850 addParallelVoices();
851
852 // If we are above the new polyphony, forcibly kill extra voices.
853 int num_voices_to_kill = active_voices_.size() - polyphony;
854 for (int i = 0; i < num_voices_to_kill; ++i) {
855 Voice* sacrifice = getVoiceToKill(polyphony);
856 if (sacrifice)
857 sacrifice->kill();
858 }
859
860 polyphony_ = polyphony;
861 }
862
864 // Returns the tuned note of the last active voice, if any.
865 if (active_voices_.size())
866 return active_voices_.back()->state().tuned_note;
867 return 0.0f;
868 }
869
871 processor->setSampleRate(getSampleRate());
872 voice_router_.addProcessor(processor);
873 }
874
876 processor->setSampleRate(getSampleRate());
877 voice_router_.addIdleProcessor(processor);
878 }
879
881 voice_router_.removeProcessor(processor);
882 }
883
885 global_router_.addProcessor(processor);
886 }
887
889 global_router_.removeProcessor(processor);
890 }
891
893 voice_router_.resetFeedbacks(reset_mask);
894 }
895
897 VITAL_ASSERT(accumulated_outputs_.count(output) == 0);
898 VITAL_ASSERT(last_voice_outputs_.count(output) == 0);
899
900 Output* new_output = new Output(output->buffer_size);
901 new_output->owner = this;
903
904 // Decide if we accumulate (summing across voices) or keep individual.
906 accumulated_outputs_[output] = std::unique_ptr<Output>(new_output);
907 else {
908 last_voice_outputs_[output] = std::unique_ptr<Output>(new_output);
909 nonaccumulated_outputs_.ensureCapacity(static_cast<int>(last_voice_outputs_.size()));
910 }
911 return new_output;
912 }
913
915 // A separate route for CR outputs that are always single-sample or non-accumulated.
916 VITAL_ASSERT(accumulated_outputs_.count(output) == 0);
917 VITAL_ASSERT(last_voice_outputs_.count(output) == 0);
918
919 cr::Output* new_output = new cr::Output();
920 new_output->owner = this;
922
923 last_voice_outputs_[output] = std::unique_ptr<Output>(new_output);
924 nonaccumulated_outputs_.ensureCapacity(static_cast<int>(last_voice_outputs_.size()));
925 if (active)
926 nonaccumulated_outputs_.push_back({ output, new_output });
927 return new_output;
928 }
929
931 // Not supported. Asserts false.
932 VITAL_ASSERT(false);
933 return output;
934 }
935
936 bool VoiceHandler::isPolyphonic(const Processor* processor) const {
937 // Voice router processes polyphonic data, so check if it is that router.
938 return (processor == &voice_router_);
939 }
940
942 if (last_voice_outputs_.count(output) == 0)
943 return;
944 std::pair<Output*, Output*> pair(output, last_voice_outputs_[output].get());
945 if (!nonaccumulated_outputs_.contains(pair))
946 nonaccumulated_outputs_.push_back(pair);
947 }
948
950 if (last_voice_outputs_.count(output) == 0)
951 return;
952 std::pair<Output*, Output*> pair(output, last_voice_outputs_[output].get());
953 utils::zeroBuffer(pair.second->buffer, pair.second->buffer_size);
954 nonaccumulated_outputs_.remove(pair);
955 }
956
957 void VoiceHandler::addParallelVoices() {
958 // Creates a new AggregateVoice with kParallelVoices voices, each referencing a shared processor clone.
959
960 poly_float voice_value = 0.0f;
961 for (int i = 0; i < kParallelVoices; ++i) {
962 voice_value.set(2 * i, i);
963 voice_value.set(2 * i + 1, i);
964 }
965
966 std::unique_ptr<AggregateVoice> aggregate_voice = std::make_unique<AggregateVoice>();
967 aggregate_voice->processor = std::unique_ptr<Processor>(voice_router_.clone());
968 aggregate_voice->processor->process(1); // Ensure the processor initializes.
969
970 aggregate_voice->voices.reserve(kParallelVoices);
971 for (int i = 0; i < kParallelVoices; ++i) {
972 std::unique_ptr<Voice> single_voice = std::make_unique<Voice>(aggregate_voice.get());
973 single_voice->setVoiceInfo(i, poly_float::equal(voice_value, i));
974
975 aggregate_voice->voices.push_back(single_voice.get());
976 free_voices_.push_back(single_voice.get());
977 all_voices_.push_back(std::move(single_voice));
978 }
979
980 all_aggregate_voices_.push_back(std::move(aggregate_voice));
981 }
982
983} // namespace vital
vital::mono_float convertMidiNote(int note) const
Converts a MIDI note number to a pitch offset based on the current tuning.
Definition tuning.cpp:370
void removeAll(T entry)
Removes all occurrences of a given element.
Definition circular_queue.h:297
force_inline void remove(T entry)
Removes the first occurrence of an element if found.
Definition circular_queue.h:283
force_inline int size() const
Returns the current number of elements in the queue.
Definition circular_queue.h:410
void reserve(int capacity)
Ensures that the queue has at least the given capacity.
Definition circular_queue.h:159
force_inline iterator end() const
Returns an iterator to the past-the-end element of the queue.
Definition circular_queue.h:432
force_inline iterator begin() const
Returns an iterator to the first element of the queue.
Definition circular_queue.h:425
force_inline void clear()
Clears all elements in the queue.
Definition circular_queue.h:388
void sort()
Sorts the elements according to a compare function.
Definition circular_queue.h:312
force_inline void push_back(T entry)
Pushes an element to the back of the queue.
Definition circular_queue.h:220
force_inline iterator erase(iterator &iter)
Erases the element at the iterator position and returns an iterator to the next element.
Definition circular_queue.h:350
Base class for all signal-processing units in Vital.
Definition processor.h:212
virtual Output * registerOutput(Output *output, int index)
Registers a new Output in the output list at a specified index.
Definition processor.cpp:223
virtual bool isPolyphonic() const
Checks if this Processor is polyphonic by querying its ProcessorRouter.
Definition processor.cpp:73
force_inline Input * input(unsigned int index=0) const
Retrieves the Input pointer at a given index.
Definition processor.h:587
force_inline int getSampleRate() const
Retrieves the current (effective) sample rate.
Definition processor.h:326
force_inline bool isControlRate() const
Checks if this Processor is running at control rate (buffer_size == 1).
Definition processor.h:342
force_inline int getOversampleAmount() const
Retrieves the current oversampling factor.
Definition processor.h:334
force_inline Output * output(unsigned int index=0) const
Retrieves the Output pointer at a given index.
Definition processor.h:616
virtual void setSampleRate(int sample_rate)
Updates the sample rate of this Processor (scaled by oversampling).
Definition processor.h:285
force_inline void router(ProcessorRouter *router)
Sets the ProcessorRouter that owns or manages this Processor.
Definition processor.h:507
virtual Processor * clone() const override
Creates a copy of this ProcessorRouter.
Definition processor_router.h:59
virtual void removeProcessor(Processor *processor)
Removes a Processor from this router.
Definition processor_router.cpp:151
virtual void init() override
Initializes the ProcessorRouter and all its Processors.
Definition processor_router.cpp:84
virtual void setSampleRate(int sample_rate) override
Sets the sample rate for all Processors in this router.
Definition processor_router.cpp:90
virtual void addIdleProcessor(Processor *processor)
Adds a Processor that should remain idle (not processed) in the router.
Definition processor_router.cpp:146
virtual void resetFeedbacks(poly_mask reset_mask)
Resets all Feedback nodes within this router using a reset mask.
Definition processor_router.cpp:273
virtual void process(int num_samples) override
Processes audio through all Processors managed by this router.
Definition processor_router.cpp:57
virtual void addProcessor(Processor *processor)
Adds a Processor to be managed by this router.
Definition processor_router.cpp:121
A ProcessorRouter that encapsulates a cohesive unit of functionality in the synthesizer.
Definition synth_module.h:129
force_inline Output * velocity()
Returns a pointer to velocity, the note-on velocity.
Definition voice_handler.h:634
void removeGlobalProcessor(Processor *processor)
Removes a Processor from the global router.
Definition voice_handler.cpp:888
void allNotesOffRange(int sample, int from_channel, int to_channel)
Definition voice_handler.cpp:546
force_inline Output * note()
Returns a pointer to the note Output, giving the current tuned note frequency or pitch.
Definition voice_handler.h:616
void setPolyphony(int polyphony)
Sets the polyphony to a new value, allocating or freeing voices as needed.
Definition voice_handler.cpp:847
virtual void noteOn(int note, mono_float velocity, int sample, int channel) override
Handles a MIDI note-on event, starting a note with a specified velocity and timing.
Definition voice_handler.cpp:721
void resetFeedbacks(poly_mask reset_mask) override
Resets any feedback paths in the poly router, applying the given mask.
Definition voice_handler.cpp:892
void setChannelAftertouch(int channel, mono_float aftertouch, int sample)
Sets channel-wide aftertouch (applies to all voices on that channel).
Definition voice_handler.cpp:812
@ kVoicePriority
Priority scheme for stealing or reassigning voices.
Definition voice_handler.h:391
@ kVoiceOverride
Behavior when exceeding polyphony: kill or steal.
Definition voice_handler.h:392
@ kPolyphony
Desired polyphony setting (1..kMaxActivePolyphony).
Definition voice_handler.h:390
force_inline Output * channel()
Returns a pointer to channel, indicating the MIDI channel of the voice.
Definition voice_handler.h:631
virtual void init() override
Initializes the voice and global routers, then calls SynthModule::init().
Definition voice_handler.cpp:409
void setAftertouch(int note, mono_float aftertouch, int sample, int channel)
Handles per-note aftertouch for a specific note and channel.
Definition voice_handler.cpp:804
force_inline Output * slide()
Returns a pointer to slide, the MPE "slide" expression value.
Definition voice_handler.h:643
void sostenutoOn(int channel)
Turns on sostenuto for a single channel.
Definition voice_handler.cpp:457
int getNumActiveVoices()
Returns the number of currently active voices (not dead).
Definition voice_handler.cpp:425
force_inline int polyphony()
Returns the current maximum polyphony (number of active voices allowed).
Definition voice_handler.h:670
virtual ~VoiceHandler()
Virtual destructor.
Definition voice_handler.cpp:141
bool isNotePlaying(int note)
Checks if a given MIDI note is playing.
Definition voice_handler.cpp:429
void setActiveNonaccumulatedOutput(Output *output)
Marks an Output as "active" for non-accumulated usage (e.g., for the last active voice only).
Definition voice_handler.cpp:941
void removeProcessor(Processor *processor) override
Removes a Processor from this router.
Definition voice_handler.cpp:880
virtual void process(int num_samples) override
Processes audio for a block of samples. For each active voice, triggers events, updates parameters,...
Definition voice_handler.cpp:321
VoiceOverride
Behavior for assigning a new note when at max polyphony.
Definition voice_handler.h:400
@ kKill
Immediately kill an existing voice to free one.
Definition voice_handler.h:401
virtual void noteOff(int note, mono_float velocity, int sample, int channel) override
Handles a MIDI note-off event, releasing a currently active note.
Definition voice_handler.cpp:752
void sostenutoOffRange(int sample, int from_channel, int to_channel)
Turns off sostenuto for a range of channels, prompting release if not sustained.
Definition voice_handler.cpp:503
void addGlobalProcessor(Processor *processor)
Adds a Processor to the "global" (monophonic) router, e.g. for final mixing or master effects.
Definition voice_handler.cpp:884
force_inline Output * aftertouch()
Returns a pointer to aftertouch, storing per-voice or channel-based aftertouch.
Definition voice_handler.h:640
void allNotesOff(int sample) override
Turns off all currently active notes, optionally specifying a sample index for timing.
Definition voice_handler.cpp:530
VoicePriority
Determines the voice stealing strategy (oldest, newest, highest, etc.).
Definition voice_handler.h:410
@ kRoundRobin
Definition voice_handler.h:415
@ kHighest
Definition voice_handler.h:413
@ kOldest
Definition voice_handler.h:412
@ kNewest
Definition voice_handler.h:411
@ kLowest
Definition voice_handler.h:414
void sustainOff(int sample, int channel)
Turns off sustain for a single channel, prompting voices to release.
Definition voice_handler.cpp:449
void addProcessor(Processor *processor) override
Adds a Processor to be managed by this router.
Definition voice_handler.cpp:870
static constexpr mono_float kLocalPitchBendRange
Range of local pitch bend in semitones for each voice.
Definition voice_handler.h:383
void addIdleProcessor(Processor *processor) override
Adds a Processor that should remain idle (not processed) in the router.
Definition voice_handler.cpp:875
VoiceHandler()=delete
Disabled default constructor; must specify polyphony and output count.
void allSoundsOff() override
Immediately turns off all sounding notes and stops all sound production.
Definition voice_handler.cpp:517
void setInactiveNonaccumulatedOutput(Output *output)
Marks an Output as "inactive" for non-accumulated usage, effectively disabling it.
Definition voice_handler.cpp:949
mono_float getLastActiveNote() const
Gets the last active note's tuned frequency (or 0 if none).
Definition voice_handler.cpp:863
void sostenutoOff(int sample, int channel)
Turns off sostenuto for a single channel, prompting release if not sustained.
Definition voice_handler.cpp:465
void sustainOffRange(int sample, int from_channel, int to_channel)
Turns off sustain for a range of channels, prompting voices to release.
Definition voice_handler.cpp:482
Output * registerOutput(Output *output) override
Registers an Output with this VoiceHandler, returning a pointer to a new accumulated or single-lane O...
Definition voice_handler.cpp:896
virtual void setSampleRate(int sample_rate) override
Sets the sample rate for both mono/global and voice (poly) routers.
Definition voice_handler.cpp:416
force_inline Output * lift()
Returns a pointer to lift, the note-off velocity or release velocity.
Definition voice_handler.h:637
Output * registerControlRateOutput(Output *output, bool active)
Registers a control-rate Output with the VoiceHandler.
Definition voice_handler.cpp:914
virtual bool shouldAccumulate(Output *output)
Determines whether an Output should be summed across voices (accumulated) or handled individually.
Definition voice_handler.cpp:316
void sustainOnRange(int from_channel, int to_channel)
Turns on sustain for a range of channels.
Definition voice_handler.cpp:477
void sustainOn(int channel)
Turns on sustain for a single channel.
Definition voice_handler.cpp:445
void setChannelRangeAftertouch(int from_channel, int to_channel, mono_float aftertouch, int sample)
Sets channel-wide aftertouch for a range of channels.
Definition voice_handler.cpp:821
poly_mask getCurrentVoiceMask()
Returns a mask for the last active voice, used for writing to output buffers.
Definition voice_handler.cpp:556
void setChannelSlide(int channel, mono_float aftertouch, int sample)
Sets channel-wide MPE "slide" for a single channel.
Definition voice_handler.cpp:830
void setChannelRangeSlide(int from_channel, int to_channel, mono_float aftertouch, int sample)
Sets channel-wide MPE "slide" for a range of channels.
Definition voice_handler.cpp:838
void sostenutoOnRange(int from_channel, int to_channel)
Turns on sostenuto for a range of channels.
Definition voice_handler.cpp:493
Represents a single playing note/voice, including voice-state and event handling.
Definition voice_handler.h:54
force_inline void shiftAftertouchEvent(int num_samples)
Shifts the aftertouch event sample index by num_samples.
Definition voice_handler.h:288
force_inline void clearSlideEvent()
Clears the unprocessed slide event, if any.
Definition voice_handler.h:307
force_inline mono_float slide()
Returns the current slide (MPE expression) value for this voice.
Definition voice_handler.h:117
force_inline void kill(int sample=0)
Immediately kills this voice (disregarding release).
Definition voice_handler.h:221
KeyState
Describes the lifecycle stage of a voice: kTriggering -> kHeld -> kReleased -> kDead,...
Definition voice_handler.h:64
@ kDead
The voice is no longer active.
Definition voice_handler.h:69
@ kReleased
The note has ended (off event) and is releasing.
Definition voice_handler.h:68
@ kSustained
The note has ended, but sustain pedal is holding it on.
Definition voice_handler.h:67
@ kTriggering
Note-on occurred, but hasn't processed yet.
Definition voice_handler.h:65
@ kHeld
The note is actively held down.
Definition voice_handler.h:66
force_inline mono_float aftertouch()
Returns the current aftertouch value for this voice.
Definition voice_handler.h:111
force_inline void setLocalPitchBend(mono_float bend)
Sets the local pitch bend (used for legato transitions or channel pitch bend).
Definition voice_handler.h:198
force_inline int event_sample()
Returns the sample index at which the latest event (on/off) was triggered.
Definition voice_handler.h:102
force_inline poly_mask voice_mask()
Returns the SIMD mask representing this voice's active lanes.
Definition voice_handler.h:108
force_inline mono_float aftertouch_sample()
Returns the sample index at which the latest aftertouch event occurred.
Definition voice_handler.h:114
force_inline void setAftertouch(mono_float aftertouch, int sample=0)
Sets the aftertouch (pressure) value for the voice.
Definition voice_handler.h:241
force_inline const KeyState last_key_state()
Returns the previous key state (before the most recent update).
Definition voice_handler.h:96
force_inline void markDead()
Marks this voice as kDead, meaning it's completely inactive.
Definition voice_handler.h:227
Voice()=delete
Disabled default constructor: Voice must belong to an AggregateVoice.
force_inline void shiftVoiceEvent(int num_samples)
Shifts the event sample index by num_samples (e.g., for partial block processing).
Definition voice_handler.h:280
force_inline const VoiceState & state()
Returns a const reference to the VoiceState struct that holds all relevant data.
Definition voice_handler.h:93
force_inline bool hasNewEvent()
Checks if there is a new (non-processed) on/off event for this voice.
Definition voice_handler.h:232
force_inline void shiftSlideEvent(int num_samples)
Shifts the slide event sample index by num_samples.
Definition voice_handler.h:296
force_inline bool hasNewSlide()
Returns true if there's a new slide event not yet processed.
Definition voice_handler.h:262
force_inline void setSlide(mono_float slide, int sample=0)
Sets the MPE "slide" value for the voice (often CC#74).
Definition voice_handler.h:251
force_inline void completeVoiceEvent()
Completes (consumes) the voice event, marking it as processed. If the voice was kTriggering,...
Definition voice_handler.h:270
force_inline void clearAftertouchEvent()
Clears the unprocessed aftertouch event, if any.
Definition voice_handler.h:302
force_inline bool hasNewAftertouch()
Returns true if there's a new aftertouch event not yet processed.
Definition voice_handler.h:257
force_inline void activate(int midi_note, mono_float tuned_note, mono_float velocity, poly_float last_note, int note_pressed, int note_count, int sample, int channel)
Activates (starts) the voice with the given note parameters.
Definition voice_handler.h:133
force_inline mono_float slide_sample()
Returns the sample index at which the latest slide event occurred.
Definition voice_handler.h:120
#define VITAL_ASSERT(x)
Definition common.h:11
#define force_inline
Definition common.h:23
Contains a collection of utility functions and classes used throughout Vital.
const poly_mask kFullMask
A mask covering all lanes of a poly_float vector.
Definition synth_constants.h:257
const poly_mask kFirstMask
A mask identifying the first voice slots in a polyphonic vector.
Definition synth_constants.h:266
force_inline int iclamp(int value, int min_val, int max_val)
Clamps an integer between [min_val, max_val].
Definition utils.h:250
force_inline poly_float mod(poly_float value)
Returns the fractional part of each lane by subtracting the floored value.
Definition poly_utils.h:814
force_inline void zeroBuffer(mono_float *buffer, int size)
Zeros a mono buffer (standard array).
Definition poly_utils.h:570
force_inline poly_int roundToInt(poly_float value)
Rounds each lane to the nearest integer.
Definition poly_utils.h:792
force_inline poly_float swapVoices(poly_float value)
Swaps the first half of the lanes with the second half.
Definition poly_utils.h:437
force_inline poly_float maskLoad(poly_float zero_value, poly_float one_value, poly_mask reset_mask)
Selects between two values (zero_value or one_value) based on a mask in each lane.
Definition poly_utils.h:351
Contains classes and functions used within the Vital synthesizer framework.
@ kVoiceOn
Definition common.h:77
@ kVoiceKill
Definition common.h:81
@ kVoiceOff
Definition common.h:80
constexpr int kNumMidiChannels
MIDI channels available per device.
Definition common.h:57
constexpr int kNotesPerOctave
Number of semitones per octave.
Definition common.h:51
constexpr int kMaxActivePolyphony
The maximum number of active voices Vital uses simultaneously.
Definition synth_constants.h:43
constexpr int kMidiSize
MIDI note count (0-127).
Definition common.h:44
poly_int poly_mask
Alias for clarity; used as a mask type in poly_float.
Definition poly_values.h:590
constexpr int kMaxPolyphony
The maximum number of voices allocated for polyphony (includes an extra for handling transitions).
Definition synth_constants.h:40
float mono_float
Definition common.h:33
An aggregate grouping that pairs multiple (parallel) voices with a shared Processor instance.
Definition voice_handler.h:365
CircularQueue< Voice * > voices
Collection of active Voice pointers.
Definition voice_handler.h:366
Holds and manages a buffer of samples (poly_float) for a Processor's output.
Definition processor.h:35
force_inline void trigger(poly_mask mask, poly_float value, poly_int offset)
Sets trigger values (mask, trigger value, and offset).
Definition processor.h:63
poly_float * buffer
Pointer to the output buffer.
Definition processor.h:110
Processor * owner
Owning processor.
Definition processor.h:112
int buffer_size
Current buffer size in samples.
Definition processor.h:114
force_inline void clearTrigger()
Clears the trigger mask, value, and offset.
Definition processor.h:72
poly_float trigger_value
Trigger values for voices.
Definition processor.h:116
int channel
Which MIDI channel this voice is associated with.
Definition voice_handler.h:41
int midi_note
MIDI note (0-127 usually).
Definition voice_handler.h:33
VoiceEvent event
The most recent voice event (on/off/kill).
Definition voice_handler.h:32
int note_pressed
Pressed note count (e.g., for note priority logic).
Definition voice_handler.h:39
mono_float velocity
Velocity of the note-on event.
Definition voice_handler.h:36
mono_float lift
Velocity of the note-off (a.k.a. release velocity).
Definition voice_handler.h:37
mono_float tuned_note
Possibly adjusted by a Tuning object.
Definition voice_handler.h:34
poly_float last_note
Holds the last note played for this voice.
Definition voice_handler.h:35
A specialized Output that always runs at control rate (buffer_size = 1).
Definition processor.h:189
Represents a vector of floating-point values using SIMD instructions.
Definition poly_values.h:600
static force_inline mask_simd_type vector_call equal(simd_type one, simd_type two)
Compares two SIMD float registers for equality, element-wise.
Definition poly_values.h:954
force_inline void vector_call set(size_t index, float new_value) noexcept
Sets a specific element in the SIMD register.
Definition poly_values.h:1182
Represents a vector of integer values using SIMD instructions.
Definition poly_values.h:56
Provides various utility functions, classes, and constants for audio, math, and general-purpose opera...
Declares classes and data structures to handle polyphonic voices in Vital, including voice assignment...