mirror of
https://github.com/holub/mame
synced 2025-04-22 08:22:15 +03:00
Add the ability to define a biquad filter using raw parameters, instead of only by using component values or by type/cutoff/bandwidth/gain. Fix some of the issues with the Multiple Feedback Bandpass filter. [Lord Nightmare]
This commit is contained in:
parent
c11d1b7844
commit
84057af59d
@ -14,10 +14,11 @@
|
||||
* It uses the Q factor directly in the filter definitions, rather than the damping factor (1/Q)
|
||||
* It implements every common type of digital biquad filter which I could find documentation for.
|
||||
* The filter is Direct-form II instead of Direct-form I, which results in shorter compiled code.
|
||||
* Optional direct control of the 5 normalized biquad parameters for a custom/raw parameter filter.
|
||||
|
||||
Possibly useful features which aren't implemented because nothing uses them yet:
|
||||
* More Sallen-Key filter variations (band-pass, high-pass)
|
||||
* Direct control of the 5 normalized biquad parameters for a custom/raw parameter filter.
|
||||
|
||||
*/
|
||||
#include "emu.h"
|
||||
#include "flt_biquad.h"
|
||||
@ -84,6 +85,17 @@ filter_biquad_device& filter_biquad_device::setup(filter_biquad_device::biquad_p
|
||||
m_gain = p.gain;
|
||||
return *this;
|
||||
}
|
||||
filter_biquad_device& filter_biquad_device::setup_raw(double a1, double a2, double b0, double b1, double b2)
|
||||
{
|
||||
m_type = biquad_type::RAWPARAMS;
|
||||
m_a1 = a1;
|
||||
m_a2 = a2;
|
||||
m_b0 = b0;
|
||||
m_b1 = b1;
|
||||
m_b2 = b2;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// modify an existing instance with new filter parameters
|
||||
void filter_biquad_device::modify(biquad_type type, double fc, double q, double gain)
|
||||
@ -104,14 +116,30 @@ void filter_biquad_device::modify(filter_biquad_device::biquad_params p)
|
||||
m_gain = p.gain;
|
||||
recalc();
|
||||
}
|
||||
void filter_biquad_device::modify_raw(double a1, double a2, double b0, double b1, double b2)
|
||||
{
|
||||
m_stream->update();
|
||||
m_type = biquad_type::RAWPARAMS;
|
||||
m_a1 = a1;
|
||||
m_a2 = a2;
|
||||
m_b0 = b0;
|
||||
m_b1 = b1;
|
||||
m_b2 = b2;
|
||||
recalc();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// Filter setup helpers for various filter models
|
||||
//-------------------------------------------------
|
||||
// NOTE: if a resistor doesn't exist, pass a value of RES_M(999.99) or the like, i.e. an 'infinite resistor'
|
||||
// NOTE: if a resistor is a direct short, set its resistance to RES_R(0.001)
|
||||
|
||||
|
||||
// Sallen-Key filters
|
||||
|
||||
/* Setup a biquad filter structure based on a single op-amp Sallen-Key low-pass filter circuit.
|
||||
* This is sometimes, incorrectly, called a "Butterworth" filter structure.
|
||||
*
|
||||
* .----------------------------.
|
||||
* | |
|
||||
@ -154,9 +182,6 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_sk_lowpass_calc(
|
||||
{
|
||||
fatalerror("filter_biquad_device::opamp_sk_lowpass_calc() - no parameters can be 0; parameters were: r1: %f, r2: %f, r3: %f, r4: %f, c1: %f, c2: %f", r1, r2, r3, r4, c1, c2); /* Filter can not be setup. Undefined results. */
|
||||
}
|
||||
// NOTE: if R3 doesn't exist (no link to ground), pass a value of RES_M(999.99) or the like, i.e. an 'infinite resistor'
|
||||
// NOTE: if R4 is a direct short, set its resistance to RES_R(0.001)
|
||||
// NOTE: if R3 doesn't exist AND R4 is a direct short, follow both rules above.
|
||||
r.type = biquad_type::LOWPASS;
|
||||
r.gain = 1.0 + (r4 / r3); // == (r3 + r4) / r3
|
||||
r.fc = 1.0 / (2 * M_PI * sqrt(r1 * r2 * c1 * c2));
|
||||
@ -179,6 +204,7 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_sk_lowpass_calc(
|
||||
* a dead short, and c1 omitted. set both c1 and r2 to 0 in this case.
|
||||
* NOTE3: a variant of NOTE2 has only the c1 capacitor left off, and r2 present. if so,
|
||||
* set c1 to 0 and r2 to its expected value.
|
||||
* TODO: make this compatible with the RES_M(999.99) and RES_R(0.001) rules!
|
||||
*
|
||||
* .--------+---------.
|
||||
* | | |
|
||||
@ -215,7 +241,7 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_mfb_lowpass_calc
|
||||
}
|
||||
r.gain = -r3 / r1;
|
||||
r.q = (M_SQRT2 / 2.0);
|
||||
if (c1 == 0) // if both R2 and C1 are 0, it is the 'proper' first order case. There do exist some unusual filters where R2 is not 0, though. In both cases this yields a single-pole filter with limited configurable gain, and a Q of ~0.707. R2 being zero makes the (r1 * r3) numerator term cancel out to 1.0.
|
||||
if (c1 == 0) // if both R2 and C1 are 0, it is the 'proper' first order case. If C1 is 0 (Williams...) the filter is 1st order. There do exist some unusual filters where R2 is not 0, though. In both cases this yields a single-pole filter with limited configurable gain, and a Q of ~0.707. R2 being zero makes the (r1 * r3) numerator term cancel out to 1.0.
|
||||
{
|
||||
r.fc = (r1 * r3) / (2 * M_PI * ((r1 * r2) + (r1 * r3) + (r2 * r3)) * r3 * c2);
|
||||
r.type = biquad_type::LOWPASS1P;
|
||||
@ -234,14 +260,14 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_mfb_lowpass_calc
|
||||
|
||||
/* Setup a biquad filter structure based on a single op-amp Multiple-Feedback band-pass filter circuit.
|
||||
* This is sometimes called a "modified Deliyannis" or "Deliyannis-friend" filter circuit,
|
||||
* or an "Infinite Gain Multiple-Feedback [band-pass] Filter", or "IGMF".
|
||||
* or an "Infinite Gain Multiple-Feedback [band-pass] Filter" aka "IGMF".
|
||||
* NOTE: vRef is not definable when setting up the filter, and is assumed to be grounded.
|
||||
* If the analog effects caused by vRef are important to the operation of the specific filter
|
||||
* in question, a netlist implementation may work better under those circumstances.
|
||||
* TODO: There is a documented modification to this filter which adds a resistor ladder between
|
||||
* ground and the op-amp output, with the 'rung' of the ladder connecting to the + input of
|
||||
* the op-amp, and this allows more control of the filter.
|
||||
* NOTE2: If r2 is not used, then set it to 0 ohms, the code will switch to an Infinite Gain MFB Bandpass
|
||||
* NOTE2: If r2 is not used, then set it to RES_M(999.99), the code will effectively be an Infinite Gain MFB Bandpass.
|
||||
*
|
||||
* .--------+---------.
|
||||
* | | |
|
||||
@ -259,27 +285,18 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_mfb_lowpass_calc
|
||||
*/
|
||||
filter_biquad_device& filter_biquad_device::opamp_mfb_bandpass_setup(double r1, double r2, double r3, double c1, double c2)
|
||||
{
|
||||
if ((r1 == 0) || (r3 == 0) || (c1 == 0) || (c2 == 0))
|
||||
if ((r1 == 0) || (r2 == 0) || (r3 == 0) || (c1 == 0) || (c2 == 0))
|
||||
{
|
||||
fatalerror("filter_biquad_device::opamp_mfb_bandpass_setup() - only r2 can be 0; parameters were: r1: %f, r2: %f, r3: %f, c1: %f, c2: %f", r1, r2, r3, c1, c2); /* Filter can not be setup. Undefined results. */
|
||||
fatalerror("filter_biquad_device::opamp_mfb_bandpass_setup() - no parameters can be 0; parameters were: r1: %f, r2: %f, r3: %f, c1: %f, c2: %f", r1, r2, r3, c1, c2); /* Filter can not be setup. Undefined results. */
|
||||
}
|
||||
|
||||
double r_in, gain;
|
||||
double const r_in = 1.0 / (1.0/r1 + 1.0/r2); // TODO: verify
|
||||
// gain = (r2 / (r1 + r2)) * (-r3 / r_in * c2 / (c1 + c2)); // ??? wrong?
|
||||
double const gain = -r3 / (2.0 * r1);
|
||||
// q = sqrt(r3 / r_in * c1 * c2) / (c1 + c2); // ??? wrong?
|
||||
double const q = 0.5 * sqrt(r3 / r1);
|
||||
|
||||
if (r2 == 0)
|
||||
{
|
||||
gain = 1;
|
||||
r_in = r1;
|
||||
}
|
||||
else
|
||||
{
|
||||
gain = r2 / (r1 + r2);
|
||||
r_in = 1.0 / (1.0/r1 + 1.0/r2);
|
||||
}
|
||||
|
||||
double const fc = 1.0 / (2 * M_PI * sqrt(r_in * r3 * c1 * c2)); // technically this is the center frequency of the bandpass
|
||||
double const q = sqrt(r3 / r_in * c1 * c2) / (c1 + c2);
|
||||
gain *= -r3 / r_in * c2 / (c1 + c2);
|
||||
double const fc = 1.0 / (sqrt(r_in * r3 * c1 * c2)); // technically this is the center frequency of the bandpass
|
||||
#ifdef FLT_BIQUAD_DEBUG_SETUP
|
||||
logerror("filter_biquad_device::opamp_mfb_bandpass_setup() yields: fc = %f, Q = %f, gain = %f\n", fc, q, gain);
|
||||
#endif
|
||||
@ -434,6 +451,9 @@ void filter_biquad_device::sound_stream_update(sound_stream &stream, std::vector
|
||||
*/
|
||||
void filter_biquad_device::recalc()
|
||||
{
|
||||
if (m_type == biquad_type::RAWPARAMS)
|
||||
return; // if we're dealing with raw parameters, just return, don't touch anything.
|
||||
|
||||
double const MGain = fabs(m_gain); // absolute multiplicative gain
|
||||
double const DBGain = log10(MGain) * 20.0; // gain in dB
|
||||
double const AMGain = pow(10, fabs(DBGain) / 20.0); // multiplicative gain of absolute DB
|
||||
@ -442,9 +462,6 @@ void filter_biquad_device::recalc()
|
||||
double const KoverQ = K / m_q;
|
||||
double normal = 1.0 / (1.0 + KoverQ + Ksquared);
|
||||
|
||||
m_a1 = 2.0 * (Ksquared - 1.0) * normal;
|
||||
m_a2 = (1.0 - KoverQ + Ksquared) * normal;
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
case biquad_type::LOWPASS1P:
|
||||
|
@ -24,7 +24,8 @@ public:
|
||||
NOTCH,
|
||||
PEAK,
|
||||
LOWSHELF,
|
||||
HIGHSHELF
|
||||
HIGHSHELF,
|
||||
RAWPARAMS
|
||||
};
|
||||
|
||||
struct biquad_params
|
||||
@ -44,7 +45,12 @@ public:
|
||||
void modify(biquad_type type, double fc, double q, double gain);
|
||||
void modify(biquad_params p);
|
||||
|
||||
// helper setup functions to create common filters representable by biquad filters:
|
||||
// set up the filter with raw biquad coefficients
|
||||
filter_biquad_device& setup_raw(double a1, double a2, double b0, double b1, double b2);
|
||||
void modify_raw(double a1, double a2, double b0, double b1, double b2);
|
||||
|
||||
// Helper setup functions to create common filters representable by biquad filters:
|
||||
// (and, as needed, modify/update/recalc helpers)
|
||||
|
||||
// Sallen-Key low-pass
|
||||
filter_biquad_device& opamp_sk_lowpass_setup(double r1, double r2, double r3, double r4, double c1, double c2);
|
||||
|
Loading…
Reference in New Issue
Block a user