MoogFilter.cpp (4502B)
1 #include <cassert> 2 #include <cstdio> 3 #include <cmath> 4 #include <stdio.h> 5 6 #include "../Misc/Util.h" 7 #include "MoogFilter.h" 8 9 // theory from "THE ART OF VA FILTER DESIGN" 10 // by Vadim Zavalishin 11 12 namespace zyn{ 13 14 MoogFilter::MoogFilter(unsigned char Ftype, float Ffreq, float Fq, 15 unsigned int srate, int bufsize) 16 :Filter(srate, bufsize), sr(srate), gain(1.0f) 17 { 18 setfreq_and_q(Ffreq/srate, Fq); 19 settype(Ftype); // q must be set before 20 21 // initialize state but not to 0, in case of being used as denominator 22 for (unsigned int i = 0; i<(sizeof(state)/sizeof(*state)); i++) 23 { 24 state[i] = 0.0001f; 25 } 26 } 27 28 MoogFilter::~MoogFilter(void) 29 { 30 31 } 32 33 inline float MoogFilter::tanhX(const float x) const 34 { 35 // Pade approximation of tanh(x) bound to [-1 .. +1] 36 // https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html 37 float x2 = x*x; 38 return (x*(105.0f+10.0f*x2)/(105.0f+(45.0f+x2)*x2)); 39 } 40 41 42 inline float MoogFilter::tanhXdivX(float x) const 43 { 44 45 //add DC offset to raise even harmonics (like transistor bias current) 46 x+= 0.1f; 47 // pre calc often used term 48 float x2 = x*x; 49 // Pade approximation for tanh(x)/x used in filter stages (5x per sample) 50 //~ return ((15.0+x2)/(15.0+6.0*x2)); 51 // faster approximation without division 52 // tuned for more distortion in self oscillation 53 return (1.0f-(0.35f*x2)+(0.06f*x2*x2)); 54 } 55 56 inline float MoogFilter::step(float input) 57 { 58 // transconductance 59 // gM(vIn) = tanh( vIn ) / vIn 60 float gm0 = tanhXdivX(state[0]); 61 // to ease calculations the others are 1.0 (so daturation) 62 // denominators 63 float d0 = 1.0f / (1.0f + c*gm0); 64 float dp1 = 1.0f / (1.0f + c); 65 float dp2 = dp1*dp1; 66 float dp3 = dp2*dp1; 67 68 // instantaneous response estimate 69 float y3Estimate = 70 cp4 * d0 * gm0 * dp3 * input + 71 cp3 * gm0 * dp3 * d0 * state[0] + 72 cp2 * dp3 * state[1] + 73 c * dp2 * state[2] + 74 dp1 * state[3]; 75 76 // mix input and gained feedback estimate for 77 // cheaper feedback gain compensation. Idea from 78 // Antti Huovilainen and Vesa Välimäki, "NEW APPROACHES TO DIGITAL SUBTRACTIVE SYNTHESIS" 79 float u = input - tanhX(feedbackGain * (y3Estimate - 0.5f*input)); 80 // output of all stages 81 float y0 = gm0 * d0 * (state[0] + c * u); 82 float y1 = dp1 * (state[1] + c * y0); 83 float y2 = dp1 * (state[2] + c * y1); 84 float y3 = dp1 * (state[3] + c * y2); 85 86 // update state 87 state[0] += ct2 * (u - y0); 88 state[1] += ct2 * (y0 - y1); 89 state[2] += ct2 * (y1 - y2); 90 state[3] += ct2 * (y2 - y3); 91 92 // calculate multimode filter output 93 return (a0 * u 94 + a1 * y0 95 + a2 * y1 96 + a3 * y2 97 + a4 * y3); 98 } 99 100 void MoogFilter::filterout(float *smp) 101 { 102 for (int i = 0; i < buffersize; i ++) 103 { 104 smp[i] = step(tanhX(smp[i]*gain)); 105 smp[i] *= outgain; 106 } 107 } 108 109 void MoogFilter::setfreq_and_q(float frequency, float q_) 110 { 111 setfreq(frequency/sr); 112 setq(q_); 113 } 114 115 inline float MoogFilter::tan_2(const float x) const 116 { 117 //Pade approximation tan(x) hand tuned to map fCutoff 118 float x2 = x*x; 119 //~ return ((9.54f*x*((11.08f - x2)))/(105.0f - x2*(45.0f + x2))); // more accurate but instable at high frequencies 120 return (x+0.15f*x2+0.3f*x2*x2); // faster, no division (by zero) 121 } 122 123 void MoogFilter::setfreq(float ff) 124 { 125 // pre warp cutoff to map to reality 126 c = tan_2(PI * ff); 127 // limit cutoff to prevent overflow 128 c = limit(c,0.0006f,1.5f); 129 // pre calculate some stuff outside the hot zone 130 ct2 = c * 2.0f; 131 cp2 = c * c; 132 cp3 = cp2 * c; 133 cp4 = cp2 * cp2; 134 } 135 136 void MoogFilter::setq(float q) 137 { 138 // flattening the Q input 139 // self oscillation begins around 4.0 140 // mapped to match the ANALOG filter class 141 feedbackGain = cbrtf(q/1000.0f)*4.0f + 0.3f; 142 // compensation factor for passband reduction by the negative feedback 143 passbandCompensation = 1.0f + limit(feedbackGain, 0.0f, 1.0f); 144 } 145 146 void MoogFilter::setgain(float dBgain) 147 { 148 gain = dB2rap(dBgain); 149 } 150 151 void MoogFilter::settype(unsigned char ftype) 152 { 153 switch (ftype) 154 { 155 case 0: 156 a0 = 1.0f; a1 =-4.0f; a2 = 6.0f; a3 =-4.0f; a4 = 1.0f; 157 break; 158 case 1: 159 a0 = 0.0f; a1 = 0.0f; a2 = 4.0f; a3 =-8.0f; a4 = 4.0f; 160 break; 161 case 2: 162 default: 163 a0 = 0.0f; a1 = 0.0f; a2 = 0.0f; a3 = 0.0f; a4 = passbandCompensation; 164 break; 165 } 166 } 167 168 };