1 #include "QuadFilterUnit.h"
2 #include "FilterCoefficientMaker.h"
3 #include "DebugHelpers.h"
4 #include "SurgeStorage.h"
5 #include "vt_dsp/basic_dsp.h"
6 #include "FastMath.h"
7
8 /*
9 ** This contains an adaptation of the filter from
10 ** https://github.com/TheWaveWarden/odin2/blob/master/Source/audio/Filters/DiodeFilter.cpp
11 */
12
clampedFrequency(float pitch,SurgeStorage * storage)13 static float clampedFrequency(float pitch, SurgeStorage *storage)
14 {
15 auto freq = storage->note_to_pitch_ignoring_tuning(pitch + 69) * Tunings::MIDI_0_FREQ;
16 freq = limit_range((float)freq, 5.f, (float)(dsamplerate_os * 0.3f));
17 return freq;
18 }
19
20 #define F(a) _mm_set_ps1(a)
21 #define M(a, b) _mm_mul_ps(a, b)
22 #define D(a, b) _mm_div_ps(a, b)
23 #define A(a, b) _mm_add_ps(a, b)
24 #define S(a, b) _mm_sub_ps(a, b)
25 // reciprocal
26 #define reci(a) _mm_rcp_ps(a)
27
getFO(const __m128 beta,const __m128 delta,const __m128 feedback,const __m128 z)28 static inline __m128 getFO(const __m128 beta, const __m128 delta, const __m128 feedback,
29 const __m128 z) noexcept
30 {
31 // (feedback * delta + z) * beta
32 return M(A(M(feedback, delta), z), beta);
33 }
34
doLpf(const __m128 input,const __m128 alpha,const __m128 beta,const __m128 gamma,const __m128 delta,const __m128 epsilon,const __m128 ma0,const __m128 feedback,const __m128 feedback_output,__m128 & z)35 static inline __m128 doLpf(const __m128 input, const __m128 alpha, const __m128 beta,
36 const __m128 gamma, const __m128 delta, const __m128 epsilon,
37 const __m128 ma0, const __m128 feedback, const __m128 feedback_output,
38 __m128 &z) noexcept
39 {
40 // input * gamma + feedback + epsilon * feedback_output
41 const __m128 i = A(A(M(input, gamma), feedback), M(epsilon, feedback_output));
42 const __m128 v = M(S(M(ma0, i), z), alpha);
43 const __m128 result = A(v, z);
44 z = A(v, result);
45 return result;
46 }
47
48 namespace DiodeLadderFilter
49 {
50 // can't fit all the coefficients in the 8-coefficient limit, so we have to compute a lot of
51 // stuff per sample q_q
52 enum dlf_coeffs
53 {
54 dlf_alpha = 0,
55 dlf_gamma,
56 dlf_g,
57 dlf_G4,
58 dlf_G3,
59 dlf_G2,
60 dlf_G1,
61 dlf_km, // k_modded
62 };
63
64 enum dlf_state
65 {
66 dlf_z1, // z-1 state for LPF 1
67 dlf_z2, // LPF2
68 dlf_z3, // ...
69 dlf_z4,
70 dlf_feedback3, // feedback for LPF3 (feedback for LPF4 is 0)
71 dlf_feedback2,
72 dlf_feedback1,
73 };
74
makeCoefficients(FilterCoefficientMaker * cm,float freq,float reso,SurgeStorage * storage)75 void makeCoefficients(FilterCoefficientMaker *cm, float freq, float reso, SurgeStorage *storage)
76 {
77 float C[n_cm_coeffs];
78
79 const float wd = clampedFrequency(freq, storage) * 2.0f * M_PI;
80 const float wa = (2.0f * dsamplerate_os) * Surge::DSP::fasttan(wd * dsamplerate_os_inv * 0.5);
81 const float g = wa * dsamplerate_os_inv * 0.5f;
82
83 const float G4 = 0.5 * g / (1.0 + g);
84 const float G3 = 0.5 * g / (1.0 + g - 0.5 * g * G4);
85 const float G2 = 0.5 * g / (1.0 + g - 0.5 * g * G3);
86 const float G1 = g / (1.0 + g - g * G2);
87 const float m_gamma = G4 * G3 * G2 * G1;
88
89 const float G = g / (1.0 + g);
90
91 const float k = reso * 16.0f;
92 // clamp to [0..16]
93 const float km = (k > 16.f) ? 16.f : ((k < 0.f) ? 0.f : k);
94
95 C[dlf_alpha] = G;
96 C[dlf_gamma] = m_gamma;
97 C[dlf_g] = g;
98 C[dlf_G4] = G4;
99 C[dlf_G3] = G3;
100 C[dlf_G2] = G2;
101 C[dlf_G1] = G1;
102 C[dlf_km] = km;
103
104 cm->FromDirect(C);
105 }
106
process(QuadFilterUnitState * __restrict f,__m128 input)107 __m128 process(QuadFilterUnitState *__restrict f, __m128 input)
108 {
109 for (int i = 0; i < n_cm_coeffs; ++i)
110 {
111 f->C[i] = A(f->C[i], f->dC[i]);
112 }
113
114 // hopefully the optimiser will take care of the duplicatey bits
115
116 const __m128 zero = F(0.0f);
117 const __m128 one = F(1.0f);
118 const __m128 half = F(0.5f);
119
120 const __m128 sg3 = f->C[dlf_G4];
121 const __m128 sg2 = M(sg3, f->C[dlf_G3]);
122 const __m128 sg1 = M(sg2, f->C[dlf_G2]);
123 // sg4 is 1.0, just inline it
124
125 const __m128 g = f->C[dlf_g];
126 // g plus one, common so do it only once
127 const __m128 gp1 = A(g, one);
128 // half of g
129 const __m128 hg = M(f->C[dlf_g], half);
130
131 // 1.0 / (gp1 - g * G2)
132 const __m128 beta1 = reci(S(gp1, M(g, f->C[dlf_G2])));
133 // 1.0 / (gp1 - g * 0.5 * G3
134 const __m128 beta2 = reci(S(gp1, M(hg, f->C[dlf_G3])));
135 // 1.0 / (gp1 - g * 0.5 * G4
136 const __m128 beta3 = reci(S(gp1, M(hg, f->C[dlf_G4])));
137 // 1.0 / gp1
138 const __m128 beta4 = reci(gp1);
139
140 // nothing to compute for deltas, inline them
141
142 // G1 * G2 + 1.0
143 const __m128 gamma1 = A(M(f->C[dlf_G1], f->C[dlf_G2]), one);
144 // G2 * G3 + 1.0
145 const __m128 gamma2 = A(M(f->C[dlf_G2], f->C[dlf_G3]), one);
146 // G3 * G4 + 1.0
147 const __m128 gamma3 = A(M(f->C[dlf_G3], f->C[dlf_G4]), one);
148 // gamma4 is always 1.0, just inline it
149
150 // nothing to compute for epsilons or ma0, inline them
151
152 // feedback4 is always zero, inline it
153 const __m128 feedback3 = getFO(beta4, zero, zero, f->R[dlf_z4]);
154 const __m128 feedback2 = getFO(beta3, hg, f->R[dlf_feedback3], f->R[dlf_z3]);
155 const __m128 feedback1 = getFO(beta2, hg, f->R[dlf_feedback2], f->R[dlf_z2]);
156
157 const __m128 sigma = A(A(A(M(sg1, getFO(beta1, g, feedback1, f->R[dlf_z1])),
158 M(sg2, getFO(beta2, hg, feedback2, f->R[dlf_z2]))),
159 M(sg3, getFO(beta3, hg, feedback3, f->R[dlf_z3]))),
160 M(one, getFO(beta4, zero, zero, f->R[dlf_z4])));
161
162 f->R[dlf_feedback3] = feedback3;
163 f->R[dlf_feedback2] = feedback2;
164 f->R[dlf_feedback1] = feedback1;
165
166 // gain compensation
167 const __m128 comp = M(A(M(F(0.3), f->C[dlf_km]), one), input);
168
169 // (comp - km * sigma) / (km * gamma + 1.0)
170 const __m128 u = D(S(comp, M(f->C[dlf_km], sigma)), A(M(f->C[dlf_km], f->C[dlf_gamma]), one));
171
172 const __m128 result1 = doLpf(u, f->C[dlf_alpha], beta1, gamma1, g, f->C[dlf_G2], one, feedback1,
173 getFO(beta1, g, feedback1, f->R[dlf_z1]), f->R[dlf_z1]);
174 const __m128 result2 =
175 doLpf(result1, f->C[dlf_alpha], beta2, gamma2, hg, f->C[dlf_G3], half, feedback2,
176 getFO(beta2, hg, feedback2, f->R[dlf_z2]), f->R[dlf_z2]);
177 const __m128 result3 =
178 doLpf(result2, f->C[dlf_alpha], beta3, gamma3, hg, f->C[dlf_G4], half, feedback3,
179 getFO(beta3, hg, feedback3, f->R[dlf_z3]), f->R[dlf_z3]);
180 const __m128 result4 = doLpf(result3, f->C[dlf_alpha], beta4, one, zero, zero, half, zero,
181 getFO(beta4, zero, zero, f->R[dlf_z4]), f->R[dlf_z4]);
182
183 // Just like in QuadFilterUnit.cpp/LPMOOGquad, it's fine for the whole quad to return the same
184 // subtype because integer parameters like f->WP are not modulatable and QuadFilterUnit is only
185 // parallel across voices, so it would have been the same for each part of the quad anyway.
186 switch (f->WP[0] & 3)
187 {
188 case 0:
189 return M(result1, F(0.125)); // 6dB/oct
190 case 1:
191 return M(result2, F(0.3)); // 12dB/oct
192 case 2:
193 return M(result3, F(0.6)); // 18dB/oct
194 default:
195 return M(result4, F(1.2)); // 24dB/oct
196 }
197 }
198 } // namespace DiodeLadderFilter
199