summaryrefslogtreecommitdiff
path: root/sid/sidplay-libs-2.1.0/resid/filter.h
diff options
context:
space:
mode:
Diffstat (limited to 'sid/sidplay-libs-2.1.0/resid/filter.h')
-rw-r--r--sid/sidplay-libs-2.1.0/resid/filter.h474
1 files changed, 474 insertions, 0 deletions
diff --git a/sid/sidplay-libs-2.1.0/resid/filter.h b/sid/sidplay-libs-2.1.0/resid/filter.h
new file mode 100644
index 00000000..7db4f31c
--- /dev/null
+++ b/sid/sidplay-libs-2.1.0/resid/filter.h
@@ -0,0 +1,474 @@
+// ---------------------------------------------------------------------------
+// This file is part of reSID, a MOS6581 SID emulator engine.
+// Copyright (C) 2002 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"
+
+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-- |
+// | | | |
+// | ------------<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);
+ RESID_INLINE
+ void clock(cycle_count delta_t,
+ sound_sample voice1, sound_sample voice2, sound_sample voice3);
+ 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;
+
+ // External audio input routed through filter.
+ // NB! Not modeled.
+ reg8 filtex;
+
+ // Voices routed through filter.
+ reg8 filt3_filt2_filt1;
+
+ // 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;
+ 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)
+{
+ // Add separate DC offset to each voice.
+ // Scale each voice down from 20 to 13 bits.
+ voice1 = voice1 >> 7;
+ voice2 = voice2 >> 7;
+
+ // NB! Voice 3 is not silenced by voice3off if it is routed through
+ // the filter.
+ if (voice3off && !(filt3_filt2_filt1 & 0x04)) {
+ voice3 = 0;
+ }
+ else {
+ voice3 = voice3 >> 7;
+ }
+
+ // This is handy for testing.
+ if (!enabled) {
+ Vnf = voice1 + voice2 + voice3;
+ 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 (filt3_filt2_filt1) {
+ default:
+ case 0x0:
+ Vi = 0;
+ Vnf = voice1 + voice2 + voice3;
+ break;
+ case 0x1:
+ Vi = voice1;
+ Vnf = voice2 + voice3;
+ break;
+ case 0x2:
+ Vi = voice2;
+ Vnf = voice1 + voice3;
+ break;
+ case 0x3:
+ Vi = voice1 + voice2;
+ Vnf = voice3;
+ break;
+ case 0x4:
+ Vi = voice3;
+ Vnf = voice1 + voice2;
+ break;
+ case 0x5:
+ Vi = voice1 + voice3;
+ Vnf = voice2;
+ break;
+ case 0x6:
+ Vi = voice2 + voice3;
+ Vnf = voice1;
+ break;
+ case 0x7:
+ Vi = voice1 + voice2 + voice3;
+ 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*Vhp >> 20);
+ sound_sample dVlp = (w0*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)
+{
+ // Add separate DC offset to each voice.
+ // Scale each voice down from 20 to 13 bits.
+ voice1 = voice1 >> 7;
+ voice2 = voice2 >> 7;
+
+ // NB! Voice 3 is not silenced by voice3off if it is routed through
+ // the filter.
+ if (voice3off && !(filt3_filt2_filt1 & 0x04)) {
+ voice3 = 0;
+ }
+ else {
+ voice3 = voice3 >> 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;
+ 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 (filt3_filt2_filt1) {
+ default:
+ case 0x0:
+ Vi = 0;
+ Vnf = voice1 + voice2 + voice3;
+ break;
+ case 0x1:
+ Vi = voice1;
+ Vnf = voice2 + voice3;
+ break;
+ case 0x2:
+ Vi = voice2;
+ Vnf = voice1 + voice3;
+ break;
+ case 0x3:
+ Vi = voice1 + voice2;
+ Vnf = voice3;
+ break;
+ case 0x4:
+ Vi = voice3;
+ Vnf = voice1 + voice2;
+ break;
+ case 0x5:
+ Vi = voice1 + voice3;
+ Vnf = voice2;
+ break;
+ case 0x6:
+ Vi = voice2 + voice3;
+ Vnf = voice1;
+ break;
+ case 0x7:
+ Vi = voice1 + voice2 + voice3;
+ 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;
+
+ // Limit f0 to 4kHz to keep filter stable.
+ const double pi = 3.1415926535897932385;
+ const sound_sample w0_max = static_cast<sound_sample>(2*pi*4000*1.048576);
+ sound_sample w0_ceil = w0 <= w0_max ? w0 : w0_max;
+
+ 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*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__)
+
+RESID_NAMESPACE_STOP
+
+#endif // not __FILTER_H__