simplify voice allocator, high level triplex synth implementation
This commit is contained in:
@@ -1,48 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace trnr {
|
|
||||||
|
|
||||||
enum midi_event_type {
|
|
||||||
note_on = 0,
|
|
||||||
note_off,
|
|
||||||
pitch_wheel,
|
|
||||||
mod_wheel
|
|
||||||
};
|
|
||||||
|
|
||||||
class midi_event {
|
|
||||||
public:
|
|
||||||
midi_event_type type;
|
|
||||||
int offset = 0;
|
|
||||||
int midi_note = 0;
|
|
||||||
float velocity = 1.f;
|
|
||||||
double data = 0;
|
|
||||||
|
|
||||||
void make_note_on(int _midi_note, float _velocity, int _offset = 0)
|
|
||||||
{
|
|
||||||
type = midi_event_type::note_on;
|
|
||||||
midi_note = _midi_note;
|
|
||||||
velocity = _velocity;
|
|
||||||
offset = _offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
void make_note_off(int _midi_note, float _velocity, int _offset = 0)
|
|
||||||
{
|
|
||||||
type = midi_event_type::note_off;
|
|
||||||
midi_note = _midi_note;
|
|
||||||
velocity = _velocity;
|
|
||||||
offset = _offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
void make_pitch_wheel(double _pitch, int _offset = 0)
|
|
||||||
{
|
|
||||||
type = midi_event_type::pitch_wheel;
|
|
||||||
data = _pitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
void make_mod_wheel(double _mod, int _offset = 0)
|
|
||||||
{
|
|
||||||
type = midi_event_type::pitch_wheel;
|
|
||||||
data = _mod;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace trnr
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "audio_buffer.h"
|
|
||||||
#include "midi_event.h"
|
|
||||||
#include "voice_allocator.h"
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace trnr {
|
|
||||||
|
|
||||||
// a generic midi synth base class with sample accurate event handling.
|
|
||||||
// the templated type t_voice must derive from ivoice
|
|
||||||
template <typename t_voice, typename t_sample>
|
|
||||||
class midi_synth : public voice_allocator<t_voice, t_sample> {
|
|
||||||
public:
|
|
||||||
std::vector<midi_event> m_event_queue;
|
|
||||||
int m_block_size;
|
|
||||||
bool m_voices_active;
|
|
||||||
|
|
||||||
midi_synth(size_t num_voices = 1)
|
|
||||||
: m_voices_active {false}
|
|
||||||
, voice_allocator<t_voice, t_sample>(num_voices)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_samplerate_blocksize(double _samplerate, int _block_size)
|
|
||||||
{
|
|
||||||
m_block_size = _block_size;
|
|
||||||
voice_allocator<t_voice, t_sample>::set_samplerate(_samplerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_block(t_sample** _outputs, int _n_frames, std::vector<audio_buffer<t_sample>> _modulators = {})
|
|
||||||
{
|
|
||||||
// clear outputs
|
|
||||||
for (auto i = 0; i < 2; i++) { memset(_outputs[i], 0, _n_frames * sizeof(t_sample)); }
|
|
||||||
|
|
||||||
// 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()) {
|
|
||||||
midi_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<t_voice, t_sample>::add_event(event);
|
|
||||||
|
|
||||||
m_event_queue.erase(m_event_queue.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_allocator<t_voice, t_sample>::process_samples(_outputs, start_index, block_size, _modulators);
|
|
||||||
|
|
||||||
samples_remaining -= block_size;
|
|
||||||
start_index += block_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_voices_active = voice_allocator<t_voice, t_sample>::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(midi_event event)
|
|
||||||
{
|
|
||||||
if (event.type == midi_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; }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace trnr
|
|
||||||
116
synth/synth.h
116
synth/synth.h
@@ -1,116 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "audio_buffer.h"
|
|
||||||
#include <array>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace trnr {
|
|
||||||
|
|
||||||
enum midi_event_type {
|
|
||||||
note_on = 0,
|
|
||||||
note_off,
|
|
||||||
pitch_wheel,
|
|
||||||
mod_wheel
|
|
||||||
};
|
|
||||||
|
|
||||||
struct midi_event {
|
|
||||||
midi_event_type type;
|
|
||||||
int offset;
|
|
||||||
int midi_note;
|
|
||||||
float velocity;
|
|
||||||
double data;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr size_t MAX_VOICES = 8;
|
|
||||||
constexpr size_t MAX_EVENTS_PER_VOICE = 32;
|
|
||||||
|
|
||||||
template <typename t_voice, typename t_sample>
|
|
||||||
void voice_process_block(t_voice& v, t_sample** frames, size_t num_frames, midi_event* events, size_t num_events,
|
|
||||||
const vector<audio_buffer<t_sample>>& mods = {});
|
|
||||||
|
|
||||||
template <typename t_voice>
|
|
||||||
struct synth {
|
|
||||||
array<t_voice, MAX_VOICES> voices;
|
|
||||||
array<array<midi_event, MAX_EVENTS_PER_VOICE>, MAX_VOICES> voice_events;
|
|
||||||
array<size_t, MAX_VOICES> counts;
|
|
||||||
int active_voice_count = 1;
|
|
||||||
size_t index_to_steal = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename t_voice>
|
|
||||||
void synth_init(synth<t_voice>& s, double samplerate)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < MAX_VOICES; ++i) {
|
|
||||||
s.voices[i] = t_voice();
|
|
||||||
s.voices[i].voice_init(samplerate);
|
|
||||||
s.counts[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename t_voice, typename t_sample>
|
|
||||||
void synth_process_block(synth<t_voice>& s, t_sample** frames, int num_frames, const vector<midi_event>& midi_events,
|
|
||||||
const vector<audio_buffer<t_sample>>& mods = {})
|
|
||||||
{
|
|
||||||
// reset voice events and counts
|
|
||||||
for (int i = 0; i < MAX_VOICES; i++) {
|
|
||||||
s.voice_events[i].fill(midi_event {});
|
|
||||||
s.counts[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& ev : midi_events) {
|
|
||||||
switch (ev.type) {
|
|
||||||
case note_on: {
|
|
||||||
bool found = false;
|
|
||||||
// attempt to find a free voice
|
|
||||||
for (size_t i = 0; i < s.active_voice_count; ++i) {
|
|
||||||
if (!s.voices[i].is_busy) {
|
|
||||||
s.voice_events[i][s.counts[i]++] = ev;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) break;
|
|
||||||
|
|
||||||
// try to find a voice that is not gated
|
|
||||||
for (size_t i = 0; i < s.active_voice_count; ++i) {
|
|
||||||
if (!s.voices[i].gate) {
|
|
||||||
s.voice_events[i][s.counts[i]++] = ev;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) break;
|
|
||||||
|
|
||||||
// if all voices are gated, steal one round-robin
|
|
||||||
s.voice_events[s.index_to_steal][s.counts[s.index_to_steal]++] = ev;
|
|
||||||
s.index_to_steal++;
|
|
||||||
if (s.index_to_steal >= s.active_voice_count) s.index_to_steal = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case note_off: {
|
|
||||||
for (size_t i = 0; i < s.active_voice_count; ++i) {
|
|
||||||
if (s.voices[i].midi_note == ev.midi_note) s.voice_events[i][s.counts[i]++] = ev;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case pitch_wheel:
|
|
||||||
case mod_wheel: {
|
|
||||||
for (size_t i = 0; i < s.active_voice_count; ++i) { s.voice_events[i][s.counts[i]++] = ev; }
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < s.active_voice_count; ++i) {
|
|
||||||
auto& v = s.voices[i];
|
|
||||||
auto& events = s.voice_events[i].data();
|
|
||||||
size_t num_events = s.counts[i];
|
|
||||||
voice_process_block(v, frames, num_frames, events, num_events, mods);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace trnr
|
|
||||||
499
synth/triplex.h
499
synth/triplex.h
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "audio_buffer.h"
|
||||||
#include "audio_math.h"
|
#include "audio_math.h"
|
||||||
#include "synth.h"
|
#include "voice_allocator.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
@@ -312,15 +313,11 @@ inline float tx_operator_process_sample(tx_operator& op, bool gate, bool trigger
|
|||||||
|
|
||||||
constexpr float MOD_INDEX_COEFF = 4.f;
|
constexpr float MOD_INDEX_COEFF = 4.f;
|
||||||
|
|
||||||
struct tx_voice {
|
struct tx_state {
|
||||||
bool gate = false;
|
|
||||||
bool trigger = false;
|
|
||||||
int midi_note = 0;
|
|
||||||
float velocity = 1.f;
|
|
||||||
float additional_pitch_mod = 0.f; // modulates pitch in frequency
|
float additional_pitch_mod = 0.f; // modulates pitch in frequency
|
||||||
|
|
||||||
int algorithm = 0;
|
int algorithm = 0;
|
||||||
float pitch_env_amt = 0.f;
|
float pitch_env_amt = 0.f;
|
||||||
|
float pitch_mod = 0.f;
|
||||||
float feedback_amt = 0.f;
|
float feedback_amt = 0.f;
|
||||||
float bit_resolution = 12.f;
|
float bit_resolution = 12.f;
|
||||||
tx_sineosc feedback_osc;
|
tx_sineosc feedback_osc;
|
||||||
@@ -328,130 +325,404 @@ struct tx_voice {
|
|||||||
tx_operator op1;
|
tx_operator op1;
|
||||||
tx_operator op2;
|
tx_operator op2;
|
||||||
tx_operator op3;
|
tx_operator op3;
|
||||||
|
|
||||||
float pitch_mod = 0.f;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline float calc_algo1(tx_voice& v, const float frequency)
|
inline void tx_voice_process_block(tx_state& t, voice_state& s, float** audio, size_t num_frames,
|
||||||
|
const vector<audio_buffer<float>>& mods)
|
||||||
{
|
{
|
||||||
float fb_freq = frequency * v.op3.ratio;
|
float frequency = midi_to_frequency(s.midi_note + t.pitch_mod + t.additional_pitch_mod);
|
||||||
float fb_mod_index = (v.feedback_amt * MOD_INDEX_COEFF);
|
|
||||||
float fb_signal = tx_sineosc_process_sample(v.feedback_osc, v.trigger, fb_freq) * fb_mod_index;
|
|
||||||
|
|
||||||
float op3_Freq = frequency * v.op3.ratio;
|
for (int i = 0; i < num_frames; i++) {
|
||||||
float op3_mod_index = (v.op3.amplitude * MOD_INDEX_COEFF);
|
|
||||||
float op3_signal =
|
|
||||||
tx_operator_process_sample(v.op3, v.gate, v.trigger, op3_Freq, v.velocity, fb_signal) * op3_mod_index;
|
|
||||||
|
|
||||||
float op2_freq = frequency * v.op2.ratio;
|
voice_process_event_for_frame(s, i);
|
||||||
float op2_mod_index = (v.op2.amplitude * MOD_INDEX_COEFF);
|
|
||||||
float op2_signal =
|
|
||||||
tx_operator_process_sample(v.op2, v.gate, v.trigger, op2_freq, v.velocity, op3_signal) * op2_mod_index;
|
|
||||||
|
|
||||||
float op1_freq = frequency * v.op1.ratio;
|
float pitch_env_signal = tx_envelope_process_sample(t.pitch_env, s.gate, s.trigger) * t.pitch_env_amt;
|
||||||
return tx_operator_process_sample(v.op1, v.gate, v.trigger, op1_freq, v.velocity, op2_signal) * v.op1.amplitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float calc_algo2(tx_voice& v, const float frequency)
|
|
||||||
{
|
|
||||||
float fb_freq = frequency * v.op3.ratio;
|
|
||||||
float fb_mod_index = (v.feedback_amt * MOD_INDEX_COEFF);
|
|
||||||
float fb_signal = tx_sineosc_process_sample(v.feedback_osc, v.trigger, fb_freq) * fb_mod_index;
|
|
||||||
|
|
||||||
float op3_freq = frequency * v.op3.ratio;
|
|
||||||
float op3_signal =
|
|
||||||
tx_operator_process_sample(v.op3, v.gate, v.trigger, op3_freq, v.velocity, fb_signal) * v.op3.amplitude;
|
|
||||||
|
|
||||||
float op2_freq = frequency * v.op2.ratio;
|
|
||||||
float op2_mod_index = (v.op2.amplitude * MOD_INDEX_COEFF);
|
|
||||||
float op2_signal = tx_operator_process_sample(v.op2, v.gate, v.trigger, op2_freq, v.velocity) * op2_mod_index;
|
|
||||||
|
|
||||||
float op1_freq = frequency * v.op1.ratio;
|
|
||||||
float op1_signal =
|
|
||||||
tx_operator_process_sample(v.op1, v.gate, v.trigger, op1_freq, v.velocity, op2_signal) * v.op1.amplitude;
|
|
||||||
|
|
||||||
return op1_signal + op3_signal;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float calc_algo3(tx_voice& v, const float frequency)
|
|
||||||
{
|
|
||||||
float fb_freq = frequency * v.op3.ratio;
|
|
||||||
float fb_mod_index = (v.feedback_amt * MOD_INDEX_COEFF);
|
|
||||||
float fb_signal = tx_sineosc_process_sample(v.feedback_osc, v.trigger, fb_freq) * fb_mod_index;
|
|
||||||
|
|
||||||
float op3_freq = frequency * v.op3.ratio;
|
|
||||||
float op3_signal =
|
|
||||||
tx_operator_process_sample(v.op3, v.gate, v.trigger, op3_freq, v.velocity, fb_signal) * v.op3.amplitude;
|
|
||||||
|
|
||||||
float op2_freq = frequency * v.op2.ratio;
|
|
||||||
float op2_signal = tx_operator_process_sample(v.op2, v.gate, v.trigger, op2_freq, v.velocity) * v.op2.amplitude;
|
|
||||||
|
|
||||||
float op1_freq = frequency * v.op1.ratio;
|
|
||||||
float op1_signal = tx_operator_process_sample(v.op1, v.gate, v.trigger, op1_freq, v.velocity) * v.op1.amplitude;
|
|
||||||
|
|
||||||
return op1_signal + op2_signal + op3_signal;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float calc_algo4(tx_voice& v, const float frequency)
|
|
||||||
{
|
|
||||||
float fb_freq = frequency * v.op3.ratio;
|
|
||||||
float fb_mod_index = (v.feedback_amt * MOD_INDEX_COEFF);
|
|
||||||
float fb_signal = tx_sineosc_process_sample(v.feedback_osc, v.trigger, fb_freq) * fb_mod_index;
|
|
||||||
|
|
||||||
float op3_freq = frequency * v.op3.ratio;
|
|
||||||
float op3_mod_index = (v.op3.amplitude * MOD_INDEX_COEFF);
|
|
||||||
float op3_signal =
|
|
||||||
tx_operator_process_sample(v.op3, v.gate, v.trigger, op3_freq, v.velocity, fb_signal) * op3_mod_index;
|
|
||||||
|
|
||||||
float op2_freq = frequency * v.op2.ratio;
|
|
||||||
float op2_mod_index = (v.op2.amplitude * MOD_INDEX_COEFF);
|
|
||||||
float op2_signal = tx_operator_process_sample(v.op2, v.gate, v.trigger, op2_freq, v.velocity) * op2_mod_index;
|
|
||||||
|
|
||||||
float op1_freq = frequency * v.op1.ratio;
|
|
||||||
return tx_operator_process_sample(v.op1, v.gate, v.trigger, op1_freq, v.velocity, op2_signal + op3_signal) *
|
|
||||||
v.op1.amplitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline void voice_process_block<tx_voice, float>(tx_voice& v, float** frames, size_t num_frames, midi_event* events,
|
|
||||||
size_t num_events, const vector<audio_buffer<float>>& mods)
|
|
||||||
{
|
|
||||||
float frequency = midi_to_frequency(v.midi_note + v.pitch_mod + v.additional_pitch_mod);
|
|
||||||
|
|
||||||
for (int s = 0; s < num_frames; s++) {
|
|
||||||
|
|
||||||
float pitch_env_signal = tx_envelope_process_sample(v.pitch_env, v.gate, v.trigger) * v.pitch_env_amt;
|
|
||||||
float pitched_freq = frequency + pitch_env_signal;
|
float pitched_freq = frequency + pitch_env_signal;
|
||||||
|
|
||||||
float output = 0.f;
|
float output = 0.f;
|
||||||
|
|
||||||
// mix operator signals according to selected algorithm
|
// mix operator signals according to selected algorithm
|
||||||
switch (v.algorithm) {
|
if (t.algorithm == 0) {
|
||||||
case 0:
|
float fb_freq = frequency * t.op3.ratio;
|
||||||
output = calc_algo1(v, pitched_freq);
|
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
|
||||||
break;
|
float fb_signal = tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) * fb_mod_index;
|
||||||
case 1:
|
|
||||||
output = calc_algo2(v, pitched_freq);
|
float op3_Freq = frequency * t.op3.ratio;
|
||||||
break;
|
float op3_mod_index = (t.op3.amplitude * MOD_INDEX_COEFF);
|
||||||
case 2:
|
float op3_signal =
|
||||||
output = calc_algo3(v, pitched_freq);
|
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_Freq, s.velocity, fb_signal) * op3_mod_index;
|
||||||
break;
|
|
||||||
case 3:
|
float op2_freq = frequency * t.op2.ratio;
|
||||||
output = calc_algo4(v, pitched_freq);
|
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
|
||||||
break;
|
float op2_signal =
|
||||||
default:
|
tx_operator_process_sample(t.op2, s.gate, s.trigger, op2_freq, s.velocity, op3_signal) * op2_mod_index;
|
||||||
output = calc_algo1(v, pitched_freq);
|
|
||||||
break;
|
float op1_freq = frequency * t.op1.ratio;
|
||||||
|
output = tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq, s.velocity, op2_signal) *
|
||||||
|
t.op1.amplitude;
|
||||||
|
} else if (t.algorithm == 1) {
|
||||||
|
float fb_freq = frequency * t.op3.ratio;
|
||||||
|
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
|
||||||
|
float fb_signal = tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) * fb_mod_index;
|
||||||
|
|
||||||
|
float op3_freq = frequency * t.op3.ratio;
|
||||||
|
float op3_signal =
|
||||||
|
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity, fb_signal) * t.op3.amplitude;
|
||||||
|
|
||||||
|
float op2_freq = frequency * t.op2.ratio;
|
||||||
|
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
|
||||||
|
float op2_signal =
|
||||||
|
tx_operator_process_sample(t.op2, s.gate, s.trigger, op2_freq, s.velocity) * op2_mod_index;
|
||||||
|
|
||||||
|
float op1_freq = frequency * t.op1.ratio;
|
||||||
|
float op1_signal = tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq, s.velocity, op2_signal) *
|
||||||
|
t.op1.amplitude;
|
||||||
|
|
||||||
|
output = op1_signal + op3_signal;
|
||||||
|
} else if (t.algorithm == 2) {
|
||||||
|
float fb_freq = frequency * t.op3.ratio;
|
||||||
|
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
|
||||||
|
float fb_signal = tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) * fb_mod_index;
|
||||||
|
|
||||||
|
float op3_freq = frequency * t.op3.ratio;
|
||||||
|
float op3_signal =
|
||||||
|
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity, fb_signal) * t.op3.amplitude;
|
||||||
|
|
||||||
|
float op2_freq = frequency * t.op2.ratio;
|
||||||
|
float op2_signal =
|
||||||
|
tx_operator_process_sample(t.op2, s.gate, s.trigger, op2_freq, s.velocity) * t.op2.amplitude;
|
||||||
|
|
||||||
|
float op1_freq = frequency * t.op1.ratio;
|
||||||
|
float op1_signal =
|
||||||
|
tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq, s.velocity) * t.op1.amplitude;
|
||||||
|
|
||||||
|
output = op1_signal + op2_signal + op3_signal;
|
||||||
|
} else if (t.algorithm == 3) {
|
||||||
|
float fb_freq = frequency * t.op3.ratio;
|
||||||
|
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
|
||||||
|
float fb_signal = tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) * fb_mod_index;
|
||||||
|
|
||||||
|
float op3_freq = frequency * t.op3.ratio;
|
||||||
|
float op3_mod_index = (t.op3.amplitude * MOD_INDEX_COEFF);
|
||||||
|
float op3_signal =
|
||||||
|
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity, fb_signal) * op3_mod_index;
|
||||||
|
|
||||||
|
float op2_freq = frequency * t.op2.ratio;
|
||||||
|
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
|
||||||
|
float op2_signal =
|
||||||
|
tx_operator_process_sample(t.op2, s.gate, s.trigger, op2_freq, s.velocity) * op2_mod_index;
|
||||||
|
|
||||||
|
float op1_freq = frequency * t.op1.ratio;
|
||||||
|
output =
|
||||||
|
tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq, s.velocity, op2_signal + op3_signal) *
|
||||||
|
t.op1.amplitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset trigger
|
// reset trigger
|
||||||
v.trigger = false;
|
s.trigger = false;
|
||||||
|
|
||||||
float res = powf(2, v.bit_resolution);
|
float res = powf(2, t.bit_resolution);
|
||||||
output = roundf(output * res) / res;
|
output = roundf(output * res) / res;
|
||||||
|
|
||||||
frames[0][s] += output / 3.;
|
audio[0][i] += output / 3.;
|
||||||
frames[1][s] = frames[0][s];
|
audio[1][i] = audio[0][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum tx_parameter {
|
||||||
|
BIT_RESOLUTION = 0,
|
||||||
|
FEEDBACKOSC_PHASE_RESOLUTION,
|
||||||
|
FEEDBACK,
|
||||||
|
ALGORITHM,
|
||||||
|
|
||||||
|
PITCH_ENVELOPE_AMOUNT,
|
||||||
|
PITCH_ENVELOPE_SKIP_SUSTAIN,
|
||||||
|
PITCH_ENVELOPE_ATTACK1_RATE,
|
||||||
|
PITCH_ENVELOPE_ATTACK1_LEVEL,
|
||||||
|
PITCH_ENVELOPE_ATTACK2_RATE,
|
||||||
|
PITCH_ENVELOPE_HOLD_RATE,
|
||||||
|
PITCH_ENVELOPE_DECAY1_RATE,
|
||||||
|
PITCH_ENVELOPE_DECAY1_LEVEL,
|
||||||
|
PITCH_ENVELOPE_DECAY2_RATE,
|
||||||
|
PITCH_ENVELOPE_SUSTAIN_LEVEL,
|
||||||
|
PITCH_ENVELOPE_RELEASE1_RATE,
|
||||||
|
PITCH_ENVELOPE_RELEASE1_LEVEL,
|
||||||
|
PITCH_ENVELOPE_RELEASE2_RATE,
|
||||||
|
|
||||||
|
OP1_RATIO,
|
||||||
|
OP1_AMPLITUDE,
|
||||||
|
OP1_PHASE_RESOLUTION,
|
||||||
|
OP1_ENVELOPE_SKIP_SUSTAIN,
|
||||||
|
OP1_ENVELOPE_ATTACK1_RATE,
|
||||||
|
OP1_ENVELOPE_ATTACK1_LEVEL,
|
||||||
|
OP1_ENVELOPE_ATTACK2_RATE,
|
||||||
|
OP1_ENVELOPE_HOLD_RATE,
|
||||||
|
OP1_ENVELOPE_DECAY1_RATE,
|
||||||
|
OP1_ENVELOPE_DECAY1_LEVEL,
|
||||||
|
OP1_ENVELOPE_DECAY2_RATE,
|
||||||
|
OP1_ENVELOPE_SUSTAIN_LEVEL,
|
||||||
|
OP1_ENVELOPE_RELEASE1_RATE,
|
||||||
|
OP1_ENVELOPE_RELEASE1_LEVEL,
|
||||||
|
OP1_ENVELOPE_RELEASE2_RATE,
|
||||||
|
|
||||||
|
OP2_RATIO,
|
||||||
|
OP2_AMPLITUDE,
|
||||||
|
OP2_PHASE_RESOLUTION,
|
||||||
|
OP2_ENVELOPE_SKIP_SUSTAIN,
|
||||||
|
OP2_ENVELOPE_ATTACK1_RATE,
|
||||||
|
OP2_ENVELOPE_ATTACK1_LEVEL,
|
||||||
|
OP2_ENVELOPE_ATTACK2_RATE,
|
||||||
|
OP2_ENVELOPE_HOLD_RATE,
|
||||||
|
OP2_ENVELOPE_DECAY1_RATE,
|
||||||
|
OP2_ENVELOPE_DECAY1_LEVEL,
|
||||||
|
OP2_ENVELOPE_DECAY2_RATE,
|
||||||
|
OP2_ENVELOPE_SUSTAIN_LEVEL,
|
||||||
|
OP2_ENVELOPE_RELEASE1_RATE,
|
||||||
|
OP2_ENVELOPE_RELEASE1_LEVEL,
|
||||||
|
OP2_ENVELOPE_RELEASE2_RATE,
|
||||||
|
|
||||||
|
OP3_RATIO,
|
||||||
|
OP3_AMPLITUDE,
|
||||||
|
OP3_PHASE_RESOLUTION,
|
||||||
|
OP3_ENVELOPE_SKIP_SUSTAIN,
|
||||||
|
OP3_ENVELOPE_ATTACK1_RATE,
|
||||||
|
OP3_ENVELOPE_ATTACK1_LEVEL,
|
||||||
|
OP3_ENVELOPE_ATTACK2_RATE,
|
||||||
|
OP3_ENVELOPE_HOLD_RATE,
|
||||||
|
OP3_ENVELOPE_DECAY1_RATE,
|
||||||
|
OP3_ENVELOPE_DECAY1_LEVEL,
|
||||||
|
OP3_ENVELOPE_DECAY2_RATE,
|
||||||
|
OP3_ENVELOPE_SUSTAIN_LEVEL,
|
||||||
|
OP3_ENVELOPE_RELEASE1_RATE,
|
||||||
|
OP3_ENVELOPE_RELEASE1_LEVEL,
|
||||||
|
OP3_ENVELOPE_RELEASE2_RATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tx_parameter_mapping {
|
||||||
|
float range_min;
|
||||||
|
float range_max;
|
||||||
|
float exponent;
|
||||||
|
tx_parameter parameter;
|
||||||
|
|
||||||
|
float apply(float _input) const
|
||||||
|
{
|
||||||
|
if (range_min == range_max && exponent == 1.f) return _input;
|
||||||
|
|
||||||
|
return powf(_input, exponent) * (range_max - range_min) + range_min;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tx_synth {
|
||||||
|
voice_allocator allocator;
|
||||||
|
array<tx_state, MAX_VOICES> voices;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void tx_synth_process_block(tx_synth& s, float** audio, size_t num_frames, const vector<midi_event>& midi_events,
|
||||||
|
const vector<audio_buffer<float>>& mods)
|
||||||
|
{
|
||||||
|
voice_allocator_process_block(s.allocator, midi_events);
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_VOICES; i++) {
|
||||||
|
tx_voice_process_block(s.voices[i], s.allocator.voices[i], audio, num_frames, mods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void tx_apply_parameter_mapping(array<tx_state, MAX_VOICES>& v, tx_parameter_mapping& m, float value)
|
||||||
|
{
|
||||||
|
if (m.range_min != m.range_max || m.exponent != 1.f)
|
||||||
|
value = powf(value, m.exponent) * (m.range_max - m.range_min) + m.range_min;
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_VOICES; i++) {
|
||||||
|
tx_state& s = v[i];
|
||||||
|
|
||||||
|
switch (m.parameter) {
|
||||||
|
case tx_parameter::BIT_RESOLUTION:
|
||||||
|
s.bit_resolution = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::FEEDBACKOSC_PHASE_RESOLUTION:
|
||||||
|
s.feedback_osc.phase_resolution = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::FEEDBACK:
|
||||||
|
s.feedback_amt = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::ALGORITHM:
|
||||||
|
s.algorithm = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_AMOUNT:
|
||||||
|
s.pitch_env_amt = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_SKIP_SUSTAIN:
|
||||||
|
s.pitch_env.skip_sustain = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_ATTACK1_RATE:
|
||||||
|
s.pitch_env.attack1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_ATTACK1_LEVEL:
|
||||||
|
s.pitch_env.attack1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_ATTACK2_RATE:
|
||||||
|
s.pitch_env.attack2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_HOLD_RATE:
|
||||||
|
s.pitch_env.hold_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_DECAY1_RATE:
|
||||||
|
s.pitch_env.decay1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_DECAY1_LEVEL:
|
||||||
|
s.pitch_env.decay1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_DECAY2_RATE:
|
||||||
|
s.pitch_env.decay2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_SUSTAIN_LEVEL:
|
||||||
|
s.pitch_env.sustain_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_RELEASE1_RATE:
|
||||||
|
s.pitch_env.release1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_RELEASE1_LEVEL:
|
||||||
|
s.pitch_env.release1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::PITCH_ENVELOPE_RELEASE2_RATE:
|
||||||
|
s.pitch_env.release2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_RATIO:
|
||||||
|
s.op1.ratio = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_AMPLITUDE:
|
||||||
|
s.op1.amplitude = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_PHASE_RESOLUTION:
|
||||||
|
s.op1.oscillator.phase_resolution = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_SKIP_SUSTAIN:
|
||||||
|
s.op1.envelope.skip_sustain = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_ATTACK1_RATE:
|
||||||
|
s.op1.envelope.attack1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_ATTACK1_LEVEL:
|
||||||
|
s.op1.envelope.attack1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_ATTACK2_RATE:
|
||||||
|
s.op1.envelope.attack2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_HOLD_RATE:
|
||||||
|
s.op1.envelope.hold_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_DECAY1_RATE:
|
||||||
|
s.op1.envelope.decay1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_DECAY1_LEVEL:
|
||||||
|
s.op1.envelope.decay1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_DECAY2_RATE:
|
||||||
|
s.op1.envelope.decay2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_SUSTAIN_LEVEL:
|
||||||
|
s.op1.envelope.sustain_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_RELEASE1_RATE:
|
||||||
|
s.op1.envelope.release1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_RELEASE1_LEVEL:
|
||||||
|
s.op1.envelope.release1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP1_ENVELOPE_RELEASE2_RATE:
|
||||||
|
s.op1.envelope.release2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_RATIO:
|
||||||
|
s.op2.ratio = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_AMPLITUDE:
|
||||||
|
s.op2.amplitude = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_PHASE_RESOLUTION:
|
||||||
|
s.op2.oscillator.phase_resolution = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_SKIP_SUSTAIN:
|
||||||
|
s.op2.envelope.skip_sustain = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_ATTACK1_RATE:
|
||||||
|
s.op2.envelope.attack1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_ATTACK1_LEVEL:
|
||||||
|
s.op2.envelope.attack1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_ATTACK2_RATE:
|
||||||
|
s.op2.envelope.attack2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_HOLD_RATE:
|
||||||
|
s.op2.envelope.hold_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_DECAY1_RATE:
|
||||||
|
s.op2.envelope.decay1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_DECAY1_LEVEL:
|
||||||
|
s.op2.envelope.decay1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_DECAY2_RATE:
|
||||||
|
s.op2.envelope.decay2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_SUSTAIN_LEVEL:
|
||||||
|
s.op2.envelope.sustain_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_RELEASE1_RATE:
|
||||||
|
s.op2.envelope.release1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_RELEASE1_LEVEL:
|
||||||
|
s.op2.envelope.release1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP2_ENVELOPE_RELEASE2_RATE:
|
||||||
|
s.op2.envelope.release2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_RATIO:
|
||||||
|
s.op3.ratio = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_AMPLITUDE:
|
||||||
|
s.op3.amplitude = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_PHASE_RESOLUTION:
|
||||||
|
s.op3.oscillator.phase_resolution = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_SKIP_SUSTAIN:
|
||||||
|
s.op3.envelope.skip_sustain = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_ATTACK1_RATE:
|
||||||
|
s.op3.envelope.attack1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_ATTACK1_LEVEL:
|
||||||
|
s.op3.envelope.attack1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_ATTACK2_RATE:
|
||||||
|
s.op3.envelope.attack2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_HOLD_RATE:
|
||||||
|
s.op3.envelope.hold_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_DECAY1_RATE:
|
||||||
|
s.op3.envelope.decay1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_DECAY1_LEVEL:
|
||||||
|
s.op3.envelope.decay1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_DECAY2_RATE:
|
||||||
|
s.op3.envelope.decay2_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_SUSTAIN_LEVEL:
|
||||||
|
s.op3.envelope.sustain_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_RELEASE1_RATE:
|
||||||
|
s.op3.envelope.release1_rate = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_RELEASE1_LEVEL:
|
||||||
|
s.op3.envelope.release1_level = value;
|
||||||
|
break;
|
||||||
|
case tx_parameter::OP3_ENVELOPE_RELEASE2_RATE:
|
||||||
|
s.op3.envelope.release2_rate = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,154 +1,138 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "audio_buffer.h"
|
|
||||||
#include "midi_event.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <array>
|
||||||
#include <cassert>
|
#include <cstddef>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
namespace trnr {
|
namespace trnr {
|
||||||
|
|
||||||
template <typename t_voice, typename t_sample>
|
enum midi_event_type {
|
||||||
class voice_allocator {
|
note_on = 0,
|
||||||
public:
|
note_off,
|
||||||
std::vector<std::shared_ptr<t_voice>> voice_ptrs;
|
pitch_wheel,
|
||||||
std::vector<midi_event> input_queue;
|
mod_wheel
|
||||||
int index_to_steal = 0;
|
|
||||||
const int internal_block_size = 16;
|
|
||||||
size_t active_voice_count;
|
|
||||||
bool steal_non_gated = true;
|
|
||||||
|
|
||||||
voice_allocator(size_t num_voices = 1)
|
|
||||||
{
|
|
||||||
assert(num_voices > 0 && "number of voices must be greater than 0");
|
|
||||||
init_voice_ptrs(num_voices);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_voice_count(const int& voice_count)
|
|
||||||
{
|
|
||||||
active_voice_count = std::min<size_t>(voice_count, voice_ptrs.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void note_on(const midi_event& event)
|
|
||||||
{
|
|
||||||
auto voice = get_free_voice();
|
|
||||||
|
|
||||||
if (!voice) { voice = steal_voice(); }
|
|
||||||
|
|
||||||
if (voice) { voice->note_on(event.midi_note, event.velocity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
void note_off(const midi_event& event)
|
|
||||||
{
|
|
||||||
for (const auto& v : voice_ptrs) {
|
|
||||||
if (v->midi_note == event.midi_note) v->note_off();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void access(std::function<void(t_voice*)> f)
|
|
||||||
{
|
|
||||||
std::for_each(voice_ptrs.begin(), voice_ptrs.end(), [&](std::shared_ptr<t_voice> ptr) {
|
|
||||||
if (ptr) {
|
|
||||||
f(ptr.get()); // Call the function with the raw pointer
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_samples(t_sample** _outputs, int _start_index, int _block_size,
|
|
||||||
std::vector<audio_buffer<t_sample>> _modulators = {})
|
|
||||||
{
|
|
||||||
for (int b = _start_index; b < _start_index + _block_size; b += internal_block_size) {
|
|
||||||
|
|
||||||
// process all events in the block (introduces potential inaccuracy of up to 16 samples)
|
|
||||||
process_events(b, internal_block_size);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < active_voice_count; ++i) {
|
|
||||||
voice_ptrs[i]->process_samples(_outputs, b, internal_block_size, _modulators);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_event(midi_event event) { input_queue.push_back(event); }
|
|
||||||
|
|
||||||
bool voices_active()
|
|
||||||
{
|
|
||||||
bool voices_active = false;
|
|
||||||
|
|
||||||
for (const auto& v : voice_ptrs) {
|
|
||||||
bool busy = v->is_busy();
|
|
||||||
voices_active |= busy;
|
|
||||||
}
|
|
||||||
|
|
||||||
return voices_active;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_samplerate(double _samplerate)
|
|
||||||
{
|
|
||||||
for (const auto& v : voice_ptrs) { v->set_samplerate(_samplerate); }
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_voice_ptrs(size_t num_voices)
|
|
||||||
{
|
|
||||||
voice_ptrs.reserve(num_voices);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < num_voices; ++i) { voice_ptrs.emplace_back(std::make_shared<t_voice>()); }
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<t_voice> get_free_voice()
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < active_voice_count; ++i) {
|
|
||||||
if (!voice_ptrs[i]->is_busy()) { return voice_ptrs[i]; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<t_voice> steal_voice()
|
|
||||||
{
|
|
||||||
// Try to find a voice that is not gated (not playing a note)
|
|
||||||
if (steal_non_gated)
|
|
||||||
for (size_t i = 0; i < active_voice_count; ++i) {
|
|
||||||
if (!voice_ptrs[i]->gate) { return voice_ptrs[i]; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all voices are gated, steal one round-robin
|
|
||||||
auto voice = voice_ptrs[index_to_steal];
|
|
||||||
index_to_steal++;
|
|
||||||
if (index_to_steal >= active_voice_count) index_to_steal = 0;
|
|
||||||
return voice;
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_events(int _start_index, int _block_size)
|
|
||||||
{
|
|
||||||
for (int s = _start_index; s < _start_index + _block_size; s++) {
|
|
||||||
auto iterator = input_queue.begin();
|
|
||||||
while (iterator != input_queue.end()) {
|
|
||||||
|
|
||||||
midi_event& event = *iterator;
|
|
||||||
if (event.offset == s) {
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case midi_event_type::note_on:
|
|
||||||
note_on(event);
|
|
||||||
break;
|
|
||||||
case midi_event_type::note_off:
|
|
||||||
note_off(event);
|
|
||||||
break;
|
|
||||||
case midi_event_type::pitch_wheel:
|
|
||||||
access([&event](t_voice* voice) { voice->modulate_pitch(event.data); });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator = input_queue.erase(iterator);
|
|
||||||
} else {
|
|
||||||
iterator++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct midi_event {
|
||||||
|
midi_event_type type;
|
||||||
|
int offset;
|
||||||
|
int midi_note;
|
||||||
|
float velocity;
|
||||||
|
double data;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef MAX_VOICES
|
||||||
|
#define MAX_VOICES 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX_EVENTS_PER_VOICE
|
||||||
|
#define MAX_EVENTS_PER_VOICE 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct voice_state {
|
||||||
|
int midi_note;
|
||||||
|
bool is_busy;
|
||||||
|
bool gate;
|
||||||
|
bool trigger;
|
||||||
|
float velocity;
|
||||||
|
array<midi_event, MAX_EVENTS_PER_VOICE> events;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct voice_allocator {
|
||||||
|
array<voice_state, MAX_VOICES> voices;
|
||||||
|
array<size_t, MAX_EVENTS_PER_VOICE> counts;
|
||||||
|
int active_voice_count = 1;
|
||||||
|
size_t index_to_steal = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void voice_allocator_init(voice_allocator& va)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < MAX_VOICES; ++i) { va.counts[i] = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void voice_allocator_process_block(voice_allocator& va, const vector<midi_event>& midi_events)
|
||||||
|
{
|
||||||
|
// reset voice events and counts
|
||||||
|
for (int i = 0; i < MAX_VOICES; i++) {
|
||||||
|
for (int j = 0; j < MAX_EVENTS_PER_VOICE; j++) { va.voices[i].events[j] = midi_event {}; }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& ev : midi_events) {
|
||||||
|
switch (ev.type) {
|
||||||
|
case note_on: {
|
||||||
|
bool found = false;
|
||||||
|
// attempt to find a free voice
|
||||||
|
for (size_t i = 0; i < va.active_voice_count; ++i) {
|
||||||
|
if (!va.voices[i].is_busy) {
|
||||||
|
va.voices[i].events[va.counts[i]++] = ev;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) break;
|
||||||
|
|
||||||
|
// try to find a voice that is not gated
|
||||||
|
for (size_t i = 0; i < va.active_voice_count; ++i) {
|
||||||
|
if (!va.voices[i].gate) {
|
||||||
|
va.voices[i].events[va.counts[i]++] = ev;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) break;
|
||||||
|
|
||||||
|
// if all voices are gated, steal one round-robin
|
||||||
|
va.voices[va.index_to_steal].events[va.counts[va.index_to_steal]++] = ev;
|
||||||
|
va.index_to_steal++;
|
||||||
|
if (va.index_to_steal >= va.active_voice_count) va.index_to_steal = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case note_off: {
|
||||||
|
for (size_t i = 0; i < va.active_voice_count; ++i) {
|
||||||
|
if (va.voices[i].midi_note == ev.midi_note) va.voices[i].events[va.counts[i]++] = ev;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case pitch_wheel:
|
||||||
|
case mod_wheel: {
|
||||||
|
for (size_t i = 0; i < va.active_voice_count; ++i) { va.voices[i].events[va.counts[i]++] = ev; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void voice_process_event_for_frame(voice_state& v, size_t frame)
|
||||||
|
{
|
||||||
|
const midi_event* best_event = nullptr;
|
||||||
|
|
||||||
|
for (int i = 0; i < v.events.size(); i++) {
|
||||||
|
const midi_event& ev = v.events[i];
|
||||||
|
if (ev.offset == frame) {
|
||||||
|
best_event = &ev;
|
||||||
|
if (ev.type == note_on) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_event) switch (best_event->type) {
|
||||||
|
case note_on:
|
||||||
|
v.midi_note = best_event->midi_note;
|
||||||
|
v.velocity = best_event->velocity;
|
||||||
|
v.is_busy = true;
|
||||||
|
v.gate = true;
|
||||||
|
v.trigger = true;
|
||||||
|
break;
|
||||||
|
case note_off:
|
||||||
|
v.gate = false;
|
||||||
|
break;
|
||||||
|
// TODO: handle pitch wheel and mod wheel events
|
||||||
|
case pitch_wheel:
|
||||||
|
case mod_wheel:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace trnr
|
} // namespace trnr
|
||||||
|
|||||||
Reference in New Issue
Block a user