679 lines
20 KiB
Plaintext
679 lines
20 KiB
Plaintext
// ---------------------------------------------------------------------------
|
|
// This file is part of reSID, a MOS6581 SID emulator engine.
|
|
// Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#define RESID_FILTER_CC
|
|
|
|
#ifdef _M_ARM
|
|
#undef _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
|
|
#define _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE 1
|
|
#endif
|
|
|
|
#include "filter.h"
|
|
#include "dac.h"
|
|
#include "spline.h"
|
|
#include <math.h>
|
|
|
|
namespace reSID
|
|
{
|
|
|
|
// This is the SID 6581 op-amp voltage transfer function, measured on
|
|
// CAP1B/CAP1A on a chip marked MOS 6581R4AR 0687 14.
|
|
// All measured chips have op-amps with output voltages (and thus input
|
|
// voltages) within the range of 0.81V - 10.31V.
|
|
|
|
static double_point opamp_voltage_6581[] = {
|
|
{ 0.81, 10.31 }, // Approximate start of actual range
|
|
{ 0.81, 10.31 }, // Repeated point
|
|
{ 2.40, 10.31 },
|
|
{ 2.60, 10.30 },
|
|
{ 2.70, 10.29 },
|
|
{ 2.80, 10.26 },
|
|
{ 2.90, 10.17 },
|
|
{ 3.00, 10.04 },
|
|
{ 3.10, 9.83 },
|
|
{ 3.20, 9.58 },
|
|
{ 3.30, 9.32 },
|
|
{ 3.50, 8.69 },
|
|
{ 3.70, 8.00 },
|
|
{ 4.00, 6.89 },
|
|
{ 4.40, 5.21 },
|
|
{ 4.54, 4.54 }, // Working point (vi = vo)
|
|
{ 4.60, 4.19 },
|
|
{ 4.80, 3.00 },
|
|
{ 4.90, 2.30 }, // Change of curvature
|
|
{ 4.95, 2.03 },
|
|
{ 5.00, 1.88 },
|
|
{ 5.05, 1.77 },
|
|
{ 5.10, 1.69 },
|
|
{ 5.20, 1.58 },
|
|
{ 5.40, 1.44 },
|
|
{ 5.60, 1.33 },
|
|
{ 5.80, 1.26 },
|
|
{ 6.00, 1.21 },
|
|
{ 6.40, 1.12 },
|
|
{ 7.00, 1.02 },
|
|
{ 7.50, 0.97 },
|
|
{ 8.50, 0.89 },
|
|
{ 10.00, 0.81 },
|
|
{ 10.31, 0.81 }, // Approximate end of actual range
|
|
{ 10.31, 0.81 } // Repeated end point
|
|
};
|
|
|
|
// This is the SID 8580 op-amp voltage transfer function, measured on
|
|
// CAP1B/CAP1A on a chip marked CSG 8580R5 1690 25.
|
|
static double_point opamp_voltage_8580[] = {
|
|
{ 1.30, 8.91 }, // Approximate start of actual range
|
|
{ 1.30, 8.91 }, // Repeated end point
|
|
{ 4.76, 8.91 },
|
|
{ 4.77, 8.90 },
|
|
{ 4.78, 8.88 },
|
|
{ 4.785, 8.86 },
|
|
{ 4.79, 8.80 },
|
|
{ 4.795, 8.60 },
|
|
{ 4.80, 8.25 },
|
|
{ 4.805, 7.50 },
|
|
{ 4.81, 6.10 },
|
|
{ 4.815, 4.05 }, // Change of curvature
|
|
{ 4.82, 2.27 },
|
|
{ 4.825, 1.65 },
|
|
{ 4.83, 1.55 },
|
|
{ 4.84, 1.47 },
|
|
{ 4.85, 1.43 },
|
|
{ 4.87, 1.37 },
|
|
{ 4.90, 1.34 },
|
|
{ 5.00, 1.30 },
|
|
{ 5.10, 1.30 },
|
|
{ 8.91, 1.30 }, // Approximate end of actual range
|
|
{ 8.91, 1.30 } // Repeated end point
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
// Op-amp transfer function.
|
|
double_point* opamp_voltage;
|
|
int opamp_voltage_size;
|
|
// Voice output characteristics.
|
|
double voice_voltage_range;
|
|
double voice_DC_voltage;
|
|
// Capacitor value.
|
|
double C;
|
|
// Transistor parameters.
|
|
double Vdd;
|
|
double Vth; // Threshold voltage
|
|
double Ut; // Thermal voltage: Ut = k*T/q = 8.61734315e-5*T ~ 26mV
|
|
double k; // Gate coupling coefficient: K = Cox/(Cox+Cdep) ~ 0.7
|
|
double uCox; // u*Cox
|
|
double WL_vcr; // W/L for VCR
|
|
double WL_snake; // W/L for "snake"
|
|
// DAC parameters.
|
|
double dac_zero;
|
|
double dac_scale;
|
|
double dac_2R_div_R;
|
|
bool dac_term;
|
|
} model_filter_init_t;
|
|
|
|
static model_filter_init_t model_filter_init[2] = {
|
|
{
|
|
opamp_voltage_6581,
|
|
sizeof(opamp_voltage_6581)/sizeof(*opamp_voltage_6581),
|
|
// The dynamic analog range of one voice is approximately 1.5V,
|
|
// riding at a DC level of approximately 5.0V.
|
|
1.5,
|
|
5.0,
|
|
// Capacitor value.
|
|
470e-12,
|
|
// Transistor parameters.
|
|
12.18,
|
|
1.31,
|
|
26.0e-3,
|
|
1.0,
|
|
20e-6,
|
|
9.0/1,
|
|
1.0/115,
|
|
// DAC parameters.
|
|
6.65,
|
|
2.63,
|
|
2.20,
|
|
false
|
|
},
|
|
{
|
|
opamp_voltage_8580,
|
|
sizeof(opamp_voltage_8580)/sizeof(*opamp_voltage_8580),
|
|
// FIXME: Measure for the 8580.
|
|
1.0,
|
|
// 4.75,
|
|
1.30, // FIXME: For now we pretend that the working point is 0V.
|
|
22e-9,
|
|
9.09,
|
|
0.80,
|
|
26.0e-3,
|
|
1.0,
|
|
10e-6,
|
|
// FIXME: 6581 only
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
2.00,
|
|
true
|
|
}
|
|
};
|
|
|
|
unsigned short Filter::vcr_kVg[1 << 16];
|
|
unsigned short Filter::vcr_n_Ids_term[1 << 16];
|
|
|
|
#ifndef HAS_LOG1P
|
|
static double log1p(double x)
|
|
{
|
|
return log(1 + x) - (((1 + x) - 1) - x) / (1 + x);
|
|
}
|
|
#endif
|
|
|
|
Filter::model_filter_t Filter::model_filter[2];
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Constructor.
|
|
// ----------------------------------------------------------------------------
|
|
Filter::Filter()
|
|
{
|
|
static bool class_init;
|
|
|
|
if (!class_init) {
|
|
// Temporary table for op-amp transfer function.
|
|
int* opamp = new int[1 << 16];
|
|
|
|
for (int m = 0; m < 2; m++) {
|
|
model_filter_init_t& fi = model_filter_init[m];
|
|
model_filter_t& mf = model_filter[m];
|
|
|
|
// Convert op-amp voltage transfer to 16 bit values.
|
|
double vmin = fi.opamp_voltage[0][0];
|
|
double opamp_max = fi.opamp_voltage[0][1];
|
|
double kVddt = fi.k*(fi.Vdd - fi.Vth);
|
|
double vmax = kVddt < opamp_max ? opamp_max : kVddt;
|
|
double denorm = vmax - vmin;
|
|
double norm = 1.0/denorm;
|
|
|
|
// Scaling and translation constants.
|
|
double N16 = norm*((1u << 16) - 1);
|
|
double N30 = norm*((1u << 30) - 1);
|
|
double N31 = norm*((1u << 31) - 1);
|
|
mf.vo_N16 = (int)(N16); // FIXME: Remove?
|
|
|
|
// The "zero" output level of the voices.
|
|
// The digital range of one voice is 20 bits; create a scaling term
|
|
// for multiplication which fits in 11 bits.
|
|
double N14 = norm*(1u << 14);
|
|
mf.voice_scale_s14 = (int)(N14*fi.voice_voltage_range);
|
|
mf.voice_DC = (int)(N16*(fi.voice_DC_voltage - vmin));
|
|
|
|
// Vdd - Vth, normalized so that translated values can be subtracted:
|
|
// k*Vddt - x = (k*Vddt - t) - (x - t)
|
|
mf.kVddt = (int)(N16*(kVddt - vmin) + 0.5);
|
|
|
|
// Normalized snake current factor, 1 cycle at 1MHz.
|
|
// Fit in 5 bits.
|
|
mf.n_snake = (int)(denorm*(1 << 13)*(fi.uCox/(2*fi.k)*fi.WL_snake*1.0e-6/fi.C) + 0.5);
|
|
|
|
// Create lookup table mapping op-amp voltage across output and input
|
|
// to input voltage: vo - vx -> vx
|
|
// FIXME: No variable length arrays in ISO C++, hardcoding to max 50
|
|
// points.
|
|
// double_point scaled_voltage[fi.opamp_voltage_size];
|
|
double_point scaled_voltage[50];
|
|
|
|
for (int i = 0; i < fi.opamp_voltage_size; i++) {
|
|
// The target output range is 16 bits, in order to fit in an unsigned
|
|
// short.
|
|
//
|
|
// The y axis is temporarily scaled to 31 bits for maximum accuracy in
|
|
// the calculated derivative.
|
|
//
|
|
// Values are normalized using
|
|
//
|
|
// x_n = m*2^N*(x - xmin)
|
|
//
|
|
// and are translated back later (for fixed point math) using
|
|
//
|
|
// m*2^N*x = x_n - m*2^N*xmin
|
|
//
|
|
scaled_voltage[fi.opamp_voltage_size - 1 - i][0] = int((N16*(fi.opamp_voltage[i][1] - fi.opamp_voltage[i][0]) + (1 << 16))/2 + 0.5);
|
|
scaled_voltage[fi.opamp_voltage_size - 1 - i][1] = N31*(fi.opamp_voltage[i][0] - vmin);
|
|
}
|
|
|
|
// Clamp x to 16 bits (rounding may cause overflow).
|
|
if (scaled_voltage[fi.opamp_voltage_size - 1][0] >= (1 << 16)) {
|
|
// The last point is repeated.
|
|
scaled_voltage[fi.opamp_voltage_size - 1][0] =
|
|
scaled_voltage[fi.opamp_voltage_size - 2][0] = (1 << 16) - 1;
|
|
}
|
|
|
|
interpolate(scaled_voltage, scaled_voltage + fi.opamp_voltage_size - 1,
|
|
PointPlotter<int>(opamp), 1.0);
|
|
|
|
// Store both fn and dfn in the same table.
|
|
mf.ak = (int)scaled_voltage[0][0];
|
|
mf.bk = (int)scaled_voltage[fi.opamp_voltage_size - 1][0];
|
|
int j;
|
|
for (j = 0; j < mf.ak; j++) {
|
|
opamp[j] = 0;
|
|
}
|
|
int f = opamp[j] - (opamp[j + 1] - opamp[j]);
|
|
for (; j <= mf.bk; j++) {
|
|
int fp = f;
|
|
f = opamp[j]; // Scaled by m*2^31
|
|
// m*2^31*dy/1 = (m*2^31*dy)/(m*2^16*dx) = 2^15*dy/dx
|
|
int df = f - fp; // Scaled by 2^15
|
|
|
|
// High 16 bits (15 bits + sign bit): 2^11*dfn
|
|
// Low 16 bits (unsigned): m*2^16*(fn - xmin)
|
|
opamp[j] = ((df << (16 + 11 - 15)) & ~0xffff) | (f >> 15);
|
|
}
|
|
for (; j < (1 << 16); j++) {
|
|
opamp[j] = 0;
|
|
}
|
|
|
|
// Create lookup tables for gains / summers.
|
|
|
|
// 4 bit "resistor" ladders in the bandpass resonance gain and the audio
|
|
// output gain necessitate 16 gain tables.
|
|
// From die photographs of the bandpass and volume "resistor" ladders
|
|
// it follows that gain ~ vol/8 and 1/Q ~ ~res/8 (assuming ideal
|
|
// op-amps and ideal "resistors").
|
|
for (int n8 = 0; n8 < 16; n8++) {
|
|
int n = n8 << 4; // Scaled by 2^7
|
|
int x = mf.ak;
|
|
for (int vi = 0; vi < (1 << 16); vi++) {
|
|
mf.gain[n8][vi] = solve_gain(opamp, n, vi, x, mf);
|
|
}
|
|
}
|
|
|
|
// The filter summer operates at n ~ 1, and has 5 fundamentally different
|
|
// input configurations (2 - 6 input "resistors").
|
|
//
|
|
// Note that all "on" transistors are modeled as one. This is not
|
|
// entirely accurate, since the input for each transistor is different,
|
|
// and transistors are not linear components. However modeling all
|
|
// transistors separately would be extremely costly.
|
|
int offset = 0;
|
|
int size;
|
|
for (int k = 0; k < 5; k++) {
|
|
int idiv = 2 + k; // 2 - 6 input "resistors".
|
|
int n_idiv = idiv << 7; // n*idiv, scaled by 2^7
|
|
size = idiv << 16;
|
|
int x = mf.ak;
|
|
for (int vi = 0; vi < size; vi++) {
|
|
mf.summer[offset + vi] =
|
|
solve_gain(opamp, n_idiv, vi/idiv, x, mf);
|
|
}
|
|
offset += size;
|
|
}
|
|
|
|
// The audio mixer operates at n ~ 8/6, and has 8 fundamentally different
|
|
// input configurations (0 - 7 input "resistors").
|
|
//
|
|
// All "on", transistors are modeled as one - see comments above for
|
|
// the filter summer.
|
|
offset = 0;
|
|
size = 1; // Only one lookup element for 0 input "resistors".
|
|
for (int l = 0; l < 8; l++) {
|
|
int idiv = l; // 0 - 7 input "resistors".
|
|
int n_idiv = (idiv << 7)*8/6; // n*idiv, scaled by 2^7
|
|
if (idiv == 0) {
|
|
// Avoid division by zero; the result will be correct since
|
|
// n_idiv = 0.
|
|
idiv = 1;
|
|
}
|
|
int x = mf.ak;
|
|
for (int vi = 0; vi < size; vi++) {
|
|
mf.mixer[offset + vi] =
|
|
solve_gain(opamp, n_idiv, vi/idiv, x, mf);
|
|
}
|
|
offset += size;
|
|
size = (l + 1) << 16;
|
|
}
|
|
|
|
// Create lookup table mapping capacitor voltage to op-amp input voltage:
|
|
// vc -> vx
|
|
for (int m = 0; m < (1 << 16); m++) {
|
|
mf.opamp_rev[m] = opamp[m] & 0xffff;
|
|
}
|
|
|
|
mf.vc_max = (int)(N30*(fi.opamp_voltage[0][1] - fi.opamp_voltage[0][0]));
|
|
mf.vc_min = (int)(N30*(fi.opamp_voltage[fi.opamp_voltage_size - 1][1] - fi.opamp_voltage[fi.opamp_voltage_size - 1][0]));
|
|
|
|
// DAC table.
|
|
int bits = 11;
|
|
build_dac_table(mf.f0_dac, bits, fi.dac_2R_div_R, fi.dac_term);
|
|
for (int n = 0; n < (1 << bits); n++) {
|
|
mf.f0_dac[n] = (unsigned short)(N16*(fi.dac_zero + mf.f0_dac[n]*fi.dac_scale/(1 << bits) - vmin) + 0.5);
|
|
}
|
|
}
|
|
|
|
// Free temporary table.
|
|
delete[] opamp;
|
|
|
|
// VCR - 6581 only.
|
|
model_filter_init_t& fi = model_filter_init[0];
|
|
|
|
double N16 = model_filter[0].vo_N16;
|
|
double vmin = N16*fi.opamp_voltage[0][0];
|
|
double k = fi.k;
|
|
double kVddt = N16*(k*(fi.Vdd - fi.Vth));
|
|
|
|
for (int i = 0; i < (1 << 16); i++) {
|
|
// The table index is right-shifted 16 times in order to fit in
|
|
// 16 bits; the argument to sqrt is thus multiplied by (1 << 16).
|
|
//
|
|
// The returned value must be corrected for translation. Vg always
|
|
// takes part in a subtraction as follows:
|
|
//
|
|
// k*Vg - Vx = (k*Vg - t) - (Vx - t)
|
|
//
|
|
// I.e. k*Vg - t must be returned.
|
|
double Vg = kVddt - sqrt((double)i*(1 << 16));
|
|
vcr_kVg[i] = (unsigned short)(k*Vg - vmin + 0.5);
|
|
}
|
|
|
|
/*
|
|
EKV model:
|
|
|
|
Ids = Is*(if - ir)
|
|
Is = 2*u*Cox*Ut^2/k*W/L
|
|
if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut))
|
|
ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut))
|
|
*/
|
|
double kVt = fi.k*fi.Vth;
|
|
double Ut = fi.Ut;
|
|
double Is = 2*fi.uCox*Ut*Ut/fi.k*fi.WL_vcr;
|
|
// Normalized current factor for 1 cycle at 1MHz.
|
|
double N15 = N16/2;
|
|
double n_Is = N15*1.0e-6/fi.C*Is;
|
|
|
|
// kVg_Vx = k*Vg - Vx
|
|
// I.e. if k != 1.0, Vg must be scaled accordingly.
|
|
for (int kVg_Vx = 0; kVg_Vx < (1 << 16); kVg_Vx++) {
|
|
double log_term = log1p(exp((kVg_Vx/N16 - kVt)/(2*Ut)));
|
|
// Scaled by m*2^15
|
|
vcr_n_Ids_term[kVg_Vx] = (unsigned short)(n_Is*log_term*log_term);
|
|
}
|
|
|
|
class_init = true;
|
|
}
|
|
|
|
enable_filter(true);
|
|
set_chip_model(MOS6581);
|
|
set_voice_mask(0x07);
|
|
input(0);
|
|
reset();
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Enable filter.
|
|
// ----------------------------------------------------------------------------
|
|
void Filter::enable_filter(bool enable)
|
|
{
|
|
enabled = enable;
|
|
set_sum_mix();
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Adjust the DAC bias parameter of the filter.
|
|
// This gives user variable control of the exact CF -> center frequency
|
|
// mapping used by the filter.
|
|
// The setting is currently only effective for 6581.
|
|
// ----------------------------------------------------------------------------
|
|
void Filter::adjust_filter_bias(double dac_bias)
|
|
{
|
|
Vw_bias = int(dac_bias*model_filter[sid_model].vo_N16);
|
|
set_w0();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Set chip model.
|
|
// ----------------------------------------------------------------------------
|
|
void Filter::set_chip_model(chip_model model)
|
|
{
|
|
sid_model = model;
|
|
/* We initialize the state variables again just to make sure that
|
|
* the earlier model didn't leave behind some foreign, unrecoverable
|
|
* state. Hopefully set_chip_model() only occurs simultaneously with
|
|
* reset(). */
|
|
Vhp = 0;
|
|
Vbp = Vbp_x = Vbp_vc = 0;
|
|
Vlp = Vlp_x = Vlp_vc = 0;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Mask for voices routed into the filter / audio output stage.
|
|
// Used to physically connect/disconnect EXT IN, and for test purposes
|
|
// (voice muting).
|
|
// ----------------------------------------------------------------------------
|
|
void Filter::set_voice_mask(reg4 mask)
|
|
{
|
|
voice_mask = 0xf0 | (mask & 0x0f);
|
|
set_sum_mix();
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SID reset.
|
|
// ----------------------------------------------------------------------------
|
|
void Filter::reset()
|
|
{
|
|
fc = 0;
|
|
res = 0;
|
|
filt = 0;
|
|
mode = 0;
|
|
vol = 0;
|
|
|
|
Vhp = 0;
|
|
Vbp = Vbp_x = Vbp_vc = 0;
|
|
Vlp = Vlp_x = Vlp_vc = 0;
|
|
|
|
set_w0();
|
|
set_Q();
|
|
set_sum_mix();
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Register functions.
|
|
// ----------------------------------------------------------------------------
|
|
void Filter::writeFC_LO(reg8 fc_lo)
|
|
{
|
|
fc = (fc & 0x7f8) | (fc_lo & 0x007);
|
|
set_w0();
|
|
}
|
|
|
|
void Filter::writeFC_HI(reg8 fc_hi)
|
|
{
|
|
fc = ((fc_hi << 3) & 0x7f8) | (fc & 0x007);
|
|
set_w0();
|
|
}
|
|
|
|
void Filter::writeRES_FILT(reg8 res_filt)
|
|
{
|
|
res = (res_filt >> 4) & 0x0f;
|
|
set_Q();
|
|
|
|
filt = res_filt & 0x0f;
|
|
set_sum_mix();
|
|
}
|
|
|
|
void Filter::writeMODE_VOL(reg8 mode_vol)
|
|
{
|
|
mode = mode_vol & 0xf0;
|
|
set_sum_mix();
|
|
|
|
vol = mode_vol & 0x0f;
|
|
}
|
|
|
|
// Set filter cutoff frequency.
|
|
void Filter::set_w0()
|
|
{
|
|
model_filter_t& f = model_filter[sid_model];
|
|
int Vw = Vw_bias + f.f0_dac[fc];
|
|
Vddt_Vw_2 = unsigned(f.kVddt - Vw)*unsigned(f.kVddt - Vw) >> 1;
|
|
|
|
// FIXME: w0 is temporarily used for MOS 8580 emulation.
|
|
// MOS 8580 cutoff: 0 - 12.5kHz.
|
|
// Multiply with 1.048576 to facilitate division by 1 000 000 by right-
|
|
// shifting 20 times (2 ^ 20 = 1048576).
|
|
// 1.048576*2*pi*12500 = 82355
|
|
w0 = 82355*(fc + 1) >> 11;
|
|
}
|
|
|
|
/*
|
|
Set filter resonance.
|
|
|
|
In the MOS 6581, 1/Q is controlled linearly by res. From die photographs
|
|
of the resonance "resistor" ladder it follows that 1/Q ~ ~res/8
|
|
(assuming an ideal op-amp and ideal "resistors"). This implies that Q
|
|
ranges from 0.533 (res = 0) to 8 (res = E). For res = F, Q is actually
|
|
theoretically unlimited, which is quite unheard of in a filter
|
|
circuit.
|
|
|
|
To obtain Q ~ 1/sqrt(2) = 0.707 for maximally flat frequency response,
|
|
res should be set to 4: Q = 8/~4 = 8/11 = 0.7272 (again assuming an ideal
|
|
op-amp and ideal "resistors").
|
|
|
|
Q as low as 0.707 is not achievable because of low gain op-amps; res = 0
|
|
should yield the flattest possible frequency response at Q ~ 0.8 - 1.0
|
|
in the op-amp's pseudo-linear range (high amplitude signals will be
|
|
clipped). As resonance is increased, the filter must be clocked more
|
|
often to keep it stable.
|
|
|
|
In the MOS 8580, the resonance "resistor" ladder above the bp feedback
|
|
op-amp is split in two parts; one ladder for the op-amp input and one
|
|
ladder for the op-amp feedback.
|
|
|
|
input: feedback:
|
|
|
|
Rf
|
|
Ri R4 RC R8 R3
|
|
R2
|
|
R1
|
|
|
|
|
|
The "resistors" are switched in as follows by bits in register $17:
|
|
|
|
feedback:
|
|
R1: bit4&!bit5
|
|
R2: !bit4&bit5
|
|
R3: bit4&bit5
|
|
Rf: always on
|
|
|
|
input:
|
|
R4: bit6&!bit7
|
|
R8: !bit6&bit7
|
|
RC: bit6&bit7
|
|
Ri: !(R4|R8|RC) = !(bit6|bit7) = !bit6&!bit7
|
|
|
|
|
|
The relative "resistor" values are approximately (using channel length):
|
|
|
|
R1 = 15.3*Ri
|
|
R2 = 7.3*Ri
|
|
R3 = 4.7*Ri
|
|
Rf = 1.4*Ri
|
|
R4 = 1.4*Ri
|
|
R8 = 2.0*Ri
|
|
RC = 2.8*Ri
|
|
|
|
|
|
Approximate values for 1/Q can now be found as follows (assuming an
|
|
ideal op-amp):
|
|
|
|
res feedback input -gain (1/Q)
|
|
--- -------- ----- ----------
|
|
0 Rf Ri Rf/Ri = 1/(Ri*(1/Rf)) = 1/0.71
|
|
1 Rf|R1 Ri (Rf|R1)/Ri = 1/(Ri*(1/Rf+1/R1)) = 1/0.78
|
|
2 Rf|R2 Ri (Rf|R2)/Ri = 1/(Ri*(1/Rf+1/R2)) = 1/0.85
|
|
3 Rf|R3 Ri (Rf|R3)/Ri = 1/(Ri*(1/Rf+1/R3)) = 1/0.92
|
|
4 Rf R4 Rf/R4 = 1/(R4*(1/Rf)) = 1/1.00
|
|
5 Rf|R1 R4 (Rf|R1)/R4 = 1/(R4*(1/Rf+1/R1)) = 1/1.10
|
|
6 Rf|R2 R4 (Rf|R2)/R4 = 1/(R4*(1/Rf+1/R2)) = 1/1.20
|
|
7 Rf|R3 R4 (Rf|R3)/R4 = 1/(R4*(1/Rf+1/R3)) = 1/1.30
|
|
8 Rf R8 Rf/R8 = 1/(R8*(1/Rf)) = 1/1.43
|
|
9 Rf|R1 R8 (Rf|R1)/R8 = 1/(R8*(1/Rf+1/R1)) = 1/1.56
|
|
A Rf|R2 R8 (Rf|R2)/R8 = 1/(R8*(1/Rf+1/R2)) = 1/1.70
|
|
B Rf|R3 R8 (Rf|R3)/R8 = 1/(R8*(1/Rf+1/R3)) = 1/1.86
|
|
C Rf RC Rf/RC = 1/(RC*(1/Rf)) = 1/2.00
|
|
D Rf|R1 RC (Rf|R1)/RC = 1/(RC*(1/Rf+1/R1)) = 1/2.18
|
|
E Rf|R2 RC (Rf|R2)/RC = 1/(RC*(1/Rf+1/R2)) = 1/2.38
|
|
F Rf|R3 RC (Rf|R3)/RC = 1/(RC*(1/Rf+1/R3)) = 1/2.60
|
|
|
|
|
|
These data indicate that the following function for 1/Q has been
|
|
modeled in the MOS 8580:
|
|
|
|
1/Q = 2^(1/2)*2^(-x/8) = 2^(1/2 - x/8) = 2^((4 - x)/8)
|
|
|
|
*/
|
|
void Filter::set_Q()
|
|
{
|
|
// Cutoff for MOS 6581.
|
|
// The coefficient 8 is dispensed of later by right-shifting 3 times
|
|
// (2 ^ 3 = 8).
|
|
_8_div_Q = ~res & 0x0f;
|
|
|
|
// FIXME: Temporary cutoff code for MOS 8580.
|
|
// 1024*1/Q = 1024*2^((4 - res)/8)
|
|
// The coefficient 1024 is dispensed of later by right-shifting 10 times
|
|
// (2 ^ 10 = 1024).
|
|
static const int _1024_div_Q_table[] = {
|
|
1448,
|
|
1328,
|
|
1218,
|
|
1117,
|
|
1024,
|
|
939,
|
|
861,
|
|
790,
|
|
724,
|
|
664,
|
|
609,
|
|
558,
|
|
512,
|
|
470,
|
|
431,
|
|
395
|
|
};
|
|
|
|
_1024_div_Q = _1024_div_Q_table[res];
|
|
}
|
|
|
|
// Set input routing bits.
|
|
void Filter::set_sum_mix()
|
|
{
|
|
// NB! voice3off (mode bit 7) only affects voice 3 if it is routed directly
|
|
// to the mixer.
|
|
sum = (enabled ? filt : 0x00) & voice_mask;
|
|
mix =
|
|
(enabled ? (mode & 0x70) | ((~(filt | (mode & 0x80) >> 5)) & 0x0f) : 0x0f)
|
|
& voice_mask;
|
|
}
|
|
|
|
} // namespace reSID
|