diff --git a/synth/ivoice.h b/synth/ivoice.h index e4f6ed2..1ef14fb 100644 --- a/synth/ivoice.h +++ b/synth/ivoice.h @@ -1,11 +1,14 @@ #pragma once -#include + +#include "audio_buffer.h" +#include namespace trnr { template struct ivoice { virtual ~ivoice() = default; - virtual void process_samples(t_sample** _outputs, int _start_index, int _block_size, std::span> _modulators = {}) = 0; + virtual void process_samples(t_sample** _outputs, int _start_index, int _block_size, + std::vector> _modulators = {}) = 0; virtual bool is_busy() = 0; virtual void set_samplerate(double samplerate) = 0; virtual void note_on(int _note, float _velocity) = 0; diff --git a/synth/midi_synth.h b/synth/midi_synth.h index 3e0470f..cb38419 100644 --- a/synth/midi_synth.h +++ b/synth/midi_synth.h @@ -1,4 +1,5 @@ #pragma once +#include "audio_buffer.h" #include "ivoice.h" #include "midi_event.h" #include "voice_allocator.h" @@ -25,7 +26,7 @@ public: voice_allocator::set_samplerate(_samplerate); } - void process_block(t_sample** _outputs, int _n_frames, std::span> _modulators = {}) + void process_block(t_sample** _outputs, int _n_frames, std::vector> _modulators = {}) { // clear outputs for (auto i = 0; i < 2; i++) { memset(_outputs[i], 0, _n_frames * sizeof(t_sample)); } diff --git a/synth/tx_voice.h b/synth/tx_voice.h index 204c3b0..2b92122 100644 --- a/synth/tx_voice.h +++ b/synth/tx_voice.h @@ -1,11 +1,12 @@ #pragma once #include "../util/audio_math.h" +#include "audio_buffer.h" #include "ivoice.h" #include "tx_envelope.h" #include "tx_operator.h" -#include "tx_sineosc.h" #include "tx_parameter_mapping.h" -#include +#include "tx_sineosc.h" +#include namespace trnr { @@ -49,7 +50,8 @@ public: // modulates the pitch in semitones void modulate_pitch(float _pitch) override { this->pitch_mod = _pitch; } - void process_samples(t_sample** _outputs, int _start_index, int _block_size, std::span> _modulators = {}) override + void process_samples(t_sample** _outputs, int _start_index, int _block_size, + std::vector> _modulators = {}) override { float frequency = midi_to_frequency(midi_note + pitch_mod + additional_pitch_mod); diff --git a/synth/voice_allocator.h b/synth/voice_allocator.h index a985c51..bcbf9ea 100644 --- a/synth/voice_allocator.h +++ b/synth/voice_allocator.h @@ -1,38 +1,39 @@ #pragma once +#include "audio_buffer.h" #include "ivoice.h" #include "midi_event.h" -#include -#include #include #include +#include +#include namespace trnr { template class voice_allocator { public: - std::vector> voicePtrs; + std::vector> voice_ptrs; voice_allocator() { // checks whether template derives from ivoice typedef t_voice assert_at_compile_time[is_convertible::value ? 1 : -1]; - voicePtrs.reserve(8); + voice_ptrs.reserve(8); } void set_voice_count(const int& voice_count) { - if (voice_count > voicePtrs.size()) { - for (int i = voicePtrs.size(); i < voice_count; ++i) { - if (voicePtrs.size() > 0) { - voicePtrs.emplace_back(std::make_shared(*voicePtrs.at(0))); + if (voice_count > voice_ptrs.size()) { + for (int i = voice_ptrs.size(); i < voice_count; ++i) { + if (voice_ptrs.size() > 0) { + voice_ptrs.emplace_back(std::make_shared(*voice_ptrs.at(0))); } else { - voicePtrs.emplace_back(std::make_shared()); + voice_ptrs.emplace_back(std::make_shared()); } } - } else if (voice_count < voicePtrs.size()) { - voicePtrs.resize(voice_count); + } else if (voice_count < voice_ptrs.size()) { + voice_ptrs.resize(voice_count); } } @@ -47,28 +48,29 @@ public: void note_off(const midi_event& event) { - for (const auto& v : voicePtrs) { + for (const auto& v : voice_ptrs) { if (v->midi_note == event.midi_note) v->note_off(); } } void access(std::function f) { - std::for_each(voicePtrs.begin(), voicePtrs.end(), [&](std::shared_ptr ptr) { + std::for_each(voice_ptrs.begin(), voice_ptrs.end(), [&](std::shared_ptr 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::span> _modulators = {}) + void process_samples(t_sample** _outputs, int _start_index, int _block_size, + std::vector> _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 (const auto& v : voicePtrs) { v->process_samples(_outputs, b, internal_block_size, _modulators); } + for (const auto& v : voice_ptrs) { v->process_samples(_outputs, b, internal_block_size, _modulators); } } } @@ -78,7 +80,7 @@ public: { bool voices_active = false; - for (const auto& v : voicePtrs) { + for (const auto& v : voice_ptrs) { bool busy = v->is_busy(); voices_active |= busy; } @@ -88,7 +90,7 @@ public: void set_samplerate(double _samplerate) { - for (const auto& v : voicePtrs) { v->set_samplerate(_samplerate); } + for (const auto& v : voice_ptrs) { v->set_samplerate(_samplerate); } } private: @@ -100,7 +102,7 @@ private: { std::shared_ptr voice = nullptr; - for (const auto& v : voicePtrs) { + for (const auto& v : voice_ptrs) { if (!v->is_busy()) voice = v; break; @@ -113,7 +115,7 @@ private: { std::shared_ptr free_voice = nullptr; - for (const auto& v : voicePtrs) { + for (const auto& v : voice_ptrs) { if (!v->gate) { free_voice = v; break; @@ -121,9 +123,9 @@ private: } if (!free_voice) { - free_voice = voicePtrs.at(index_to_steal); + free_voice = voice_ptrs.at(index_to_steal); - if (index_to_steal < voicePtrs.size() - 1) { + if (index_to_steal < voice_ptrs.size() - 1) { index_to_steal++; } else { index_to_steal = 0; diff --git a/util/audio_buffer.h b/util/audio_buffer.h new file mode 100644 index 0000000..4ebbdeb --- /dev/null +++ b/util/audio_buffer.h @@ -0,0 +1,77 @@ +#pragma once +#include +#include +#include + +template +class audio_buffer { +public: + audio_buffer(const t_sample** input, int channels, int frames) + : m_data(channels * frames) + , m_channels(channels) + , m_frames(frames) + , m_channel_ptrs(channels) + { + for (int ch = 0; ch < channels; ++ch) { + std::copy(input[ch], input[ch] + frames, m_data.begin() + ch * frames); + } + update_channel_ptrs(); + } + + audio_buffer(int channels, int frames) + : m_channels(channels) + , m_frames(frames) + , m_data(channels * frames) + , m_channel_ptrs(channels) + { + update_channel_ptrs(); + } + + void set_size(int channels, int frames) + { + m_channels = channels; + m_frames = frames; + m_data.resize(channels * frames); + m_channel_ptrs.resize(channels); + update_channel_ptrs(); + } + + void set_data(const t_sample** input, int channels, int frames) + { + set_size(channels, frames); + for (int ch = 0; ch < channels; ++ch) { + std::copy(input[ch], input[ch] + frames, m_data.begin() + ch * frames); + } + update_channel_ptrs(); + } + + int num_samples() const { return m_frames; } + + int num_channels() const { return m_channels; } + + t_sample* data() { return m_data.data(); } + + const t_sample* data() const { return m_data.data(); } + + // t_sample** access, always up-to-date after construction/resize + t_sample** write_ptrs() { return m_channel_ptrs.data(); } + + t_sample* write_ptr(int channel) + { + assert(channel >= 0 && channel < m_channels); + return m_channel_ptrs[channel]; + } + + const t_sample* const* channel_ptrs() const { return m_channel_ptrs.data(); } + +private: + void update_channel_ptrs() + { + for (int ch = 0; ch < m_channels; ++ch) { m_channel_ptrs[ch] = m_data.data() + ch * m_frames; } + } + + std::vector m_data; + int m_channels; + int m_frames; // samples per channel + std::vector m_channel_ptrs; +}; \ No newline at end of file