1 // license:BSD-3-Clause
2 // copyright-holders:K.Wilkins,Couriersud,Derrick Renaud,Frank Palazzolo,Jonathan Gevaryahu
3 /*
4 This is an implementation of a Direct-form II digital biquad filter,
5 intended for use in audio paths for filtering audio to or from other
6 stream devices.
7
8 It has a number of constructor-helpers for automatically generating
9 a biquad filter equivalent to the filter response of a few standard
10 analog second order filter topographies.
11
12 This biquad filter implementation is based on one written by Frank
13 Palazzolo, K. Wilkins, Couriersud, and Derrick Renaud, with some changes:
14 * It uses the Q factor directly in the filter definitions, rather than the damping factor (1/Q)
15 * It implements every common type of digital biquad filter which I could find documentation for.
16 * The filter is Direct-form II instead of Direct-form I, which results in shorter compiled code.
17
18 Possibly useful features which aren't implemented because nothing uses them yet:
19 * More Sallen-Key filter variations (band-pass, high-pass)
20 * Direct control of the 5 normalized biquad parameters for a custom/raw parameter filter.
21 */
22 #include "emu.h"
23 #include "flt_biquad.h"
24
25 // we need the M_SQRT2 constant
26 #ifndef M_SQRT2
27 #define M_SQRT2 1.41421356237309504880
28 #endif
29
30 // define this to display debug info about the filters being set up
31 #undef FLT_BIQUAD_DEBUG_SETUP
32
33 // device type definition
34 DEFINE_DEVICE_TYPE(FILTER_BIQUAD, filter_biquad_device, "filter_biquad", "Biquad Filter")
35
36 // allow the enum class for the biquad filter type to be saved by the savestate system
37 ALLOW_SAVE_TYPE(filter_biquad_device::biquad_type);
38
39 //**************************************************************************
40 // LIVE DEVICE
41 //**************************************************************************
42
43 //-------------------------------------------------
44 // filter_biquad_device - constructor
45 //-------------------------------------------------
46
47 // initialize with some sane defaults for a highpass filter with a cutoff at 16hz, same as flt_rc's 'ac' mode.
filter_biquad_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)48 filter_biquad_device::filter_biquad_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
49 : device_t(mconfig, FILTER_BIQUAD, tag, owner, clock),
50 device_sound_interface(mconfig, *this),
51 m_stream(nullptr),
52 m_type(biquad_type::HIGHPASS),
53 m_last_sample_rate(0),
54 m_fc(16.0),
55 m_q(M_SQRT2/2.0),
56 m_gain(1.0),
57 m_input(0.0),
58 m_w0(0.0),
59 m_w1(0.0),
60 m_w2(0.0),
61 m_output(0.0),
62 m_a1(0.0),
63 m_a2(0.0),
64 m_b0(1.0),
65 m_b1(0.0),
66 m_b2(0.0)
67 {
68 }
69
70 // set up the filter with the specified parameters and return a pointer to the new device
setup(biquad_type type,double fc,double q,double gain)71 filter_biquad_device& filter_biquad_device::setup(biquad_type type, double fc, double q, double gain)
72 {
73 m_type = type;
74 m_fc = fc;
75 m_q = q;
76 m_gain = gain;
77 return *this;
78 }
79
80 // update an existing instance with new filter parameters
update_params(biquad_type type,double fc,double q,double gain)81 void filter_biquad_device::update_params(biquad_type type, double fc, double q, double gain)
82 {
83 m_stream->update();
84 m_type = type;
85 m_fc = fc;
86 m_q = q;
87 m_gain = gain;
88 recalc();
89 }
90
91
92 //-------------------------------------------------
93 // Filter setup helpers for various filter models
94 //-------------------------------------------------
95
96 // Sallen-Key filters
97
98 /* Setup a biquad filter structure based on a single op-amp Sallen-Key low-pass filter circuit.
99 *
100 * .----------------------------.
101 * | |
102 * --- c2 |
103 * --- |
104 * | |
105 * r1 | r2 |\ |
106 * In >----ZZZZ----+--ZZZZ---+--------+ | \ |
107 * | '--|+ \ |
108 * --- c1 | >--+------> out
109 * --- .--|- / |
110 * | | | / |
111 * gnd | |/ |
112 * | |
113 * | r4 |
114 * +--ZZZZ---'
115 * |
116 * Z
117 * Z r3
118 * Z
119 * |
120 * gnd
121 */
opamp_sk_lowpass_setup(double r1,double r2,double r3,double r4,double c1,double c2)122 filter_biquad_device& filter_biquad_device::opamp_sk_lowpass_setup(double r1, double r2, double r3, double r4, double c1, double c2)
123 {
124 if ((r1 == 0) || (r2 == 0) || (r3 == 0) || (r4 == 0) || (c1 == 0) || (c2 == 0))
125 {
126 fatalerror("filter_biquad_device::opamp_sk_lowpass_setup() - 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. */
127 }
128
129 // 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'
130 double const gain = (r3 + r4) / r3;
131 double const fc = 1.0 / (2 * M_PI * sqrt(r1 * r2 * c1 * c2));
132 double const q = sqrt(r1 * r2 * c1 * c2) / ((r1 * c1) + (r2 * c1) + ((r2 * c2) * (1.0 - gain)));
133 #ifdef FLT_BIQUAD_DEBUG_SETUP
134 logerror("filter_biquad_device::opamp_sk_lowpass_setup() yields: fc = %f, Q = %f, gain = %f\n", fc, q, gain);
135 #endif
136 return setup(biquad_type::LOWPASS, fc, q, gain);
137 }
138
139 // Multiple-Feedback filters
140 // (This is sometimes called a 'Rauch' filter circuit.)
141
142 /* Setup a biquad filter structure based on a single op-amp Multiple-Feedback low-pass filter circuit.
143 * NOTE: vRef is not definable when setting up the filter.
144 * If the analog effects caused by vRef are important to the operation of the specific filter
145 * in question, a netlist implementation may work better under those circumstances.
146 * NOTE2: a variant of this circuit has the c1 capacitor left off. if so, set c1 to 0
147 * which will form a controllable gain 1st order version of the circuit.
148 * NOTE3: There is a well known 'proper' 1st order version of this circuit where r2 is
149 * a dead short, and c1 is also ommitted as in NOTE2. Set both r2 to 0 and c1 to 0 to
150 * emulate a circuit of this type.
151 *
152 * .--------+---------.
153 * | | |
154 * Z --- c2 |
155 * Z r3 --- |
156 * Z | |
157 * r1 | r2 | |\ |
158 * In >----ZZZZ----+---------+--ZZZZ--+ | \ |
159 * | '--|- \ |
160 * --- c1 | >--+------> out
161 * --- .--|+ /
162 * | | | /
163 * gnd vRef >---' |/
164 *
165 */
opamp_mfb_lowpass_setup(double r1,double r2,double r3,double c1,double c2)166 filter_biquad_device& filter_biquad_device::opamp_mfb_lowpass_setup(double r1, double r2, double r3, double c1, double c2)
167 {
168 if ((r1 == 0) || ((r2 == 0) && (c1 != 0)) || (r3 == 0) || (c2 == 0))
169 {
170 fatalerror("filter_biquad_device::opamp_mfb_lowpass_setup() - only c1 can be 0 (and if c1 is 0, r2 can also 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. */
171 }
172 double const gain = -r3 / r1;
173 double fc, q = (M_SQRT2 / 2.0);
174 if ((r2 != 0) && (c1 == 0)) // set C1 to 0 to run this filter in single pole mode where C1 was left off the filter entirely. Certain Williams boards seem to have omitted C1, presumably by accident.
175 {
176 fc = (r1 * r3) / (2 * M_PI * ((r1 * r2) + (r1 * r3) + (r2 * r3)) * r3 * c2);
177 #ifdef FLT_BIQUAD_DEBUG_SETUP
178 logerror("filter_biquad_device::opamp_mfb_lowpass_setup() in degraded mode yields: fc = %f, Q = %f(ignored), gain = %f\n", fc, q, gain);
179 #endif
180 return setup(biquad_type::LOWPASS1P, fc, q, gain);
181 }
182 else if ((r2 == 0) && (c1 == 0)) // proper first order case, set both C1 to 0 and R2 to 0 for the first order
183 {
184 fc = 1.0 / (2 * M_PI * r3 * c2);
185 #ifdef FLT_BIQUAD_DEBUG_SETUP
186 logerror("filter_biquad_device::opamp_mfb_lowpass_setup() in 1st order mode yields: fc = %f, Q = %f(ignored), gain = %f\n", fc, q, gain);
187 #endif
188 return setup(biquad_type::LOWPASS1P, fc, q, gain);
189 }
190 else // common case, (r2 != 0) && (c1 != 0)
191 {
192 fc = 1.0 / (2 * M_PI * sqrt(r2 * r3 * c1 * c2));
193 q = sqrt(r2 * r3 * c1 * c2) / ((r3 * c2) + (r2 * c2) + ((r2 * c2) * -gain));
194 #ifdef FLT_BIQUAD_DEBUG_SETUP
195 logerror("filter_biquad_device::opamp_mfb_lowpass_setup() in 2nd order mode yields: fc = %f, Q = %f, gain = %f\n", fc, q, gain);
196 #endif
197 return setup(biquad_type::LOWPASS, fc, q, gain);
198 }
199 }
200
201 /* Setup a biquad filter structure based on a single op-amp Multiple-Feedback band-pass filter circuit.
202 * NOTE: vRef is not definable when setting up the filter.
203 * If the analog effects caused by vRef are important to the operation of the specific filter
204 * in question, a netlist implementation may work better under those circumstances.
205 * NOTE2: If r2 is not used, then set it to 0 ohms, the code will ignore it and assume a fixed gain of 1.
206 *
207 * .--------+---------.
208 * | | |
209 * --- c1 Z |
210 * --- Z r3 |
211 * | Z |
212 * r1 | c2 | |\ |
213 * In >----ZZZZ----+---------+--||----+ | \ |
214 * Z '--|- \ |
215 * Z r2 | >--+------> out
216 * Z .--|+ /
217 * | | | /
218 * gnd vRef >---' |/
219 *
220 */
opamp_mfb_bandpass_setup(double r1,double r2,double r3,double c1,double c2)221 filter_biquad_device& filter_biquad_device::opamp_mfb_bandpass_setup(double r1, double r2, double r3, double c1, double c2)
222 {
223 if ((r1 == 0) || (r3 == 0) || (c1 == 0) || (c2 == 0))
224 {
225 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. */
226 }
227
228 double r_in, gain;
229
230 if (r2 == 0)
231 {
232 gain = 1;
233 r_in = r1;
234 }
235 else
236 {
237 gain = r2 / (r1 + r2);
238 r_in = 1.0 / (1.0/r1 + 1.0/r2);
239 }
240
241 double const fc = 1.0 / (2 * M_PI * sqrt(r_in * r3 * c1 * c2)); // technically this is the center frequency of the bandpass
242 double const q = sqrt(r3 / r_in * c1 * c2) / (c1 + c2);
243 gain *= -r3 / r_in * c2 / (c1 + c2);
244 #ifdef FLT_BIQUAD_DEBUG_SETUP
245 logerror("filter_biquad_device::opamp_mfb_bandpass_setup() yields: fc = %f, Q = %f, gain = %f\n", fc, q, gain);
246 #endif
247 return setup(biquad_type::BANDPASS, fc, q, gain);
248 }
249
250 /* Setup a biquad filter structure based on a single op-amp Multiple-Feedback high-pass filter circuit.
251 * NOTE: vRef is not definable when setting up the filter.
252 * If the analog effects caused by vRef are important to the operation of the specific filter
253 * in question, a netlist implementation may work better under those circumstances.
254 *
255 * .--------+---------.
256 * | | |
257 * --- c3 Z |
258 * --- Z r2 |
259 * | Z |
260 * c1 | c2 | |\ |
261 * In >-----||-----+---------+---||---+ | \ |
262 * Z '--|- \ |
263 * Z r1 | >--+------> out
264 * Z .--|+ /
265 * | | | /
266 * gnd vRef >---' |/
267 *
268 */
opamp_mfb_highpass_setup(double r1,double r2,double c1,double c2,double c3)269 filter_biquad_device& filter_biquad_device::opamp_mfb_highpass_setup(double r1, double r2, double c1, double c2, double c3)
270 {
271 if ((r1 == 0) || (r2 == 0) || (c1 == 0) || (c2 == 0) || (c3 == 0))
272 {
273 fatalerror("filter_biquad_device::opamp_mfb_highpass_setup() - no parameters can be 0; parameters were: r1: %f, r2: %f, c1: %f, c2: %f, c3: %f", r1, r2, c1, c2, c3); /* Filter can not be setup. Undefined results. */
274 }
275 // TODO: if c1 is 0/shorted, should the circuit should work with a gain of 1 in a first order mode?
276
277 double const gain = -c1 / c3;
278 double const fc = 1.0 / (2 * M_PI * sqrt(c2 * c3 * r1 * r2));
279 double const q = sqrt(c2 * c3 * r1 * r2) / ((c2 * r1) + (c3 * r1) + ((c3 * r1) * -gain));
280 #ifdef FLT_BIQUAD_DEBUG_SETUP
281 logerror("filter_biquad_device::opamp_mfb_highpass_setup() yields: fc = %f, Q = %f, gain = %f\n", fc, q, gain);
282 #endif
283 return setup(biquad_type::HIGHPASS, fc, q, gain);
284 }
285
286
287 //-------------------------------------------------
288 // device_start - device-specific startup
289 //-------------------------------------------------
290
device_start()291 void filter_biquad_device::device_start()
292 {
293 m_stream = stream_alloc(1, 1, SAMPLE_RATE_OUTPUT_ADAPTIVE);
294 m_last_sample_rate = 0;
295 recalc();
296
297 save_item(NAME(m_type));
298 save_item(NAME(m_last_sample_rate));
299 save_item(NAME(m_fc));
300 save_item(NAME(m_q));
301 save_item(NAME(m_gain));
302 save_item(NAME(m_input));
303 save_item(NAME(m_w0));
304 save_item(NAME(m_w1));
305 save_item(NAME(m_w2));
306 save_item(NAME(m_output));
307 save_item(NAME(m_a1));
308 save_item(NAME(m_a2));
309 save_item(NAME(m_b0));
310 save_item(NAME(m_b1));
311 save_item(NAME(m_b2));
312 }
313
314
315 //-------------------------------------------------
316 // sound_stream_update - handle a stream update
317 //-------------------------------------------------
318
sound_stream_update(sound_stream & stream,std::vector<read_stream_view> const & inputs,std::vector<write_stream_view> & outputs)319 void filter_biquad_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
320 {
321 auto &src = inputs[0];
322 auto &dst = outputs[0];
323
324 if (m_last_sample_rate != m_stream->sample_rate())
325 {
326 recalc();
327 m_last_sample_rate = m_stream->sample_rate();
328 }
329
330 for (int sampindex = 0; sampindex < dst.samples(); sampindex++)
331 {
332 m_input = src.get(sampindex);
333 step();
334 dst.put(sampindex, m_output);
335 }
336 }
337
338
339 /* Calculate the filter context based on the passed filter type info.
340 * m_type - 1 of the 9 defined filter types
341 * m_fc - center frequency
342 * m_q - 'Q' (quality) factor of filter (1/damp)
343 * m_gain - overall filter gain. Set to 1.0 if not needed. The exact meaning of gain changes depending on the filter type.
344 */
recalc()345 void filter_biquad_device::recalc()
346 {
347 double const MGain = fabs(m_gain); // absolute multiplicative gain
348 double const DBGain = log10(MGain) * 20.0; // gain in dB
349 double const AMGain = pow(10, fabs(DBGain) / 20.0); // multiplicative gain of absolute DB
350 double const K = tan(M_PI * m_fc / m_stream->sample_rate());
351 double const Ksquared = K * K;
352 double const KoverQ = K / m_q;
353 double normal = 1.0 / (1.0 + KoverQ + Ksquared);
354
355 m_a1 = 2.0 * (Ksquared - 1.0) * normal;
356 m_a2 = (1.0 - KoverQ + Ksquared) * normal;
357
358 switch (m_type)
359 {
360 case biquad_type::LOWPASS1P:
361 m_a1 = exp(-2.0 * M_PI * (m_fc / m_stream->sample_rate()));
362 m_b0 = 1.0 - m_a1;
363 m_a1 = -m_a1;
364 m_b1 = m_b2 = m_a2 = 0.0;
365 break;
366 case biquad_type::HIGHPASS1P:
367 m_a1 = -exp(-2.0 * M_PI * (0.5 - m_fc / m_stream->sample_rate()));
368 m_b0 = 1.0 + m_a1;
369 m_a1 = -m_a1;
370 m_b1 = m_b2 = m_a2 = 0.0;
371 break;
372 case biquad_type::LOWPASS:
373 m_b0 = Ksquared * normal;
374 m_b1 = 2.0 * m_b0;
375 m_b2 = 1.0 * m_b0;
376 m_a1 = 2.0 * (Ksquared - 1.0) * normal;
377 m_a2 = (1.0 - KoverQ + Ksquared) * normal;
378 break;
379 case biquad_type::HIGHPASS:
380 m_b0 = 1.0 * normal;
381 m_b1 = -2.0 * m_b0;
382 m_b2 = 1.0 * m_b0;
383 m_a1 = 2.0 * (Ksquared - 1.0) * normal;
384 m_a2 = (1.0 - KoverQ + Ksquared) * normal;
385 break;
386 case biquad_type::BANDPASS:
387 m_b0 = KoverQ * normal;
388 m_b1 = 0.0;
389 m_b2 = -1.0 * m_b0;
390 m_a1 = 2.0 * (Ksquared - 1.0) * normal;
391 m_a2 = (1.0 - KoverQ + Ksquared) * normal;
392 break;
393 case biquad_type::NOTCH:
394 m_b0 = (1.0 + Ksquared) * normal;
395 m_b1 = 2.0 * (Ksquared - 1.0) * normal;
396 m_b2 = 1.0 * m_b0;
397 m_a1 = 1.0 * m_b1;
398 m_a2 = (1.0 - KoverQ + Ksquared) * normal;
399 break;
400 case biquad_type::PEAK:
401 if (DBGain >= 0.0)
402 {
403 m_b0 = (1.0 + (AMGain * KoverQ) + Ksquared) * normal;
404 m_b1 = 2.0 * (Ksquared - 1.0) * normal;
405 m_b2 = (1.0 - (AMGain * KoverQ) + Ksquared) * normal;
406 m_a1 = 1.0 * m_b1;
407 m_a2 = (1.0 - KoverQ + Ksquared) * normal;
408 }
409 else
410 {
411 normal = 1.0 / (1.0 + (AMGain * KoverQ) + Ksquared);
412 m_b0 = (1.0 + KoverQ + Ksquared) * normal;
413 m_b1 = 2.0 * (Ksquared - 1.0) * normal;
414 m_b2 = (1.0 - KoverQ + Ksquared) * normal;
415 m_a1 = 1.0 * m_b1;
416 m_a2 = (1.0 - (AMGain * KoverQ) + Ksquared) * normal;
417 }
418 break;
419 case biquad_type::LOWSHELF:
420 if (DBGain >= 0.0)
421 {
422 normal = 1.0 / (1.0 + M_SQRT2 * K + Ksquared);
423 m_b0 = (1.0 + sqrt(2.0 * AMGain) * K + AMGain * Ksquared) * normal;
424 m_b1 = 2.0 * (AMGain * Ksquared - 1.0) * normal;
425 m_b2 = (1.0 - sqrt(2.0 * AMGain) * K + AMGain * Ksquared) * normal;
426 m_a1 = 2.0 * (Ksquared - 1.0) * normal;
427 m_a2 = (1.0 - M_SQRT2 * K + Ksquared) * normal;
428 }
429 else
430 {
431 normal = 1.0 / (1.0 + sqrt(2.0 * AMGain) * K + AMGain * Ksquared);
432 m_b0 = (1.0 + M_SQRT2 * K + Ksquared) * normal;
433 m_b1 = 2.0 * (Ksquared - 1.0) * normal;
434 m_b2 = (1.0 - M_SQRT2 * K + Ksquared) * normal;
435 m_a1 = 2.0 * (AMGain * Ksquared - 1.0) * normal;
436 m_a2 = (1.0 - sqrt(2.0 * AMGain) * K + AMGain * Ksquared) * normal;
437 }
438 break;
439 case biquad_type::HIGHSHELF:
440 if (DBGain >= 0.0)
441 {
442 normal = 1.0 / (1.0 + M_SQRT2 * K + Ksquared);
443 m_b0 = (AMGain + sqrt(2.0 * AMGain) * K + Ksquared) * normal;
444 m_b1 = 2.0 * (Ksquared - AMGain) * normal;
445 m_b2 = (AMGain - sqrt(2.0 * AMGain) * K + Ksquared) * normal;
446 m_a1 = 2.0 * (Ksquared - 1.0) * normal;
447 m_a2 = (1.0 - M_SQRT2 * K + Ksquared) * normal;
448 }
449 else
450 {
451 normal = 1.0 / (AMGain + sqrt(2.0 * AMGain) * K + Ksquared);
452 m_b0 = (1.0 + M_SQRT2 * K + Ksquared) * normal;
453 m_b1 = 2.0 * (Ksquared - 1.0) * normal;
454 m_b2 = (1.0 - M_SQRT2 * K + Ksquared) * normal;
455 m_a1 = 2.0 * (Ksquared - AMGain) * normal;
456 m_a2 = (AMGain - sqrt(2.0 * AMGain) * K + Ksquared) * normal;
457 }
458 break;
459 default:
460 fatalerror("filter_biquad_device::recalc() - Invalid filter type!");
461 break;
462 }
463 #ifdef FLT_BIQUAD_DEBUG
464 logerror("Calculated Parameters:\n");
465 logerror( "Gain (dB): %f, (raw): %f\n", DBGain, MGain);
466 logerror( "k: %f\n", K);
467 logerror( "normal: %f\n", normal);
468 logerror("b0: %f\n", m_b0);
469 logerror("b1: %f\n", m_b1);
470 logerror("b2: %f\n", m_b2);
471 logerror("a1: %f\n", m_a1);
472 logerror("a2: %f\n", m_a2);
473 #endif
474 // peak and shelf filters do not use gain for the entire signal, only for the peak/shelf portions
475 // side note: the first order lowpass and highpass filter analogues technically don't have gain either,
476 // but this can be 'faked' by adjusting the bx factors, so we support that anyway, even if it isn't realistic.
477 if ( (m_type != biquad_type::PEAK)
478 && (m_type != biquad_type::LOWSHELF)
479 && (m_type != biquad_type::HIGHSHELF) )
480 {
481 m_b0 *= m_gain;
482 m_b1 *= m_gain;
483 m_b2 *= m_gain;
484 #ifdef FLT_BIQUAD_DEBUG
485 logerror("b0g: %f\n", m_b0);
486 logerror("b1g: %f\n", m_b1);
487 logerror("b2g: %f\n", m_b2);
488 #endif
489 }
490 }
491
492 /* Step the filter */
step()493 void filter_biquad_device::step()
494 {
495 m_w2 = m_w1;
496 m_w1 = m_w0;
497 m_w0 = (-m_a1 * m_w1) + (-m_a2 * m_w2) + m_input;
498 m_output = (m_b0 * m_w0) + (m_b1 * m_w1) + (m_b2 * m_w2);
499 }
500