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}