Skip to content

Commit 5a072da

Browse files
committed
biquad.c: Add macro for Butterworth Q-factors.
1 parent 9047abc commit 5a072da

File tree

4 files changed

+105
-45
lines changed

4 files changed

+105
-45
lines changed

README.md

+20-6
Original file line numberDiff line numberDiff line change
@@ -186,15 +186,15 @@ Example:
186186
Second-order lowshelf filter.
187187
* `highshelf f0[k] width[q|s|d|o|h|k] gain`
188188
Second-order highshelf filter.
189-
* `lowpass_transform fz[k] qz fp[k] qp`
189+
* `lowpass_transform fz[k] width_z[q] fp[k] width_p[q]`
190190
Second-order lowpass transformation filter. Cancels the poles defined by
191-
`fz` and `qz` and replaces them with new poles defined by `fp` and `qp`.
192-
Gain is unity at DC.
193-
* `highpass_transform fz[k] qz fp[k] qp`
191+
`fz` and `width_z` and replaces them with new poles defined by `fp` and
192+
`width_p`. Gain is unity at DC.
193+
* `highpass_transform fz[k] width_z[q] fp[k] width_p[q]`
194194
Second-order highpass transformation filter. Also known as a Linkwitz
195195
transform (see http://www.linkwitzlab.com/filters.htm#9). Same as
196196
`lowpass_transform` except the gain is unity at Fs/2.
197-
* `linkwitz_transform fz[k] qz fp[k] qp`
197+
* `linkwitz_transform fz[k] width_z[q] fp[k] width_p[q]`
198198
Alias for `highpass_transform`.
199199
* `deemph`
200200
Compact Disc de-emphasis filter.
@@ -425,7 +425,9 @@ Example | Description
425425
**Note:** There is no difference between `1,3` and `3,1`. Order is not
426426
preserved.
427427

428-
#### Filter width suffixes
428+
#### Filter width
429+
430+
The following suffixes are supported:
429431

430432
Suffix | Description
431433
------ | ------------------------------
@@ -439,6 +441,18 @@ Suffix | Description
439441
**Note:** The `d` width suffix also changes the definition of `f0` from center
440442
frequency to corner frequency (like Room EQ Wizard and the Behringer DCX2496).
441443

444+
Additionally, a macro is provided for constructing arbitrary-order Butterworth
445+
filters from cascaded second-order sections: `bw<order>[.n]`, where `<order>` is
446+
the filter order and `n` is an index corresponding to a particular pair of
447+
poles. The Q-factors are always in ascending order. For example,
448+
449+
lowpass 1k bw6.0 lowpass 1k bw6.1 lowpass 1k bw6.2
450+
451+
creates a 6th-order Butterworth lowpass filter. Odd-order filters require an
452+
additional first-order section:
453+
454+
lowpass_1 1k lowpass 1k bw5.0 lowpass 1k bw5.1
455+
442456
#### File paths
443457

444458
On the command line, relative paths are relative to `$PWD`. Within an effects

biquad.c

+61-30
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,66 @@
2525

2626
static double parse_width(const char *s, int *type, char **endptr)
2727
{
28-
double w = strtod(s, endptr);
29-
if (*endptr != NULL && *endptr != s) {
30-
switch(**endptr) {
31-
case 'q':
32-
*type = BIQUAD_WIDTH_Q;
33-
++(*endptr);
34-
break;
35-
case 's':
36-
*type = BIQUAD_WIDTH_SLOPE;
37-
++(*endptr);
38-
break;
39-
case 'd':
40-
*type = BIQUAD_WIDTH_SLOPE_DB;
41-
++(*endptr);
42-
break;
43-
case 'o':
44-
*type = BIQUAD_WIDTH_BW_OCT;
45-
++(*endptr);
46-
break;
47-
case 'k':
48-
w *= 1000.0;
49-
case 'h':
50-
*type = BIQUAD_WIDTH_BW_HZ;
51-
++(*endptr);
52-
break;
28+
*type = BIQUAD_WIDTH_Q;
29+
double w = M_SQRT1_2;
30+
if (s[0] == 'b' && s[1] == 'w' && s[2] != '\0') {
31+
const char *s_ptr = s + 2;
32+
const int order = strtol(s_ptr, endptr, 10);
33+
if (*endptr == s_ptr || (**endptr != '\0' && **endptr != '.'))
34+
goto fail; /* failed to parse order */
35+
if (order < 2) {
36+
LOG_FMT(LL_ERROR, "%s(): filter order must be >= 2", __func__);
37+
goto fail;
38+
}
39+
const int n_biquads = order / 2;
40+
int p_idx = 0;
41+
if (**endptr == '.') {
42+
s_ptr = *endptr + 1;
43+
p_idx = strtol(s_ptr, endptr, 10);
44+
if (*endptr == s_ptr || **endptr != '\0')
45+
goto fail; /* failed to parse index */
46+
if (p_idx < 0 || p_idx >= n_biquads) {
47+
LOG_FMT(LL_ERROR, "%s(): filter index out of range", __func__);
48+
goto fail;
49+
}
50+
}
51+
p_idx = n_biquads - p_idx; /* index from outermost conjugate pair */
52+
w = 1.0/(2.0*sin(M_PI/order*(p_idx-0.5)));
53+
}
54+
else {
55+
w = strtod(s, endptr);
56+
if (*endptr != s) {
57+
switch(**endptr) {
58+
case 'q':
59+
*type = BIQUAD_WIDTH_Q;
60+
++(*endptr);
61+
break;
62+
case 's':
63+
*type = BIQUAD_WIDTH_SLOPE;
64+
++(*endptr);
65+
break;
66+
case 'd':
67+
*type = BIQUAD_WIDTH_SLOPE_DB;
68+
++(*endptr);
69+
break;
70+
case 'o':
71+
*type = BIQUAD_WIDTH_BW_OCT;
72+
++(*endptr);
73+
break;
74+
case 'k':
75+
w *= 1000.0;
76+
case 'h':
77+
*type = BIQUAD_WIDTH_BW_HZ;
78+
++(*endptr);
79+
break;
80+
}
81+
if (**endptr != '\0') LOG_FMT(LL_ERROR, "%s(): trailing characters: %s", __func__, *endptr);
5382
}
54-
if (**endptr != '\0') LOG_FMT(LL_ERROR, "%s(): trailing characters: %s", __func__, *endptr);
5583
}
5684
return w;
85+
fail:
86+
*endptr = (char *) s;
87+
return w;
5788
}
5889

5990
void biquad_init(struct biquad_state *state, double b0, double b1, double b2, double a0, double a1, double a2)
@@ -427,11 +458,11 @@ struct effect * biquad_effect_init(const struct effect_info *ei, const struct st
427458
case BIQUAD_HIGHPASS_TRANSFORM:
428459
INIT_COMMON(4, ei->effect_number);
429460
GET_FREQ_ARG(arg0, argv[1], "fz");
430-
GET_ARG(arg1, argv[2], "qz");
431-
CHECK_RANGE(arg1 > 0.0, "qz", return NULL);
461+
GET_WIDTH_ARG(arg1, argv[2], "width_z");
462+
CHECK_WIDTH_TYPE(width_type == BIQUAD_WIDTH_Q);
432463
GET_FREQ_ARG(arg2, argv[3], "fp");
433-
GET_ARG(arg3, argv[4], "qp");
434-
CHECK_RANGE(arg3 > 0.0, "qp", return NULL);
464+
GET_WIDTH_ARG(arg3, argv[4], "width_p");
465+
CHECK_WIDTH_TYPE(width_type == BIQUAD_WIDTH_Q);
435466
break;
436467
case BIQUAD_DEEMPH:
437468
INIT_COMMON(0, BIQUAD_HIGHSHELF);

dsp.1

+21-6
Original file line numberDiff line numberDiff line change
@@ -170,17 +170,17 @@ Second-order lowshelf filter.
170170
\fBhighshelf\fR \fIf0\fR[\fBk\fR] \fIwidth\fR[\fBq\fR|\fBs\fR|\fBd\fR|\fBo\fR|\fBh\fR|\fBk\fR] \fIgain\fR
171171
Second-order highshelf filter.
172172
.TP
173-
\fBlowpass_transform\fR \fIfz\fR[\fBk\fR] \fIqz\fR \fIfp\fR[\fBk\fR] \fIqp\fR
173+
\fBlowpass_transform\fR \fIfz\fR[\fBk\fR] \fIwidth_z\fR[\fBq\fR] \fIfp\fR[\fBk\fR] \fIwidth_p\fR[\fBq\fR]
174174
Second-order lowpass transformation filter. Cancels the poles defined by
175-
\fIfz\fR and \fIqz\fR and replaces them with new poles defined by \fIfp\fR and \fIqp\fR.
176-
Gain is unity at DC.
175+
\fIfz\fR and \fIwidth_z\fR and replaces them with new poles defined by \fIfp\fR and
176+
\fIwidth_p\fR. Gain is unity at DC.
177177
.TP
178-
\fBhighpass_transform\fR \fIfz\fR[\fBk\fR] \fIqz\fR \fIfp\fR[\fBk\fR] \fIqp\fR
178+
\fBhighpass_transform\fR \fIfz\fR[\fBk\fR] \fIwidth_z\fR[\fBq\fR] \fIfp\fR[\fBk\fR] \fIwidth_p\fR[\fBq\fR]
179179
Second-order highpass transformation filter. Also known as a Linkwitz
180180
transform (see http://www.linkwitzlab.com/filters.htm#9). Same as
181181
\fBlowpass_transform\fR except the gain is unity at Fs/2.
182182
.TP
183-
\fBlinkwitz_transform\fR \fIfz\fR[\fBk\fR] \fIqz\fR \fIfp\fR[\fBk\fR] \fIqp\fR
183+
\fBlinkwitz_transform\fR \fIfz\fR[\fBk\fR] \fIwidth_z\fR[\fBq\fR] \fIfp\fR[\fBk\fR] \fIwidth_p\fR[\fBq\fR]
184184
Alias for \fBhighpass_transform\fR.
185185
.TP
186186
\fBdeemph\fR
@@ -467,7 +467,9 @@ _
467467
.PP
468468
Note: There is no difference between `1,3' and `3,1'. Order is not
469469
preserved.
470-
.SS Filter width suffixes
470+
.SS Filter width
471+
The following suffixes are supported:
472+
471473
.TS
472474
tab (|);
473475
lB lB
@@ -484,6 +486,19 @@ k|Bandwidth in kHz.
484486
.PP
485487
Note: The `\fBd\fR' width suffix also changes the definition of \fIf0\fR from center
486488
frequency to corner frequency (like Room EQ Wizard and the Behringer DCX2496).
489+
.PP
490+
Additionally, a macro is provided for constructing arbitrary-order Butterworth
491+
filters from cascaded second-order sections: `bw<\fIorder\fR>[.\fIn\fR]', where `<\fIorder\fR>' is
492+
the filter order and `\fIn\fR' is an index corresponding to a particular pair of
493+
poles. The Q-factors are always in ascending order. For example,
494+
.EX
495+
lowpass 1k bw6.0 lowpass 1k bw6.1 lowpass 1k bw6.2
496+
.EE
497+
creates a 6th-order Butterworth lowpass filter. Odd-order filters require an
498+
additional first-order section:
499+
.EX
500+
lowpass_1 1k lowpass 1k bw5.0 lowpass 1k bw5.1
501+
.EE
487502
.SS File paths
488503
On the command line, relative paths are relative to `$PWD'. Within an effects
489504
file, relative paths are relative to the directory containing said effects

effect.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ static const struct effect_info effects[] = {
6262
{ "eq", "eq f0[k] width[q|o|h|k] gain", biquad_effect_init, BIQUAD_PEAK },
6363
{ "lowshelf", "lowshelf f0[k] width[q|s|d|o|h|k] gain", biquad_effect_init, BIQUAD_LOWSHELF },
6464
{ "highshelf", "highshelf f0[k] width[q|s|d|o|h|k] gain", biquad_effect_init, BIQUAD_HIGHSHELF },
65-
{ "lowpass_transform", "lowpass_transform fz[k] qz fp[k] qp", biquad_effect_init, BIQUAD_LOWPASS_TRANSFORM },
66-
{ "highpass_transform", "highpass_transform fz[k] qz fp[k] qp", biquad_effect_init, BIQUAD_HIGHPASS_TRANSFORM },
67-
{ "linkwitz_transform", "linkwitz_transform fz[k] qz fp[k] qp", biquad_effect_init, BIQUAD_HIGHPASS_TRANSFORM },
65+
{ "lowpass_transform", "lowpass_transform fz[k] width_z[q] fp[k] width_p[q]", biquad_effect_init, BIQUAD_LOWPASS_TRANSFORM },
66+
{ "highpass_transform", "highpass_transform fz[k] width_z[q] fp[k] width_p[q]", biquad_effect_init, BIQUAD_HIGHPASS_TRANSFORM },
67+
{ "linkwitz_transform", "linkwitz_transform fz[k] width_z[q] fp[k] width_p[q]", biquad_effect_init, BIQUAD_HIGHPASS_TRANSFORM },
6868
{ "deemph", "deemph", biquad_effect_init, BIQUAD_DEEMPH },
6969
{ "biquad", "biquad b0 b1 b2 a0 a1 a2", biquad_effect_init, BIQUAD_BIQUAD },
7070
{ "gain", "gain gain_dB", gain_effect_init, GAIN_EFFECT_NUMBER_GAIN },

0 commit comments

Comments
 (0)