Skip to content

Detection of lowpass BiquadFilter implementation

Hongchan Choi edited this page May 2, 2016 · 5 revisions

In WebAudio Issue 771 it was discovered that the current BiquadFilterNode could not represent some valid lowpass and highpass filters. This issue was resolved by changing the formulas for the lowpass and highpass filters. Of course, this changes the output of the filters.

If you need to detect which filter formula is being used, here is a bit of code to tell you. This works by creating two lowpass filters with different negative Q values. In the original formulation, negative Q values are silently clamped to 0. So, if you have the original formulas, the output of the two filters are exactly the same. This code tests for that. The new formulas don't have this restriction on Q, so the output of the two filters are different. (It would be a bug if they were the same.)

// Check the Q implementation and return a promise.
function checkBiquadFilterQ () {
  'use strict';
  var context = new OfflineAudioContext(1, 128, 48000);
  var osc = context.createOscillator();
  var filter1 = context.createBiquadFilter();
  var filter2 = context.createBiquadFilter();
  var inverter = context.createGain();
  osc.type = 'sawtooth';
  osc.frequency.value = 8 * 440;
  inverter.gain.value = -1;
  // each filter should get a different Q value
  filter1.Q.value = -1; 
  filter2.Q.value = -20;
  osc.connect(filter1);
  osc.connect(filter2);
  filter1.connect(context.destination);
  filter2.connect(inverter);
  inverter.connect(context.destination);
  osc.start();
  
  return context.startRendering().then(function (buffer) {
    return Math.max(...buffer.getChannelData(0)) !== 0;
  });
}

The below is an example of this feature detection code. To detect multiple features using OfflineAudioContext, consider using Promise.all().

checkBiquadFilterQ().then(function (flag) {
  if (flag)
    console.log('biquad has new Q.');
  else
    console.log('biquad has wrong Q.');
});

If you've determined that you're using the new formulas but want to preserve exactly the filter response, use the following function to convert the old Q value to an equivalent Q value for the new filter formula:

// Convert the old Q parameter to the Qnew parameter for the new filter formulation.
function convertQToQnew(QdB) {
  var Qlin = Math.pow(10, QdB / 20);
  var Qnew = 1 / Math.sqrt((4 - Math.sqrt(16 - 16 / (Qlin * Qlin))) / 2);
  Qnew = 20 * Math.log10(Qnew)
  return Qnew;
}