diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/webaudio/resources/biquad-filters.js | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/webaudio/resources/biquad-filters.js')
-rw-r--r-- | testing/web-platform/tests/webaudio/resources/biquad-filters.js | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/resources/biquad-filters.js b/testing/web-platform/tests/webaudio/resources/biquad-filters.js new file mode 100644 index 0000000000..467436326a --- /dev/null +++ b/testing/web-platform/tests/webaudio/resources/biquad-filters.js @@ -0,0 +1,376 @@ +// A biquad filter has a z-transform of +// H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2) +// +// The formulas for the various filters were taken from +// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt. + + +// Lowpass filter. +function createLowpassFilter(freq, q, gain) { + let b0; + let b1; + let b2; + let a0; + let a1; + let a2; + + if (freq == 1) { + // The formula below works, except for roundoff. When freq = 1, + // the filter is just a wire, so hardwire the coefficients. + b0 = 1; + b1 = 0; + b2 = 0; + a0 = 1; + a1 = 0; + a2 = 0; + } else { + let theta = Math.PI * freq; + let alpha = Math.sin(theta) / (2 * Math.pow(10, q / 20)); + let cosw = Math.cos(theta); + let beta = (1 - cosw) / 2; + + b0 = beta; + b1 = 2 * beta; + b2 = beta; + a0 = 1 + alpha; + a1 = -2 * cosw; + a2 = 1 - alpha; + } + + return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); +} + +function createHighpassFilter(freq, q, gain) { + let b0; + let b1; + let b2; + let a0; + let a1; + let a2; + + if (freq == 1) { + // The filter is 0 + b0 = 0; + b1 = 0; + b2 = 0; + a0 = 1; + a1 = 0; + a2 = 0; + } else if (freq == 0) { + // The filter is 1. Computation of coefficients below is ok, but + // there's a pole at 1 and a zero at 1, so round-off could make + // the filter unstable. + b0 = 1; + b1 = 0; + b2 = 0; + a0 = 1; + a1 = 0; + a2 = 0; + } else { + let theta = Math.PI * freq; + let alpha = Math.sin(theta) / (2 * Math.pow(10, q / 20)); + let cosw = Math.cos(theta); + let beta = (1 + cosw) / 2; + + b0 = beta; + b1 = -2 * beta; + b2 = beta; + a0 = 1 + alpha; + a1 = -2 * cosw; + a2 = 1 - alpha; + } + + return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); +} + +function normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2) { + let scale = 1 / a0; + + return { + b0: b0 * scale, + b1: b1 * scale, + b2: b2 * scale, + a1: a1 * scale, + a2: a2 * scale + }; +} + +function createBandpassFilter(freq, q, gain) { + let b0; + let b1; + let b2; + let a0; + let a1; + let a2; + let coef; + + if (freq > 0 && freq < 1) { + let w0 = Math.PI * freq; + if (q > 0) { + let alpha = Math.sin(w0) / (2 * q); + let k = Math.cos(w0); + + b0 = alpha; + b1 = 0; + b2 = -alpha; + a0 = 1 + alpha; + a1 = -2 * k; + a2 = 1 - alpha; + + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // q = 0, and frequency is not 0 or 1. The above formula has a + // divide by zero problem. The limit of the z-transform as q + // approaches 0 is 1, so set the filter that way. + coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0}; + } + } else { + // When freq = 0 or 1, the z-transform is identically 0, + // independent of q. + coef = { b0: 0, b1: 0, b2: 0, a1: 0, a2: 0 } + } + + return coef; +} + +function createLowShelfFilter(freq, q, gain) { + // q not used + let b0; + let b1; + let b2; + let a0; + let a1; + let a2; + let coef; + + let S = 1; + let A = Math.pow(10, gain / 40); + + if (freq == 1) { + // The filter is just a constant gain + coef = {b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0}; + } else if (freq == 0) { + // The filter is 1 + coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0}; + } else { + let w0 = Math.PI * freq; + let alpha = 1 / 2 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2); + let k = Math.cos(w0); + let k2 = 2 * Math.sqrt(A) * alpha; + let Ap1 = A + 1; + let Am1 = A - 1; + + b0 = A * (Ap1 - Am1 * k + k2); + b1 = 2 * A * (Am1 - Ap1 * k); + b2 = A * (Ap1 - Am1 * k - k2); + a0 = Ap1 + Am1 * k + k2; + a1 = -2 * (Am1 + Ap1 * k); + a2 = Ap1 + Am1 * k - k2; + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } + + return coef; +} + +function createHighShelfFilter(freq, q, gain) { + // q not used + let b0; + let b1; + let b2; + let a0; + let a1; + let a2; + let coef; + + let A = Math.pow(10, gain / 40); + + if (freq == 1) { + // When freq = 1, the z-transform is 1 + coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0}; + } else if (freq > 0) { + let w0 = Math.PI * freq; + let S = 1; + let alpha = 0.5 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2); + let k = Math.cos(w0); + let k2 = 2 * Math.sqrt(A) * alpha; + let Ap1 = A + 1; + let Am1 = A - 1; + + b0 = A * (Ap1 + Am1 * k + k2); + b1 = -2 * A * (Am1 + Ap1 * k); + b2 = A * (Ap1 + Am1 * k - k2); + a0 = Ap1 - Am1 * k + k2; + a1 = 2 * (Am1 - Ap1 * k); + a2 = Ap1 - Am1 * k - k2; + + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // When freq = 0, the filter is just a gain + coef = {b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0}; + } + + return coef; +} + +function createPeakingFilter(freq, q, gain) { + let b0; + let b1; + let b2; + let a0; + let a1; + let a2; + let coef; + + let A = Math.pow(10, gain / 40); + + if (freq > 0 && freq < 1) { + if (q > 0) { + let w0 = Math.PI * freq; + let alpha = Math.sin(w0) / (2 * q); + let k = Math.cos(w0); + + b0 = 1 + alpha * A; + b1 = -2 * k; + b2 = 1 - alpha * A; + a0 = 1 + alpha / A; + a1 = -2 * k; + a2 = 1 - alpha / A; + + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // q = 0, we have a divide by zero problem in the formulas + // above. But if we look at the z-transform, we see that the + // limit as q approaches 0 is A^2. + coef = {b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0}; + } + } else { + // freq = 0 or 1, the z-transform is 1 + coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0}; + } + + return coef; +} + +function createNotchFilter(freq, q, gain) { + let b0; + let b1; + let b2; + let a0; + let a1; + let a2; + let coef; + + if (freq > 0 && freq < 1) { + if (q > 0) { + let w0 = Math.PI * freq; + let alpha = Math.sin(w0) / (2 * q); + let k = Math.cos(w0); + + b0 = 1; + b1 = -2 * k; + b2 = 1; + a0 = 1 + alpha; + a1 = -2 * k; + a2 = 1 - alpha; + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // When q = 0, we get a divide by zero above. The limit of the + // z-transform as q approaches 0 is 0, so set the coefficients + // appropriately. + coef = {b0: 0, b1: 0, b2: 0, a1: 0, a2: 0}; + } + } else { + // When freq = 0 or 1, the z-transform is 1 + coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0}; + } + + return coef; +} + +function createAllpassFilter(freq, q, gain) { + let b0; + let b1; + let b2; + let a0; + let a1; + let a2; + let coef; + + if (freq > 0 && freq < 1) { + if (q > 0) { + let w0 = Math.PI * freq; + let alpha = Math.sin(w0) / (2 * q); + let k = Math.cos(w0); + + b0 = 1 - alpha; + b1 = -2 * k; + b2 = 1 + alpha; + a0 = 1 + alpha; + a1 = -2 * k; + a2 = 1 - alpha; + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // q = 0 + coef = {b0: -1, b1: 0, b2: 0, a1: 0, a2: 0}; + } + } else { + coef = {b0: 1, b1: 0, b2: 0, a1: 0, a2: 0}; + } + + return coef; +} + +function filterData(filterCoef, signal, len) { + let y = new Array(len); + let b0 = filterCoef.b0; + let b1 = filterCoef.b1; + let b2 = filterCoef.b2; + let a1 = filterCoef.a1; + let a2 = filterCoef.a2; + + // Prime the pump. (Assumes the signal has length >= 2!) + y[0] = b0 * signal[0]; + y[1] = b0 * signal[1] + b1 * signal[0] - a1 * y[0]; + + // Filter all of the signal that we have. + for (let k = 2; k < Math.min(signal.length, len); ++k) { + y[k] = b0 * signal[k] + b1 * signal[k - 1] + b2 * signal[k - 2] - + a1 * y[k - 1] - a2 * y[k - 2]; + } + + // If we need to filter more, but don't have any signal left, + // assume the signal is zero. + for (let k = signal.length; k < len; ++k) { + y[k] = -a1 * y[k - 1] - a2 * y[k - 2]; + } + + return y; +} + +// Map the filter type name to a function that computes the filter coefficents +// for the given filter type. +let filterCreatorFunction = { + 'lowpass': createLowpassFilter, + 'highpass': createHighpassFilter, + 'bandpass': createBandpassFilter, + 'lowshelf': createLowShelfFilter, + 'highshelf': createHighShelfFilter, + 'peaking': createPeakingFilter, + 'notch': createNotchFilter, + 'allpass': createAllpassFilter +}; + +let filterTypeName = { + 'lowpass': 'Lowpass filter', + 'highpass': 'Highpass filter', + 'bandpass': 'Bandpass filter', + 'lowshelf': 'Lowshelf filter', + 'highshelf': 'Highshelf filter', + 'peaking': 'Peaking filter', + 'notch': 'Notch filter', + 'allpass': 'Allpass filter' +}; + +function createFilter(filterType, freq, q, gain) { + return filterCreatorFunction[filterType](freq, q, gain); +} |