1 //  ---------------------------------------------------------------------------
2 //  This file is part of reSID, a MOS6581 SID emulator engine.
3 //  Copyright (C) 2004  Dag Lem <resid@nimrod.no>
4 //
5 //  This program is free software; you can redistribute it and/or modify
6 //  it under the terms of the GNU General Public License as published by
7 //  the Free Software Foundation; either version 2 of the License, or
8 //  (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 //  ---------------------------------------------------------------------------
19 
20 #define __FILTER_CC__
21 #include "filter.h"
22 
23 // Maximum cutoff frequency is specified as
24 // FCmax = 2.6e-5/C = 2.6e-5/2200e-12 = 11818.
25 //
26 // Measurements indicate a cutoff frequency range of approximately
27 // 220Hz - 18kHz on a MOS6581 fitted with 470pF capacitors. The function
28 // mapping FC to cutoff frequency has the shape of the tanh function, with
29 // a discontinuity at FCHI = 0x80.
30 // In contrast, the MOS8580 almost perfectly corresponds with the
31 // specification of a linear mapping from 30Hz to 12kHz.
32 //
33 // The mappings have been measured by feeding the SID with an external
34 // signal since the chip itself is incapable of generating waveforms of
35 // higher fundamental frequency than 4kHz. It is best to use the bandpass
36 // output at full resonance to pick out the cutoff frequency at any given
37 // FC setting.
38 //
39 // The mapping function is specified with spline interpolation points and
40 // the function values are retrieved via table lookup.
41 //
42 // NB! Cutoff frequency characteristics may vary, we have modeled two
43 // particular Commodore 64s.
44 
45 fc_point Filter::f0_points_6581[] =
46 {
47   //  FC      f         FCHI FCLO
48   // ----------------------------
49   {    0,   220 },   // 0x00      - repeated end point
50   {    0,   220 },   // 0x00
51   {  128,   230 },   // 0x10
52   {  256,   250 },   // 0x20
53   {  384,   300 },   // 0x30
54   {  512,   420 },   // 0x40
55   {  640,   780 },   // 0x50
56   {  768,  1600 },   // 0x60
57   {  832,  2300 },   // 0x68
58   {  896,  3200 },   // 0x70
59   {  960,  4300 },   // 0x78
60   {  992,  5000 },   // 0x7c
61   { 1008,  5400 },   // 0x7e
62   { 1016,  5700 },   // 0x7f
63   { 1023,  6000 },   // 0x7f 0x07
64   { 1023,  6000 },   // 0x7f 0x07 - discontinuity
65   { 1024,  4600 },   // 0x80      -
66   { 1024,  4600 },   // 0x80
67   { 1032,  4800 },   // 0x81
68   { 1056,  5300 },   // 0x84
69   { 1088,  6000 },   // 0x88
70   { 1120,  6600 },   // 0x8c
71   { 1152,  7200 },   // 0x90
72   { 1280,  9500 },   // 0xa0
73   { 1408, 12000 },   // 0xb0
74   { 1536, 14500 },   // 0xc0
75   { 1664, 16000 },   // 0xd0
76   { 1792, 17100 },   // 0xe0
77   { 1920, 17700 },   // 0xf0
78   { 2047, 18000 },   // 0xff 0x07
79   { 2047, 18000 }    // 0xff 0x07 - repeated end point
80 };
81 
82 fc_point Filter::f0_points_8580[] =
83 {
84   //  FC      f         FCHI FCLO
85   // ----------------------------
86   {    0,     0 },   // 0x00      - repeated end point
87   {    0,     0 },   // 0x00
88   {  128,   800 },   // 0x10
89   {  256,  1600 },   // 0x20
90   {  384,  2500 },   // 0x30
91   {  512,  3300 },   // 0x40
92   {  640,  4100 },   // 0x50
93   {  768,  4800 },   // 0x60
94   {  896,  5600 },   // 0x70
95   { 1024,  6500 },   // 0x80
96   { 1152,  7500 },   // 0x90
97   { 1280,  8400 },   // 0xa0
98   { 1408,  9200 },   // 0xb0
99   { 1536,  9800 },   // 0xc0
100   { 1664, 10500 },   // 0xd0
101   { 1792, 11000 },   // 0xe0
102   { 1920, 11700 },   // 0xf0
103   { 2047, 12500 },   // 0xff 0x07
104   { 2047, 12500 }    // 0xff 0x07 - repeated end point
105 };
106 
107 
108 // ----------------------------------------------------------------------------
109 // Constructor.
110 // ----------------------------------------------------------------------------
Filter()111 Filter::Filter()
112 {
113   fc = 0;
114 
115   res = 0;
116 
117   filt = 0;
118 
119   voice3off = 0;
120 
121   hp_bp_lp = 0;
122 
123   vol = 0;
124 
125   // State of filter.
126   Vhp = 0;
127   Vbp = 0;
128   Vlp = 0;
129   Vnf = 0;
130 
131   enable_filter(true);
132 
133   // Create mappings from FC to cutoff frequency.
134   interpolate(f0_points_6581, f0_points_6581
135         + sizeof(f0_points_6581)/sizeof(*f0_points_6581) - 1,
136         PointPlotter<sound_sample>(f0_6581), 1.0);
137   interpolate(f0_points_8580, f0_points_8580
138         + sizeof(f0_points_8580)/sizeof(*f0_points_8580) - 1,
139         PointPlotter<sound_sample>(f0_8580), 1.0);
140 
141   set_chip_model(MOS6581);
142 }
143 
144 
145 // ----------------------------------------------------------------------------
146 // Enable filter.
147 // ----------------------------------------------------------------------------
enable_filter(bool enable)148 void Filter::enable_filter(bool enable)
149 {
150   enabled = enable;
151 }
152 
153 
154 // ----------------------------------------------------------------------------
155 // Set chip model.
156 // ----------------------------------------------------------------------------
set_chip_model(chip_model model)157 void Filter::set_chip_model(chip_model model)
158 {
159   if (model == MOS6581) {
160     // The mixer has a small input DC offset. This is found as follows:
161     //
162     // The "zero" output level of the mixer measured on the SID audio
163     // output pin is 5.50V at zero volume, and 5.44 at full
164     // volume. This yields a DC offset of (5.44V - 5.50V) = -0.06V.
165     //
166     // The DC offset is thus -0.06V/1.05V ~ -1/18 of the dynamic range
167     // of one voice. See voice.cc for measurement of the dynamic
168     // range.
169 
170     mixer_DC = -0xfff*0xff/18 >> 7;
171 
172     f0 = f0_6581;
173     f0_points = f0_points_6581;
174     f0_count = sizeof(f0_points_6581)/sizeof(*f0_points_6581);
175   }
176   else {
177     // No DC offsets in the MOS8580.
178     mixer_DC = 0;
179 
180     f0 = f0_8580;
181     f0_points = f0_points_8580;
182     f0_count = sizeof(f0_points_8580)/sizeof(*f0_points_8580);
183   }
184 
185   set_w0();
186   set_Q();
187 }
188 
189 
190 // ----------------------------------------------------------------------------
191 // SID reset.
192 // ----------------------------------------------------------------------------
reset()193 void Filter::reset()
194 {
195   fc = 0;
196 
197   res = 0;
198 
199   filt = 0;
200 
201   voice3off = 0;
202 
203   hp_bp_lp = 0;
204 
205   vol = 0;
206 
207   // State of filter.
208   Vhp = 0;
209   Vbp = 0;
210   Vlp = 0;
211   Vnf = 0;
212 
213   set_w0();
214   set_Q();
215 }
216 
217 
218 // ----------------------------------------------------------------------------
219 // Register functions.
220 // ----------------------------------------------------------------------------
writeFC_LO(reg8 fc_lo)221 void Filter::writeFC_LO(reg8 fc_lo)
222 {
223   fc = fc & 0x7f8 | fc_lo & 0x007;
224   set_w0();
225 }
226 
writeFC_HI(reg8 fc_hi)227 void Filter::writeFC_HI(reg8 fc_hi)
228 {
229   fc = (fc_hi << 3) & 0x7f8 | fc & 0x007;
230   set_w0();
231 }
232 
writeRES_FILT(reg8 res_filt)233 void Filter::writeRES_FILT(reg8 res_filt)
234 {
235   res = (res_filt >> 4) & 0x0f;
236   set_Q();
237 
238   filt = res_filt & 0x0f;
239 }
240 
writeMODE_VOL(reg8 mode_vol)241 void Filter::writeMODE_VOL(reg8 mode_vol)
242 {
243   voice3off = mode_vol & 0x80;
244 
245   hp_bp_lp = (mode_vol >> 4) & 0x07;
246 
247   vol = mode_vol & 0x0f;
248 }
249 
250 // Set filter cutoff frequency.
set_w0()251 void Filter::set_w0()
252 {
253   const double pi = 3.1415926535897932385;
254 
255   // Multiply with 1.048576 to facilitate division by 1 000 000 by right-
256   // shifting 20 times (2 ^ 20 = 1048576).
257   w0 = static_cast<sound_sample>(2*pi*f0[fc]*1.048576);
258 
259   // Limit f0 to 16kHz to keep 1 cycle filter stable.
260   const sound_sample w0_max_1 = static_cast<sound_sample>(2*pi*16000*1.048576);
261   w0_ceil_1 = w0 <= w0_max_1 ? w0 : w0_max_1;
262 
263   // Limit f0 to 4kHz to keep delta_t cycle filter stable.
264   const sound_sample w0_max_dt = static_cast<sound_sample>(2*pi*4000*1.048576);
265   w0_ceil_dt = w0 <= w0_max_dt ? w0 : w0_max_dt;
266 }
267 
268 // Set filter resonance.
set_Q()269 void Filter::set_Q()
270 {
271   // Q is controlled linearly by res. Q has approximate range [0.707, 1.7].
272   // As resonance is increased, the filter must be clocked more often to keep
273   // stable.
274 
275   // The coefficient 1024 is dispensed of later by right-shifting 10 times
276   // (2 ^ 10 = 1024).
277   _1024_div_Q = static_cast<sound_sample>(1024.0/(0.707 + 1.0*res/0x0f));
278 }
279 
280 // ----------------------------------------------------------------------------
281 // Spline functions.
282 // ----------------------------------------------------------------------------
283 
284 // ----------------------------------------------------------------------------
285 // Return the array of spline interpolation points used to map the FC register
286 // to filter cutoff frequency.
287 // ----------------------------------------------------------------------------
fc_default(const fc_point * & points,int & count)288 void Filter::fc_default(const fc_point*& points, int& count)
289 {
290   points = f0_points;
291   count = f0_count;
292 }
293 
294 // ----------------------------------------------------------------------------
295 // Given an array of interpolation points p with n points, the following
296 // statement will specify a new FC mapping:
297 //   interpolate(p, p + n - 1, filter.fc_plotter(), 1.0);
298 // Note that the x range of the interpolation points *must* be [0, 2047],
299 // and that additional end points *must* be present since the end points
300 // are not interpolated.
301 // ----------------------------------------------------------------------------
fc_plotter()302 PointPlotter<sound_sample> Filter::fc_plotter()
303 {
304   return PointPlotter<sound_sample>(f0);
305 }
306