DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/hardware/reSID/filter.cpp
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