Skip to content

Commit 889d9dc

Browse files
committed
cap5.c: Add elliptic filter design functions.
1 parent 30a6f31 commit 889d9dc

7 files changed

+191
-27
lines changed

README.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,18 @@ Example:
248248
filter bank. Has no effect with `matrix4`. Requires the `fir` effect.
249249
* `surround_delay=delay[s|m|S]`
250250
Surround output delay. Default is zero.
251-
* `filter_type=filter[:stop_dB]` (`matrix4_mb` only)
251+
* `filter_type=filter[:stop_dB[:stop_dB]]` (`matrix4_mb` only)
252252
Type of filter used for low pass sections of the filter bank. `filter`
253253
may be `butterworth`, `chebyshev1`, `chebyshev2` (default), or
254-
`elliptic`. The optional `stop_dB` parameter sets the stopband
255-
attenuation in decibels for Chebyshev filters. The default is 25.
254+
`elliptic`.
255+
256+
The optional `stop_dB` parameter(s) set the stopband attenuation in
257+
decibels for the Chebyshev and elliptic filters. Only the first
258+
parameter is used for `chebyshev1` and `chebyshev2`. For `elliptic`,
259+
the first parameter applies to the lowpass and the second to the
260+
highpass. If only one parameter is given, it applies to both stopbands.
261+
Default values are 25 for `chebyshev1` and `chebyshev2`, and 35:50 for
262+
`elliptic`.
256263

257264
* `matrix4_mb [options] [surround_level]`
258265
Like the `matrix4` effect, but divides the input into ten individually

cap5.c

+138-10
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717
*/
1818

19-
#include <complex.h>
19+
#include <float.h>
2020
#include <math.h>
2121
#include "cap5.h"
2222

@@ -35,7 +35,7 @@ void cap5_reset(struct cap5_state *state)
3535
void cap5_butterworth_ap(double complex ap[3])
3636
{
3737
for (int i = 0; i < 3; ++i) {
38-
const double theta = (2*i+1)*M_PI/10.0;
38+
const double theta = (2*i+1)*M_PI/(2.0*5);
3939
ap[i] = -sin(theta) + cos(theta)*I; /* normalized pole in s-plane */
4040
}
4141
}
@@ -47,21 +47,149 @@ void cap5_chebyshev_ap(int gen_type2, double stop_dB, double complex ap[3])
4747
return;
4848
}
4949
const double epsilon = sqrt(pow(10.0, stop_dB/10.0) - 1.0);
50-
const double sigma = asinh(epsilon)/5.0;
50+
const double sigma = asinh(epsilon)/5;
51+
const double scale = cosh(acosh(epsilon)/5);
5152
for (int i = 0; i < 3; ++i) {
52-
const double theta = (2*i+1)*M_PI/10.0;
53+
const double theta = (2*i+1)*M_PI/(2.0*5);
5354
ap[i] = -sinh(sigma)*sin(theta) + cosh(sigma)*cos(theta)*I; /* normalized pole in s-plane */
54-
ap[i] = ap[i] / cosh(acosh(epsilon)/5.0); /* scale so H(1) = sqrt(0.5) */
55+
ap[i] = ap[i] / scale; /* scale so H(1) = sqrt(0.5) */
5556
if (gen_type2) ap[i] = 1.0/ap[i];
5657
}
5758
}
5859

59-
void cap5_elliptic_ap(double complex ap[3])
60+
static inline int fz_sgn(double x)
6061
{
61-
/* 35dB stopband attenuation for low pass; 45dB for high pass */
62-
ap[0] = -0.185287191997037 + 0.990129317340409*I;
63-
ap[1] = -0.686015538373767 + 0.810354587786414*I;
64-
ap[2] = -1.118174003343493;
62+
if (x < 0.0) return -1;
63+
else if (x > 0.0) return 1;
64+
return 0;
65+
}
66+
67+
#define FIND_ZERO_MAX_ITER 100
68+
static double find_zero(double (*fn)(double, const void *), const void *arg, double a, double b, double tol)
69+
{
70+
double c = a, fn_a = fn(a, arg), fn_b = fn(b, arg);
71+
if (tol < DBL_EPSILON) tol = DBL_EPSILON*2;
72+
for (int i = 0, side = 0; i < FIND_ZERO_MAX_ITER; ++i) {
73+
c = (fn_a*b - fn_b*a) / (fn_a - fn_b);
74+
if (fabs(b-a) < tol*fabs(b+a)) return c;
75+
const double fn_c = fn(c, arg);
76+
if (fz_sgn(fn_b) == fz_sgn(fn_c)) {
77+
b = c; fn_b = fn_c;
78+
if (side == -1) fn_a /= 2.0;
79+
side = -1;
80+
}
81+
else if (fz_sgn(fn_a) == fz_sgn(fn_c)) {
82+
a = c; fn_a = fn_c;
83+
if (side == 1) fn_b /= 2.0;
84+
side = 1;
85+
}
86+
else {
87+
if (i == 0) return -NAN; /* no zero within interval [a, b] */
88+
return c;
89+
}
90+
}
91+
return -NAN; /* failed to converge */
92+
}
93+
94+
static double ellip_q_err(double k, const void *arg)
95+
{
96+
const double target_q = *((double *) arg);
97+
//LOG_FMT(LL_VERBOSE, "%s(): k=%.15e; target_q=%.15e", __func__, k, target_q);
98+
const double kp = sqrt(sqrt(1.0-k*k));
99+
const double l = (1.0-kp)/((1.0+kp)*2.0);
100+
return (l + 2.0*pow(l, 5) + 15.0*pow(l, 9) + 150.0*pow(l, 13)) - target_q;
101+
}
102+
103+
/* Evaluate allpass given by poles ap at jw (=j*w) */
104+
static double complex eval_allpass_ap(const double complex *ap, int n, double complex jw)
105+
{
106+
const int has_real = (cimag(ap[n-1]) == 0); /* real root is always last */
107+
double complex num = (has_real) ? jw + ap[n-1] : 1.0;
108+
double complex den = (has_real) ? jw - ap[n-1] : 1.0;
109+
for (int i = 0, np = (has_real)?n-1:n; i < np; ++i) {
110+
num *= (jw + ap[i]) * (jw + conj(ap[i])); /* conjugates not stored */
111+
den *= (jw - ap[i]) * (jw - conj(ap[i]));
112+
}
113+
return num / den;
114+
}
115+
116+
/* Dot product treating a and b as vectors */
117+
static inline double geom_dot(double complex a, double complex b)
118+
{
119+
return creal(a)*creal(b) + cimag(a)*cimag(b);
120+
}
121+
122+
struct ellip_wc_err_arg {
123+
const double complex *ap0, *ap1;
124+
int n0, n1;
125+
};
126+
127+
static double ellip_wc_err(double w, const void *arg)
128+
{
129+
const double complex jw = w*I;
130+
struct ellip_wc_err_arg *a = (struct ellip_wc_err_arg *) arg;
131+
//LOG_FMT(LL_VERBOSE, "%s(): w=%.15e", __func__, w);
132+
return geom_dot(eval_allpass_ap(a->ap0, a->n0, jw), eval_allpass_ap(a->ap1, a->n1, jw));
133+
}
134+
135+
void cap5_elliptic_ap(double stop_dB_lp, double stop_dB_hp, double complex ap[3])
136+
{
137+
if (stop_dB_lp > 100.0) {
138+
cap5_chebyshev_ap(0, stop_dB_hp, ap);
139+
return;
140+
}
141+
else if (stop_dB_hp > 100.0) {
142+
cap5_chebyshev_ap(1, stop_dB_lp, ap);
143+
return;
144+
}
145+
146+
const double e2 = 1.0 / (pow(10.0, stop_dB_hp/10.0) - 1.0);
147+
const double D = (pow(10.0, stop_dB_lp/10.0) - 1.0) / e2;
148+
const double q = 1.0 / (exp2(4.0/5) * pow(D, 1.0/5));
149+
const double k = find_zero(ellip_q_err, &q, 0.0, 1.0, 0);
150+
if (!isnormal(k)) goto fail_fz;
151+
152+
const double L = log((sqrt(1.0+e2)+1.0) / (sqrt(1.0+e2)-1.0)) / (2.0*5);
153+
double sigma0_s0 = sinh(L), sigma0_s1 = 0.0;
154+
for (int m = 1; m < 6; ++m) {
155+
const int sgn = (m&1)?-1:1;
156+
sigma0_s0 += sgn * pow(q, m*(m+1)) * sinh((2*m+1)*L);
157+
sigma0_s1 += sgn * pow(q, m*m) * cosh(2*m*L);
158+
}
159+
const double sigma0 = fabs((2.0*sqrt(sqrt(q))*sigma0_s0) / (1.0+2.0*sigma0_s1));
160+
const double sigma02 = sigma0*sigma0;
161+
162+
const double W = sqrt((1.0+k*sigma02) * (1.0+sigma02/k));
163+
for (int i = 0; i < 2; ++i) {
164+
const double mu = 2.0-i;
165+
double omega_s0 = sin(M_PI*mu/5), omega_s1 = 0.0;
166+
for (int m = 1; m < 6; ++m) {
167+
const int sgn = (m&1)?-1:1;
168+
omega_s0 += sgn * pow(q, m*(m+1)) * sin((2*m+1)*M_PI*mu/5);
169+
omega_s1 += sgn * pow(q, m*m) * cos(2*m*M_PI*mu/5);
170+
}
171+
const double omega = (2.0*sqrt(sqrt(q))*omega_s0) / (1.0+2.0*omega_s1);
172+
const double omega2 = omega*omega;
173+
const double Vi = sqrt((1.0-k*omega2)*(1.0-omega2/k));
174+
ap[i] = (-2.0*sigma0*Vi + 2.0*omega*W*I) / (2.0*(1.0+sigma02*omega2)); /* complex poles (conjugates not stored) */
175+
}
176+
ap[2] = -sigma0; /* real pole */
177+
178+
if (fabs(stop_dB_lp - stop_dB_hp) > 0.01) {
179+
/* scale so that magnitude at w=1 is sqrt(0.5) */
180+
const double complex ap0[1] = { ap[1] };
181+
const double complex ap1[2] = { ap[0], ap[2] };
182+
const struct ellip_wc_err_arg err_arg = { .ap0 = ap0, .ap1 = ap1, .n0 = 1, .n1 = 2 };
183+
const double half_width = sqrt(1.0/k);
184+
const double wc = find_zero(ellip_wc_err, &err_arg, 1.0/half_width, half_width, 0);
185+
if (!isnormal(wc)) goto fail_fz;
186+
for (int i = 0; i < 3; ++i) ap[i] /= wc;
187+
}
188+
return;
189+
190+
fail_fz:
191+
LOG_FMT(LL_ERROR, "%s(): BUG: failed to converge; falling back to butterworth", __func__);
192+
cap5_butterworth_ap(ap);
65193
}
66194

67195
void cap5_init(struct cap5_state *state, double fs, double fc, const double complex ap[3])

cap5.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ void ap3_reset(struct ap3_state *);
3737
void cap5_reset(struct cap5_state *);
3838
void cap5_butterworth_ap(double complex [3]);
3939
void cap5_chebyshev_ap(int, double, double complex [3]);
40-
void cap5_elliptic_ap(double complex [3]);
40+
void cap5_elliptic_ap(double, double, double complex [3]);
4141
void cap5_init(struct cap5_state *, double, double, const double complex [3]);
4242

4343
static inline sample_t ap3_run(struct ap3_state *state, sample_t s)

dsp.1

+10-3
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,18 @@ filter bank. Has no effect with \fBmatrix4\fR. Requires the \fBfir\fR effect.
245245
surround_delay=\fIdelay\fR[\fBs\fR|\fBm\fR|\fBS\fR]
246246
Surround output delay. Default is zero.
247247
.TP
248-
filter_type=\fIfilter\fR[:\fIstop_dB\fR] (\fBmatrix4_mb\fR only)
248+
filter_type=\fIfilter\fR[:\fIstop_dB\fR[:\fIstop_dB\fR]] (\fBmatrix4_mb\fR only)
249249
Type of filter used for low pass sections of the filter bank. \fIfilter\fR
250250
may be \fIbutterworth\fR, \fIchebyshev1\fR, \fIchebyshev2\fR (default), or
251-
\fIelliptic\fR. The optional \fIstop_dB\fR parameter sets the stopband
252-
attenuation in decibels for Chebyshev filters. The default is 25.
251+
\fIelliptic\fR.
252+
.sp 0.5
253+
The optional \fIstop_dB\fR parameter(s) set the stopband attenuation in
254+
decibels for the Chebyshev and elliptic filters. Only the first
255+
parameter is used for \fIchebyshev1\fR and \fIchebyshev2\fR. For \fIelliptic\fR,
256+
the first parameter applies to the lowpass and the second to the
257+
highpass. If only one parameter is given, it applies to both stopbands.
258+
Default values are 25 for \fIchebyshev1\fR and \fIchebyshev2\fR, and 35:50 for
259+
\fIelliptic\fR.
253260
.RE
254261
.TP
255262
\fBmatrix4_mb\fR [\fIoptions\fR] [\fIsurround_level\fR]

matrix4_common.c

+27-5
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,18 @@ static void set_fb_stop_default(struct matrix4_config *config)
8181
{
8282
switch (config->fb_type) {
8383
case FILTER_BANK_TYPE_BUTTERWORTH:
84-
config->fb_stop = 0.0; break; /* not used */
84+
config->fb_stop[0] = 0.0;
85+
config->fb_stop[1] = 0.0;
86+
break;
8587
case FILTER_BANK_TYPE_CHEBYSHEV1:
8688
case FILTER_BANK_TYPE_CHEBYSHEV2:
87-
config->fb_stop = 25.0; break;
89+
config->fb_stop[0] = 25.0;
90+
config->fb_stop[1] = 0.0;
91+
break;
8892
case FILTER_BANK_TYPE_ELLIPTIC:
89-
config->fb_stop = 0.0; break; /* not used */
93+
config->fb_stop[0] = 35.0;
94+
config->fb_stop[1] = 50.0;
95+
break;
9096
}
9197
}
9298

@@ -130,15 +136,31 @@ int parse_effect_opts(const char *const *argv, const struct stream_info *istream
130136
}
131137
set_fb_stop_default(config);
132138
if (*opt_subarg != '\0') {
139+
char *opt_subarg1 = isolate(opt_subarg, ':');
133140
switch (config->fb_type) {
134141
case FILTER_BANK_TYPE_CHEBYSHEV1:
135142
case FILTER_BANK_TYPE_CHEBYSHEV2:
136-
config->fb_stop = strtod(opt_subarg, &endptr);
143+
config->fb_stop[0] = strtod(opt_subarg, &endptr);
137144
CHECK_ENDPTR(opt_arg, endptr, "stop_dB", goto fail);
138-
if (config->fb_stop < 10.0) {
145+
if (config->fb_stop[0] < 10.0) {
139146
LOG_FMT(LL_ERROR, "%s: error: %s: stopband attenuation must be at least 10dB", argv[0], opt_arg);
140147
goto fail;
141148
}
149+
if (*opt_subarg1 != '\0')
150+
LOG_FMT(LL_ERROR, "%s: warning: %s: ignoring argument: %s", argv[0], opt_arg, opt_subarg1);
151+
break;
152+
case FILTER_BANK_TYPE_ELLIPTIC:
153+
config->fb_stop[0] = strtod(opt_subarg, &endptr);
154+
CHECK_ENDPTR(opt_arg, endptr, "stop_dB", goto fail);
155+
if (*opt_subarg1 != '\0') {
156+
config->fb_stop[1] = strtod(opt_subarg1, &endptr);
157+
CHECK_ENDPTR(opt_arg, endptr, "stop_dB", goto fail);
158+
}
159+
else config->fb_stop[1] = config->fb_stop[0];
160+
if (config->fb_stop[0] < 20.0 || config->fb_stop[1] < 20.0) {
161+
LOG_FMT(LL_ERROR, "%s: error: %s: stopband attenuation must be at least 20dB", argv[0], opt_arg);
162+
goto fail;
163+
}
142164
break;
143165
default:
144166
LOG_FMT(LL_ERROR, "%s: warning: %s: ignoring argument: %s", argv[0], opt_arg, opt_subarg);

matrix4_common.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ enum filter_bank_type {
132132

133133
struct matrix4_config {
134134
int n_channels, opt_str_idx, c0, c1;
135-
double surr_mult, fb_stop;
135+
double surr_mult, fb_stop[2];
136136
ssize_t surr_delay_frames;
137137
char show_status, do_dir_boost, enable_signal, do_phase_lin;
138138
enum filter_bank_type fb_type;

matrix4_mb.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,21 @@ struct matrix4_mb_state {
9999
ssize_t len, p, drain_frames, fade_frames, fade_p;
100100
};
101101

102-
static void filter_bank_init(struct filter_bank *fb, double fs, enum filter_bank_type fb_type, double fb_stop)
102+
static void filter_bank_init(struct filter_bank *fb, double fs, enum filter_bank_type fb_type, double fb_stop[2])
103103
{
104104
double complex ap[3];
105105
switch (fb_type) {
106106
case FILTER_BANK_TYPE_BUTTERWORTH:
107107
cap5_butterworth_ap(ap);
108108
break;
109109
case FILTER_BANK_TYPE_CHEBYSHEV1:
110-
cap5_chebyshev_ap(0, fb_stop, ap);
110+
cap5_chebyshev_ap(0, fb_stop[0], ap);
111111
break;
112112
case FILTER_BANK_TYPE_CHEBYSHEV2:
113-
cap5_chebyshev_ap(1, fb_stop, ap);
113+
cap5_chebyshev_ap(1, fb_stop[0], ap);
114114
break;
115115
case FILTER_BANK_TYPE_ELLIPTIC:
116-
cap5_elliptic_ap(ap);
116+
cap5_elliptic_ap(fb_stop[0], fb_stop[1], ap);
117117
break;
118118
}
119119
for (int i = 0; i < LENGTH(fb_freqs); ++i)

0 commit comments

Comments
 (0)