summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/flt_biquad.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/flt_biquad.cpp')
-rw-r--r--src/devices/sound/flt_biquad.cpp253
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)