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; diff --git a/filter/ysvf.h b/filter/ysvf.h index a4a18bd..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 } @@ -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) { diff --git a/synth/tx_envelope.h b/synth/tx_envelope.h index 6cf2fe6..e50f09e 100644 --- a/synth/tx_envelope.h +++ b/synth/tx_envelope.h @@ -17,55 +17,46 @@ 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() - : 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 } - , start_level { 0.f } - , h1 { 0. } - , h2 { 0. } - , h3 { 0. } + tx_envelope(bool _retrigger = false) + : retrigger { _retrigger } { } 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); + return process_sample(gate, trigger, 0, 0); + } + + template + float process_sample(bool gate, bool trigger, t_sample _attack_mod, t_sample _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) { - start_level = level; + if (retrigger) + start_level = 0.f; + else + start_level = level; phase = 0; state = attack1; } @@ -73,7 +64,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 @@ -90,7 +81,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 @@ -121,7 +112,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 @@ -138,7 +129,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 @@ -161,7 +152,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 @@ -178,7 +169,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 @@ -202,37 +193,41 @@ 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(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 = attack1_rate; + float b_x = scale(attack1_rate, _max_attack / 2); float b_y = attack1_level; - float c_x = b_x + attack2_rate; + float c_x = b_x + scale(attack2_rate, _max_attack / 2); float c_y = 1; float d_x = c_x + hold_rate; float d_y = 1; - float e_x = d_x + decay1_rate; + float e_x = d_x + scale(decay1_rate, _max_decay / 2); float e_y = decay1_level; - float f_x = e_x + decay2_rate; + float f_x = e_x + scale(decay2_rate, _max_decay / 2); float f_y = sustain_level; - float g_x = f_x + 125; + float g_x = _max_attack + _max_decay; float g_y = sustain_level; - float h_x = g_x + release1_rate; + float h_x = g_x + scale(release1_rate, _max_decay / 2); float h_y = release1_level; - float i_x = h_x + release2_rate; + float i_x = h_x + scale(release2_rate, _max_decay / 2); float i_y = 0; - float total = i_x; + float total = _max_attack + _max_decay + _max_release; return { a_x, @@ -257,13 +252,14 @@ public: } private: - double samplerate; - int 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)); } @@ -275,6 +271,8 @@ private: return (h1 + h2 + h3) / 3.f; } - float ms_to_samples(float ms) { return ms * samplerate / 1000.f; } + size_t ms_to_samples(float ms) { + return static_cast(ms * samplerate / 1000.f); + } }; } \ No newline at end of file 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 +} 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 diff --git a/util/retro_buf.h b/util/retro_buf.h new file mode 100644 index 0000000..fa0d719 --- /dev/null +++ b/util/retro_buf.h @@ -0,0 +1,156 @@ +#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; + } + } + + // @return is active + bool 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)wrap(quantized_index + jitterize(_mod.jitter), m_buffer_size), 0); + if (m_channel_count > 0) { + output_r = get_sample((size_t)wrap(quantized_index + jitterize(_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 * 9) + 1); + + 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; + } + + return m_playback_pos > -1; + } + + 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 jitterize(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