From 8fc7fbfd30a393b22d9c883476f5f2a447e5365f Mon Sep 17 00:00:00 2001 From: HiveBeats Date: Wed, 17 Jan 2024 05:00:26 +0300 Subject: [PATCH] [feat]: Filter LFO (#23) Reviewed-on: https://gitea.e1lama.ru/e1lama/SeeSynth/pulls/23 Co-authored-by: HiveBeats Co-committed-by: HiveBeats --- docs/StateVariableFilter.md | 214 ++++++++++++++++++++++++++++++++++++ docs/image-1.png | Bin 0 -> 3091 bytes docs/image.png | Bin 0 -> 3398 bytes docs/matrix.md | 19 ++++ inc/BandPassFilter.h | 9 +- inc/Filter.h | 41 +++---- inc/HighPassFilter.h | 4 +- inc/LFO.h | 7 -- inc/LowPassFilter.h | 3 +- inc/Synth.h | 3 +- src/BandPassFilter.cpp | 29 +++-- src/Filter.cpp | 50 ++++++--- src/HighPassFilter.cpp | 29 +++-- src/LFO.cpp | 10 ++ src/LowPassFilter.cpp | 29 ++--- src/Renderer.cpp | 4 + src/Synth.cpp | 26 +++-- 17 files changed, 363 insertions(+), 114 deletions(-) create mode 100644 docs/StateVariableFilter.md create mode 100644 docs/image-1.png create mode 100644 docs/image.png create mode 100644 docs/matrix.md create mode 100644 src/LFO.cpp diff --git a/docs/StateVariableFilter.md b/docs/StateVariableFilter.md new file mode 100644 index 0000000..9a57aa4 --- /dev/null +++ b/docs/StateVariableFilter.md @@ -0,0 +1,214 @@ +https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/ + +The frequency control coefficient, f, is defined as + +![Alt text](image.png) + +where Fs is the sample rate and Fc is the filter’s corner frequency you want to set. The q coefficient is defined as + +![Alt text](image-1.png) + +where Q normally ranges from 0.5 to inifinity (where the filter oscillates). + + +The main drawback of the digital state variable is that it becomes unstable at higher frequencies. It depends on the Q setting, but basically the upper bound of stability is about where f reaches 1, which is at one-sixth of the sample rate (8 kHz at 48 kHz). The only way around this is to oversample. A simple way to double the filter’s sample rate (and thereby double the filter’s frequency range) is to run the filter twice with the same input sample, and discard one output sample. + + + +example with double-sampling + +``` +input = input buffer; +output = output buffer; +fs = sampling frequency; +fc = cutoff frequency normally something like: + 440.0*pow(2.0, (midi_note - 69.0)/12.0); +res = resonance 0 to 1; +drive = internal distortion 0 to 0.1 +freq = 2.0*sin(PI*MIN(0.25, fc/(fs*2))); // the fs*2 is because it's double sampled +damp = MIN(2.0*(1.0 - pow(res, 0.25)), MIN(2.0, 2.0/freq - freq*0.5)); +notch = notch output +low = low pass output +high = high pass output +band = band pass output +peak = peaking output = low - high +-- +double sampled svf loop: +for (i=0; i + class MoogFilter + { + public: + MoogFilter(); + ~MoogFilter() {}; + + T getSampleRate() const { return sampleRate; } + void setSampleRate(T fs) { sampleRate = fs; calc(); } + T getResonance() const { return resonance; } + void setResonance(T filterRezo) { resonance = filterRezo; calc(); } + T getCutoff() const { return cutoff; } + T getCutoffHz() const { return cutoff * sampleRate * 0.5; } + void setCutoff(T filterCutoff) { cutoff = filterCutoff; calc(); } + + void init(); + void calc(); + T process(T input); + // filter an input sample using normalized params + T filter(T input, T cutoff, T resonance); + + protected: + // cutoff and resonance [0 - 1] + T cutoff; + T resonance; + T sampleRate; + T fs; + T y1,y2,y3,y4; + T oldx; + T oldy1,oldy2,oldy3; + T x; + T r; + T p; + T k; + }; + + /** + * Construct Moog-filter. + */ + template + MoogFilter::MoogFilter() + : sampleRate(T(44100.0)) + , cutoff(T(1.0)) + , resonance(T(0.0)) + { + init(); + } + + /** + * Initialize filter buffers. + */ + template + void MoogFilter::init() + { + // initialize values + y1=y2=y3=y4=oldx=oldy1=oldy2=oldy3=T(0.0); + calc(); + } + + /** + * Calculate coefficients. + */ + template + void MoogFilter::calc() + { + // TODO: replace with your constant + const double kPi = 3.1415926535897931; + + // empirical tuning + p = cutoff * (T(1.8) - T(0.8) * cutoff); + // k = p + p - T(1.0); + // A much better tuning seems to be: + k = T(2.0) * sin(cutoff * kPi * T(0.5)) - T(1.0); + + T t1 = (T(1.0) - p) * T(1.386249); + T t2 = T(12.0) + t1 * t1; + r = resonance * (t2 + T(6.0) * t1) / (t2 - T(6.0) * t1); + }; + + /** + * Process single sample. + */ + template + T MoogFilter::process(T input) + { + // process input + x = input - r * y4; + + // four cascaded one-pole filters (bilinear transform) + y1 = x * p + oldx * p - k * y1; + y2 = y1 * p + oldy1 * p - k * y2; + y3 = y2 * p + oldy2 * p - k * y3; + y4 = y3 * p + oldy3 * p - k * y4; + + // clipper band limited sigmoid + y4 -= (y4 * y4 * y4) / T(6.0); + + oldx = x; oldy1 = y1; oldy2 = y2; oldy3 = y3; + + return y4; + } + + /** + * Filter single sample using specified params. + */ + template + T MoogFilter::filter(T input, T filterCutoff, T filterRezo) + { + // set params first + cutoff = filterCutoff; + resonance = filterRezo; + calc(); + + return process(input); + } +} +``` \ No newline at end of file diff --git a/docs/image-1.png b/docs/image-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d162ca29d49342172599e48fd921aa54857b43 GIT binary patch literal 3091 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 literal 0 HcmV?d00001 diff --git a/docs/image.png b/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..6bbf6e65e3cd9e91b8b7ec115e8671a4aa9100f5 GIT binary patch literal 3398 zcmV-M4Y~4(P)4Tx07!|Imj_T&=@!Sod(#^&Ap%lDZ-R6JNC}~ZE*)%yBtR%327-beSzLh? zMMVS^L01J7T$LhgMHCB*fMVALSrn9Yv0y{xU0?|AzIpG>J2Pj_FDK{wf9Jb%znOCZ zpeS&~;uKf`fK*Y2B*fdD5fvT7zzqQi5C9pFfEkyUE@lM>`vb&g;@|E49i$quTV|75 z|Nj4fHATK4od*CEjBp%3otKL6Da2p!#F7jEQ2UX*?urcYB)1_zN z~RLNdf?529jrF@)8govKzt{BEC?Buoz*N)YLRS!p9LdLTZp4 zc~YMl_nl6{-!$W7np|#zjHk~sWq1!^x;TZK^?yE&|81!$(wRLB5GN-}d_s_@ggmo* z$!UHv7R3by&9IO^RX@dvQlIb{=B2Y^W|+_Q@RO}g3G|=gc%c_Zrjx-5n_)q^XXp$| z(n4f=#Y@<%8Rkl6_mw7x%kqf=j!Zv0F)VV1Glda>Gn}3r>Ni`%mgObV5Ly2Mk+=Ko zzFxAkr>1{*ULi-eHX||2M|O7ZZ2tlgYgRKoO4b`+;Ndx26E3S4XSmDu5~l>q>IEs@ zvV3}GsBCQp@i}Hc#gj@hr*l(x2?K~g9?*a)&;+``2$%sYUEj(I#dg)9d!%UhZ;eBLSxZXGy`phc0&80 z!_hqSa`aksA-W8G1l@>kL*GL8qes!-FcgeB#uVd-;b5XL378d_JWL7Z5T*fh8Pkgy z#JtC1v2?5<)*kDFjm9QnS78gWd$IM{3)pV#0Co(A!>Qs-an86P92b{?%fs!)9mSo; z_233^AMqr-7TyZ)g^$6D@HzMrd=35_z6bvtKTc2}=o1_XK?FV_i%>*3NN6G4Bn%S9 ziHbx+qBAj!m_*z_EG5syGr(~ey zsl->xRXU(_NojzJqUuuFR33FB^&s^!b&!Uo8PYhkBw7LO7_E~ws;s1Jqa2~UQn_6D zta2Y6MK_>x=qdCf`U(0S`sXok`)H-GMlxewG~brbav^;PNz)H~G2G&D85 zHN+abH7;q4Xwo&^G?O%UXr9v?)}m^;XbH7;Xr0%3!BA$f7%7ZWMjK;PTV2~nTdG~D z-KjmUW2h6ZlcQ6w^H`Ut%hZk6-J#p6JF2Ip7ofLVuU7AYK0%+UpQyh}zg_=>fuTX9 z!6t(ygCRo|Lylp#VXfgKBZ`r$(Q>0Iqua(QkUo@idxyt`&(GnQGVS*=;WIo;gfJjcAr{MCHD`7!gi&2OLo&BE3q#p0mF zLrWz~U&|cJ7RxtQCRRMFa;w|c6l*W*b=J++uWd|i1UCC@?%7gp18g_hw%UHRv$vDj z)!7Z(>)FTJ@3Ftjq%nh;h0LoC7zcNUbq;47J~`SsW;mX3{L9JADcPyUX=s7r0>OfV z3kIBZoVm`G&V4QnmspqmE`6@ru3XnD*Qai}ZhW^xZqHf9tR&V^)+=`lcZqw0`vYhu4} zgSb`Pu{ciLp14sSo41SilJCaf!5;qz}nK$%m6CQX*68Q}L;>si#FsqGVB9nr7O{v~IDfI8WTS+;REN<)acm$)R*8 zeMx$A1`QdEI;2L@AEf=6E}7+-A6G=IIJr`BrD$bGmPyv;tmoNY**~vBui~w0U9G)( z!|MJutTmNup|x>qTi5BX%Uw6P-h2I#4a5z}8-B~N$SKZww=r^KORh%l`rLsZynm?8 zlgpFj-QDE0sp3cUkHQ~2H(PJsz4=={KmU4xML|iymqK3Q^`9(%D*b6+|Gi4@%El^#s*(fP1DOY&9gH~G z{xkFE>O*RW3J!feEIQnGB;-h2wS9GUje1Q{EnF+D9X`7FXxA~1W6gDDb(Qt>`hw%& zxb*moUt)i`bHeXL>q+Lxx>Ne6_B2o%@*APXtj5u%#HPOH=;rRzzNcGToLU-wHUIVS z8SOLWXO+(uog<#hJvVVa`~27i@r99#2^R-im$g2;6m{u#+rqZa%YK)yUh%qesh!n+ z{;KoUGuIrioxaYz-gLw6Mq`I1*4KPLE zKk@X;J!N(P)FEMVEMi{(;5gz2>k(}vPL4!jZUDBnmif%=J2j_I8W#Ct+3!3_y(&Q+ z;2$k_wE_Q({{mio2nyOCHcbEk061k>NoGw=04e|g00;m9hiL!=00001 z0000Q0000000N)_00aO40096109c>{00aO40096103!eZ002r#yYv760uxC@K~!i3 z?N-rl#2^e?r~UsgcdmT$MIk^-dmX99LluG(ViUMq)^+`wek{xKi8^iC@99OjslWt6 z*mJv96?7SrGiL%Jt|ec~_AkUsnV*_MdHw2kQP}g)KCmN58($8|(KhmRggbDsZ7{SC ztpdZ4WI~V(nOKsg7NX51ft_E0vtJDnV8!8KVyUDDWC(UnOc^`75IV88;phom^9vv& zy%1$Dh+AmX@VSKmtMbfA)e+_u2=UY_*<*>K#axCv`J;Y@=k5q6!I|k>2(%5xnOulv zlKpx1BTp9A4S!t?6jbY|#*)i!k=AxFJFRVMG56S;5Gb18c3FhkvK-z`EmF3U08E>P zoeF%Xu?eA4!i}*?zl;!OonRP(<+l=M7xuHXxxK@1TEfCwoztaqt0$*@Ayjm?4k1yKwN+y1)au_Fn-Hjv-!9c|txN@2 zEfoP=)xJ!NsrLdCa{IxXu)li{( z3`-j}hNS6+KS~I(-Opbk>4hNYk~usWXbMqn8|mMOkZR~`3z48B4qf#&TeoUkCV>#H c`VUQi0ZR%aS20h*R{#J207*qoM6N<$f>NeV0ssI2 literal 0 HcmV?d00001 diff --git a/docs/matrix.md b/docs/matrix.md new file mode 100644 index 0000000..cc8832f --- /dev/null +++ b/docs/matrix.md @@ -0,0 +1,19 @@ + +// signal -> adsr -> filter +// ^ ^ ^ +// | | | +// can be modulated +// by lfo, or adsr + + + +у каждого из них должна быть ручка для изменения состояния +на каждом семпле????? + + + +багует сам алгоритм изменения частоты, либо алгоритм пересчета коэфициентов фильтра + + + + diff --git a/inc/BandPassFilter.h b/inc/BandPassFilter.h index e83c929..e2c0fd0 100644 --- a/inc/BandPassFilter.h +++ b/inc/BandPassFilter.h @@ -1,14 +1,17 @@ #pragma once + #include "Filter.h" class BandPassFilter : public Filter { - private: - void CalculateCoefficients() override; + protected: + float GetSampleForFilterType() override; public: + BandPassFilter(); BandPassFilter(Filter* filter); BandPassFilter(float freq, float res, float q); - BandPassFilter(/* args */); ~BandPassFilter(); bool IsSameFilterType(FilterType type) override { return type == BandPass; }; }; + + diff --git a/inc/Filter.h b/inc/Filter.h index c4d8ba4..2307f1c 100644 --- a/inc/Filter.h +++ b/inc/Filter.h @@ -1,24 +1,25 @@ #pragma once #include "IEffect.h" +#include "Settings.h" -enum FilterType { - LowPass, - BandPass, - HighPass -}; +enum FilterType { LowPass, BandPass, HighPass }; class Filter : public IEffect { protected: - float m_freq; // cutoff frequency - float m_q; // filter quantity (resonance) - float m_order; // filter order (peakGain) - /* todo: filter adsr */ - float m_norm, m_v, m_k; - float m_a0, m_a1, m_a2, m_b1, m_b2; - float m_z1, m_z2; - - void CalculateNormals(); - virtual void CalculateCoefficients(){}; + // 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: Filter(/* args */); @@ -27,9 +28,9 @@ class Filter : public IEffect { void Release() override final; float Process(float in); void Process(std::vector& samples) override final; - void SetParameters(float freq, float res, float q); - float GetFreq() { return m_freq; } - float GetRes() { return m_q; } - float GetPeakGain() { return m_norm; } - virtual bool IsSameFilterType(FilterType type){ return false; }; + 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/HighPassFilter.h b/inc/HighPassFilter.h index 69556a5..fa430ce 100644 --- a/inc/HighPassFilter.h +++ b/inc/HighPassFilter.h @@ -2,8 +2,8 @@ #include "Filter.h" class HighPassFilter : public Filter { - private: - void CalculateCoefficients() override; + protected: + float GetSampleForFilterType() override; public: HighPassFilter(); 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/LowPassFilter.h b/inc/LowPassFilter.h index 1ee043b..f447670 100644 --- a/inc/LowPassFilter.h +++ b/inc/LowPassFilter.h @@ -4,7 +4,7 @@ class LowPassFilter : public Filter { protected: - void CalculateCoefficients() override; + float GetSampleForFilterType() override; public: LowPassFilter(); @@ -13,3 +13,4 @@ class LowPassFilter : public Filter { ~LowPassFilter(); bool IsSameFilterType(FilterType type) override { return type == LowPass; }; }; + diff --git a/inc/Synth.h b/inc/Synth.h index d6acede..c150d11 100644 --- a/inc/Synth.h +++ b/inc/Synth.h @@ -8,6 +8,7 @@ #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(); diff --git a/src/BandPassFilter.cpp b/src/BandPassFilter.cpp index c751e3d..2f1ee52 100644 --- a/src/BandPassFilter.cpp +++ b/src/BandPassFilter.cpp @@ -1,23 +1,22 @@ #include "BandPassFilter.h" +#include "Settings.h" -BandPassFilter::BandPassFilter(/* args */) {} - -BandPassFilter::BandPassFilter(Filter* filter) { - m_freq = filter->GetFreq(); - m_q = filter->GetRes(); - m_order = filter->GetPeakGain(); +BandPassFilter::BandPassFilter() { + SetParameters(200, 0.1, 0.001); } -BandPassFilter::BandPassFilter(float freq, float res, float q) {} +BandPassFilter::BandPassFilter( + Filter* filter) { + SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain()); +} + +BandPassFilter::BandPassFilter(float freq, float res, + float q) { + SetParameters(freq, res, q); +} BandPassFilter::~BandPassFilter() {} -void BandPassFilter::CalculateCoefficients() { - CalculateNormals(); - m_norm = 1 / (1 + m_k / m_q + m_k * m_k); - m_a0 = m_k / m_q * m_norm; - m_a1 = 0; - m_a2 = -m_a0; - m_b1 = 2 * (m_k * m_k - 1) * m_norm; - m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm; +float BandPassFilter::GetSampleForFilterType() { + return m_lowo; } \ No newline at end of file diff --git a/src/Filter.cpp b/src/Filter.cpp index 4beecce..a8fc39e 100644 --- a/src/Filter.cpp +++ b/src/Filter.cpp @@ -1,36 +1,50 @@ #include "Filter.h" #include "Settings.h" +#include +#include Filter::Filter(/* args */) {} Filter::~Filter() {} -void Filter::CalculateNormals() { - m_v = powf(10, fabs(m_order) / 20.0); - m_k = tanf(M_PI * m_freq); -} - void Filter::Trigger() {} void Filter::Release() {} -float Filter::Process(float in) { - // may move to a compile-time dictionary calculation, if needed - CalculateCoefficients(); - float out = in * m_a0 + m_z1; - m_z1 = in * m_a1 + m_z2 - m_b1 * out; - m_z2 = in * m_a2 - m_b2 * out; - return out; -} - void Filter::Process(std::vector& samples) { for (std::size_t i = 0; i < samples.size(); i++) { samples[i] = Process(samples[i]); } } -void Filter::SetParameters(float freq, float res, float q) { - m_freq = freq / SAMPLE_RATE; - m_q = res; - m_order = q; + +float Filter::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 Filter::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)); } diff --git a/src/HighPassFilter.cpp b/src/HighPassFilter.cpp index 587d520..90879d0 100644 --- a/src/HighPassFilter.cpp +++ b/src/HighPassFilter.cpp @@ -1,23 +1,22 @@ #include "HighPassFilter.h" +#include "Settings.h" -HighPassFilter::HighPassFilter(/* args */) {} - -HighPassFilter::HighPassFilter(Filter* filter) { - m_freq = filter->GetFreq(); - m_q = filter->GetRes(); - m_order = filter->GetPeakGain(); +HighPassFilter::HighPassFilter() { + SetParameters(200, 0.1, 0.001); } -HighPassFilter::HighPassFilter(float freq, float res, float q) {} +HighPassFilter::HighPassFilter( + Filter* filter) { + SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain()); +} + +HighPassFilter::HighPassFilter(float freq, float res, + float q) { + SetParameters(freq, res, q); +} HighPassFilter::~HighPassFilter() {} -void HighPassFilter::CalculateCoefficients() { - CalculateNormals(); - m_norm = 1 / (1 + m_k / m_q + m_k * m_k); - m_a0 = 1 * m_norm; - m_a1 = -2 * m_a0; - m_a2 = m_a0; - m_b1 = 2 * (m_k * m_k - 1) * m_norm; - m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm; +float HighPassFilter::GetSampleForFilterType() { + return m_higho; } \ 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..73cd8dc 100644 --- a/src/LowPassFilter.cpp +++ b/src/LowPassFilter.cpp @@ -2,32 +2,21 @@ #include "Settings.h" LowPassFilter::LowPassFilter() { - // todo: defaults - m_freq = 200.f / SAMPLE_RATE; - m_q = 1.0f;//0.707f; - m_order = 0; + SetParameters(200, 0.1, 0.001); } -LowPassFilter::LowPassFilter(float freq, float res, float q) { - m_freq = freq / SAMPLE_RATE; - m_q = res; - m_order = q; +LowPassFilter::LowPassFilter( + Filter* filter) { + SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain()); } -LowPassFilter::LowPassFilter(Filter* filter) { - m_freq = filter->GetFreq(); - m_q = filter->GetRes(); - m_order = filter->GetPeakGain(); +LowPassFilter::LowPassFilter(float freq, float res, + float q) { + SetParameters(freq, res, q); } LowPassFilter::~LowPassFilter() {} -void LowPassFilter::CalculateCoefficients() { - CalculateNormals(); - m_norm = 1 / (1 + m_k / m_q + m_k * m_k); - m_a0 = m_k * m_k * m_norm; - m_a1 = 2 * m_a0; - m_a2 = m_a0; - m_b1 = 2 * (m_k * m_k - 1) * m_norm; - m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm; +float LowPassFilter::GetSampleForFilterType() { + return m_lowo; } \ No newline at end of file diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 4a9a626..8ce824d 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -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 filter 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/Synth.cpp b/src/Synth.cpp index 089dcdc..4165b8a 100644 --- a/src/Synth.cpp +++ b/src/Synth.cpp @@ -1,13 +1,14 @@ #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 "LowPassFilter.h" Synth::Synth(/* args */) { m_lfo = new LFO(); + m_lfo->SetFreq(5.0); AddOscillator(); AddOscillator(); AddEffect(new ADSR()); @@ -38,8 +39,14 @@ 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); + + Filter* filter = (Filter*)m_effects[1]; + + 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 +63,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) { @@ -70,19 +76,14 @@ void Synth::Trigger(Note input) { TriggerNoteOnEffects(); } -// todo: fix this void Synth::ApplyFilterLfo() { float dt = m_lfo->Process(); Filter* filter = (Filter*)m_effects[1]; float freq = filter->GetFreq(); - //todo: check formula - //filter->SetParameters(freq + dt * 0.2f, filter->GetRes(), filter->GetPeakGain()); + 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 - //ApplyFilterLfo(); GetNote(); ApplyEffects(); } @@ -98,6 +99,7 @@ void Synth::AddEffect(IEffect* fx) { m_effects.push_back(fx); } void Synth::SetFilter(FilterType type) { Filter* old_filter = this->GetFilter(); if (!old_filter->IsSameFilterType(type)) { + // todo: implement other types of state variable filters; Filter* new_filter = FilterFactory::CreateFilter(old_filter, type); delete old_filter; m_effects[1] = new_filter;