home *** CD-ROM | disk | FTP | other *** search
- // ---------------------------------------------------------------------------
- // This file is part of reSID, a MOS6581 SID emulator engine.
- // Copyright (C) 2004 Dag Lem <resid@nimrod.no>
- //
- // 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"
-
- // ----------------------------------------------------------------------------
- // 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-- |
- // | | | |
- // | ------------<A]-----R1--------- |
- // | | | |
- // | | ---C---| ---C---|
- // | | | | | |
- // | --R1-- ---R1-- |---Rs--| |---Rs--|
- // | | | | | | | |
- // ----R1--|-----[A>--|--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<sound_sample> 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;
- static fc_point f0_points_6581[];
- static fc_point f0_points_8580[];
- 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<sound_sample>(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<sound_sample>(vol);
- }
-
- #endif // RESID_INLINING || defined(__FILTER_CC__)
-
- #endif // not __FILTER_H__
-