summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Lord-Nightmare <Lord-Nightmare@users.noreply.github.com>2022-09-28 22:42:11 -0400
committer Lord-Nightmare <Lord-Nightmare@users.noreply.github.com>2022-09-28 22:42:11 -0400
commit84057af59d87803489baddca29ffa1b3ebf87e3d (patch)
treeffb1354eded4a8339ae8974c9da20c4cd8596407
parentc11d1b7844e5cff2995323db4a410b64e557bd73 (diff)
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]
-rw-r--r--src/devices/sound/flt_biquad.cpp71
-rw-r--r--src/devices/sound/flt_biquad.h10
2 files changed, 52 insertions, 29 deletions
diff --git a/src/devices/sound/flt_biquad.cpp b/src/devices/sound/flt_biquad.cpp
index a023c1ff0a5..6ae1bde0109 100644
--- a/src/devices/sound/flt_biquad.cpp
+++ b/src/devices/sound/flt_biquad.cpp
@@ -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:
diff --git a/src/devices/sound/flt_biquad.h b/src/devices/sound/flt_biquad.h
index 9ac3f820893..e565aa6da8a 100644
--- a/src/devices/sound/flt_biquad.h
+++ b/src/devices/sound/flt_biquad.h
@@ -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);