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}