| // 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); |
| } |