diff --git a/inc/LFO.h b/inc/LFO.h index d5676fe..c0c5025 100644 --- a/inc/LFO.h +++ b/inc/LFO.h @@ -11,10 +11,3 @@ public: void SetFreq(float freq) { m_phase_dt = (this->*m_dt_function)(freq); } }; -LFO::LFO(/* args */): Oscillator(Sine, 0.f, 0.5f) -{ -} - -LFO::~LFO() -{ -} diff --git a/inc/LowPassStateVariableFilter.h b/inc/LowPassStateVariableFilter.h new file mode 100644 index 0000000..7357341 --- /dev/null +++ b/inc/LowPassStateVariableFilter.h @@ -0,0 +1,15 @@ +#pragma once + +#include "StateVariableFilter.h" + +class LowPassStateVariableFilter : StateVariableFilter { + protected: + float GetSampleForFilterType() override; + + public: + LowPassStateVariableFilter(); + LowPassStateVariableFilter(StateVariableFilter* filter); + LowPassStateVariableFilter(float freq, float res, float q); + ~LowPassStateVariableFilter(); + bool IsSameFilterType(FilterType type) override { return type == LowPass; }; +}; diff --git a/inc/StateVariableFilter.h b/inc/StateVariableFilter.h new file mode 100644 index 0000000..05db56a --- /dev/null +++ b/inc/StateVariableFilter.h @@ -0,0 +1,35 @@ +#pragma once +#include "Filter.h" +#include "IEffect.h" +#include "Settings.h" + +class StateVariableFilter : public IEffect { + protected: + // float* m_output; // output buffer + float m_fs = SAMPLE_RATE; // sampling frequency; + float m_fc; // cutoff frequency normally something like: 440.0*pow(2.0, + // (midi_note - 69.0)/12.0); + float m_res; // resonance 0 to 1; + float m_drive; // internal distortion 0 to 0.1 + float m_freq; + float m_damp; + float m_notcho; // notch output + float m_lowo; // low pass output + float m_higho; // high pass output + float m_bando; // band pass output + float m_peako; // peaking output = low - high + virtual float GetSampleForFilterType(){ return 0.0; }; + + public: + StateVariableFilter(/* args */); + virtual ~StateVariableFilter(); + void Trigger() override final; + void Release() override final; + float Process(float in); + void Process(std::vector& samples) override final; + void SetParameters(float freq, float res, float drive); + float GetFreq() { return m_fc; } + float GetRes() { return m_res; } + float GetPeakGain() { return m_drive; } + virtual bool IsSameFilterType(FilterType type) { return false; }; +}; diff --git a/inc/Synth.h b/inc/Synth.h index d6acede..a7b2505 100644 --- a/inc/Synth.h +++ b/inc/Synth.h @@ -1,13 +1,14 @@ #pragma once #include "ADSR.h" -#include "Filter.h" +#include "StateVariableFilter.h" #include "Adder.h" #include "IEffect.h" #include "Note.h" #include "Oscillator.h" #include "Settings.h" #include +#include "LFO.h" class Synth { private: @@ -15,7 +16,7 @@ class Synth { std::vector m_oscillators; std::vector m_effects; std::vector m_out_signal; - Oscillator* m_lfo; + LFO* m_lfo; void ZeroSignal(); void GetNote(); void TriggerNoteOnEffects(); @@ -34,6 +35,6 @@ class Synth { const std::vector& GetOscillators() { return m_oscillators; } const bool& GetIsNoteTriggered() { return is_note_triggered; } ADSR* GetADSR() { return (ADSR*)m_effects[0]; } - Filter* GetFilter() { return (Filter*)m_effects[1]; } + StateVariableFilter* GetFilter() { return (StateVariableFilter*)m_effects[1]; } void SetFilter(FilterType type); }; \ No newline at end of file diff --git a/src/LFO.cpp b/src/LFO.cpp new file mode 100644 index 0000000..5969b94 --- /dev/null +++ b/src/LFO.cpp @@ -0,0 +1,10 @@ +#include "LFO.h" + + +LFO::LFO(/* args */): Oscillator(Sine, 0.f, 0.5f) +{ +} + +LFO::~LFO() +{ +} \ No newline at end of file diff --git a/src/LowPassFilter.cpp b/src/LowPassFilter.cpp index 2ee08dd..5d745b7 100644 --- a/src/LowPassFilter.cpp +++ b/src/LowPassFilter.cpp @@ -4,7 +4,7 @@ LowPassFilter::LowPassFilter() { // todo: defaults m_freq = 200.f / SAMPLE_RATE; - m_q = 1.0f;//0.707f; + m_q = 0.707f;//0.707f; m_order = 0; } diff --git a/src/LowPassStateVariableFilter.cpp b/src/LowPassStateVariableFilter.cpp new file mode 100644 index 0000000..d3488b5 --- /dev/null +++ b/src/LowPassStateVariableFilter.cpp @@ -0,0 +1,21 @@ +#include "LowPassStateVariableFilter.h" + +LowPassStateVariableFilter::LowPassStateVariableFilter() { + SetParameters(200, 0.1, 0.001); +} + +LowPassStateVariableFilter::LowPassStateVariableFilter( + StateVariableFilter* filter) { + SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain()); +} + +LowPassStateVariableFilter::LowPassStateVariableFilter(float freq, float res, + float q) { + SetParameters(freq, res, q); +} + +LowPassStateVariableFilter::~LowPassStateVariableFilter() {} + +float LowPassStateVariableFilter::GetSampleForFilterType() { + return m_lowo; +} \ No newline at end of file diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 4a9a626..ece9ead 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -203,7 +203,7 @@ void Renderer::draw_second_panel(Rectangle& bounds) { float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter, const Rectangle& panel_bounds) { #define FILTER_TYPE_OPTIONS "LP;BP;HP" - Filter* filter = synth.GetFilter(); + StateVariableFilter* filter = synth.GetFilter(); float panel_y_offset = 0; // Draw Filter Panel @@ -257,6 +257,10 @@ float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter, // apply values to real one // todo: thrid (order) parameter // todo: why resonance changing does not work? + // todo: limit statevariablefilter lowest frequency to ~40 hz + if (gui_filter.freq < 40.0) { + gui_filter.freq = 50.0; + } filter->SetParameters(gui_filter.freq, filter->GetRes(), filter->GetPeakGain()); diff --git a/src/StateVariableFilter.cpp b/src/StateVariableFilter.cpp new file mode 100644 index 0000000..d9d4bf7 --- /dev/null +++ b/src/StateVariableFilter.cpp @@ -0,0 +1,50 @@ +#include "StateVariableFilter.h" +#include "Settings.h" +#include +#include + +StateVariableFilter::StateVariableFilter(/* args */) {} + +StateVariableFilter::~StateVariableFilter() {} + +void StateVariableFilter::Trigger() {} + +void StateVariableFilter::Release() {} + +void StateVariableFilter::Process(std::vector& samples) { + for (std::size_t i = 0; i < samples.size(); i++) { + samples[i] = Process(samples[i]); + } +} + + +float StateVariableFilter::Process(float in) { + m_notcho = in - m_damp * m_bando; + m_lowo = m_lowo + m_freq * m_bando; + m_higho = m_notcho - m_lowo; + m_bando = + m_freq * m_higho + m_bando - m_drive * m_bando * m_bando * m_bando; + // (m_notcho or m_lowo or m_higho or m_bando or m_peako) + float out = 0.5 * GetSampleForFilterType(); + m_notcho = in - m_damp * m_bando; + m_lowo = m_lowo + m_freq * m_bando; + m_higho = m_notcho - m_lowo; + m_bando = + m_freq * m_higho + m_bando - m_drive * m_bando * m_bando * m_bando; + out += 0.5 * GetSampleForFilterType(); + + return out; +} + +void StateVariableFilter::SetParameters(float freq, float res, float drive) { + m_fc = freq; + m_res = res; + m_drive = drive; + + // the fs*2 is because it's double sampled + m_freq = + 2.0 * std::sinf(SYNTH_PI * std::min(0.25f, m_fc / (m_fs * 2))); + + m_damp = std::min(2.0f * (1.0f - std::powf(m_res, 0.25f)), + std::min(2.0f, 2.0f / m_freq - m_freq * 0.5f)); +} \ No newline at end of file diff --git a/src/Synth.cpp b/src/Synth.cpp index 089dcdc..e99c98b 100644 --- a/src/Synth.cpp +++ b/src/Synth.cpp @@ -1,17 +1,20 @@ #include "Synth.h" +#include "FilterFactory.h" #include "KeyBoard.h" #include "Logger.h" #include "OscillatorType.h" #include "Settings.h" -#include "FilterFactory.h" -#include "LFO.h" +#include "LowPassStateVariableFilter.h" Synth::Synth(/* args */) { m_lfo = new LFO(); + m_lfo->SetFreq(5.0); AddOscillator(); AddOscillator(); AddEffect(new ADSR()); - AddEffect(FilterFactory::GetDefaultFilter()); + // todo: implement state-variable filters in a factory + AddEffect((StateVariableFilter*)new LowPassStateVariableFilter()); + // AddEffect(FilterFactory::GetDefaultFilter()); for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) { float sample = 0.0f; m_out_signal.push_back(sample); @@ -38,8 +41,15 @@ void Synth::GetNote() { } void Synth::ApplyEffects() { - for (IEffect* effect : m_effects) { - effect->Process(m_out_signal); + auto* adsr = m_effects[0]; + adsr->Process(m_out_signal); + + StateVariableFilter* filter = (StateVariableFilter*)m_effects[1]; + // assert(filter); + + for (std::size_t i = 0; i < m_out_signal.size(); i++) { + ApplyFilterLfo(); + m_out_signal[i] = filter->Process(m_out_signal[i]); } } @@ -56,8 +66,7 @@ void Synth::UntriggerNoteOnEffects() { } void Synth::AddOscillator() { - m_oscillators.push_back( - new Oscillator(OscillatorType::Sine, 0.0f, VOLUME)); + m_oscillators.push_back(new Oscillator(OscillatorType::Sine, 0.0f, VOLUME)); } void Synth::Trigger(Note input) { @@ -73,15 +82,16 @@ void Synth::Trigger(Note input) { // todo: fix this void Synth::ApplyFilterLfo() { float dt = m_lfo->Process(); - Filter* filter = (Filter*)m_effects[1]; + StateVariableFilter* filter = (StateVariableFilter*)m_effects[1]; float freq = filter->GetFreq(); - //todo: check formula - //filter->SetParameters(freq + dt * 0.2f, filter->GetRes(), filter->GetPeakGain()); + // todo: check formula + // write_log("LFO DT: %f\n", dt); + filter->SetParameters(freq + dt, filter->GetRes(), filter->GetPeakGain()); } void Synth::Process() { - //todo: on each sample. - //in order to do that, we need to move to per-sample processing + // todo: on each sample. + // in order to do that, we need to move to per-sample processing //ApplyFilterLfo(); GetNote(); ApplyEffects(); @@ -96,9 +106,10 @@ void Synth::Release() { void Synth::AddEffect(IEffect* fx) { m_effects.push_back(fx); } void Synth::SetFilter(FilterType type) { - Filter* old_filter = this->GetFilter(); + StateVariableFilter* old_filter = this->GetFilter(); if (!old_filter->IsSameFilterType(type)) { - Filter* new_filter = FilterFactory::CreateFilter(old_filter, type); + // todo: implement other types of state variable filters; + StateVariableFilter* new_filter = (StateVariableFilter*)new LowPassStateVariableFilter(old_filter); // FilterFactory::CreateFilter(old_filter, type); delete old_filter; m_effects[1] = new_filter; }