The Complete Matlab/Octave Source Code for Design of Assisted Alignments
function params = calc_vented_box_params_n(qt, ql, n, class)
%CALC_VENTED_BOX_PARAMS_N Compute h, alpha and filter params given QT, QL, n and class
angles = get_butterworth_pole_angles(n, class);
theta_a = angles.theta_a;
theta_b = angles.theta_b;
theta_i = angles.theta_i;
params = find_system_params(qt, ql, n, theta_a, theta_b, theta_i);
end
function angles = get_butterworth_pole_angles(n, class)
if n < 5
error('calc_vented_box_params_n is only for assisted alignments (n >= 5)');
end
is_odd = isodd(n);
% Find the number of second-quadrant complex poles
if is_odd
ang_count = (n - 1) / 2;
else
ang_count = n / 2;
end
% Make sure the class spec has the right number of indices
class_size = size(class, 2);
if class_size < (ang_count - 2)
error('class array has too few elements');
end
if class_size > (ang_count - 2)
error('class array has too many elements');
end
if n == 5
theta_a = pi / 5;
theta_b = 2 * pi / 5;
theta_i = [];
else
% Calculate the Butterworth pole angles
theta_all = zeros(1, ang_count);
for i = 1:ang_count
if i == 1
if is_odd
theta_all(1) = pi / n;
else
theta_all(1) = pi / (2 * n);
end
else
theta_all(i) = theta_all(1) + (i - 1) * pi / n;
end
end
% Sort the Butterworth pole angles in descending order
theta_all = sort(theta_all, 'descend');
% Partition the Butterworth pole angles between loudspeaker and filter transfer functions
spk_pole_count = 0;
filt_pole_count = 0;
for i = 1:ang_count
index = find(class == i);
if isempty(index)
% Not found - must be a speaker pole
spk_pole_count = spk_pole_count + 1;
theta_ab(spk_pole_count) = theta_all(i);
else
% Found - must be a filter pole
filt_pole_count = filt_pole_count + 1;
theta_i(filt_pole_count) = theta_all(i);
end
end
if size(theta_ab, 2) ~= 2 || size(theta_i, 2) ~= class_size
error('Error in class array: may be some out-of-range, invalid or repeated indices');
end
theta_ab = sort(theta_ab);
theta_a = theta_ab(1);
theta_b = theta_ab(2);
end
angles.theta_a = theta_a;
angles.theta_b = theta_b;
angles.theta_i = theta_i;
end
function params = find_system_params(qt, ql, n, theta_a, theta_b, theta_i)
a = 2 * (cos(theta_a) + cos(theta_b));
b = 2 * (cos(theta_a) * cos(theta_b)^2 + cos(theta_b) * cos(theta_a)^2);
c = 2 * (cos(theta_a) * sin(theta_b)^2 + cos(theta_b) * sin(theta_a)^2);
d = cos(theta_a)^2 + cos(theta_b)^2 + 4 * cos(theta_a) * cos(theta_b);
e = sin(theta_a)^2 + sin(theta_b)^2;
f = cos(theta_a)^2 * cos(theta_b)^2;
g = cos(theta_a)^2 * sin(theta_b)^2 + sin(theta_a)^2 * cos(theta_b)^2;
h = sin(theta_a)^2 * sin(theta_b)^2;
qtb = calc_qtb_n(theta_a, theta_b, ql);
% guess_for_k = 1.0;
if qt < qtb
guess_for_k = 1.5;
elseif qt > qtb
guess_for_k = 0.5;
else
guess_for_k = 1.0;
end
k = fzero(@(k)func_to_solve_k(k, qt, ql, a, b, c, d, e, f, g, h), guess_for_k, get_fzero_opts());
coeffs = calc_tf_coeffs_cheby_butter(k, a, b, c, d, e, f, g, h);
a1 = coeffs.a1;
a2 = coeffs.a2;
a3 = coeffs.a3;
w1 = coeffs.w1;
sqrt_h = calc_sqrt_h(qt, ql, a1, a3);
hval = sqrt_h^2;
alpha = calc_alpha(qt, ql, a2, hval);
params.h = hval;
params.alpha = alpha;
params.k = k;
if k < 1
% Chebyshev case
epsi = 1 / sinh(n * atanh(k));
params.dBripple = 10 * log10(1 + epsi^2);
mu = 1 / cosh(1 / n * asinh(1 / epsi));
if isodd(n)
w3LP = mu * cosh(1 / n * acosh(1 / epsi));
else
w3LP = mu * cosh(1 / n * acosh(sqrt(2 + 1 / epsi^2)));
end
params.f3_norm = w1 * sqrt_h / w3LP;
elseif k == 1
% Butterworth case
params.dBripple = 0;
params.f3_norm = 1;
else
% sub-Chebyshev case: no closed-form expression for f3
params.dBripple = 0;
params.f3_norm = find_f3_norm(a1, a2, a3, hval, n, k, w1, theta_i);
end
filt_params = find_filter_params(n, k, hval, w1, theta_i);
params.qf = filt_params.qf;
params.ff_norm = filt_params.ff_norm;
params.fp_norm = filt_params.fp_norm;
end
function options = get_fzero_opts()
% Forces the fzero() termination tolerance for
% Octave to be the same as for Matlab
options = optimset('fzero');
options.TolX = eps;
end
function qtb = calc_qtb_n(theta_a, theta_b, ql)
%calc_qtsb_n Compute the Butterworth QTS for the box Q given by QL
qtb = 1 / (2 * (cos(theta_a) + cos(theta_b)) - 1 / ql);
end
function to_zero = func_to_solve_k(k, qt, ql, a, b, c, d, e, f, g, h)
coeffs = calc_tf_coeffs_cheby_butter(k, a, b, c, d, e, f, g, h);
a1 = coeffs.a1;
a3 = coeffs.a3;
sqrth = calc_sqrt_h(qt, ql, a1, a3);
to_zero = sqrth^2 - a1 * ql * sqrth + ql / qt;
end
function coeffs = calc_tf_coeffs_cheby_butter(k, a, b, c, d, e, f, g, h)
w1 = (k^4 * f + k^2 * g + h)^0.25;
coeffs.a1 = (k^3 * b + k * c) / w1^3;
coeffs.a2 = (k^2 * d + e) / w1^2;
coeffs.a3 = k * a / w1;
coeffs.w1 = w1;
end
function sqrt_h = calc_sqrt_h(qt, ql, a1, a3)
sqrt_h = (a3 * ql - a1 * qt) / (ql / qt - qt / ql);
end
function alpha = calc_alpha(qt, ql, a2, hval)
alpha = hval * (ql * qt * a2 - 1) / (ql * qt) - 1 - hval^2;
end
function filt_params = find_filter_params(n, k, h, w1, theta_i)
if isodd(n)
filt_params.fp_norm = calc_fp_norm(k, h, w1);
else
filt_params.fp_norm = []; % No first-order filter
end
filt_count = size(theta_i, 2);
if filt_count > 0
filt_params.qf = zeros(1, filt_count);
filt_params.ff_norm = zeros(1, filt_count);
for i = 1:filt_count
filt_params.qf(i) = calc_qf(k, theta_i(i));
filt_params.ff_norm(i) = calc_ff_norm(k, h, w1, theta_i(i));
end
else
filt_params.qf = [];
filt_params.ff_norm = [];
end
end
function fp_norm = calc_fp_norm(k, h, w1)
fp_norm = sqrt(h) * w1 / k;
end
function qf = calc_qf(k, theta)
qf = 0.5 * sqrt(1 + (tan(theta) / k)^2);
end
function ff_norm = calc_ff_norm(k, h, w1, theta)
ff_norm = sqrt(h) * w1 / sqrt((k * cos(theta))^2 + sin(theta)^2);
end
function filt = G(s, a1, a2, a3, h)
% normalize the frequency
sn = s / sqrt(h);
filt = sn^4 / (sn^4 + a1 * sn^3 + a2 * sn^2 + a3 * sn + 1);
end
function filt = H(s, a1, a2, a3, h, n, k, w1, theta_i)
filt = G(s, a1, a2, a3, h);
if isodd(n)
wpn = calc_fp_norm(k, h, w1);
filt = filt * s / (s + wpn);
end
filt_count = size(theta_i, 2);
for i = 1:filt_count
qf = calc_qf(k, theta_i(i));
wf = calc_ff_norm(k, h, w1, theta_i(i));
filt = filt * hpf_second_order(s, qf, wf);
end
end
function filt = hpf_second_order(s, qf, wf)
sn = s / wf;
filt = sn^2 / (sn^2 + 1/qf * sn + 1);
end
function resp = mag_squared_of_H(w, a1, a2, a3, h, n, k, w1, theta_i)
cplx_H = H(j * w, a1, a2, a3, h, n, k, w1, theta_i);
resp = real(cplx_H)^2 + imag(cplx_H)^2;
end
function to_zero = cutoff_freq_of_H(w, a1, a2, a3, h, n, k, w1, theta_i)
to_zero = mag_squared_of_H(w, a1, a2, a3, h, n, k, w1, theta_i) - 0.5;
end
function w = find_f3_norm(a1, a2, a3, h, n, k, w1, theta_i)
guess_for_w = sqrt(h);
w = fzero(@(w)cutoff_freq_of_H(w, a1, a2, a3, h, n, k, w1, theta_i), guess_for_w, get_fzero_opts());
end