resid/
filter.rs

1// This file is part of resid-rs.
2// Copyright (c) 2017-2019 Sebastian Jastrzebski <sebby2k@gmail.com>. All rights reserved.
3// Portions (c) 2004 Dag Lem <resid@nimrod.no>
4// Licensed under the GPLv3. See LICENSE file in the project root for full license text.
5
6#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
7
8use core::f64;
9
10use super::data::{SPLINE6581_F0, SPLINE8580_F0};
11use super::ChipModel;
12
13const MIXER_DC: i32 = (-0xfff * 0xff / 18) >> 7;
14
15/// The SID filter is modeled with a two-integrator-loop biquadratic filter,
16/// which has been confirmed by Bob Yannes to be the actual circuit used in
17/// the SID chip.
18///
19/// Measurements show that excellent emulation of the SID filter is achieved,
20/// except when high resonance is combined with high sustain levels.
21/// In this case the SID op-amps are performing less than ideally and are
22/// causing some peculiar behavior of the SID filter. This however seems to
23/// have more effect on the overall amplitude than on the color of the sound.
24///
25/// The theory for the filter circuit can be found in "Microelectric Circuits"
26/// by Adel S. Sedra and Kenneth C. Smith.
27/// The circuit is modeled based on the explanation found there except that
28/// an additional inverter is used in the feedback from the bandpass output,
29/// allowing the summer op-amp to operate in single-ended mode. This yields
30/// inverted filter outputs with levels independent of Q, which corresponds with
31/// the results obtained from a real SID.
32///
33/// We have been able to model the summer and the two integrators of the circuit
34/// to form components of an IIR filter.
35/// Vhp is the output of the summer, Vbp is the output of the first integrator,
36/// and Vlp is the output of the second integrator in the filter circuit.
37///
38/// According to Bob Yannes, the active stages of the SID filter are not really
39/// op-amps. Rather, simple NMOS inverters are used. By biasing an inverter
40/// into its region of quasi-linear operation using a feedback resistor from
41/// input to output, a MOS inverter can be made to act like an op-amp for
42/// small signals centered around the switching threshold.
43#[derive(Clone, Copy)]
44pub struct Filter {
45    // Configuration
46    enabled: bool,
47    fc: u16,
48    filt: u8,
49    res: u8,
50    // Mode
51    voice3_off: bool,
52    hp_bp_lp: u8,
53    vol: u8,
54    // Runtime State
55    pub vhp: i32,
56    pub vbp: i32,
57    pub vlp: i32,
58    pub vnf: i32,
59    // Cutoff Freq/Res
60    mixer_dc: i32,
61    q_1024_div: i32,
62    w0: i32,
63    w0_ceil_1: i32,
64    w0_ceil_dt: i32,
65    // Cutoff Freq Tables
66    f0: &'static [i16; 2048],
67}
68
69impl Filter {
70    pub fn new(chip_model: ChipModel) -> Self {
71        let f0 = match chip_model {
72            ChipModel::Mos6581 => &SPLINE6581_F0,
73            ChipModel::Mos8580 => &SPLINE8580_F0,
74        };
75        let mut filter = Filter {
76            enabled: true,
77            fc: 0,
78            filt: 0,
79            res: 0,
80            voice3_off: false,
81            hp_bp_lp: 0,
82            vol: 0,
83            vhp: 0,
84            vbp: 0,
85            vlp: 0,
86            vnf: 0,
87            mixer_dc: MIXER_DC,
88            q_1024_div: 0,
89            w0: 0,
90            w0_ceil_1: 0,
91            w0_ceil_dt: 0,
92            f0,
93        };
94        filter.set_q();
95        filter.set_w0();
96        filter
97    }
98
99    pub fn get_fc_hi(&self) -> u8 {
100        (self.fc >> 3) as u8
101    }
102
103    pub fn get_fc_lo(&self) -> u8 {
104        (self.fc & 0x007) as u8
105    }
106
107    pub fn get_mode_vol(&self) -> u8 {
108        let value = if self.voice3_off { 0x80 } else { 0 };
109        value | (self.hp_bp_lp << 4) | (self.vol & 0x0f)
110    }
111
112    pub fn get_res_filt(&self) -> u8 {
113        (self.res << 4) | (self.filt & 0x0f)
114    }
115
116    pub fn set_enabled(&mut self, enabled: bool) {
117        self.enabled = enabled;
118    }
119
120    pub fn set_fc_hi(&mut self, value: u8) {
121        let result = ((value as u16) << 3) & 0x7f8 | self.fc & 0x007;
122        self.fc = result;
123        self.set_w0();
124    }
125
126    pub fn set_fc_lo(&mut self, value: u8) {
127        let result = self.fc & 0x7f8 | (value as u16) & 0x007;
128        self.fc = result;
129        self.set_w0();
130    }
131
132    pub fn set_mode_vol(&mut self, value: u8) {
133        self.voice3_off = value & 0x80 != 0;
134        self.hp_bp_lp = (value >> 4) & 0x07;
135        self.vol = value & 0x0f;
136    }
137
138    pub fn set_res_filt(&mut self, value: u8) {
139        self.res = (value >> 4) & 0x0f;
140        self.filt = value & 0x0f;
141        self.set_q();
142    }
143
144    #[inline]
145    pub fn clock(&mut self, mut voice1: i32, mut voice2: i32, mut voice3: i32, mut ext_in: i32) {
146        // Scale each voice down from 20 to 13 bits.
147        voice1 >>= 7;
148        voice2 >>= 7;
149        // NB! Voice 3 is not silenced by voice3off if it is routed through
150        // the filter.
151        voice3 = if self.voice3_off && self.filt & 0x04 == 0 {
152            0
153        } else {
154            voice3 >> 7
155        };
156        ext_in >>= 7;
157
158        // This is handy for testing.
159        if !self.enabled {
160            self.vnf = voice1 + voice2 + voice3 + ext_in;
161            self.vhp = 0;
162            self.vbp = 0;
163            self.vlp = 0;
164            return;
165        }
166
167        // Route voices into or around filter.
168        // The code below is expanded to a switch for faster execution.
169        // (filt1 ? Vi : Vnf) += voice1;
170        // (filt2 ? Vi : Vnf) += voice2;
171        // (filt3 ? Vi : Vnf) += voice3;
172        let vi = match self.filt {
173            0x0 => {
174                self.vnf = voice1 + voice2 + voice3 + ext_in;
175                0
176            }
177            0x1 => {
178                self.vnf = voice2 + voice3 + ext_in;
179                voice1
180            }
181            0x2 => {
182                self.vnf = voice1 + voice3 + ext_in;
183                voice2
184            }
185            0x3 => {
186                self.vnf = voice3 + ext_in;
187                voice1 + voice2
188            }
189            0x4 => {
190                self.vnf = voice1 + voice2 + ext_in;
191                voice3
192            }
193            0x5 => {
194                self.vnf = voice2 + ext_in;
195                voice1 + voice3
196            }
197            0x6 => {
198                self.vnf = voice1 + ext_in;
199                voice2 + voice3
200            }
201            0x7 => {
202                self.vnf = ext_in;
203                voice1 + voice2 + voice3
204            }
205            0x8 => {
206                self.vnf = voice1 + voice2 + voice3;
207                ext_in
208            }
209            0x9 => {
210                self.vnf = voice2 + voice3;
211                voice1 + ext_in
212            }
213            0xa => {
214                self.vnf = voice1 + voice3;
215                voice2 + ext_in
216            }
217            0xb => {
218                self.vnf = voice3;
219                voice1 + voice2 + ext_in
220            }
221            0xc => {
222                self.vnf = voice1 + voice2;
223                voice3 + ext_in
224            }
225            0xd => {
226                self.vnf = voice2;
227                voice1 + voice3 + ext_in
228            }
229            0xe => {
230                self.vnf = voice1;
231                voice2 + voice3 + ext_in
232            }
233            0xf => {
234                self.vnf = 0;
235                voice1 + voice2 + voice3 + ext_in
236            }
237            _ => {
238                self.vnf = voice1 + voice2 + voice3 + ext_in;
239                0
240            }
241        };
242
243        // delta_t = 1 is converted to seconds given a 1MHz clock by dividing
244        // with 1 000 000.
245
246        // Calculate filter outputs.
247        // Vhp = Vbp/Q - Vlp - Vi;
248        // dVbp = -w0*Vhp*dt;
249        // dVlp = -w0*Vbp*dt;
250        let dvbp = (self.w0_ceil_1 * self.vhp) >> 20;
251        let dvlp = (self.w0_ceil_1 * self.vbp) >> 20;
252        self.vbp -= dvbp;
253        self.vlp -= dvlp;
254        self.vhp = ((self.vbp * self.q_1024_div) >> 10) - self.vlp - vi;
255    }
256
257    #[inline]
258    pub fn clock_delta(
259        &mut self,
260        mut delta: u32,
261        mut voice1: i32,
262        mut voice2: i32,
263        mut voice3: i32,
264        mut ext_in: i32,
265    ) {
266        // Scale each voice down from 20 to 13 bits.
267        voice1 >>= 7;
268        voice2 >>= 7;
269        if self.voice3_off && self.filt & 0x04 == 0 {
270            voice3 = 0;
271        } else {
272            voice3 >>= 7;
273        }
274        ext_in >>= 7;
275        // Enable filter on/off.
276        // This is not really part of SID, but is useful for testing.
277        // On slow CPUs it may be necessary to bypass the filter to lower the CPU
278        // load.
279        if !self.enabled {
280            self.vnf = voice1 + voice2 + voice3 + ext_in;
281            self.vhp = 0;
282            self.vbp = 0;
283            self.vlp = 0;
284            return;
285        }
286
287        // Route voices into or around filter.
288        // The code below is expanded to a switch for faster execution.
289        // (filt1 ? Vi : Vnf) += voice1;
290        // (filt2 ? Vi : Vnf) += voice2;
291        // (filt3 ? Vi : Vnf) += voice3;
292        let vi = match self.filt {
293            0x0 => {
294                self.vnf = voice1 + voice2 + voice3 + ext_in;
295                0
296            }
297            0x1 => {
298                self.vnf = voice2 + voice3 + ext_in;
299                voice1
300            }
301            0x2 => {
302                self.vnf = voice1 + voice3 + ext_in;
303                voice2
304            }
305            0x3 => {
306                self.vnf = voice3 + ext_in;
307                voice1 + voice2
308            }
309            0x4 => {
310                self.vnf = voice1 + voice2 + ext_in;
311                voice3
312            }
313            0x5 => {
314                self.vnf = voice2 + ext_in;
315                voice1 + voice3
316            }
317            0x6 => {
318                self.vnf = voice1 + ext_in;
319                voice2 + voice3
320            }
321            0x7 => {
322                self.vnf = ext_in;
323                voice1 + voice2 + voice3
324            }
325            0x8 => {
326                self.vnf = voice1 + voice2 + voice3;
327                ext_in
328            }
329            0x9 => {
330                self.vnf = voice2 + voice3;
331                voice1 + ext_in
332            }
333            0xa => {
334                self.vnf = voice1 + voice3;
335                voice2 + ext_in
336            }
337            0xb => {
338                self.vnf = voice3;
339                voice1 + voice2 + ext_in
340            }
341            0xc => {
342                self.vnf = voice1 + voice2;
343                voice3 + ext_in
344            }
345            0xd => {
346                self.vnf = voice2;
347                voice1 + voice3 + ext_in
348            }
349            0xe => {
350                self.vnf = voice1;
351                voice2 + voice3 + ext_in
352            }
353            0xf => {
354                self.vnf = 0;
355                voice1 + voice2 + voice3 + ext_in
356            }
357            _ => {
358                self.vnf = voice1 + voice2 + voice3 + ext_in;
359                0
360            }
361        };
362
363        // Maximum delta cycles for the filter to work satisfactorily under current
364        // cutoff frequency and resonance constraints is approximately 8.
365        let mut delta_flt = 8;
366
367        while delta != 0 {
368            if delta < delta_flt {
369                delta_flt = delta;
370            }
371            // delta_t is converted to seconds given a 1MHz clock by dividing
372            // with 1 000 000. This is done in two operations to avoid integer
373            // multiplication overflow.
374
375            // Calculate filter outputs.
376            // Vhp = Vbp/Q - Vlp - Vi;
377            // dVbp = -w0*Vhp*dt;
378            // dVlp = -w0*Vbp*dt;
379            let w0_delta_t = (self.w0_ceil_dt * delta_flt as i32) >> 6;
380            let dvbp = (w0_delta_t * self.vhp) >> 14;
381            let dvlp = (w0_delta_t * self.vbp) >> 14;
382            self.vbp -= dvbp;
383            self.vlp -= dvlp;
384            self.vhp = ((self.vbp * self.q_1024_div) >> 10) - self.vlp - vi;
385
386            delta -= delta_flt;
387        }
388    }
389
390    #[inline]
391    pub fn output(&self) -> i32 {
392        // This is handy for testing.
393        if !self.enabled {
394            (self.vnf + self.mixer_dc) * self.vol as i32
395        } else {
396            // Mix highpass, bandpass, and lowpass outputs. The sum is not
397            // weighted, this can be confirmed by sampling sound output for
398            // e.g. bandpass, lowpass, and bandpass+lowpass from a SID chip.
399            // The code below is expanded to a switch for faster execution.
400            // if (hp) Vf += Vhp;
401            // if (bp) Vf += Vbp;
402            // if (lp) Vf += Vlp;
403            let vf = match self.hp_bp_lp {
404                0x0 => 0,
405                0x1 => self.vlp,
406                0x2 => self.vbp,
407                0x3 => self.vlp + self.vbp,
408                0x4 => self.vhp,
409                0x5 => self.vlp + self.vhp,
410                0x6 => self.vbp + self.vhp,
411                0x7 => self.vlp + self.vbp + self.vhp,
412                _ => 0,
413            };
414            // Sum non-filtered and filtered output.
415            // Multiply the sum with volume.
416            (self.vnf + vf + self.mixer_dc) * self.vol as i32
417        }
418    }
419
420    pub fn reset(&mut self) {
421        self.fc = 0;
422        self.filt = 0;
423        self.res = 0;
424        self.voice3_off = false;
425        self.hp_bp_lp = 0;
426        self.vol = 0;
427        self.vhp = 0;
428        self.vbp = 0;
429        self.vlp = 0;
430        self.vnf = 0;
431        self.set_w0();
432        self.set_q();
433    }
434
435    fn set_q(&mut self) {
436        // Q is controlled linearly by res. Q has approximate range [0.707, 1.7].
437        // As resonance is increased, the filter must be clocked more often to keep
438        // stable.
439
440        // The coefficient 1024 is dispensed of later by right-shifting 10 times
441        // (2 ^ 10 = 1024).
442        self.q_1024_div = (1024.0 / (0.707 + 1.0 * self.res as f64 / 15.0)) as i32;
443    }
444
445    fn set_w0(&mut self) {
446        // Multiply with 1.048576 to facilitate division by 1 000 000 by right-
447        // shifting 20 times (2 ^ 20 = 1048576).
448        self.w0 = (2.0 * f64::consts::PI * self.f0[self.fc as usize] as f64 * 1.048_576) as i32;
449
450        // Limit f0 to 16kHz to keep 1 cycle filter stable.
451        let w0_max_1 = (2.0 * f64::consts::PI * 16000.0 * 1.048_576) as i32;
452        self.w0_ceil_1 = if self.w0 <= w0_max_1 {
453            self.w0
454        } else {
455            w0_max_1
456        };
457
458        // Limit f0 to 4kHz to keep delta_t cycle filter stable.
459        let w0_max_dt = (2.0 * f64::consts::PI * 4000.0 * 1.048_576) as i32;
460        self.w0_ceil_dt = if self.w0 <= w0_max_dt {
461            self.w0
462        } else {
463            w0_max_dt
464        };
465    }
466}