From feba37aae89a79bf46a8f4cbe716729586de89ca Mon Sep 17 00:00:00 2001 From: Christopher Herb Date: Fri, 7 Jul 2023 15:48:32 +0200 Subject: [PATCH] added midi synth base class and voice allocator --- synth/midi_synth.h | 93 +++++++++++++++++++++++++++++++++++++++++ synth/voice_allocator.h | 7 ++-- 2 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 synth/midi_synth.h diff --git a/synth/midi_synth.h b/synth/midi_synth.h new file mode 100644 index 0000000..bd9758a --- /dev/null +++ b/synth/midi_synth.h @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include "note_event.h" +#include "voice_allocator.h" + +namespace trnr::lib::synth { + +// a generic midi synth base class with sample accurate event handling. +template +class midi_synth : public voice_allocator { +public: + midi_synth(int _n_voices, double _samplerate, int _block_size) + : m_samplerate { _samplerate } + , m_block_size { _block_size } + , m_voices_active { false } + { + } + + void set_samplerate(double _samplerate, int _block_size) + { + m_samplerate = _samplerate; + m_block_size = _block_size; + voice_allocator::set_samplerate(_samplerate); + } + + void process_block(double* _output, int _n_frames) + { + // sample accurate event handling based on the iPlug2 synth by Oli Larkin + if (m_voices_active || !m_event_queue.empty()) { + int block_size = m_block_size; + int samples_remaining = _n_frames; + int start_index = 0; + + while (samples_remaining > 0) { + + if (samples_remaining < block_size) + block_size = samples_remaining; + + while (!m_event_queue.empty()) { + note_event event = m_event_queue.front(); + + // we assume the messages are in chronological order. If we find one later than the current block we are done. + if (event.offset > start_index + block_size) + break; + + // send performance messages to the voice allocator + // message offset is relative to the start of this process_samples() block + event.offset -= start_index; + voice_allocator::add_event(event); + + m_event_queue.erase(m_event_queue.begin()); + } + + voice_allocator::process_samples(_output, start_index, block_size); + + samples_remaining -= block_size; + start_index += block_size; + } + + voices_active = voice_allocator::voices_active(); + + flush_event_queue(_n_frames); + } else { + for (int s = 0; s < _n_frames; s++) { + outputs[0][s] = 0.; + outputs[1][s] = 0.; + } + } + } + + void add_event(note_event event) + { + if (event.type == note_event_type::note_on) + m_voices_active = true; + + m_event_queue.push_back(event); + } + + void flush_event_queue(int frames) + { + for (int i = 0; i < m_event_queue.size(); i++) { + m_event_queue.at(i).offset -= frames; + } + } + +private: + std::vector m_event_queue; + double m_samplerate; + int m_block_size; + bool m_voices_active; +}; +} diff --git a/synth/voice_allocator.h b/synth/voice_allocator.h index a19cbe1..97bc375 100644 --- a/synth/voice_allocator.h +++ b/synth/voice_allocator.h @@ -47,17 +47,16 @@ public: std::for_each(voices.begin(), voices.end(), f); } - void process_samples(double** _outputs, int _start_index, int _block_size) + void process_samples(double* _output, int _start_index, int _block_size) { for (int s = _start_index; s < _start_index + _block_size; s++) { process_events(s); float voices_signal = 0.; - std::for_each(voices.begin(), voices.end(), [&voices_signal](t_voice& voice) { voices_signal += (voice.ProcessSample() / 3.); }); + std::for_each(voices.begin(), voices.end(), [&voices_signal](t_voice& voice) { voices_signal += (voice.process_sample() / 3.); }); - _outputs[0][s] = voices_signal; - _outputs[1][s] = voices_signal; + _output[s] = voices_signal; } }