// --------------------------------------------------------------------------- // This file is part of reSID, a MOS6581 SID emulator engine. // Copyright (C) 2004 Dag Lem // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // --------------------------------------------------------------------------- #ifndef __FILTER_H__ #define __FILTER_H__ #include "siddefs.h" #include "spline.h" RESID_NAMESPACE_START // ---------------------------------------------------------------------------- // The SID filter is modeled with a two-integrator-loop biquadratic filter, // which has been confirmed by Bob Yannes to be the actual circuit used in // the SID chip. // // Measurements show that excellent emulation of the SID filter is achieved, // except when high resonance is combined with high sustain levels. // In this case the SID op-amps are performing less than ideally and are // causing some peculiar behavior of the SID filter. This however seems to // have more effect on the overall amplitude than on the color of the sound. // // The theory for the filter circuit can be found in "Microelectric Circuits" // by Adel S. Sedra and Kenneth C. Smith. // The circuit is modeled based on the explanation found there except that // an additional inverter is used in the feedback from the bandpass output, // allowing the summer op-amp to operate in single-ended mode. This yields // inverted filter outputs with levels independent of Q, which corresponds with // the results obtained from a real SID. // // We have been able to model the summer and the two integrators of the circuit // to form components of an IIR filter. // Vhp is the output of the summer, Vbp is the output of the first integrator, // and Vlp is the output of the second integrator in the filter circuit. // // According to Bob Yannes, the active stages of the SID filter are not really // op-amps. Rather, simple NMOS inverters are used. By biasing an inverter // into its region of quasi-linear operation using a feedback resistor from // input to output, a MOS inverter can be made to act like an op-amp for // small signals centered around the switching threshold. // // Qualified guesses at SID filter schematics are depicted below. // // SID filter // ---------- // // ----------------------------------------------- // | | // | ---Rq-- | // | | | | // | --------------|--R-----[A>--|--R-----[A>--| // | | | | // vi -----R1-- | | | // // vhp vbp vlp // // // vi - input voltage // vhp - highpass output // vbp - bandpass output // vlp - lowpass output // [A> - op-amp // R1 - summer resistor // Rq - resistor array controlling resonance (4 resistors) // R - NMOS FET voltage controlled resistor controlling cutoff frequency // Rs - shunt resitor // C - capacitor // // // // SID integrator // -------------- // // V+ // // | // | // -----| // | | // | ||-- // -|| // ---C--- ||-> // | | | // |---Rs-----------|---- vo // | | // | ||-- // vi ---- -----|------------|| // | ^ | ||-> // |___| | | // ----- | | // | | | // |---R2-- | // | // R1 V- // | // | // // Vw // // ---------------------------------------------------------------------------- class Filter { public: Filter(); void enable_filter(bool enable); // void set_chip_model(chip_model model); RESID_INLINE void clock(sound_sample voice1, sound_sample voice2, sound_sample voice3, sound_sample ext_in); RESID_INLINE void clock(cycle_count delta_t, sound_sample voice1, sound_sample voice2, sound_sample voice3, sound_sample ext_in); void reset(); // Write registers. void writeFC_LO(reg8); void writeFC_HI(reg8); void writeRES_FILT(reg8); void writeMODE_VOL(reg8); // SID audio output (16 bits). sound_sample output(); // Spline functions. void fc_default(const fc_point*& points, int& count); PointPlotter fc_plotter(); protected: void set_w0(); void set_Q(); // Filter enabled. bool enabled; // Filter cutoff frequency. reg12 fc; // Filter resonance. reg8 res; // Selects which inputs to route through filter. reg8 filt; // Switch voice 3 off. reg8 voice3off; // Highpass, bandpass, and lowpass filter modes. reg8 hp_bp_lp; // Output master volume. reg4 vol; // Mixer DC offset. sound_sample mixer_DC; // State of filter. sound_sample Vhp; // highpass sound_sample Vbp; // bandpass sound_sample Vlp; // lowpass sound_sample Vnf; // not filtered // Cutoff frequency, resonance. sound_sample w0, w0_ceil_1, w0_ceil_dt; sound_sample _1024_div_Q; // Cutoff frequency tables. // FC is an 11 bit register. sound_sample f0_6581[2048]; //sound_sample f0_8580[2048]; sound_sample* f0; const static fc_point f0_points_6581[]; //const static fc_point f0_points_8580[]; const fc_point* f0_points; int f0_count; friend class SID; }; // ---------------------------------------------------------------------------- // Inline functions. // The following functions are defined inline because they are called every // time a sample is calculated. // ---------------------------------------------------------------------------- #if RESID_INLINING || defined(__FILTER_CC__) // ---------------------------------------------------------------------------- // SID clocking - 1 cycle. // ---------------------------------------------------------------------------- RESID_INLINE void Filter::clock(sound_sample voice1, sound_sample voice2, sound_sample voice3, sound_sample ext_in) { // Scale each voice down from 20 to 13 bits. voice1 >>= 7; voice2 >>= 7; // NB! Voice 3 is not silenced by voice3off if it is routed through // the filter. if (voice3off && !(filt & 0x04)) { voice3 = 0; } else { voice3 >>= 7; } ext_in >>= 7; // This is handy for testing. if (!enabled) { Vnf = voice1 + voice2 + voice3 + ext_in; Vhp = Vbp = Vlp = 0; return; } // Route voices into or around filter. // The code below is expanded to a switch for faster execution. // (filt1 ? Vi : Vnf) += voice1; // (filt2 ? Vi : Vnf) += voice2; // (filt3 ? Vi : Vnf) += voice3; sound_sample Vi; switch (filt) { default: case 0x0: Vi = 0; Vnf = voice1 + voice2 + voice3 + ext_in; break; case 0x1: Vi = voice1; Vnf = voice2 + voice3 + ext_in; break; case 0x2: Vi = voice2; Vnf = voice1 + voice3 + ext_in; break; case 0x3: Vi = voice1 + voice2; Vnf = voice3 + ext_in; break; case 0x4: Vi = voice3; Vnf = voice1 + voice2 + ext_in; break; case 0x5: Vi = voice1 + voice3; Vnf = voice2 + ext_in; break; case 0x6: Vi = voice2 + voice3; Vnf = voice1 + ext_in; break; case 0x7: Vi = voice1 + voice2 + voice3; Vnf = ext_in; break; case 0x8: Vi = ext_in; Vnf = voice1 + voice2 + voice3; break; case 0x9: Vi = voice1 + ext_in; Vnf = voice2 + voice3; break; case 0xa: Vi = voice2 + ext_in; Vnf = voice1 + voice3; break; case 0xb: Vi = voice1 + voice2 + ext_in; Vnf = voice3; break; case 0xc: Vi = voice3 + ext_in; Vnf = voice1 + voice2; break; case 0xd: Vi = voice1 + voice3 + ext_in; Vnf = voice2; break; case 0xe: Vi = voice2 + voice3 + ext_in; Vnf = voice1; break; case 0xf: Vi = voice1 + voice2 + voice3 + ext_in; Vnf = 0; break; } // delta_t = 1 is converted to seconds given a 1MHz clock by dividing // with 1 000 000. // Calculate filter outputs. // Vhp = Vbp/Q - Vlp - Vi; // dVbp = -w0*Vhp*dt; // dVlp = -w0*Vbp*dt; sound_sample dVbp = (w0_ceil_1*Vhp >> 20); sound_sample dVlp = (w0_ceil_1*Vbp >> 20); Vbp -= dVbp; Vlp -= dVlp; Vhp = (Vbp*_1024_div_Q >> 10) - Vlp - Vi; } // ---------------------------------------------------------------------------- // SID clocking - delta_t cycles. // ---------------------------------------------------------------------------- RESID_INLINE void Filter::clock(cycle_count delta_t, sound_sample voice1, sound_sample voice2, sound_sample voice3, sound_sample ext_in) { // Scale each voice down from 20 to 13 bits. voice1 >>= 7; voice2 >>= 7; // NB! Voice 3 is not silenced by voice3off if it is routed through // the filter. if (voice3off && !(filt & 0x04)) { voice3 = 0; } else { voice3 >>= 7; } ext_in >>= 7; // Enable filter on/off. // This is not really part of SID, but is useful for testing. // On slow CPUs it may be necessary to bypass the filter to lower the CPU // load. if (!enabled) { Vnf = voice1 + voice2 + voice3 + ext_in; Vhp = Vbp = Vlp = 0; return; } // Route voices into or around filter. // The code below is expanded to a switch for faster execution. // (filt1 ? Vi : Vnf) += voice1; // (filt2 ? Vi : Vnf) += voice2; // (filt3 ? Vi : Vnf) += voice3; sound_sample Vi; switch (filt) { default: case 0x0: Vi = 0; Vnf = voice1 + voice2 + voice3 + ext_in; break; case 0x1: Vi = voice1; Vnf = voice2 + voice3 + ext_in; break; case 0x2: Vi = voice2; Vnf = voice1 + voice3 + ext_in; break; case 0x3: Vi = voice1 + voice2; Vnf = voice3 + ext_in; break; case 0x4: Vi = voice3; Vnf = voice1 + voice2 + ext_in; break; case 0x5: Vi = voice1 + voice3; Vnf = voice2 + ext_in; break; case 0x6: Vi = voice2 + voice3; Vnf = voice1 + ext_in; break; case 0x7: Vi = voice1 + voice2 + voice3; Vnf = ext_in; break; case 0x8: Vi = ext_in; Vnf = voice1 + voice2 + voice3; break; case 0x9: Vi = voice1 + ext_in; Vnf = voice2 + voice3; break; case 0xa: Vi = voice2 + ext_in; Vnf = voice1 + voice3; break; case 0xb: Vi = voice1 + voice2 + ext_in; Vnf = voice3; break; case 0xc: Vi = voice3 + ext_in; Vnf = voice1 + voice2; break; case 0xd: Vi = voice1 + voice3 + ext_in; Vnf = voice2; break; case 0xe: Vi = voice2 + voice3 + ext_in; Vnf = voice1; break; case 0xf: Vi = voice1 + voice2 + voice3 + ext_in; Vnf = 0; break; } // Maximum delta cycles for the filter to work satisfactorily under current // cutoff frequency and resonance constraints is approximately 8. cycle_count delta_t_flt = 8; while (delta_t) { if (delta_t < delta_t_flt) { delta_t_flt = delta_t; } // delta_t is converted to seconds given a 1MHz clock by dividing // with 1 000 000. This is done in two operations to avoid integer // multiplication overflow. // Calculate filter outputs. // Vhp = Vbp/Q - Vlp - Vi; // dVbp = -w0*Vhp*dt; // dVlp = -w0*Vbp*dt; sound_sample w0_delta_t = w0_ceil_dt * delta_t_flt >> 6; sound_sample dVbp = (w0_delta_t*Vhp >> 14); sound_sample dVlp = (w0_delta_t*Vbp >> 14); Vbp -= dVbp; Vlp -= dVlp; Vhp = (Vbp*_1024_div_Q >> 10) - Vlp - Vi; delta_t -= delta_t_flt; } } // ---------------------------------------------------------------------------- // SID audio output (20 bits). // ---------------------------------------------------------------------------- RESID_INLINE sound_sample Filter::output() { // This is handy for testing. if (!enabled) { return (Vnf + mixer_DC)*static_cast(vol); } // Mix highpass, bandpass, and lowpass outputs. The sum is not // weighted, this can be confirmed by sampling sound output for // e.g. bandpass, lowpass, and bandpass+lowpass from a SID chip. // The code below is expanded to a switch for faster execution. // if (hp) Vf += Vhp; // if (bp) Vf += Vbp; // if (lp) Vf += Vlp; sound_sample Vf; switch (hp_bp_lp) { default: case 0x0: Vf = 0; break; case 0x1: Vf = Vlp; break; case 0x2: Vf = Vbp; break; case 0x3: Vf = Vlp + Vbp; break; case 0x4: Vf = Vhp; break; case 0x5: Vf = Vlp + Vhp; break; case 0x6: Vf = Vbp + Vhp; break; case 0x7: Vf = Vlp + Vbp + Vhp; break; } // Sum non-filtered and filtered output. // Multiply the sum with volume. return (Vnf + Vf + mixer_DC)*static_cast(vol); } #endif // RESID_INLINING || defined(__FILTER_CC__) RESID_NAMESPACE_STOP #endif // not __FILTER_H__