added midi synth base class and voice allocator

This commit is contained in:
Christopher Herb
2023-07-07 15:48:32 +02:00
parent d9bcd96276
commit feba37aae8
2 changed files with 96 additions and 4 deletions

93
synth/midi_synth.h Normal file
View File

@@ -0,0 +1,93 @@
#pragma once
#include <memory>
#include <vector>
#include "note_event.h"
#include "voice_allocator.h"
namespace trnr::lib::synth {
// a generic midi synth base class with sample accurate event handling.
template <typename t_synth>
class midi_synth : public voice_allocator<t_synth> {
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<note_event> m_event_queue;
double m_samplerate;
int m_block_size;
bool m_voices_active;
};
}

View File

@@ -47,17 +47,16 @@ public:
std::for_each(voices.begin(), voices.end(), f); 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++) { for (int s = _start_index; s < _start_index + _block_size; s++) {
process_events(s); process_events(s);
float voices_signal = 0.; 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; _output[s] = voices_signal;
_outputs[1][s] = voices_signal;
} }
} }