diff options
Diffstat (limited to 'src/devices/sound/flt_biquad.cpp')
-rw-r--r-- | src/devices/sound/flt_biquad.cpp | 253 |
1 files changed, 156 insertions, 97 deletions
diff --git a/src/devices/sound/flt_biquad.cpp b/src/devices/sound/flt_biquad.cpp index f74bf6bae73..de6bfe63462 100644 --- a/src/devices/sound/flt_biquad.cpp +++ b/src/devices/sound/flt_biquad.cpp @@ -7,18 +7,15 @@ It has a number of constructor-helpers for automatically generating a biquad filter equivalent to the filter response of a few standard - analog second order filter topographies. + analog first and second order filter topographies. This biquad filter implementation is based on one written by Frank Palazzolo, K. Wilkins, Couriersud, and Derrick Renaud, with some changes: * It uses the Q factor directly in the filter definitions, rather than the damping factor (1/2Q) * 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. + * (There are advantages to Direct-form I if the code used fixed-point math, but it does not.) * 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) - */ #include "emu.h" #include "flt_biquad.h" @@ -149,60 +146,106 @@ void filter_biquad_device::modify_raw(double a1, double a2, double b0, double b1 // Sallen-Key filters +// (sometimes referred to as KRC or VCVS filter structures) + +/* + * The calculation of the cutoff parameter for the Sallen-Key low-pass and + * high-pass is identical, the only differences being the biquad filter + * type used, and the calculation of the q factor. + */ +filter_biquad_device::biquad_params filter_biquad_device::opamp_sk_lphp_calc(biquad_type type, double r1, double r2, double r3, double r4, double c1, double c2) +{ + filter_biquad_device::biquad_params r; + if ((r1 == 0) || (r2 == 0) || (r3 == 0) || (r4 == 0) || (c1 == 0) || (c2 == 0)) + { + fatalerror("filter_biquad_device::opamp_sk_lphp_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. */ + } + r.type = type; + r.gain = 1.0 + (r4 / r3); // == (r3 + r4) / r3 + r.fc = 1.0 / (2 * M_PI * sqrt(r1 * r2 * c1 * c2)); + if (type == biquad_type::LOWPASS) + r.q = sqrt(r1 * r2 * c1 * c2) / ((r1 * c2) + (r2 * c2) + ((r1 * c1) * (1.0 - r.gain))); + else if (type == biquad_type::HIGHPASS) + r.q = sqrt(r1 * r2 * c1 * c2) / ((r1 * c2) + (r1 * c1) + ((r2 * c2) * (1.0 - r.gain))); + else + r.q = M_SQRT2/2.0; // we shouldn't get here but fail gracefully if we do. + LOGMASKED(LOG_SETUP,"filter_biquad_device::opamp_sk_lphp_calc(%f, %f, %f, %f, %f, %f) yields: fc = %f, Q = %f, gain = %f\n", r1, r2, r3, r4, c1*1000000, c2*1000000, r.fc, r.q, r.gain); + return r; +} /* 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. * - * .----------------------------. - * | | - * --- c1 | - * --- | - * | | - * r1 | r2 |\ | - * In >----ZZZZ----+--ZZZZ---+--------+ | \ | - * | '--|+ \ | - * --- c2 | >--+------> out - * --- .--|- / | - * | | | / | - * gnd | |/ | - * | | - * | r4 | - * +--ZZZZ---' - * | - * Z - * Z r3 - * Z - * | - * gnd + * ,--------------------------. + * | | + * --- c1 | + * --- | + * | |\ | + * r1 | r2 | \ | + * In >---ZZZZ--+--ZZZZ--+----------|+ \ | + * | | >--+---> out + * --- c2 ,--|- / | + * --- | | / | + * | | |/ | + * | | | + * gnd | r4 | + * +--ZZZZ---' + * | + * Z r3 + * Z + * Z + * | + * gnd */ filter_biquad_device& filter_biquad_device::opamp_sk_lowpass_setup(double r1, double r2, double r3, double r4, double c1, double c2) { - filter_biquad_device::biquad_params p = opamp_sk_lowpass_calc(r1, r2, r3, r4, c1, c2); + filter_biquad_device::biquad_params p = opamp_sk_lphp_calc(biquad_type::LOWPASS, r1, r2, r3, r4, c1, c2); return setup(p); } void filter_biquad_device::opamp_sk_lowpass_modify(double r1, double r2, double r3, double r4, double c1, double c2) { - filter_biquad_device::biquad_params p = opamp_sk_lowpass_calc(r1, r2, r3, r4, c1, c2); + filter_biquad_device::biquad_params p = opamp_sk_lphp_calc(biquad_type::LOWPASS, r1, r2, r3, r4, c1, c2); modify(p); } -filter_biquad_device::biquad_params filter_biquad_device::opamp_sk_lowpass_calc(double r1, double r2, double r3, double r4, double c1, double c2) + +/* Setup a biquad filter structure based on a single op-amp Sallen-Key high-pass filter circuit. + * + * ,--------------------------. + * | | + * Z r1 | + * Z | + * Z |\ | + * c1 | c2 | \ | + * In >----||---+---||---+----------|+ \ | + * | | >--+---> out + * Z r2 ,--|- / | + * Z | | / | + * Z | |/ | + * | | | + * gnd | r4 | + * +--ZZZZ---' + * | + * Z r3 + * Z + * Z + * | + * gnd + */ +filter_biquad_device& filter_biquad_device::opamp_sk_highpass_setup(double r1, double r2, double r3, double r4, double c1, double c2) { - filter_biquad_device::biquad_params r; - if ((r1 == 0) || (r2 == 0) || (r3 == 0) || (r4 == 0) || (c1 == 0) || (c2 == 0)) - { - 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. */ - } - 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)); - r.q = sqrt(r1 * r2 * c1 * c2) / ((r1 * c2) + (r2 * c2) + ((r2 * c1) * (1.0 - r.gain))); - LOGMASKED(LOG_SETUP,"filter_biquad_device::opamp_sk_lowpass_calc(%f, %f, %f, %f, %f, %f) yields: fc = %f, Q = %f, gain = %f\n", r1, r2, r3, r4, c1*1000000, c2*1000000, r.fc, r.q, r.gain); - return r; + filter_biquad_device::biquad_params p = opamp_sk_lphp_calc(biquad_type::HIGHPASS, r1, r2, r3, r4, c1, c2); + return setup(p); +} + +void filter_biquad_device::opamp_sk_highpass_modify(double r1, double r2, double r3, double r4, double c1, double c2) +{ + filter_biquad_device::biquad_params p = opamp_sk_lphp_calc(biquad_type::HIGHPASS, r1, r2, r3, r4, c1, c2); + modify(p); } -// TODO when needed: Sallen-Key high-pass filter +// TODO when needed: Sallen-Key band-pass (there are several versions of this in the 1955 Sallen-Key paper) // Multiple-Feedback filters @@ -210,24 +253,39 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_sk_lowpass_calc( /* Setup a biquad filter structure based on a single op-amp Multiple-Feedback low-pass filter circuit. * This is sometimes called a "Rauch" filter circuit. * NOTE: There is a well known 'proper' 1st order version of this circuit where - * r2 is a dead short, and c1 omitted. As an exception to the usual rule about - * missing components, set both c1 and r2 to 0 in this case. - * NOTE: There is a variant of this filter with 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! + * r2 is a dead short, and c1 omitted: set r2 to 0 or RES_R(0.001) and c1 to 0 + * in this case. + * NOTE: There is a variant of this filter where r2 is present but c1 is + * omitted: set r2 to its expected value, and c1 to 0. + * + * Typical variant: (set c1 to 0 if missing) + * ,--------+---------. + * | | | + * Z r3 --- c2 | + * Z --- | + * Z | | + * r1 | r2 | |\ | + * In >---ZZZZ--+-------+--ZZZZ--+ | \ | + * | `--|- \ | + * --- c1 | >--+---> out + * --- ,--|+ / + * | | | / + * | vRef >---' |/ + * gnd * - * .--------+---------. - * | | | - * Z --- c2 | - * Z r3 --- | - * Z | | - * r1 | r2 | |\ | - * In >----ZZZZ----+---------+--ZZZZ--+ | \ | - * | '--|- \ | - * --- c1 | >--+------> out - * --- .--|+ / - * | | | / - * gnd vRef >---' |/ + * First order variant: (set c1 and r2 to 0) + * ,-------+---------. + * | | | + * Z r3 --- c2 | + * Z --- | + * Z | | + * r1 | | |\ | + * In >---ZZZZ--+-------+ | \ | + * `--|- \ | + * | >--+---> out + * ,--|+ / + * | | / + * vRef >--' |/ * */ filter_biquad_device& filter_biquad_device::opamp_mfb_lowpass_setup(double r1, double r2, double r3, double c1, double c2) @@ -269,23 +327,21 @@ 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" aka "IGMF". - * 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 present, then set it to RES_M(999.99), the code will effectively be an Infinite Gain MFB Bandpass. * - * .--------+---------. - * | | | - * --- c1 Z | - * --- Z r3 | - * | Z | - * r1 | c2 | |\ | - * In >----ZZZZ----+---------+--||----+ | \ | - * Z '--|- \ | - * Z r2 | >--+------> out - * Z .--|+ / - * | | | / - * gnd vRef >---' |/ + * ,--------+---------. + * | | | + * --- c1 Z r3 | + * --- Z | + * | Z | + * r1 | c2 | |\ | + * In >---ZZZZ--+-------+---||---+ | \ | + * | `--|- \ | + * Z r2 | >--+---> out + * Z ,--|+ / + * Z | | / + * | vRef >---' |/ + * gnd * */ filter_biquad_device& filter_biquad_device::opamp_mfb_bandpass_setup(double r1, double r2, double r3, double c1, double c2) @@ -305,18 +361,18 @@ filter_biquad_device& filter_biquad_device::opamp_mfb_bandpass_setup(double r1, /* Setup a biquad filter structure based on a single op-amp Multiple-Feedback high-pass filter circuit. * - * .--------+---------. - * | | | - * --- c3 Z | - * --- Z r2 | - * | Z | - * c1 | c2 | |\ | - * In >-----||-----+---------+---||---+ | \ | - * Z '--|- \ | - * Z r1 | >--+------> out - * Z .--|+ / - * | | | / - * gnd vRef >---' |/ + * ,--------+---------. + * | | | + * --- c3 Z | + * --- Z r2 | + * | Z | + * c1 | c2 | |\ | + * In >---||----+-------+---||---+ | \ | + * Z `--|- \ | + * Z r1 | >--+---> out + * Z ,--|+ / + * | | | / + * gnd vRef >---' |/ * */ filter_biquad_device& filter_biquad_device::opamp_mfb_highpass_setup(double r1, double r2, double c1, double c2, double c3) @@ -333,23 +389,26 @@ filter_biquad_device& filter_biquad_device::opamp_mfb_highpass_setup(double r1, return setup(biquad_type::HIGHPASS, fc, q, gain); } + +// Other filters: + // Differentiator Filter /* Setup a biquad filter structure based on a single op-amp Differentiator band-pass filter circuit. * This circuit is sometimes called an "Inverting Band Pass Filter Circuit" * - * .--------+---------. - * | | | - * --- c2 Z | - * --- Z r2 | - * | Z | - * r1 c1 | | |\ | - * In >----ZZZZ-----||-----+--------+ | \ | - * '--|- \ | - * | >--+------> out - * .--|+ / - * | | / - * vRef >---' |/ + * ,--------+---------. + * | | | + * --- c2 Z r2 | + * --- Z | + * | Z | + * r1 c1 | | |\ | + * In >---ZZZZ-----||---+--------+ | \ | + * `--|- \ | + * | >--+---> out + * ,--|+ / + * | | / + * vRef >---' |/ * */ filter_biquad_device& filter_biquad_device::opamp_diff_bandpass_setup(double r1, double r2, double c1, double c2) |