DOSBox-X
|
00001 // --------------------------------------------------------------------------- 00002 // This file is part of reSID, a MOS6581 SID emulator engine. 00003 // Copyright (C) 2004 Dag Lem <resid@nimrod.no> 00004 // 00005 // This program is free software; you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation; either version 2 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // This program is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License along 00016 // with this program; if not, write to the Free Software Foundation, Inc., 00017 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00018 // --------------------------------------------------------------------------- 00019 00020 #define __FILTER_CC__ 00021 #include "filter.h" 00022 00023 // Maximum cutoff frequency is specified as 00024 // FCmax = 2.6e-5/C = 2.6e-5/2200e-12 = 11818. 00025 // 00026 // Measurements indicate a cutoff frequency range of approximately 00027 // 220Hz - 18kHz on a MOS6581 fitted with 470pF capacitors. The function 00028 // mapping FC to cutoff frequency has the shape of the tanh function, with 00029 // a discontinuity at FCHI = 0x80. 00030 // In contrast, the MOS8580 almost perfectly corresponds with the 00031 // specification of a linear mapping from 30Hz to 12kHz. 00032 // 00033 // The mappings have been measured by feeding the SID with an external 00034 // signal since the chip itself is incapable of generating waveforms of 00035 // higher fundamental frequency than 4kHz. It is best to use the bandpass 00036 // output at full resonance to pick out the cutoff frequency at any given 00037 // FC setting. 00038 // 00039 // The mapping function is specified with spline interpolation points and 00040 // the function values are retrieved via table lookup. 00041 // 00042 // NB! Cutoff frequency characteristics may vary, we have modeled two 00043 // particular Commodore 64s. 00044 00045 fc_point Filter::f0_points_6581[] = 00046 { 00047 // FC f FCHI FCLO 00048 // ---------------------------- 00049 { 0, 220 }, // 0x00 - repeated end point 00050 { 0, 220 }, // 0x00 00051 { 128, 230 }, // 0x10 00052 { 256, 250 }, // 0x20 00053 { 384, 300 }, // 0x30 00054 { 512, 420 }, // 0x40 00055 { 640, 780 }, // 0x50 00056 { 768, 1600 }, // 0x60 00057 { 832, 2300 }, // 0x68 00058 { 896, 3200 }, // 0x70 00059 { 960, 4300 }, // 0x78 00060 { 992, 5000 }, // 0x7c 00061 { 1008, 5400 }, // 0x7e 00062 { 1016, 5700 }, // 0x7f 00063 { 1023, 6000 }, // 0x7f 0x07 00064 { 1023, 6000 }, // 0x7f 0x07 - discontinuity 00065 { 1024, 4600 }, // 0x80 - 00066 { 1024, 4600 }, // 0x80 00067 { 1032, 4800 }, // 0x81 00068 { 1056, 5300 }, // 0x84 00069 { 1088, 6000 }, // 0x88 00070 { 1120, 6600 }, // 0x8c 00071 { 1152, 7200 }, // 0x90 00072 { 1280, 9500 }, // 0xa0 00073 { 1408, 12000 }, // 0xb0 00074 { 1536, 14500 }, // 0xc0 00075 { 1664, 16000 }, // 0xd0 00076 { 1792, 17100 }, // 0xe0 00077 { 1920, 17700 }, // 0xf0 00078 { 2047, 18000 }, // 0xff 0x07 00079 { 2047, 18000 } // 0xff 0x07 - repeated end point 00080 }; 00081 00082 fc_point Filter::f0_points_8580[] = 00083 { 00084 // FC f FCHI FCLO 00085 // ---------------------------- 00086 { 0, 0 }, // 0x00 - repeated end point 00087 { 0, 0 }, // 0x00 00088 { 128, 800 }, // 0x10 00089 { 256, 1600 }, // 0x20 00090 { 384, 2500 }, // 0x30 00091 { 512, 3300 }, // 0x40 00092 { 640, 4100 }, // 0x50 00093 { 768, 4800 }, // 0x60 00094 { 896, 5600 }, // 0x70 00095 { 1024, 6500 }, // 0x80 00096 { 1152, 7500 }, // 0x90 00097 { 1280, 8400 }, // 0xa0 00098 { 1408, 9200 }, // 0xb0 00099 { 1536, 9800 }, // 0xc0 00100 { 1664, 10500 }, // 0xd0 00101 { 1792, 11000 }, // 0xe0 00102 { 1920, 11700 }, // 0xf0 00103 { 2047, 12500 }, // 0xff 0x07 00104 { 2047, 12500 } // 0xff 0x07 - repeated end point 00105 }; 00106 00107 00108 // ---------------------------------------------------------------------------- 00109 // Constructor. 00110 // ---------------------------------------------------------------------------- 00111 Filter::Filter() 00112 { 00113 fc = 0; 00114 00115 res = 0; 00116 00117 filt = 0; 00118 00119 voice3off = 0; 00120 00121 hp_bp_lp = 0; 00122 00123 vol = 0; 00124 00125 // State of filter. 00126 Vhp = 0; 00127 Vbp = 0; 00128 Vlp = 0; 00129 Vnf = 0; 00130 00131 enable_filter(true); 00132 00133 // Create mappings from FC to cutoff frequency. 00134 interpolate(f0_points_6581, f0_points_6581 00135 + sizeof(f0_points_6581)/sizeof(*f0_points_6581) - 1, 00136 PointPlotter<sound_sample>(f0_6581), 1.0); 00137 interpolate(f0_points_8580, f0_points_8580 00138 + sizeof(f0_points_8580)/sizeof(*f0_points_8580) - 1, 00139 PointPlotter<sound_sample>(f0_8580), 1.0); 00140 00141 set_chip_model(MOS6581); 00142 } 00143 00144 00145 // ---------------------------------------------------------------------------- 00146 // Enable filter. 00147 // ---------------------------------------------------------------------------- 00148 void Filter::enable_filter(bool enable) 00149 { 00150 enabled = enable; 00151 } 00152 00153 00154 // ---------------------------------------------------------------------------- 00155 // Set chip model. 00156 // ---------------------------------------------------------------------------- 00157 void Filter::set_chip_model(chip_model model) 00158 { 00159 if (model == MOS6581) { 00160 // The mixer has a small input DC offset. This is found as follows: 00161 // 00162 // The "zero" output level of the mixer measured on the SID audio 00163 // output pin is 5.50V at zero volume, and 5.44 at full 00164 // volume. This yields a DC offset of (5.44V - 5.50V) = -0.06V. 00165 // 00166 // The DC offset is thus -0.06V/1.05V ~ -1/18 of the dynamic range 00167 // of one voice. See voice.cc for measurement of the dynamic 00168 // range. 00169 00170 mixer_DC = -0xfff*0xff/18 >> 7; 00171 00172 f0 = f0_6581; 00173 f0_points = f0_points_6581; 00174 f0_count = int(sizeof(f0_points_6581)/sizeof(*f0_points_6581)); 00175 } 00176 else { 00177 // No DC offsets in the MOS8580. 00178 mixer_DC = 0; 00179 00180 f0 = f0_8580; 00181 f0_points = f0_points_8580; 00182 f0_count = int(sizeof(f0_points_8580)/sizeof(*f0_points_8580)); 00183 } 00184 00185 set_w0(); 00186 set_Q(); 00187 } 00188 00189 00190 // ---------------------------------------------------------------------------- 00191 // SID reset. 00192 // ---------------------------------------------------------------------------- 00193 void Filter::reset() 00194 { 00195 fc = 0; 00196 00197 res = 0; 00198 00199 filt = 0; 00200 00201 voice3off = 0; 00202 00203 hp_bp_lp = 0; 00204 00205 vol = 0; 00206 00207 // State of filter. 00208 Vhp = 0; 00209 Vbp = 0; 00210 Vlp = 0; 00211 Vnf = 0; 00212 00213 set_w0(); 00214 set_Q(); 00215 } 00216 00217 00218 // ---------------------------------------------------------------------------- 00219 // Register functions. 00220 // ---------------------------------------------------------------------------- 00221 void Filter::writeFC_LO(reg8 fc_lo) 00222 { 00223 fc = (fc & 0x7f8) | (fc_lo & 0x007); 00224 set_w0(); 00225 } 00226 00227 void Filter::writeFC_HI(reg8 fc_hi) 00228 { 00229 fc = ((fc_hi << 3) & 0x7f8) | (fc & 0x007); 00230 set_w0(); 00231 } 00232 00233 void Filter::writeRES_FILT(reg8 res_filt) 00234 { 00235 res = (res_filt >> 4) & 0x0f; 00236 set_Q(); 00237 00238 filt = res_filt & 0x0f; 00239 } 00240 00241 void Filter::writeMODE_VOL(reg8 mode_vol) 00242 { 00243 voice3off = mode_vol & 0x80; 00244 00245 hp_bp_lp = (mode_vol >> 4) & 0x07; 00246 00247 vol = mode_vol & 0x0f; 00248 } 00249 00250 // Set filter cutoff frequency. 00251 void Filter::set_w0() 00252 { 00253 const double pi = 3.1415926535897932385; 00254 00255 // Multiply with 1.048576 to facilitate division by 1 000 000 by right- 00256 // shifting 20 times (2 ^ 20 = 1048576). 00257 w0 = static_cast<sound_sample>(2*pi*f0[fc]*1.048576); 00258 00259 // Limit f0 to 16kHz to keep 1 cycle filter stable. 00260 const sound_sample w0_max_1 = static_cast<sound_sample>(2*pi*16000*1.048576); 00261 w0_ceil_1 = w0 <= w0_max_1 ? w0 : w0_max_1; 00262 00263 // Limit f0 to 4kHz to keep delta_t cycle filter stable. 00264 const sound_sample w0_max_dt = static_cast<sound_sample>(2*pi*4000*1.048576); 00265 w0_ceil_dt = w0 <= w0_max_dt ? w0 : w0_max_dt; 00266 } 00267 00268 // Set filter resonance. 00269 void Filter::set_Q() 00270 { 00271 // Q is controlled linearly by res. Q has approximate range [0.707, 1.7]. 00272 // As resonance is increased, the filter must be clocked more often to keep 00273 // stable. 00274 00275 // The coefficient 1024 is dispensed of later by right-shifting 10 times 00276 // (2 ^ 10 = 1024). 00277 _1024_div_Q = static_cast<sound_sample>(1024.0/(0.707 + 1.0*res/0x0f)); 00278 } 00279 00280 // ---------------------------------------------------------------------------- 00281 // Spline functions. 00282 // ---------------------------------------------------------------------------- 00283 00284 // ---------------------------------------------------------------------------- 00285 // Return the array of spline interpolation points used to map the FC register 00286 // to filter cutoff frequency. 00287 // ---------------------------------------------------------------------------- 00288 void Filter::fc_default(const fc_point*& points, int& count) 00289 { 00290 points = f0_points; 00291 count = f0_count; 00292 } 00293 00294 // ---------------------------------------------------------------------------- 00295 // Given an array of interpolation points p with n points, the following 00296 // statement will specify a new FC mapping: 00297 // interpolate(p, p + n - 1, filter.fc_plotter(), 1.0); 00298 // Note that the x range of the interpolation points *must* be [0, 2047], 00299 // and that additional end points *must* be present since the end points 00300 // are not interpolated. 00301 // ---------------------------------------------------------------------------- 00302 PointPlotter<sound_sample> Filter::fc_plotter() 00303 { 00304 return PointPlotter<sound_sample>(f0); 00305 } 00306