Skip to main content

resid/
external_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
6use super::ChipModel;
7
8/// Maximum mixer DC output level; to be removed if the external
9/// filter is turned off: ((wave DC + voice DC)*voices + mixer DC)*volume
10/// See voice.cc and filter.cc for an explanation of the values.
11const MIXER_DC_6581: i32 = ((((0x800 - 0x380) + 0x800) * 0xff * 3 - 0xfff * 0xff / 18) >> 7) * 0x0f;
12
13/// Low-pass:  R = 10kOhm, C = 1000pF; w0l = 1/RC = 1/(1e4*1e-9) = 100000
14/// High-pass: R =  1kOhm, C =   10uF; w0h = 1/RC = 1/(1e3*1e-5) =    100
15/// Multiply with 1.048576 to facilitate division by 1 000 000 by right-
16/// shifting 20 times (2 ^ 20 = 1048576).
17const W0_LP: i32 = 104_858;
18const W0_HP: i32 = 105;
19
20/// The audio output stage in a Commodore 64 consists of two STC networks,
21/// a low-pass filter with 3-dB frequency 16kHz followed by a high-pass
22/// filter with 3-dB frequency 16Hz (the latter provided an audio equipment
23/// input impedance of 1kOhm).
24/// The STC networks are connected with a BJT supposedly meant to act as
25/// a unity gain buffer, which is not really how it works. A more elaborate
26/// model would include the BJT, however DC circuit analysis yields BJT
27/// base-emitter and emitter-base impedances sufficiently low to produce
28/// additional low-pass and high-pass 3dB-frequencies in the order of hundreds
29/// of kHz. This calls for a sampling frequency of several MHz, which is far
30/// too high for practical use.
31#[derive(Clone, Copy)]
32pub struct ExternalFilter {
33    // Configuration
34    enabled: bool,
35    mixer_dc: i32,
36    w0_lp: i32,
37    w0_hp: i32,
38    // Runtime State
39    vlp: i32,
40    vhp: i32,
41    vo: i32,
42}
43
44impl ExternalFilter {
45    pub fn new(chip_model: ChipModel) -> Self {
46        let mixer_dc = match chip_model {
47            ChipModel::Mos6581 => MIXER_DC_6581,
48            ChipModel::Mos8580 => 0,
49        };
50        let mut filter = ExternalFilter {
51            enabled: true,
52            mixer_dc,
53            w0_lp: W0_LP,
54            w0_hp: W0_HP,
55            vlp: 0,
56            vhp: 0,
57            vo: 0,
58        };
59        filter.reset();
60        filter
61    }
62
63    pub fn set_enabled(&mut self, enabled: bool) {
64        self.enabled = enabled;
65    }
66
67    #[inline]
68    pub fn clock(&mut self, vi: i32) {
69        // delta_t is converted to seconds given a 1MHz clock by dividing
70        // with 1 000 000.
71        // Calculate filter outputs.
72        // Vo  = Vlp - Vhp;
73        // Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t;
74        // Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t;
75        if self.enabled {
76            let dvlp = ((self.w0_lp >> 8) * (vi - self.vlp)) >> 12;
77            let dvhp = (self.w0_hp * (self.vlp - self.vhp)) >> 20;
78            self.vo = self.vlp - self.vhp;
79            self.vlp += dvlp;
80            self.vhp += dvhp;
81        } else {
82            self.vlp = 0;
83            self.vhp = 0;
84            self.vo = vi - self.mixer_dc;
85        }
86    }
87
88    #[inline]
89    pub fn clock_delta(&mut self, mut delta: u32, vi: i32) {
90        if self.enabled {
91            // Maximum delta cycles for the external filter to work satisfactorily
92            // is approximately 8.
93            let mut delta_flt: u32 = 8;
94            while delta != 0 {
95                if delta < delta_flt {
96                    delta_flt = delta;
97                }
98                // delta_t is converted to seconds given a 1MHz clock by dividing
99                // with 1 000 000.
100                // Calculate filter outputs.
101                // Vo  = Vlp - Vhp;
102                // Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t;
103                // Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t;
104                let dvlp = (((self.w0_lp * delta_flt as i32) >> 8) * (vi - self.vlp)) >> 12;
105                let dvhp = (self.w0_hp * delta_flt as i32 * (self.vlp - self.vhp)) >> 20;
106                self.vo = self.vlp - self.vhp;
107                self.vlp += dvlp;
108                self.vhp += dvhp;
109                delta -= delta_flt;
110            }
111        } else {
112            self.vlp = 0;
113            self.vhp = 0;
114            self.vo = vi - self.mixer_dc;
115        }
116    }
117
118    #[inline]
119    pub fn output(&self) -> i32 {
120        self.vo
121    }
122
123    pub fn reset(&mut self) {
124        self.vlp = 0;
125        self.vhp = 0;
126        self.vo = 0;
127    }
128}