1 //  This file is part of reSID, a MOS6581 SID emulator engine.
2 //  Copyright (C) 2004  Dag Lem <resid@nimrod.no>
3 //
4 //  This program is free software; you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation; either version 2 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; if not, write to the Free Software
16 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 //  ---------------------------------------------------------------------------
18 //  Filter distortion code written by Antti S. Lankila 2007 - 2008.
19 
20 #ifndef __FILTER_H__
21 #define __FILTER_H__
22 
23 #include <math.h>
24 #include "siddefs-fp.h"
25 
26 // ----------------------------------------------------------------------------
27 // The SID filter is modeled with a two-integrator-loop biquadratic filter,
28 // which has been confirmed by Bob Yannes to be the actual circuit used in
29 // the SID chip.
30 //
31 // Measurements show that excellent emulation of the SID filter is achieved,
32 // except when high resonance is combined with high sustain levels.
33 // In this case the SID op-amps are performing less than ideally and are
34 // causing some peculiar behavior of the SID filter. This however seems to
35 // have more effect on the overall amplitude than on the color of the sound.
36 //
37 // The theory for the filter circuit can be found in "Microelectric Circuits"
38 // by Adel S. Sedra and Kenneth C. Smith.
39 // The circuit is modeled based on the explanation found there except that
40 // an additional inverter is used in the feedback from the bandpass output,
41 // allowing the summer op-amp to operate in single-ended mode. This yields
42 // inverted filter outputs with levels independent of Q, which corresponds with
43 // the results obtained from a real SID.
44 //
45 // We have been able to model the summer and the two integrators of the circuit
46 // to form components of an IIR filter.
47 // Vhp is the output of the summer, Vbp is the output of the first integrator,
48 // and Vlp is the output of the second integrator in the filter circuit.
49 //
50 // According to Bob Yannes, the active stages of the SID filter are not really
51 // op-amps. Rather, simple NMOS inverters are used. By biasing an inverter
52 // into its region of quasi-linear operation using a feedback resistor from
53 // input to output, a MOS inverter can be made to act like an op-amp for
54 // small signals centered around the switching threshold.
55 //
56 // Qualified guesses at SID filter schematics are depicted below.
57 //
58 // SID filter
59 // ----------
60 //
61 //     -----------------------------------------------
62 //    |                                               |
63 //    |            ---Rq--                            |
64 //    |           |       |                           |
65 //    |  ------------<A]-----R1---------              |
66 //    | |                               |             |
67 //    | |                        ---C---|      ---C---|
68 //    | |                       |       |     |       |
69 //    |  --R1--    ---R1--      |---Rs--|     |---Rs--|
70 //    |        |  |       |     |       |     |       |
71 //     ----R1--|-----[A>--|--R-----[A>--|--R-----[A>--|
72 //             |          |             |             |
73 // vi -----R1--           |             |             |
74 //
75 //                       vhp           vbp           vlp
76 //
77 //
78 // vi  - input voltage
79 // vhp - highpass output
80 // vbp - bandpass output
81 // vlp - lowpass output
82 // [A> - op-amp
83 // R1  - summer resistor
84 // Rq  - resistor array controlling resonance (4 resistors)
85 // R   - NMOS FET voltage controlled resistor controlling cutoff frequency
86 // Rs  - shunt resitor
87 // C   - capacitor
88 //
89 //
90 //
91 // SID integrator
92 // --------------
93 //
94 //                                   V+
95 //
96 //                                   |
97 //                                   |
98 //                              -----|
99 //                             |     |
100 //                             | ||--
101 //                              -||
102 //                   ---C---     ||->
103 //                  |       |        |
104 //                  |---Rs-----------|---- vo
105 //                  |                |
106 //                  |            ||--
107 // vi ----     -----|------------||
108 //        |   ^     |            ||->
109 //        |___|     |                |
110 //        -----     |                |
111 //          |       |                |
112 //          |---R2--                 |
113 //          |
114 //          R1                       V-
115 //          |
116 //          |
117 //
118 //          Vw
119 //
120 // ----------------------------------------------------------------------------
121 class FilterFP
122 {
123 public:
124   FilterFP();
125 
126   void enable_filter(bool enable);
127   void set_chip_model(chip_model model);
128   void set_distortion_properties(float, float, float);
129   void set_type3_properties(float, float, float, float);
130   void set_type4_properties(float, float);
131   void set_clock_frequency(float);
132 
133   RESID_INLINE
134   float clock(float voice1, float voice2, float voice3,
135               float ext_in);
136   void reset();
137 
138   // Write registers.
139   void writeFC_LO(reg8);
140   void writeFC_HI(reg8);
141   void writeRES_FILT(reg8);
142   void writeMODE_VOL(reg8);
143 
144 private:
145   void set_Q();
146   void set_w0();
147   float type3_w0(const float source, const float offset);
148   float type4_w0();
149   void calculate_helpers();
150   void nuke_denormals();
151 
152   // Filter enabled.
153   bool enabled;
154 
155   // 6581/8580 filter model (XXX: we should specialize in separate classes)
156   chip_model model;
157 
158   // Filter cutoff frequency.
159   reg12 fc;
160 
161   // Filter resonance.
162   reg8 res;
163 
164   // Selects which inputs to route through filter.
165   reg8 filt;
166 
167   // Switch voice 3 off.
168   reg8 voice3off;
169 
170   // Highpass, bandpass, and lowpass filter modes.
171   reg8 hp_bp_lp;
172 
173   // Output master volume.
174   reg4 vol;
175   float volf; /* avoid integer-to-float conversion at output */
176 
177   // clock
178   float clock_frequency;
179 
180   /* Distortion params for Type3 */
181   float distortion_rate, distortion_point, distortion_cf_threshold;
182 
183   /* Type3 params. */
184   float type3_baseresistance, type3_offset, type3_steepness, type3_minimumfetresistance;
185 
186   /* Type4 params */
187   float type4_k, type4_b;
188 
189   // State of filter.
190   float Vhp, Vbp, Vlp;
191 
192   /* Resonance/Distortion/Type3/Type4 helpers. */
193   float type4_w0_cache, _1_div_Q, type3_fc_kink_exp, distortion_CT,
194         type3_fc_distortion_offset_bp, type3_fc_distortion_offset_hp;
195 
196 friend class SIDFP;
197 };
198 
199 // ----------------------------------------------------------------------------
200 // Inline functions.
201 // The following functions are defined inline because they are called every
202 // time a sample is calculated.
203 // ----------------------------------------------------------------------------
204 
205 /* kinkiness of DAC:
206  * some chips have more, some less. We should make this tunable. */
207 const float kinkiness = 0.966f;
208 const float sidcaps_6581 = 470e-12f;
209 const float outputleveldifference_lp_bp = 1.4f;
210 const float outputleveldifference_bp_hp = 1.2f;
211 
212 RESID_INLINE
fastexp(float val)213 static float fastexp(float val) {
214     typedef union {
215         int i;
216         float f;
217     } conv;
218 
219     conv tmp;
220 
221     /* single precision fp has 1 + 8 + 23 bits, exponent bias is 127.
222      * It therefore follows that we need to shift left by 23 bits, and to
223      * calculate exp(x) instead of pow(2, x) we divide the power by ln(2). */
224     const float a = (1 << 23) / M_LN2_f;
225     /* The other factor corrects for the exponent bias so that 2^0 = 1. */
226     const float b = (1 << 23) * 127;
227     /* According to "A Fast, Compact Approximation of the Exponential Function"
228      * by Nicol N. Schraudolph, 60801.48 yields the minimum RMS error for the
229      * piecewise-linear approximation when using doubles (20 bits residual).
230      * We have 23 bits, so we scale this value by 8. */
231     const float c = 60801.48f * 8.f + 0.5f;
232 
233     /* Parenthesis are important: C standard disallows folding subtraction.
234      * Unfortunately GCC appears to generate a write to memory rather than
235      * handle this conversion entirely in registers. */
236     tmp.i = (int)(a * val + (b - c));
237     return tmp.f;
238 }
239 
240 RESID_INLINE
type3_w0(const float source,const float distoffset)241 float FilterFP::type3_w0(const float source, const float distoffset)
242 {
243     /* The distortion appears to be the result of MOSFET entering saturation
244      * mode. The conductance of a FET is proportional to:
245      *
246      * ohmic = 2 * (Vgs - Vt) * Vds - Vds^2
247      * saturation = (Vgs - Vt)^2
248      *
249      * The FET switches to saturation mode when Vgs - Vt < Vds.
250      *
251      * In the circuit, the Vgs is mixed with the Vds signal, which gives
252      * (Vgs + Vds) / 2 as the gate voltage. Doing the substitutions we get:
253      *
254      * ohmic = 2 * ((Vgs + Vds) / 2 - Vt) * Vds - Vds^2 = (Vgs - Vt) * Vds
255      * saturation = ((Vgs + Vds) / 2 - Vt)^2
256      *
257      * Therefore: once the Vds crosses a threshold given by the gate and
258      * threshold FET conductance begins to increase faster. The exact shape
259      * for this effect is a parabola.
260      *
261      * The scaling term here tries to match the FC control level with
262      * the signal level in simulation. On the chip, the FC control is
263      * biased by forcing its highest DAC bit in the 1 position, thus
264      * limiting the electrical range to half. Therefore one can guess that
265      * the real FC range is half of the full voice range.
266      *
267      * On the simulation, FC goes to 2047 and the voices to 4095 * 255.
268      * If the FC control was intact, then the scaling factor would be
269      * 1/512. (Simulation voices are 512 times "louder" intrinsically.)
270      * As the real chip's FC has reduced range, the scaling required to
271      * match levels is 1/256. */
272 
273     float fetresistance = type3_fc_kink_exp;
274     if (source > distoffset) {
275         const float dist = source - distoffset;
276         fetresistance *= fastexp(dist * type3_steepness * distortion_rate);
277     }
278     const float dynamic_resistance = type3_minimumfetresistance + fetresistance;
279 
280     /* 2 parallel resistors */
281     const float _1_div_resistance = (type3_baseresistance + dynamic_resistance) / (type3_baseresistance * dynamic_resistance);
282     /* 1.f / (clock * caps * resistance) */
283     return distortion_CT * _1_div_resistance;
284 }
285 
286 RESID_INLINE
type4_w0()287 float FilterFP::type4_w0()
288 {
289     const float freq = type4_k * fc + type4_b;
290     return 2.f * M_PI_f * freq / clock_frequency;
291 }
292 
293 // ----------------------------------------------------------------------------
294 // SID clocking - 1 cycle.
295 // ----------------------------------------------------------------------------
296 RESID_INLINE
clock(float voice1,float voice2,float voice3,float ext_in)297 float FilterFP::clock(float voice1,
298                    float voice2,
299                    float voice3,
300                    float ext_in)
301 {
302     /* Avoid denormal numbers by using small offsets from 0 */
303     float Vi = 0.f, Vnf = 0.f, Vf = 0.f;
304 
305     // Route voices into or around filter.
306     ((filt & 1) ? Vi : Vnf) += voice1;
307     ((filt & 2) ? Vi : Vnf) += voice2;
308     // NB! Voice 3 is not silenced by voice3off if it is routed through
309     // the filter.
310     if (filt & 4)
311         Vi += voice3;
312     else if (! voice3off)
313         Vnf += voice3;
314     ((filt & 8) ? Vi : Vnf) += ext_in;
315 
316     if (! enabled)
317         return (Vnf - Vi) * volf;
318 
319     if (hp_bp_lp & 1)
320         Vf += Vlp;
321     if (hp_bp_lp & 2)
322         Vf += Vbp;
323     if (hp_bp_lp & 4)
324         Vf += Vhp;
325 
326     if (model == MOS6581FP) {
327         float diff1, diff2;
328 
329         Vhp = Vbp * _1_div_Q * (1.f/outputleveldifference_bp_hp) - Vlp * (1.f/outputleveldifference_bp_hp) - Vi * 0.5f;
330 
331         /* the input summer mixing, or something like it... */
332         diff1 = (Vlp - Vbp) * distortion_cf_threshold;
333         diff2 = (Vhp - Vbp) * distortion_cf_threshold;
334         Vlp -= diff1;
335         Vbp += diff1;
336         Vbp += diff2;
337         Vhp -= diff2;
338 
339         /* Model output strip mixing. Doing it now that HP state
340          * variable modifying still makes some difference.
341          * (Phase error, though.) */
342         if (hp_bp_lp & 1)
343             Vlp += (Vf + Vnf - Vlp) * distortion_cf_threshold;
344         if (hp_bp_lp & 2)
345             Vbp += (Vf + Vnf - Vbp) * distortion_cf_threshold;
346         if (hp_bp_lp & 4)
347             Vhp += (Vf + Vnf - Vhp) * distortion_cf_threshold;
348 
349         /* Simulating the exponential VCR that the FET block is... */
350         Vlp -= Vbp * type3_w0(Vbp, type3_fc_distortion_offset_bp);
351         Vbp -= Vhp * type3_w0(Vhp, type3_fc_distortion_offset_hp) * outputleveldifference_bp_hp;
352 
353         /* Tuned based on Fred Gray's Break Thru. It is probably not a hard
354          * discontinuity but a saturation effect... */
355         if (Vnf > 3.2e6f)
356             Vnf = 3.2e6f;
357 
358         Vf += Vnf + Vlp * (outputleveldifference_lp_bp - 1.f);
359     } else {
360         /* On the 8580, BP appears mixed in phase with the rest. */
361         Vhp = -Vbp * _1_div_Q - Vlp - Vi;
362         Vlp += Vbp * type4_w0_cache;
363         Vbp += Vhp * type4_w0_cache;
364 
365         Vf += Vnf;
366     }
367 
368     return Vf * volf;
369 }
370 
371 RESID_INLINE
nuke_denormals()372 void FilterFP::nuke_denormals()
373 {
374     /* We could use the flush-to-zero flag or denormals-are-zero on systems
375      * where compiling with -msse and -mfpmath=sse is acceptable. Since this
376      * doesn't include general VICE builds, we do this instead. */
377     if (Vbp > -1e-12f && Vbp < 1e-12f)
378         Vbp = 0;
379     if (Vlp > -1e-12f && Vlp < 1e-12f)
380         Vlp = 0;
381 }
382 
383 #endif // not __FILTER_H__
384