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