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