459 lines
15 KiB
Plaintext
459 lines
15 KiB
Plaintext
/*
|
|
* This file is part of libsidplayfp, a SID player engine.
|
|
*
|
|
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
|
* Copyright 2007-2010 Antti Lankila
|
|
* Copyright 2004,2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifndef FILTER6581_H
|
|
#define FILTER6581_H
|
|
|
|
#include "siddefs-fp.h"
|
|
|
|
#include <memory>
|
|
|
|
#include "Filter.h"
|
|
#include "FilterModelConfig.h"
|
|
|
|
namespace reSIDfp
|
|
{
|
|
|
|
class Integrator;
|
|
|
|
/**
|
|
* 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
|
|
* 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.
|
|
*
|
|
* In 2008, Michael Huth facilitated closer investigation of the SID 6581
|
|
* filter circuit by publishing high quality microscope photographs of the die.
|
|
* Tommi Lempinen has done an impressive work on re-vectorizing and annotating
|
|
* the die photographs, substantially simplifying further analysis of the
|
|
* filter circuit.
|
|
*
|
|
* The filter schematics below are reverse engineered from these re-vectorized
|
|
* and annotated die photographs. While the filter first depicted in reSID 0.9
|
|
* is a correct model of the basic filter, the schematics are now completed
|
|
* with the audio mixer and output stage, including details on intended
|
|
* relative resistor values. Also included are schematics for the NMOS FET
|
|
* voltage controlled resistors (VCRs) used to control cutoff frequency, the
|
|
* DAC which controls the VCRs, the NMOS op-amps, and the output buffer.
|
|
*
|
|
*
|
|
* SID filter / mixer / output
|
|
* ---------------------------
|
|
* ~~~
|
|
* ---------------------------------------------------
|
|
* | |
|
|
* | --1R1-- \-- D7 |
|
|
* | ---R1-- | | |
|
|
* | | | |--2R1-- \--| D6 |
|
|
* | ------------<A]-----| | $17 |
|
|
* | | |--4R1-- \--| D5 1=open | (3.5R1)
|
|
* | | | | |
|
|
* | | --8R1-- \--| D4 | (7.0R1)
|
|
* | | | |
|
|
* $17 | | (CAP2B) | (CAP1B) |
|
|
* 0=to mixer | --R8-- ---R8-- ---C---| ---C---|
|
|
* 1=to filter | | | | | | | |
|
|
* ------R8--|-----[A>--|--Rw-----[A>--|--Rw-----[A>--|
|
|
* ve (EXT IN) | | | |
|
|
* D3 \ ---------------R8--| | | (CAP2A) | (CAP1A)
|
|
* | v3 | | vhp | vbp | vlp
|
|
* D2 | \ -----------R8--| ----- | |
|
|
* | | v2 | | | |
|
|
* D1 | | \ -------R8--| | ---------------- |
|
|
* | | | v1 | | | |
|
|
* D0 | | | \ ---R8-- | | ---------------------------
|
|
* | | | | | | |
|
|
* R6 R6 R6 R6 R6 R6 R6
|
|
* | | | | $18 | | | $18
|
|
* | \ | | D7: 1=open \ \ \ D6 - D4: 0=open
|
|
* | | | | | | |
|
|
* --------------------------------- 12V
|
|
* |
|
|
* | D3 --/ --1R2-- |
|
|
* | ---R8-- | | ---R2-- |
|
|
* | | | D2 |--/ --2R2--| | | ||--
|
|
* ------[A>---------| |-----[A>-----||
|
|
* D1 |--/ --4R2--| (4.25R2) ||--
|
|
* $18 | | |
|
|
* 0=open D0 --/ --8R2-- (8.75R2) |
|
|
*
|
|
* vo (AUDIO
|
|
* OUT)
|
|
*
|
|
*
|
|
* v1 - voice 1
|
|
* v2 - voice 2
|
|
* v3 - voice 3
|
|
* ve - ext in
|
|
* vhp - highpass output
|
|
* vbp - bandpass output
|
|
* vlp - lowpass output
|
|
* vo - audio out
|
|
* [A> - single ended inverting op-amp (self-biased NMOS inverter)
|
|
* Rn - "resistors", implemented with custom NMOS FETs
|
|
* Rw - cutoff frequency resistor (VCR)
|
|
* C - capacitor
|
|
* ~~~
|
|
* Notes:
|
|
*
|
|
* R2 ~ 2.0*R1
|
|
* R6 ~ 6.0*R1
|
|
* R8 ~ 8.0*R1
|
|
* R24 ~ 24.0*R1
|
|
*
|
|
* The Rn "resistors" in the circuit are implemented with custom NMOS FETs,
|
|
* probably because of space constraints on the SID die. The silicon substrate
|
|
* is laid out in a narrow strip or "snake", with a strip length proportional
|
|
* to the intended resistance. The polysilicon gate electrode covers the entire
|
|
* silicon substrate and is fixed at 12V in order for the NMOS FET to operate
|
|
* in triode mode (a.k.a. linear mode or ohmic mode).
|
|
*
|
|
* Even in "linear mode", an NMOS FET is only an approximation of a resistor,
|
|
* as the apparant resistance increases with increasing drain-to-source
|
|
* voltage. If the drain-to-source voltage should approach the gate voltage
|
|
* of 12V, the NMOS FET will enter saturation mode (a.k.a. active mode), and
|
|
* the NMOS FET will not operate anywhere like a resistor.
|
|
*
|
|
*
|
|
*
|
|
* NMOS FET voltage controlled resistor (VCR)
|
|
* ------------------------------------------
|
|
* ~~~
|
|
* Vw
|
|
*
|
|
* |
|
|
* |
|
|
* R1
|
|
* |
|
|
* --R1--|
|
|
* | __|__
|
|
* | -----
|
|
* | | |
|
|
* vi ---------- -------- vo
|
|
* | |
|
|
* ----R24----
|
|
*
|
|
*
|
|
* vi - input
|
|
* vo - output
|
|
* Rn - "resistors", implemented with custom NMOS FETs
|
|
* Vw - voltage from 11-bit DAC (frequency cutoff control)
|
|
* ~~~
|
|
* Notes:
|
|
*
|
|
* An approximate value for R24 can be found by using the formula for the
|
|
* filter cutoff frequency:
|
|
*
|
|
* FCmin = 1/(2*pi*Rmax*C)
|
|
*
|
|
* Assuming that a the setting for minimum cutoff frequency in combination with
|
|
* a low level input signal ensures that only negligible current will flow
|
|
* through the transistor in the schematics above, values for FCmin and C can
|
|
* be substituted in this formula to find Rmax.
|
|
* Using C = 470pF and FCmin = 220Hz (measured value), we get:
|
|
*
|
|
* FCmin = 1/(2*pi*Rmax*C)
|
|
* Rmax = 1/(2*pi*FCmin*C) = 1/(2*pi*220*470e-12) ~ 1.5MOhm
|
|
*
|
|
* From this it follows that:
|
|
* R24 = Rmax ~ 1.5MOhm
|
|
* R1 ~ R24/24 ~ 64kOhm
|
|
* R2 ~ 2.0*R1 ~ 128kOhm
|
|
* R6 ~ 6.0*R1 ~ 384kOhm
|
|
* R8 ~ 8.0*R1 ~ 512kOhm
|
|
*
|
|
* Note that these are only approximate values for one particular SID chip,
|
|
* due to process variations the values can be substantially different in
|
|
* other chips.
|
|
*
|
|
*
|
|
*
|
|
* Filter frequency cutoff DAC
|
|
* ---------------------------
|
|
*
|
|
* ~~~
|
|
* 12V 10 9 8 7 6 5 4 3 2 1 0 VGND
|
|
* | | | | | | | | | | | | | Missing
|
|
* 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R termination
|
|
* | | | | | | | | | | | | |
|
|
* Vw ----R---R---R---R---R---R---R---R---R---R---R-- ---
|
|
*
|
|
*
|
|
* Bit on: 12V
|
|
* Bit off: 5V (VGND)
|
|
* ~~~
|
|
* As is the case with all MOS 6581 DACs, the termination to (virtual) ground
|
|
* at bit 0 is missing.
|
|
*
|
|
* Furthermore, the control of the two VCRs imposes a load on the DAC output
|
|
* which varies with the input signals to the VCRs. This can be seen from the
|
|
* VCR figure above.
|
|
*
|
|
*
|
|
*
|
|
* "Op-amp" (self-biased NMOS inverter)
|
|
* ------------------------------------
|
|
* ~~~
|
|
*
|
|
* 12V
|
|
*
|
|
* |
|
|
* -----------|
|
|
* | |
|
|
* | ------|
|
|
* | | |
|
|
* | | ||--
|
|
* | --||
|
|
* | ||--
|
|
* ||-- |
|
|
* vi -----|| |--------- vo
|
|
* ||-- | |
|
|
* | ||-- |
|
|
* |-------|| |
|
|
* | ||-- |
|
|
* ||-- | |
|
|
* --|| | |
|
|
* | ||-- | |
|
|
* | | | |
|
|
* | -----------| |
|
|
* | | |
|
|
* | |
|
|
* | GND |
|
|
* | |
|
|
* ----------------------
|
|
*
|
|
*
|
|
* vi - input
|
|
* vo - output
|
|
* ~~~
|
|
* Notes:
|
|
*
|
|
* The schematics above are laid out to show that the "op-amp" logically
|
|
* consists of two building blocks; a saturated load NMOS inverter (on the
|
|
* right hand side of the schematics) with a buffer / bias input stage
|
|
* consisting of a variable saturated load NMOS inverter (on the left hand
|
|
* side of the schematics).
|
|
*
|
|
* Provided a reasonably high input impedance and a reasonably low output
|
|
* impedance, the "op-amp" can be modeled as a voltage transfer function
|
|
* mapping input voltage to output voltage.
|
|
*
|
|
*
|
|
*
|
|
* Output buffer (NMOS voltage follower)
|
|
* -------------------------------------
|
|
* ~~~
|
|
*
|
|
* 12V
|
|
*
|
|
* |
|
|
* |
|
|
* ||--
|
|
* vi -----||
|
|
* ||--
|
|
* |
|
|
* |------ vo
|
|
* | (AUDIO
|
|
* Rext OUT)
|
|
* |
|
|
* |
|
|
*
|
|
* GND
|
|
*
|
|
* vi - input
|
|
* vo - output
|
|
* Rext - external resistor, 1kOhm
|
|
* ~~~
|
|
* Notes:
|
|
*
|
|
* The external resistor Rext is needed to complete the NMOS voltage follower,
|
|
* this resistor has a recommended value of 1kOhm.
|
|
*
|
|
* Die photographs show that actually, two NMOS transistors are used in the
|
|
* voltage follower. However the two transistors are coupled in parallel (all
|
|
* terminals are pairwise common), which implies that we can model the two
|
|
* transistors as one.
|
|
*/
|
|
class Filter6581 : public Filter
|
|
{
|
|
private:
|
|
/// Current volume amplifier setting.
|
|
unsigned short* currentGain;
|
|
|
|
/// Current filter/voice mixer setting.
|
|
unsigned short* currentMixer;
|
|
|
|
/// Filter input summer setting.
|
|
unsigned short* currentSummer;
|
|
|
|
/// Filter resonance value.
|
|
unsigned short* currentResonance;
|
|
|
|
const unsigned short* f0_dac;
|
|
|
|
unsigned short** mixer;
|
|
unsigned short** summer;
|
|
unsigned short** gain;
|
|
|
|
/// Filter highpass state.
|
|
int Vhp;
|
|
|
|
/// Filter bandpass state.
|
|
int Vbp;
|
|
|
|
/// Filter lowpass state.
|
|
int Vlp;
|
|
|
|
/// Filter external input.
|
|
int ve;
|
|
|
|
const int voiceScaleS14;
|
|
const int voiceDC;
|
|
|
|
/// VCR + associated capacitor connected to highpass output.
|
|
std::auto_ptr<Integrator> const hpIntegrator;
|
|
|
|
/// VCR + associated capacitor connected to lowpass output.
|
|
std::auto_ptr<Integrator> const bpIntegrator;
|
|
|
|
public:
|
|
Filter6581() :
|
|
currentGain(0),
|
|
currentMixer(0),
|
|
currentSummer(0),
|
|
currentResonance(0),
|
|
f0_dac(FilterModelConfig::getInstance()->getDAC(0.5)),
|
|
mixer(FilterModelConfig::getInstance()->getMixer()),
|
|
summer(FilterModelConfig::getInstance()->getSummer()),
|
|
gain(FilterModelConfig::getInstance()->getGain()),
|
|
Vhp(0),
|
|
Vbp(0),
|
|
Vlp(0),
|
|
ve(0),
|
|
voiceScaleS14(FilterModelConfig::getInstance()->getVoiceScaleS14()),
|
|
voiceDC(FilterModelConfig::getInstance()->getVoiceDC()),
|
|
hpIntegrator(FilterModelConfig::getInstance()->buildIntegrator()),
|
|
bpIntegrator(FilterModelConfig::getInstance()->buildIntegrator())
|
|
{
|
|
input(0);
|
|
}
|
|
|
|
~Filter6581();
|
|
|
|
int clock(int voice1, int voice2, int voice3);
|
|
|
|
void input(int sample) { ve = (sample * voiceScaleS14 * 3 >> 10) + mixer[0][0]; }
|
|
|
|
/**
|
|
* Set filter cutoff frequency.
|
|
*/
|
|
void updatedCenterFrequency();
|
|
|
|
/**
|
|
* Set filter resonance.
|
|
*
|
|
* In the MOS 6581, 1/Q is controlled linearly by res.
|
|
*/
|
|
void updatedResonance() { currentResonance = gain[~res & 0xf]; }
|
|
|
|
void updatedMixing();
|
|
|
|
public:
|
|
/**
|
|
* Set filter curve type based on single parameter.
|
|
*
|
|
* @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5
|
|
*/
|
|
void setFilterCurve(double curvePosition);
|
|
};
|
|
|
|
} // namespace reSIDfp
|
|
|
|
#if RESID_INLINING || defined(FILTER6581_CPP)
|
|
|
|
#include "Integrator.h"
|
|
|
|
namespace reSIDfp
|
|
{
|
|
|
|
RESID_INLINE
|
|
int Filter6581::clock(int voice1, int voice2, int voice3)
|
|
{
|
|
voice1 = (voice1 * voiceScaleS14 >> 18) + voiceDC;
|
|
voice2 = (voice2 * voiceScaleS14 >> 18) + voiceDC;
|
|
voice3 = (voice3 * voiceScaleS14 >> 18) + voiceDC;
|
|
|
|
int Vi = 0;
|
|
int Vo = 0;
|
|
|
|
(filt1 ? Vi : Vo) += voice1;
|
|
(filt2 ? Vi : Vo) += voice2;
|
|
|
|
// NB! Voice 3 is not silenced by voice3off if it is routed
|
|
// through the filter.
|
|
if (filt3) Vi += voice3;
|
|
else if (!voice3off) Vo += voice3;
|
|
|
|
(filtE ? Vi : Vo) += ve;
|
|
|
|
const int oldVhp = Vhp;
|
|
Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi];
|
|
Vlp = bpIntegrator->solve(Vbp);
|
|
Vbp = hpIntegrator->solve(oldVhp);
|
|
|
|
if (lp) Vo += Vlp;
|
|
if (bp) Vo += Vbp;
|
|
if (hp) Vo += Vhp;
|
|
|
|
return currentGain[currentMixer[Vo]] - (1 << 15);
|
|
}
|
|
|
|
} // namespace reSIDfp
|
|
|
|
#endif
|
|
|
|
#endif
|