chromium/third_party/blink/web_tests/webaudio/resources/biquad-filters.js

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