From ba5dd579b024a6228919db694e1cd324bf4c1afc Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Jul 2023 11:14:46 +0200 Subject: [PATCH 01/18] added demo noise generator --- util/demo_noise.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 util/demo_noise.h diff --git a/util/demo_noise.h b/util/demo_noise.h new file mode 100644 index 0000000..d51ae47 --- /dev/null +++ b/util/demo_noise.h @@ -0,0 +1,38 @@ +#pragma once +#define _USE_MATH_DEFINES +#include +#include + +namespace trnr { +template +class demo_noise { +public: + void set_samplerate(double _samplerate) { + samplerate = _samplerate; + } + + void process_block(t_sample** samples, long sample_frames) { + + for (int s = 0; s < sample_frames; s++) { + demo_counter++; + + if (demo_counter == samplerate * 20) { + demo_counter = 0; + } + if (demo_counter > samplerate * 17) { + t_sample r1 = static_cast(rand()) / static_cast(RAND_MAX); + t_sample r2 = static_cast(rand()) / static_cast(RAND_MAX); + + t_sample noise = static_cast(sqrt(-2.0 * log(r1)) * cos(2.0 * M_PI * r2)); + + samples[0][s] = noise / 10.0; + samples[1][s] = noise / 10.0; + } + } + } + +private: + double samplerate = 44100; + int demo_counter = 0; +}; +} \ No newline at end of file From f54121fcfb2b1446bf291835e9eda82b4cf02738 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Jul 2023 11:15:51 +0200 Subject: [PATCH 02/18] fixed templated filters --- filter/ysvf.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/filter/ysvf.h b/filter/ysvf.h index a4a18bd..57df068 100644 --- a/filter/ysvf.h +++ b/filter/ysvf.h @@ -96,10 +96,10 @@ public: private: filter_types filter_type; - ylowpass lowpass; - yhighpass highpass; - ybandpass bandpass; - ynotch notch; + ylowpass lowpass; + yhighpass highpass; + ybandpass bandpass; + ynotch notch; double clamp(double& value, double min, double max) { if (value < min) { From ebbd8ba71364cf13169c3927f000d36bd92ce75a Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 25 Jul 2023 12:45:49 +0200 Subject: [PATCH 03/18] changed double to sample template --- filter/ybandpass.h | 8 ++++---- filter/yhighpass.h | 8 ++++---- filter/ynotch.h | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/filter/ybandpass.h b/filter/ybandpass.h index 9cb048f..27801fa 100644 --- a/filter/ybandpass.h +++ b/filter/ybandpass.h @@ -73,10 +73,10 @@ public: } void processblock(t_sample** inputs, t_sample** outputs, int blockSize) { - double* in1 = inputs[0]; - double* in2 = inputs[1]; - double* out1 = outputs[0]; - double* out2 = outputs[1]; + t_sample* in1 = inputs[0]; + t_sample* in2 = inputs[1]; + t_sample* out1 = outputs[0]; + t_sample* out2 = outputs[1]; int inFramesToProcess = blockSize; double overallscale = 1.0; diff --git a/filter/yhighpass.h b/filter/yhighpass.h index 1abb1e6..a5387c7 100644 --- a/filter/yhighpass.h +++ b/filter/yhighpass.h @@ -73,10 +73,10 @@ public: } void processblock(t_sample** inputs, t_sample** outputs, int blockSize) { - double* in1 = inputs[0]; - double* in2 = inputs[1]; - double* out1 = outputs[0]; - double* out2 = outputs[1]; + t_sample* in1 = inputs[0]; + t_sample* in2 = inputs[1]; + t_sample* out1 = outputs[0]; + t_sample* out2 = outputs[1]; int inFramesToProcess = blockSize; double overallscale = 1.0; diff --git a/filter/ynotch.h b/filter/ynotch.h index 23db0de..74584d3 100644 --- a/filter/ynotch.h +++ b/filter/ynotch.h @@ -73,10 +73,10 @@ public: } void processblock(t_sample** inputs, t_sample** outputs, int blockSize) { - double* in1 = inputs[0]; - double* in2 = inputs[1]; - double* out1 = outputs[0]; - double* out2 = outputs[1]; + t_sample* in1 = inputs[0]; + t_sample* in2 = inputs[1]; + t_sample* out1 = outputs[0]; + t_sample* out2 = outputs[1]; int inFramesToProcess = blockSize; double overallscale = 1.0; From feaf503bd46a4ab680f0b06c51b590ddbb0aa8a1 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 27 Jul 2023 11:58:08 +0200 Subject: [PATCH 04/18] provide default value --- filter/ysvf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filter/ysvf.h b/filter/ysvf.h index 57df068..01bbd1f 100644 --- a/filter/ysvf.h +++ b/filter/ysvf.h @@ -16,7 +16,7 @@ enum filter_types { template class ysvf { public: - ysvf(double _samplerate) + ysvf(double _samplerate = 44100) : lowpass { _samplerate } , highpass { _samplerate } , bandpass { _samplerate } From a7a42cfa20404b4fef52d4db6116344ec4c10656 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 28 Jul 2023 18:44:45 +0200 Subject: [PATCH 05/18] intial commit of retro buffer --- util/retro_buf.h | 154 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 util/retro_buf.h diff --git a/util/retro_buf.h b/util/retro_buf.h new file mode 100644 index 0000000..853e699 --- /dev/null +++ b/util/retro_buf.h @@ -0,0 +1,154 @@ +#pragma once + +#include "../filter/chebyshev.h" +#include "../companding/ulaw.h" +#include + +namespace trnr { + +struct retro_buf_modulation { + double midi_note; + double pitch_mod; + double samplerate; // the (re)samplerate + double bitrate; + size_t start; // sets the start point from which to play + size_t end; // sets the end point + bool looping; // sets whether the sample should loop + bool reset; // resets the phase + int jitter; // jitter amount + double deviation; +}; + +// base class for accessing a sample buffer with adjustable samplerate, bitrate and other options. +class retro_buf { +public: + void set_host_samplerate(double _samplerate) { + m_host_samplerate = _samplerate; + m_imaging_filter_l.set_samplerate(_samplerate); + m_imaging_filter_r.set_samplerate(_samplerate); + } + + void set_buf_samplerate(double _samplerate) { + m_buf_samplerate = _samplerate; + } + + void set_buffer_size(size_t _buffer_size) { + m_buffer_size = _buffer_size; + } + + void set_channel_count(size_t _channel_count) { + m_channel_count = _channel_count; + } + + void start_playback() { + if (m_modulation.reset || (!m_modulation.reset && m_playback_pos == -1)) { + m_playback_pos = (double)m_modulation.start; + } + } + + void process_block(double** _outputs, size_t _block_size, retro_buf_modulation _mod) { + + m_modulation = _mod; + + for (int i = 0; i < _block_size; ++i) { + double output_l = 0; + double output_r = 0; + + // if within bounds + if (m_playback_pos > -1 && m_playback_pos <= _mod.end) { + + // quantize index + double samplerate_divisor = m_host_samplerate / _mod.samplerate; + size_t quantized_index = static_cast(static_cast(m_playback_pos / samplerate_divisor) * samplerate_divisor); + + // get sample for each channel + output_l = get_sample((size_t)quantized_index, 0); + if (m_channel_count > 0) { + output_r = get_sample(wrap(quantized_index + calc_jitter(_mod.jitter), m_buffer_size), 1); + } else { + output_r = output_l; + } + + // advance position + double note_ratio = midi_to_ratio(_mod.midi_note + _mod.pitch_mod); + m_playback_pos += note_ratio * (m_buf_samplerate / m_host_samplerate); + + reduce_bitrate(output_l, output_r, _mod.bitrate); + + // calculate imaging filter frequency + deviation + double filter_frequency = ((_mod.samplerate / 2) * note_ratio) * _mod.deviation; + + m_imaging_filter_l.process_sample(output_l, filter_frequency); + m_imaging_filter_r.process_sample(output_r, filter_frequency); + } + // else if loop + else if(_mod.looping) { + // loop + m_playback_pos = (double)_mod.start; + } + // else + else { + // stop + m_playback_pos = -1; + } + + _outputs[0][i] = output_l; + _outputs[1][i] = output_r; + _outputs[2][i] = (double)_mod.end; + } + } + + virtual float get_sample(size_t _index, size_t _channel) = 0; + +private: + size_t m_channel_count = 0; + size_t m_buffer_size = 0; + double m_buf_samplerate = 44100.0; + double m_host_samplerate = 44100.0; + double m_playback_pos = -1; + + chebyshev m_imaging_filter_l; + chebyshev m_imaging_filter_r; + ulaw m_compander; + retro_buf_modulation m_modulation; + + float midi_to_ratio(double midi_note) { + return powf(powf(2, (float)midi_note - 60.f), 1.f / 12.f); + } + + template + T clamp(T& value, T min, T max) { + if (value < min) { + value = min; + } else if (value > max) { + value = max; + } + return value; + } + + double wrap(double value, double max) { + while (value > max) { + value =- max; + } + return value; + } + + int calc_jitter(int jitter) { + if (jitter > 0) { + return static_cast(rand() % jitter); + } else { + return 0; + } + } + + void reduce_bitrate(double& value1, double& value2, double bit) { + m_compander.encode_samples(value1, value2); + + float resolution = powf(2, bit); + value1 = round(value1 * resolution) / resolution; + value2 = round(value2 * resolution) / resolution; + + m_compander.decode_samples(value1, value2); + } +}; +} \ No newline at end of file From 44123591dd3c4227043ab7e3dd725f81f89f94c6 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 31 Jul 2023 09:58:37 +0200 Subject: [PATCH 06/18] remove info output --- util/retro_buf.h | 1 - 1 file changed, 1 deletion(-) diff --git a/util/retro_buf.h b/util/retro_buf.h index 853e699..ce2557c 100644 --- a/util/retro_buf.h +++ b/util/retro_buf.h @@ -94,7 +94,6 @@ public: _outputs[0][i] = output_l; _outputs[1][i] = output_r; - _outputs[2][i] = (double)_mod.end; } } From 19e68c6d4e397694bca2a4420f8e1480784d604c Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 31 Jul 2023 11:18:11 +0200 Subject: [PATCH 07/18] cast to size_t --- util/retro_buf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/retro_buf.h b/util/retro_buf.h index ce2557c..e4c44ad 100644 --- a/util/retro_buf.h +++ b/util/retro_buf.h @@ -64,7 +64,7 @@ public: // get sample for each channel output_l = get_sample((size_t)quantized_index, 0); if (m_channel_count > 0) { - output_r = get_sample(wrap(quantized_index + calc_jitter(_mod.jitter), m_buffer_size), 1); + output_r = get_sample((size_t)wrap(quantized_index + calc_jitter(_mod.jitter), m_buffer_size), 1); } else { output_r = output_l; } From 1fc0cfd48d514c8ec28c2995616e3eddf5c17834 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 31 Jul 2023 11:19:12 +0200 Subject: [PATCH 08/18] add jitter to both channels --- util/retro_buf.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/retro_buf.h b/util/retro_buf.h index e4c44ad..1087811 100644 --- a/util/retro_buf.h +++ b/util/retro_buf.h @@ -62,9 +62,9 @@ public: size_t quantized_index = static_cast(static_cast(m_playback_pos / samplerate_divisor) * samplerate_divisor); // get sample for each channel - output_l = get_sample((size_t)quantized_index, 0); + output_l = get_sample((size_t)wrap(quantized_index + jitterize(_mod.jitter), m_buffer_size), 0); if (m_channel_count > 0) { - output_r = get_sample((size_t)wrap(quantized_index + calc_jitter(_mod.jitter), m_buffer_size), 1); + output_r = get_sample((size_t)wrap(quantized_index + jitterize(_mod.jitter), m_buffer_size), 1); } else { output_r = output_l; } @@ -132,7 +132,7 @@ private: return value; } - int calc_jitter(int jitter) { + int jitterize(int jitter) { if (jitter > 0) { return static_cast(rand() % jitter); } else { From 7e73fc7f1618bf973331f66745a7fc420796b5a8 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 31 Jul 2023 12:50:28 +0200 Subject: [PATCH 09/18] scale deviation input --- util/retro_buf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/retro_buf.h b/util/retro_buf.h index 1087811..3993a22 100644 --- a/util/retro_buf.h +++ b/util/retro_buf.h @@ -76,7 +76,7 @@ public: reduce_bitrate(output_l, output_r, _mod.bitrate); // calculate imaging filter frequency + deviation - double filter_frequency = ((_mod.samplerate / 2) * note_ratio) * _mod.deviation; + double filter_frequency = ((_mod.samplerate / 2) * note_ratio) * ((_mod.deviation * 9) + 1); m_imaging_filter_l.process_sample(output_l, filter_frequency); m_imaging_filter_r.process_sample(output_r, filter_frequency); From 68af6b15f90de8f8612b4606ab47d4faa9d81ee6 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Aug 2023 11:17:05 +0200 Subject: [PATCH 10/18] return busy flag --- util/retro_buf.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/util/retro_buf.h b/util/retro_buf.h index 3993a22..fa0d719 100644 --- a/util/retro_buf.h +++ b/util/retro_buf.h @@ -46,7 +46,8 @@ public: } } - void process_block(double** _outputs, size_t _block_size, retro_buf_modulation _mod) { + // @return is active + bool process_block(double** _outputs, size_t _block_size, retro_buf_modulation _mod) { m_modulation = _mod; @@ -95,6 +96,8 @@ public: _outputs[0][i] = output_l; _outputs[1][i] = output_r; } + + return m_playback_pos > -1; } virtual float get_sample(size_t _index, size_t _channel) = 0; From 1c71eb6f0f38b8703b26b35d32c315332998f1a2 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Aug 2023 14:25:28 +0200 Subject: [PATCH 11/18] option for retriggering the envelope --- synth/tx_envelope.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/synth/tx_envelope.h b/synth/tx_envelope.h index 6cf2fe6..7198670 100644 --- a/synth/tx_envelope.h +++ b/synth/tx_envelope.h @@ -30,7 +30,7 @@ public: float release1_level; float release2_rate; - tx_envelope() + tx_envelope(bool _retrigger = false) : samplerate { 44100. } , attack1_rate { 0 } , attack1_level { 0 } @@ -50,6 +50,7 @@ public: , h1 { 0. } , h2 { 0. } , h3 { 0. } + , retrigger { _retrigger } { } @@ -65,7 +66,10 @@ public: // if note on is triggered, transition to attack phase if (trigger) { - start_level = level; + if (retrigger) + start_level = 0.f; + else + start_level = level; phase = 0; state = attack1; } @@ -264,6 +268,7 @@ private: float h1; float h2; float h3; + bool retrigger; float lerp(float x1, float y1, float x2, float y2, float x) { return y1 + (((x - x1) * (y2 - y1)) / (x2 - x1)); } From 252a741e32d9c20b43308a45cc68e8429f72f37e Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Aug 2023 16:29:31 +0200 Subject: [PATCH 12/18] some casts to supress warnings --- synth/tx_envelope.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/synth/tx_envelope.h b/synth/tx_envelope.h index 7198670..75d622b 100644 --- a/synth/tx_envelope.h +++ b/synth/tx_envelope.h @@ -56,13 +56,13 @@ public: float process_sample(bool gate, bool trigger) { - int attack_mid_x1 = ms_to_samples(attack1_rate); - int attack_mid_x2 = ms_to_samples(attack2_rate); - int hold_samp = ms_to_samples(hold_rate); - int decay_mid_x1 = ms_to_samples(decay1_rate); - int decay_mid_x2 = ms_to_samples(decay2_rate); - int release_mid_x1 = ms_to_samples(release1_rate); - int release_mid_x2 = ms_to_samples(release2_rate); + int attack_mid_x1 = (int)ms_to_samples(attack1_rate); + int attack_mid_x2 = (int)ms_to_samples(attack2_rate); + int hold_samp = (int)ms_to_samples(hold_rate); + int decay_mid_x1 = (int)ms_to_samples(decay1_rate); + int decay_mid_x2 = (int)ms_to_samples(decay2_rate); + int release_mid_x1 = (int)ms_to_samples(release1_rate); + int release_mid_x2 = (int)ms_to_samples(release2_rate); // if note on is triggered, transition to attack phase if (trigger) { @@ -77,7 +77,7 @@ public: if (state == attack1) { // while in attack phase if (phase < attack_mid_x1) { - level = lerp(0, start_level, attack_mid_x1, attack1_level, phase); + level = lerp(0, start_level, (float)attack_mid_x1, attack1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -94,7 +94,7 @@ public: if (state == attack2) { // while in attack phase if (phase < attack_mid_x2) { - level = lerp(0, attack1_level, attack_mid_x2, 1, phase); + level = lerp(0, attack1_level, (float)attack_mid_x2, 1, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -125,7 +125,7 @@ public: if (state == decay1) { // while in decay phase if (phase < decay_mid_x1) { - level = lerp(0, 1, decay_mid_x1, decay1_level, phase); + level = lerp(0, 1, (float)decay_mid_x1, decay1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -142,7 +142,7 @@ public: if (state == decay2) { // while in decay phase if (phase < decay_mid_x2) { - level = lerp(0, decay1_level, decay_mid_x2, sustain_level, phase); + level = lerp(0, decay1_level, (float)decay_mid_x2, sustain_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -165,7 +165,7 @@ public: if (state == release1) { // while in release phase if (phase < release_mid_x1) { - level = lerp(0, sustain_level, release_mid_x1, release1_level, phase); + level = lerp(0, sustain_level, (float)release_mid_x1, release1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -182,7 +182,7 @@ public: if (state == release2) { // while in release phase if (phase < release_mid_x2) { - level = lerp(0, release1_level, release_mid_x2, 0, phase); + level = lerp(0, release1_level, (float)release_mid_x2, 0, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -280,6 +280,6 @@ private: return (h1 + h2 + h3) / 3.f; } - float ms_to_samples(float ms) { return ms * samplerate / 1000.f; } + float ms_to_samples(float ms) { return ms * (float)samplerate / 1000.f; } }; } \ No newline at end of file From bc6c9b85ffff2e0fa465ebf4969620c46d273ca5 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Aug 2023 16:51:12 +0200 Subject: [PATCH 13/18] put parameters in separate struct --- synth/tx_envelope.h | 80 ++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/synth/tx_envelope.h b/synth/tx_envelope.h index 75d622b..71edd9c 100644 --- a/synth/tx_envelope.h +++ b/synth/tx_envelope.h @@ -15,9 +15,7 @@ enum env_state { release2 }; -class tx_envelope { -public: - env_state state; +struct env_params { float attack1_rate; float attack1_level; float attack2_rate; @@ -29,20 +27,14 @@ public: float release1_rate; float release1_level; float release2_rate; +}; + +class tx_envelope { +public: + env_state state; tx_envelope(bool _retrigger = false) : samplerate { 44100. } - , attack1_rate { 0 } - , attack1_level { 0 } - , attack2_rate { 0 } - , hold_rate { 0 } - , decay1_rate { 0 } - , decay1_level { 0 } - , decay2_rate { 0 } - , sustain_level { 0 } - , release1_rate { 0 } - , release1_level { 0 } - , release2_rate { 0 } , level { 0.f } , phase { 0 } , state { idle } @@ -54,15 +46,15 @@ public: { } - float process_sample(bool gate, bool trigger) { + float process_sample(bool gate, bool trigger, env_params& _params) { - int attack_mid_x1 = (int)ms_to_samples(attack1_rate); - int attack_mid_x2 = (int)ms_to_samples(attack2_rate); - int hold_samp = (int)ms_to_samples(hold_rate); - int decay_mid_x1 = (int)ms_to_samples(decay1_rate); - int decay_mid_x2 = (int)ms_to_samples(decay2_rate); - int release_mid_x1 = (int)ms_to_samples(release1_rate); - int release_mid_x2 = (int)ms_to_samples(release2_rate); + int attack_mid_x1 = (int)ms_to_samples(_params.attack1_rate); + int attack_mid_x2 = (int)ms_to_samples(_params.attack2_rate); + int hold_samp = (int)ms_to_samples(_params.hold_rate); + int decay_mid_x1 = (int)ms_to_samples(_params.decay1_rate); + int decay_mid_x2 = (int)ms_to_samples(_params.decay2_rate); + int release_mid_x1 = (int)ms_to_samples(_params.release1_rate); + int release_mid_x2 = (int)ms_to_samples(_params.release2_rate); // if note on is triggered, transition to attack phase if (trigger) { @@ -77,7 +69,7 @@ public: if (state == attack1) { // while in attack phase if (phase < attack_mid_x1) { - level = lerp(0, start_level, (float)attack_mid_x1, attack1_level, (float)phase); + level = lerp(0, start_level, (float)attack_mid_x1, _params.attack1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -94,7 +86,7 @@ public: if (state == attack2) { // while in attack phase if (phase < attack_mid_x2) { - level = lerp(0, attack1_level, (float)attack_mid_x2, 1, (float)phase); + level = lerp(0, _params.attack1_level, (float)attack_mid_x2, 1, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -125,7 +117,7 @@ public: if (state == decay1) { // while in decay phase if (phase < decay_mid_x1) { - level = lerp(0, 1, (float)decay_mid_x1, decay1_level, (float)phase); + level = lerp(0, 1, (float)decay_mid_x1, _params.decay1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -142,7 +134,7 @@ public: if (state == decay2) { // while in decay phase if (phase < decay_mid_x2) { - level = lerp(0, decay1_level, (float)decay_mid_x2, sustain_level, (float)phase); + level = lerp(0, _params.decay1_level, (float)decay_mid_x2, _params.sustain_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -153,19 +145,19 @@ public: if (phase == decay_mid_x2) { state = sustain; phase = 0; - level = sustain_level; + level = _params.sustain_level; } } // while sustain phase: if note off is triggered, transition to release phase if (state == sustain && !gate) { state = release1; - level = sustain_level; + level = _params.sustain_level; } // release 1st half if (state == release1) { // while in release phase if (phase < release_mid_x1) { - level = lerp(0, sustain_level, (float)release_mid_x1, release1_level, (float)phase); + level = lerp(0, _params.sustain_level, (float)release_mid_x1, _params.release1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -182,7 +174,7 @@ public: if (state == release2) { // while in release phase if (phase < release_mid_x2) { - level = lerp(0, release1_level, (float)release_mid_x2, 0, (float)phase); + level = lerp(0, _params.release1_level, (float)release_mid_x2, 0, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -206,34 +198,34 @@ public: this->samplerate = sampleRate; } - // returns the x/y coordinates of the envelope points as a list for graphical representation. - std::array calc_coordinates() { + // converts the x/y coordinates of the envelope points as a list for graphical representation. + std::array calc_coordinates(env_params _params) { float a_x = 0; float a_y = 0; - float b_x = attack1_rate; - float b_y = attack1_level; + float b_x = _params.attack1_rate; + float b_y = _params.attack1_level; - float c_x = b_x + attack2_rate; + float c_x = b_x + _params.attack2_rate; float c_y = 1; - float d_x = c_x + hold_rate; + float d_x = c_x + _params.hold_rate; float d_y = 1; - float e_x = d_x + decay1_rate; - float e_y = decay1_level; + float e_x = d_x + _params.decay1_rate; + float e_y = _params.decay1_level; - float f_x = e_x + decay2_rate; - float f_y = sustain_level; + float f_x = e_x + _params.decay2_rate; + float f_y = _params.sustain_level; float g_x = f_x + 125; - float g_y = sustain_level; + float g_y = _params.sustain_level; - float h_x = g_x + release1_rate; - float h_y = release1_level; + float h_x = g_x + _params.release1_rate; + float h_y = _params.release1_level; - float i_x = h_x + release2_rate; + float i_x = h_x + _params.release2_rate; float i_y = 0; float total = i_x; From 11c8fd90453d1b07fa6387ff98bc84a05c1a25f6 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Aug 2023 17:20:55 +0200 Subject: [PATCH 14/18] added optional modulation input --- synth/tx_envelope.h | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/synth/tx_envelope.h b/synth/tx_envelope.h index 71edd9c..bbc9372 100644 --- a/synth/tx_envelope.h +++ b/synth/tx_envelope.h @@ -29,6 +29,7 @@ struct env_params { float release2_rate; }; +template class tx_envelope { public: env_state state; @@ -48,13 +49,18 @@ public: float process_sample(bool gate, bool trigger, env_params& _params) { - int attack_mid_x1 = (int)ms_to_samples(_params.attack1_rate); - int attack_mid_x2 = (int)ms_to_samples(_params.attack2_rate); - int hold_samp = (int)ms_to_samples(_params.hold_rate); - int decay_mid_x1 = (int)ms_to_samples(_params.decay1_rate); - int decay_mid_x2 = (int)ms_to_samples(_params.decay2_rate); - int release_mid_x1 = (int)ms_to_samples(_params.release1_rate); - int release_mid_x2 = (int)ms_to_samples(_params.release2_rate); + return process_sample(gate, trigger, _params, 0, 0); + } + + float process_sample(bool gate, bool trigger, env_params& _params, t_sample _attack_mod, t_sample _decay_mod) { + + size_t attack_mid_x1 = ms_to_samples(_params.attack1_rate + (float)_attack_mod); + size_t attack_mid_x2 = ms_to_samples(_params.attack2_rate + (float)_attack_mod); + size_t hold_samp = ms_to_samples(_params.hold_rate); + size_t decay_mid_x1 = ms_to_samples(_params.decay1_rate + (float)_decay_mod); + size_t decay_mid_x2 = ms_to_samples(_params.decay2_rate + (float)_decay_mod); + size_t release_mid_x1 = ms_to_samples(_params.release1_rate + (float)_decay_mod); + size_t release_mid_x2 = ms_to_samples(_params.release2_rate + (float)_decay_mod); // if note on is triggered, transition to attack phase if (trigger) { @@ -254,7 +260,7 @@ public: private: double samplerate; - int phase; + size_t phase; float level; float start_level; float h1; @@ -272,6 +278,8 @@ private: return (h1 + h2 + h3) / 3.f; } - float ms_to_samples(float ms) { return ms * (float)samplerate / 1000.f; } + size_t ms_to_samples(float ms) { + return static_cast(ms * samplerate / 1000.f); + } }; } \ No newline at end of file From ffb32bc47ebab82aad1998113b3bcbf20458ead5 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 2 Aug 2023 11:12:31 +0200 Subject: [PATCH 15/18] optimized coordinate calculation --- synth/tx_envelope.h | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/synth/tx_envelope.h b/synth/tx_envelope.h index bbc9372..8c64b73 100644 --- a/synth/tx_envelope.h +++ b/synth/tx_envelope.h @@ -205,36 +205,40 @@ public: } // converts the x/y coordinates of the envelope points as a list for graphical representation. - std::array calc_coordinates(env_params _params) { + std::array calc_coordinates(env_params _params, float _max_attack, float _max_decay, float _max_release) { + + auto scale = [](float _value, float _max) { + return powf(_value / _max, 0.25) * _max; + }; float a_x = 0; float a_y = 0; - float b_x = _params.attack1_rate; + float b_x = scale(_params.attack1_rate, _max_attack / 2); float b_y = _params.attack1_level; - float c_x = b_x + _params.attack2_rate; + float c_x = b_x + scale(_params.attack2_rate, _max_attack / 2); float c_y = 1; float d_x = c_x + _params.hold_rate; float d_y = 1; - float e_x = d_x + _params.decay1_rate; + float e_x = d_x + scale(_params.decay1_rate, _max_decay / 2); float e_y = _params.decay1_level; - float f_x = e_x + _params.decay2_rate; + float f_x = e_x + scale(_params.decay2_rate, _max_decay / 2); float f_y = _params.sustain_level; - float g_x = f_x + 125; + float g_x = _max_attack + _max_decay; float g_y = _params.sustain_level; - float h_x = g_x + _params.release1_rate; + float h_x = g_x + scale(_params.release1_rate, _max_decay / 2); float h_y = _params.release1_level; - float i_x = h_x + _params.release2_rate; + float i_x = h_x + scale(_params.release2_rate, _max_decay / 2); float i_y = 0; - float total = i_x; + float total = _max_attack + _max_decay + _max_release; return { a_x, From acab32bb43b9d14f72b2cf45f63db30fe5f99377 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Aug 2023 08:57:36 +0200 Subject: [PATCH 16/18] reverted parameter struct --- synth/tx_envelope.h | 73 ++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/synth/tx_envelope.h b/synth/tx_envelope.h index 8c64b73..506ed1c 100644 --- a/synth/tx_envelope.h +++ b/synth/tx_envelope.h @@ -15,7 +15,9 @@ enum env_state { release2 }; -struct env_params { +class tx_envelope { +public: + env_state state; float attack1_rate; float attack1_level; float attack2_rate; @@ -27,12 +29,6 @@ struct env_params { float release1_rate; float release1_level; float release2_rate; -}; - -template -class tx_envelope { -public: - env_state state; tx_envelope(bool _retrigger = false) : samplerate { 44100. } @@ -47,20 +43,21 @@ public: { } - float process_sample(bool gate, bool trigger, env_params& _params) { + float process_sample(bool gate, bool trigger) { - return process_sample(gate, trigger, _params, 0, 0); + return process_sample(gate, trigger, 0, 0); } - float process_sample(bool gate, bool trigger, env_params& _params, t_sample _attack_mod, t_sample _decay_mod) { + template + float process_sample(bool gate, bool trigger, t_sample _attack_mod, t_sample _decay_mod) { - size_t attack_mid_x1 = ms_to_samples(_params.attack1_rate + (float)_attack_mod); - size_t attack_mid_x2 = ms_to_samples(_params.attack2_rate + (float)_attack_mod); - size_t hold_samp = ms_to_samples(_params.hold_rate); - size_t decay_mid_x1 = ms_to_samples(_params.decay1_rate + (float)_decay_mod); - size_t decay_mid_x2 = ms_to_samples(_params.decay2_rate + (float)_decay_mod); - size_t release_mid_x1 = ms_to_samples(_params.release1_rate + (float)_decay_mod); - size_t release_mid_x2 = ms_to_samples(_params.release2_rate + (float)_decay_mod); + size_t attack_mid_x1 = ms_to_samples(attack1_rate + (float)_attack_mod); + size_t attack_mid_x2 = ms_to_samples(attack2_rate + (float)_attack_mod); + size_t hold_samp = ms_to_samples(hold_rate); + size_t decay_mid_x1 = ms_to_samples(decay1_rate + (float)_decay_mod); + size_t decay_mid_x2 = ms_to_samples(decay2_rate + (float)_decay_mod); + size_t release_mid_x1 = ms_to_samples(release1_rate + (float)_decay_mod); + size_t release_mid_x2 = ms_to_samples(release2_rate + (float)_decay_mod); // if note on is triggered, transition to attack phase if (trigger) { @@ -75,7 +72,7 @@ public: if (state == attack1) { // while in attack phase if (phase < attack_mid_x1) { - level = lerp(0, start_level, (float)attack_mid_x1, _params.attack1_level, (float)phase); + level = lerp(0, start_level, (float)attack_mid_x1, attack1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -92,7 +89,7 @@ public: if (state == attack2) { // while in attack phase if (phase < attack_mid_x2) { - level = lerp(0, _params.attack1_level, (float)attack_mid_x2, 1, (float)phase); + level = lerp(0, attack1_level, (float)attack_mid_x2, 1, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -123,7 +120,7 @@ public: if (state == decay1) { // while in decay phase if (phase < decay_mid_x1) { - level = lerp(0, 1, (float)decay_mid_x1, _params.decay1_level, (float)phase); + level = lerp(0, 1, (float)decay_mid_x1, decay1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -140,7 +137,7 @@ public: if (state == decay2) { // while in decay phase if (phase < decay_mid_x2) { - level = lerp(0, _params.decay1_level, (float)decay_mid_x2, _params.sustain_level, (float)phase); + level = lerp(0, decay1_level, (float)decay_mid_x2, sustain_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -151,19 +148,19 @@ public: if (phase == decay_mid_x2) { state = sustain; phase = 0; - level = _params.sustain_level; + level = sustain_level; } } // while sustain phase: if note off is triggered, transition to release phase if (state == sustain && !gate) { state = release1; - level = _params.sustain_level; + level = sustain_level; } // release 1st half if (state == release1) { // while in release phase if (phase < release_mid_x1) { - level = lerp(0, _params.sustain_level, (float)release_mid_x1, _params.release1_level, (float)phase); + level = lerp(0, sustain_level, (float)release_mid_x1, release1_level, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -180,7 +177,7 @@ public: if (state == release2) { // while in release phase if (phase < release_mid_x2) { - level = lerp(0, _params.release1_level, (float)release_mid_x2, 0, (float)phase); + level = lerp(0, release1_level, (float)release_mid_x2, 0, (float)phase); phase += 1; } // reset phase if parameter was changed @@ -205,7 +202,7 @@ public: } // converts the x/y coordinates of the envelope points as a list for graphical representation. - std::array calc_coordinates(env_params _params, float _max_attack, float _max_decay, float _max_release) { + std::array calc_coordinates(float _max_attack, float _max_decay, float _max_release) { auto scale = [](float _value, float _max) { return powf(_value / _max, 0.25) * _max; @@ -214,28 +211,28 @@ public: float a_x = 0; float a_y = 0; - float b_x = scale(_params.attack1_rate, _max_attack / 2); - float b_y = _params.attack1_level; + float b_x = scale(attack1_rate, _max_attack / 2); + float b_y = attack1_level; - float c_x = b_x + scale(_params.attack2_rate, _max_attack / 2); + float c_x = b_x + scale(attack2_rate, _max_attack / 2); float c_y = 1; - float d_x = c_x + _params.hold_rate; + float d_x = c_x + hold_rate; float d_y = 1; - float e_x = d_x + scale(_params.decay1_rate, _max_decay / 2); - float e_y = _params.decay1_level; + float e_x = d_x + scale(decay1_rate, _max_decay / 2); + float e_y = decay1_level; - float f_x = e_x + scale(_params.decay2_rate, _max_decay / 2); - float f_y = _params.sustain_level; + float f_x = e_x + scale(decay2_rate, _max_decay / 2); + float f_y = sustain_level; float g_x = _max_attack + _max_decay; - float g_y = _params.sustain_level; + float g_y = sustain_level; - float h_x = g_x + scale(_params.release1_rate, _max_decay / 2); - float h_y = _params.release1_level; + float h_x = g_x + scale(release1_rate, _max_decay / 2); + float h_y = release1_level; - float i_x = h_x + scale(_params.release2_rate, _max_decay / 2); + float i_x = h_x + scale(release2_rate, _max_decay / 2); float i_y = 0; float total = _max_attack + _max_decay + _max_release; From 046a74ef5b6544227cf35e12a81e87daa8652724 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 3 Aug 2023 12:41:36 +0200 Subject: [PATCH 17/18] initialize all members --- synth/tx_envelope.h | 48 +++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/synth/tx_envelope.h b/synth/tx_envelope.h index 506ed1c..e50f09e 100644 --- a/synth/tx_envelope.h +++ b/synth/tx_envelope.h @@ -17,29 +17,21 @@ enum env_state { class tx_envelope { public: - env_state state; - float attack1_rate; - float attack1_level; - float attack2_rate; - float hold_rate; - float decay1_rate; - float decay1_level; - float decay2_rate; - float sustain_level; - float release1_rate; - float release1_level; - float release2_rate; + env_state state = idle; + float attack1_rate = 0; + float attack1_level = 0; + float attack2_rate = 0; + float hold_rate = 0; + float decay1_rate = 0; + float decay1_level = 0; + float decay2_rate = 0; + float sustain_level = 0; + float release1_rate = 0; + float release1_level = 0; + float release2_rate = 0; tx_envelope(bool _retrigger = false) - : samplerate { 44100. } - , level { 0.f } - , phase { 0 } - , state { idle } - , start_level { 0.f } - , h1 { 0. } - , h2 { 0. } - , h3 { 0. } - , retrigger { _retrigger } + : retrigger { _retrigger } { } @@ -260,13 +252,13 @@ public: } private: - double samplerate; - size_t phase; - float level; - float start_level; - float h1; - float h2; - float h3; + double samplerate = 44100.; + size_t phase = 0; + float level = 0.f; + float start_level = 0.f; + float h1 = 0.f; + float h2 = 0.f; + float h3 = 0.f; bool retrigger; float lerp(float x1, float y1, float x2, float y2, float x) { return y1 + (((x - x1) * (y2 - y1)) / (x2 - x1)); } From 1824fcd4d87c009c861a72ffb5705ede34dd949c Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 7 Aug 2023 13:44:24 +0200 Subject: [PATCH 18/18] optimized voice stealing --- synth/voice_allocator.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/synth/voice_allocator.h b/synth/voice_allocator.h index 0f85716..16f58ab 100644 --- a/synth/voice_allocator.h +++ b/synth/voice_allocator.h @@ -11,7 +11,7 @@ public: std::vector voices; voice_allocator() - : voices(8, t_voice()) + : voices(4, t_voice()) { // checks whether template derives from ivoice typedef t_voice assert_at_compile_time[is_convertible::value ? 1 : -1]; @@ -92,6 +92,7 @@ public: private: std::vector input_queue; + int index_to_steal = 0; t_voice* get_free_voice(float frequency) { @@ -118,7 +119,13 @@ private: } if (free_voice == nullptr) { - free_voice = &voices.at(0); + free_voice = &voices.at(index_to_steal); + + if (index_to_steal < voices.size() - 1) { + index_to_steal++; + } else { + index_to_steal = 0; + } } return free_voice; @@ -153,4 +160,4 @@ private: } } }; -} \ No newline at end of file +}