Index: third_party/WebKit/LayoutTests/webaudio/IIRFilter/iirfilter.html |
diff --git a/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iirfilter.html b/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iirfilter.html |
index bee1559964bcad5d729947c688c1d7fcd927321a..4c6d573b14cbd1e8c788aeaa32772cb5dcfe0de3 100644 |
--- a/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iirfilter.html |
+++ b/third_party/WebKit/LayoutTests/webaudio/IIRFilter/iirfilter.html |
@@ -1,75 +1,75 @@ |
-<!doctype html> |
+<!DOCTYPE html> |
<html> |
<head> |
- <title>Test Basic IIRFilterNode Operation</title> |
+ <title> |
+ Test Basic IIRFilterNode Operation |
+ </title> |
<script src="../../resources/testharness.js"></script> |
- <script src="../../resources/testharnessreport.js"></script> |
+ <script src="../../resources/testharnessreport.js"></script> |
<script src="../resources/audit-util.js"></script> |
<script src="../resources/audit.js"></script> |
<script src="../resources/biquad-filters.js"></script> |
</head> |
- |
<body> |
- <script> |
- var sampleRate = 24000; |
- var testDurationSec = 0.25; |
- var testFrames = testDurationSec * sampleRate; |
+ <script id="layout-test-code"> |
+ let sampleRate = 24000; |
+ let testDurationSec = 0.25; |
+ let testFrames = testDurationSec * sampleRate; |
- var audit = Audit.createTaskRunner(); |
+ let audit = Audit.createTaskRunner(); |
- audit.define("coefficient-normalization", (task, should) => { |
- // Test that the feedback coefficients are normalized. Do this be creating two |
- // IIRFilterNodes. One has normalized coefficients, and one doesn't. Compute the |
- // difference and make sure they're the same. |
- var context = new OfflineAudioContext(2, testFrames, sampleRate); |
+ audit.define('coefficient-normalization', (task, should) => { |
+ // Test that the feedback coefficients are normalized. Do this be |
+ // creating two IIRFilterNodes. One has normalized coefficients, and |
+ // one doesn't. Compute the difference and make sure they're the same. |
+ let context = new OfflineAudioContext(2, testFrames, sampleRate); |
// Use a simple impulse as the source. |
- var buffer = context.createBuffer(1, 1, sampleRate); |
+ let buffer = context.createBuffer(1, 1, sampleRate); |
buffer.getChannelData(0)[0] = 1; |
- var source = context.createBufferSource(); |
+ let source = context.createBufferSource(); |
source.buffer = buffer; |
// Gain node for computing the difference between the filters. |
- var gain = context.createGain(); |
+ let gain = context.createGain(); |
gain.gain.value = -1; |
// The IIR filters. Use a common feedforward array. |
- var ff = [1]; |
+ let ff = [1]; |
- var fb1 = [1, .9]; |
+ let fb1 = [1, .9]; |
- var fb2 = new Float64Array(2); |
+ let fb2 = new Float64Array(2); |
// Scale the feedback coefficients by an arbitrary factor. |
- var coefScaleFactor = 2; |
- for (var k = 0; k < fb2.length; ++k) { |
+ let coefScaleFactor = 2; |
+ for (let k = 0; k < fb2.length; ++k) { |
fb2[k] = coefScaleFactor * fb1[k]; |
} |
- var iir1; |
- var iir2; |
+ let iir1; |
+ let iir2; |
- should(function () { |
- iir1 = context.createIIRFilter(ff, fb1); |
- }, |
- "createIIRFilter with normalized coefficients") |
- .notThrow(); |
+ should(function() { |
+ iir1 = context.createIIRFilter(ff, fb1); |
+ }, 'createIIRFilter with normalized coefficients').notThrow(); |
- should(function () { |
- iir2 = context.createIIRFilter(ff, fb2); |
- }, |
- "createIIRFilter with unnormalized coefficients").notThrow(); |
+ should(function() { |
+ iir2 = context.createIIRFilter(ff, fb2); |
+ }, 'createIIRFilter with unnormalized coefficients').notThrow(); |
- // Create the graph. The output of iir1 (normalized coefficients) is channel 0, and the |
- // output of iir2 (unnormalized coefficients), with appropriate scaling, is channel 1. |
- var merger = context.createChannelMerger(2); |
+ // Create the graph. The output of iir1 (normalized coefficients) is |
+ // channel 0, and the output of iir2 (unnormalized coefficients), with |
+ // appropriate scaling, is channel 1. |
+ let merger = context.createChannelMerger(2); |
source.connect(iir1); |
source.connect(iir2); |
iir1.connect(merger, 0, 0); |
iir2.connect(gain); |
- // The gain for the gain node should be set to compensate for the scaling of the |
- // coefficients. Since iir2 has scaled the coefficients by coefScaleFactor, the output is |
- // reduced by the same factor, so adjust the gain to scale the output of iir2 back up. |
+ // The gain for the gain node should be set to compensate for the |
+ // scaling of the coefficients. Since iir2 has scaled the coefficients |
+ // by coefScaleFactor, the output is reduced by the same factor, so |
+ // adjust the gain to scale the output of iir2 back up. |
gain.gain.value = coefScaleFactor; |
gain.connect(merger, 0, 1); |
@@ -79,35 +79,37 @@ |
// Rock and roll! |
- context.startRendering().then(function (result) { |
- // Find the max amplitude of the result, which should be near zero. |
- var iir1Data = result.getChannelData(0); |
- var iir2Data = result.getChannelData(1); |
- |
- // Threshold isn't exactly zero because the arithmetic is done differently between the |
- // IIRFilterNode and the BiquadFilterNode. |
- should(iir2Data, |
- "Output of IIR filter with unnormalized coefficients") |
- .beCloseToArray(iir1Data, { |
- absoluteThreshold: 2.1958e-38 |
- }); |
- }).then(() => task.done()); |
+ context.startRendering() |
+ .then(function(result) { |
+ // Find the max amplitude of the result, which should be near |
+ // zero. |
+ let iir1Data = result.getChannelData(0); |
+ let iir2Data = result.getChannelData(1); |
+ |
+ // Threshold isn't exactly zero because the arithmetic is done |
+ // differently between the IIRFilterNode and the BiquadFilterNode. |
+ should( |
+ iir2Data, |
+ 'Output of IIR filter with unnormalized coefficients') |
+ .beCloseToArray(iir1Data, {absoluteThreshold: 2.1958e-38}); |
+ }) |
+ .then(() => task.done()); |
}); |
- audit.define("one-zero", (task, should) => { |
+ audit.define('one-zero', (task, should) => { |
// Create a simple 1-zero filter and compare with the expected output. |
- var context = new OfflineAudioContext(1, testFrames, sampleRate); |
+ let context = new OfflineAudioContext(1, testFrames, sampleRate); |
// Use a simple impulse as the source |
- var buffer = context.createBuffer(1, 1, sampleRate); |
+ let buffer = context.createBuffer(1, 1, sampleRate); |
buffer.getChannelData(0)[0] = 1; |
- var source = context.createBufferSource(); |
+ let source = context.createBufferSource(); |
source.buffer = buffer; |
- // The filter is y(n) = 0.5*(x(n) + x(n-1)), a simple 2-point moving average. This is |
- // rather arbitrary; keep it simple. |
+ // The filter is y(n) = 0.5*(x(n) + x(n-1)), a simple 2-point moving |
+ // average. This is rather arbitrary; keep it simple. |
- var iir = context.createIIRFilter([0.5, 0.5], [1]); |
+ let iir = context.createIIRFilter([0.5, 0.5], [1]); |
// Create the graph |
source.connect(iir); |
@@ -116,37 +118,39 @@ |
// Rock and roll! |
source.start(); |
- context.startRendering().then(function (result) { |
- var actual = result.getChannelData(0); |
- var expected = new Float64Array(testFrames); |
- // The filter is a simple 2-point moving average of an impulse, so the first two values |
- // are non-zero and the rest are zero. |
- expected[0] = 0.5; |
- expected[1] = 0.5; |
- should(actual, 'IIR 1-zero output') |
- .beCloseToArray(expected, { |
- absoluteThreshold: 0 |
- }); |
- }).then(() => task.done()); |
+ context.startRendering() |
+ .then(function(result) { |
+ let actual = result.getChannelData(0); |
+ let expected = new Float64Array(testFrames); |
+ // The filter is a simple 2-point moving average of an impulse, so |
+ // the first two values are non-zero and the rest are zero. |
+ expected[0] = 0.5; |
+ expected[1] = 0.5; |
+ should(actual, 'IIR 1-zero output').beCloseToArray(expected, { |
+ absoluteThreshold: 0 |
+ }); |
+ }) |
+ .then(() => task.done()); |
}); |
- audit.define("one-pole", (task, should) => { |
+ audit.define('one-pole', (task, should) => { |
// Create a simple 1-pole filter and compare with the expected output. |
- // The filter is y(n) + c*y(n-1)= x(n). The analytical response is (-c)^n, so choose a |
- // suitable number of frames to run the test for where the output isn't flushed to zero. |
- var c = 0.9; |
- var eps = 1e-20; |
- var duration = Math.floor(Math.log(eps) / Math.log(Math.abs(c))); |
- var context = new OfflineAudioContext(1, duration, sampleRate); |
+ // The filter is y(n) + c*y(n-1)= x(n). The analytical response is |
+ // (-c)^n, so choose a suitable number of frames to run the test for |
+ // where the output isn't flushed to zero. |
+ let c = 0.9; |
+ let eps = 1e-20; |
+ let duration = Math.floor(Math.log(eps) / Math.log(Math.abs(c))); |
+ let context = new OfflineAudioContext(1, duration, sampleRate); |
// Use a simple impulse as the source |
- var buffer = context.createBuffer(1, 1, sampleRate); |
+ let buffer = context.createBuffer(1, 1, sampleRate); |
buffer.getChannelData(0)[0] = 1; |
- var source = context.createBufferSource(); |
+ let source = context.createBufferSource(); |
source.buffer = buffer; |
- var iir = context.createIIRFilter([1], [1, c]); |
+ let iir = context.createIIRFilter([1], [1, c]); |
// Create the graph |
source.connect(iir); |
@@ -155,57 +159,60 @@ |
// Rock and roll! |
source.start(); |
- context.startRendering().then(function (result) { |
- var actual = result.getChannelData(0); |
- var expected = new Float64Array(actual.length); |
- |
- // The filter is a simple 1-pole filter: y(n) = -c*y(n-k)+x(n), with an impulse as the |
- // input. |
- expected[0] = 1; |
- for (k = 1; k < testFrames; ++k) { |
- expected[k] = -c * expected[k-1]; |
- } |
- |
- // Threshold isn't exactly zero due to round-off in the single-precision IIRFilterNode |
- // computations versus the double-precision Javascript computations. |
- should(actual, 'IIR 1-pole output') |
- .beCloseToArray(expected, { |
- absoluteThreshold: 2.7657e-8 |
- }); |
- }).then(() => task.done()); |
+ context.startRendering() |
+ .then(function(result) { |
+ let actual = result.getChannelData(0); |
+ let expected = new Float64Array(actual.length); |
+ |
+ // The filter is a simple 1-pole filter: y(n) = -c*y(n-k)+x(n), |
+ // with an impulse as the input. |
+ expected[0] = 1; |
+ for (k = 1; k < testFrames; ++k) { |
+ expected[k] = -c * expected[k - 1]; |
+ } |
+ |
+ // Threshold isn't exactly zero due to round-off in the |
+ // single-precision IIRFilterNode computations versus the |
+ // double-precision Javascript computations. |
+ should(actual, 'IIR 1-pole output').beCloseToArray(expected, { |
+ absoluteThreshold: 2.7657e-8 |
+ }); |
+ }) |
+ .then(() => task.done()); |
}); |
- // Return a function suitable for use as a defineTask function. This function creates an |
- // IIRFilterNode equivalent to the specified BiquadFilterNode and compares the outputs. The |
- // outputs from the two filters should be virtually identical. |
- function testWithBiquadFilter (filterType, errorThreshold, snrThreshold) { |
+ // Return a function suitable for use as a defineTask function. This |
+ // function creates an IIRFilterNode equivalent to the specified |
+ // BiquadFilterNode and compares the outputs. The outputs from the two |
+ // filters should be virtually identical. |
+ function testWithBiquadFilter(filterType, errorThreshold, snrThreshold) { |
return (task, should) => { |
- var context = new OfflineAudioContext(2, testFrames, sampleRate); |
+ let context = new OfflineAudioContext(2, testFrames, sampleRate); |
// Use a constant (step function) as the source |
- var buffer = createConstantBuffer(context, testFrames, 1); |
- var source = context.createBufferSource(); |
+ let buffer = createConstantBuffer(context, testFrames, 1); |
+ let source = context.createBufferSource(); |
source.buffer = buffer; |
- |
- // Create the biquad. Choose some rather arbitrary values for Q and gain for the biquad |
- // so that the shelf filters aren't identical. |
- var biquad = context.createBiquadFilter(); |
+ |
+ // Create the biquad. Choose some rather arbitrary values for Q and |
+ // gain for the biquad so that the shelf filters aren't identical. |
+ let biquad = context.createBiquadFilter(); |
biquad.type = filterType; |
biquad.Q.value = 10; |
biquad.gain.value = 10; |
- // Create the equivalent IIR Filter node by computing the coefficients of the given biquad |
- // filter type. |
- var nyquist = sampleRate / 2; |
- var coef = createFilter(filterType, |
- biquad.frequency.value / nyquist, |
- biquad.Q.value, |
- biquad.gain.value); |
+ // Create the equivalent IIR Filter node by computing the coefficients |
+ // of the given biquad filter type. |
+ let nyquist = sampleRate / 2; |
+ let coef = createFilter( |
+ filterType, biquad.frequency.value / nyquist, biquad.Q.value, |
+ biquad.gain.value); |
- var iir = context.createIIRFilter([coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]); |
+ let iir = context.createIIRFilter( |
+ [coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]); |
- var merger = context.createChannelMerger(2); |
+ let merger = context.createChannelMerger(2); |
// Create the graph |
source.connect(biquad); |
source.connect(iir); |
@@ -218,125 +225,118 @@ |
// Rock and roll! |
source.start(); |
- context.startRendering().then(function (result) { |
- // Find the max amplitude of the result, which should be near zero. |
- var expected = result.getChannelData(0); |
- var actual = result.getChannelData(1); |
- |
- // On MacOSX, WebAudio uses an optimized Biquad implementation that is different from |
- // the implementation used for Linux and Windows. This will cause the output to differ, |
- // even if the threshold passes. Thus, only print out a very small number of elements |
- // of the array where we have tested that they are consistent. |
- should(actual, "IIRFilter for Biquad " + filterType) |
- .beCloseToArray(expected, errorThreshold); |
- |
- var snr = 10*Math.log10(computeSNR(actual, expected)); |
- should(snr, |
- "SNR for IIRFIlter for Biquad " + filterType) |
- .beGreaterThanOrEqualTo(snrThreshold); |
- }).then(() => task.done()); |
+ context.startRendering() |
+ .then(function(result) { |
+ // Find the max amplitude of the result, which should be near |
+ // zero. |
+ let expected = result.getChannelData(0); |
+ let actual = result.getChannelData(1); |
+ |
+ // On MacOSX, WebAudio uses an optimized Biquad implementation |
+ // that is different from the implementation used for Linux and |
+ // Windows. This will cause the output to differ, even if the |
+ // threshold passes. Thus, only print out a very small number |
+ // of elements of the array where we have tested that they are |
+ // consistent. |
+ should(actual, 'IIRFilter for Biquad ' + filterType) |
+ .beCloseToArray(expected, errorThreshold); |
+ |
+ let snr = 10 * Math.log10(computeSNR(actual, expected)); |
+ should(snr, 'SNR for IIRFIlter for Biquad ' + filterType) |
+ .beGreaterThanOrEqualTo(snrThreshold); |
+ }) |
+ .then(() => task.done()); |
}; |
} |
// Thresholds here are experimentally determined. |
- var biquadTestConfigs = [{ |
- filterType: "lowpass", |
- snrThreshold: 91.221, |
- errorThreshold: { |
- relativeThreshold: 4.9834e-5 |
- } |
- }, { |
- filterType: "highpass", |
- snrThreshold: 105.4590, |
- errorThreshold: { |
- absoluteThreshold: 2.9e-6, |
- relativeThreshold: 3e-5 |
- } |
- }, { |
- filterType: "bandpass", |
- snrThreshold: 104.060, |
- errorThreshold: { |
- absoluteThreshold: 2e-7, |
- relativeThreshold: 8.7e-4 |
- } |
- }, { |
- filterType: "notch", |
- snrThreshold: 91.312, |
- errorThreshold: { |
- absoluteThreshold: 0, |
- relativeThreshold: 4.22e-5 |
- } |
- }, { |
- filterType: "allpass", |
- snrThreshold: 91.319, |
- errorThreshold: { |
- absoluteThreshold: 0, |
- relativeThreshold: 4.31e-5 |
- } |
- }, { |
- filterType: "lowshelf", |
- snrThreshold: 90.609, |
- errorThreshold: { |
- absoluteThreshold: 0, |
- relativeThreshold: 2.98e-5 |
- } |
- }, { |
- filterType: "highshelf", |
- snrThreshold: 103.159, |
- errorThreshold: { |
- absoluteThreshold: 0, |
- relativeThreshold: 1.24e-5 |
+ let biquadTestConfigs = [ |
+ { |
+ filterType: 'lowpass', |
+ snrThreshold: 91.221, |
+ errorThreshold: {relativeThreshold: 4.9834e-5} |
+ }, |
+ { |
+ filterType: 'highpass', |
+ snrThreshold: 105.4590, |
+ errorThreshold: {absoluteThreshold: 2.9e-6, relativeThreshold: 3e-5} |
+ }, |
+ { |
+ filterType: 'bandpass', |
+ snrThreshold: 104.060, |
+ errorThreshold: {absoluteThreshold: 2e-7, relativeThreshold: 8.7e-4} |
+ }, |
+ { |
+ filterType: 'notch', |
+ snrThreshold: 91.312, |
+ errorThreshold: {absoluteThreshold: 0, relativeThreshold: 4.22e-5} |
+ }, |
+ { |
+ filterType: 'allpass', |
+ snrThreshold: 91.319, |
+ errorThreshold: {absoluteThreshold: 0, relativeThreshold: 4.31e-5} |
+ }, |
+ { |
+ filterType: 'lowshelf', |
+ snrThreshold: 90.609, |
+ errorThreshold: {absoluteThreshold: 0, relativeThreshold: 2.98e-5} |
+ }, |
+ { |
+ filterType: 'highshelf', |
+ snrThreshold: 103.159, |
+ errorThreshold: {absoluteThreshold: 0, relativeThreshold: 1.24e-5} |
+ }, |
+ { |
+ filterType: 'peaking', |
+ snrThreshold: 91.504, |
+ errorThreshold: {absoluteThreshold: 0, relativeThreshold: 5.05e-5} |
} |
- }, { |
- filterType: "peaking", |
- snrThreshold: 91.504, |
- errorThreshold: { |
- absoluteThreshold: 0, |
- relativeThreshold: 5.05e-5 |
- } |
- }]; |
+ ]; |
// Create a set of tasks based on biquadTestConfigs. |
for (k = 0; k < biquadTestConfigs.length; ++k) { |
- var config = biquadTestConfigs[k]; |
- var name = k + ": " + config.filterType; |
- audit.define(name, testWithBiquadFilter(config.filterType, config.errorThreshold, |
- config.snrThreshold)); |
+ let config = biquadTestConfigs[k]; |
+ let name = k + ': ' + config.filterType; |
+ audit.define( |
+ name, |
+ testWithBiquadFilter( |
+ config.filterType, config.errorThreshold, config.snrThreshold)); |
} |
- audit.define("multi-channel", (task, should) => { |
- // Multi-channel test. Create a biquad filter and the equivalent IIR filter. Filter the |
- // same multichannel signal and compare the results. |
- var nChannels = 3; |
- var context = new OfflineAudioContext(nChannels, testFrames, sampleRate); |
+ audit.define('multi-channel', (task, should) => { |
+ // Multi-channel test. Create a biquad filter and the equivalent IIR |
+ // filter. Filter the same multichannel signal and compare the results. |
+ let nChannels = 3; |
+ let context = |
+ new OfflineAudioContext(nChannels, testFrames, sampleRate); |
// Create a set of oscillators as the multi-channel source. |
- var source = []; |
+ let source = []; |
for (k = 0; k < nChannels; ++k) { |
source[k] = context.createOscillator(); |
- source[k].type = "sawtooth"; |
- // The frequency of the oscillator is pretty arbitrary, but each oscillator should have a |
- // different frequency. |
+ source[k].type = 'sawtooth'; |
+ // The frequency of the oscillator is pretty arbitrary, but each |
+ // oscillator should have a different frequency. |
source[k].frequency.value = 100 + k * 100; |
} |
- var merger = context.createChannelMerger(3); |
+ let merger = context.createChannelMerger(3); |
- var biquad = context.createBiquadFilter(); |
+ let biquad = context.createBiquadFilter(); |
// Create the equivalent IIR Filter node. |
- var nyquist = sampleRate / 2; |
- var coef = createFilter(biquad.type, |
- biquad.frequency.value / nyquist, |
- biquad.Q.value, |
- biquad.gain.value); |
- var fb = [1, coef.a1, coef.a2]; |
- var ff = [coef.b0, coef.b1, coef.b2]; |
- |
- var iir = context.createIIRFilter(ff, fb); |
- // Gain node to compute the difference between the IIR and biquad filter. |
- var gain = context.createGain(); |
+ let nyquist = sampleRate / 2; |
+ let coef = createFilter( |
+ biquad.type, biquad.frequency.value / nyquist, biquad.Q.value, |
+ biquad.gain.value); |
+ let fb = [1, coef.a1, coef.a2]; |
+ let ff = [coef.b0, coef.b1, coef.b2]; |
+ |
+ let iir = context.createIIRFilter(ff, fb); |
+ // Gain node to compute the difference between the IIR and biquad |
+ // filter. |
+ let gain = context.createGain(); |
gain.gain.value = -1; |
// Create the graph. |
@@ -352,23 +352,29 @@ |
for (k = 0; k < nChannels; ++k) |
source[k].start(); |
- context.startRendering().then(function (result) { |
- var errorThresholds = [3.7671e-5, 3.0071e-5, 2.6241e-5]; |
- |
- // Check the difference signal on each channel |
- for (channel = 0; channel < result.numberOfChannels; ++channel) { |
- // Find the max amplitude of the result, which should be near zero. |
- var data = result.getChannelData(channel); |
- var maxError = data.reduce(function(reducedValue, currentValue) { |
- return Math.max(reducedValue, Math.abs(currentValue)); |
- }); |
- |
- should(maxError, |
- "Max difference between IIR and Biquad on channel " + channel) |
- .beLessThanOrEqualTo(errorThresholds[channel]); |
- } |
- |
- }).then(() => task.done()); |
+ context.startRendering() |
+ .then(function(result) { |
+ let errorThresholds = [3.7671e-5, 3.0071e-5, 2.6241e-5]; |
+ |
+ // Check the difference signal on each channel |
+ for (channel = 0; channel < result.numberOfChannels; ++channel) { |
+ // Find the max amplitude of the result, which should be near |
+ // zero. |
+ let data = result.getChannelData(channel); |
+ let maxError = |
+ data.reduce(function(reducedValue, currentValue) { |
+ return Math.max(reducedValue, Math.abs(currentValue)); |
+ }); |
+ |
+ should( |
+ maxError, |
+ 'Max difference between IIR and Biquad on channel ' + |
+ channel) |
+ .beLessThanOrEqualTo(errorThresholds[channel]); |
+ } |
+ |
+ }) |
+ .then(() => task.done()); |
}); |
// Apply an IIRFilter to the given input signal. |
@@ -378,27 +384,27 @@ |
// y[n] = sum(ff[k]*x[n-k], k, 0, M) - sum(fb[k]*y[n-k], k, 1, N) |
// |
function iirFilter(input, feedforward, feedback) { |
- // For simplicity, create an x buffer that contains the input, and a y buffer that contains |
- // the output. Both of these buffers have an initial work space to implement the initial |
- // memory of the filter. |
- var workSize = Math.max(feedforward.length, feedback.length); |
- var x = new Float32Array(input.length + workSize); |
+ // For simplicity, create an x buffer that contains the input, and a y |
+ // buffer that contains the output. Both of these buffers have an |
+ // initial work space to implement the initial memory of the filter. |
+ let workSize = Math.max(feedforward.length, feedback.length); |
+ let x = new Float32Array(input.length + workSize); |
- // Float64 because we want to match the implementation that uses doubles to minimize |
- // roundoff. |
- var y = new Float64Array(input.length + workSize); |
+ // Float64 because we want to match the implementation that uses doubles |
+ // to minimize roundoff. |
+ let y = new Float64Array(input.length + workSize); |
// Copy the input over. |
- for (var k = 0; k < input.length; ++k) |
+ for (let k = 0; k < input.length; ++k) |
x[k + feedforward.length] = input[k]; |
// Run the filter |
- for (var n = 0; n < input.length; ++n) { |
- var index = n + workSize; |
- var yn = 0; |
- for (var k = 0; k < feedforward.length; ++k) |
+ for (let n = 0; n < input.length; ++n) { |
+ let index = n + workSize; |
+ let yn = 0; |
+ for (let k = 0; k < feedforward.length; ++k) |
yn += feedforward[k] * x[index - k]; |
- for (var k = 0; k < feedback.length; ++k) |
+ for (let k = 0; k < feedback.length; ++k) |
yn -= feedback[k] * y[index - k]; |
y[index] = yn; |
@@ -414,113 +420,116 @@ |
// f1 = (b10 + b11/z + b12/z^2)/(1 + a11/z + a12/z^2); |
// f2 = (b20 + b21/z + b22/z^2)/(1 + a21/z + a22/z^2); |
// |
- // To cascade them, multiply the two transforms together to get a fourth order IIR filter. |
+ // To cascade them, multiply the two transforms together to get a fourth |
+ // order IIR filter. |
- var numProduct = [f1Coef.b0 * f2Coef.b0, |
- f1Coef.b0 * f2Coef.b1 + f1Coef.b1 * f2Coef.b0, |
+ let numProduct = [ |
+ f1Coef.b0 * f2Coef.b0, f1Coef.b0 * f2Coef.b1 + f1Coef.b1 * f2Coef.b0, |
f1Coef.b0 * f2Coef.b2 + f1Coef.b1 * f2Coef.b1 + f1Coef.b2 * f2Coef.b0, |
- f1Coef.b1 * f2Coef.b2 + f1Coef.b2 * f2Coef.b1, |
- f1Coef.b2 * f2Coef.b2 |
+ f1Coef.b1 * f2Coef.b2 + f1Coef.b2 * f2Coef.b1, f1Coef.b2 * f2Coef.b2 |
]; |
- var denProduct = [1, |
- f2Coef.a1 + f1Coef.a1, |
+ let denProduct = [ |
+ 1, f2Coef.a1 + f1Coef.a1, |
f2Coef.a2 + f1Coef.a1 * f2Coef.a1 + f1Coef.a2, |
- f1Coef.a1 * f2Coef.a2 + f1Coef.a2 * f2Coef.a1, |
- f1Coef.a2 * f2Coef.a2 |
+ f1Coef.a1 * f2Coef.a2 + f1Coef.a2 * f2Coef.a1, f1Coef.a2 * f2Coef.a2 |
]; |
return { |
- ff: numProduct, |
- fb: denProduct |
+ ff: numProduct, fb: denProduct |
} |
} |
- // Find the magnitude of the root of the quadratic that has the maximum magnitude. |
+ // Find the magnitude of the root of the quadratic that has the maximum |
+ // magnitude. |
// |
- // The quadratic is z^2 + a1 * z + a2 and we want the root z that has the largest magnitude. |
+ // The quadratic is z^2 + a1 * z + a2 and we want the root z that has the |
+ // largest magnitude. |
function largestRootMagnitude(a1, a2) { |
- var discriminant = a1 * a1 - 4 * a2; |
+ let discriminant = a1 * a1 - 4 * a2; |
if (discriminant < 0) { |
- // Complex roots: -a1/2 +/- i*sqrt(-d)/2. Thus the magnitude of each root is the same |
- // and is sqrt(a1^2/4 + |d|/4) |
- var d = Math.sqrt(-discriminant); |
+ // Complex roots: -a1/2 +/- i*sqrt(-d)/2. Thus the magnitude of each |
+ // root is the same and is sqrt(a1^2/4 + |d|/4) |
+ let d = Math.sqrt(-discriminant); |
return Math.hypot(a1 / 2, d / 2); |
} else { |
// Real roots |
- var d = Math.sqrt(discriminant); |
+ let d = Math.sqrt(discriminant); |
return Math.max(Math.abs((-a1 + d) / 2), Math.abs((-a1 - d) / 2)); |
} |
} |
- audit.define("4th-order-iir", (task, should) => { |
- // Cascade 2 lowpass biquad filters and compare that with the equivalent 4th order IIR |
- // filter. |
+ audit.define('4th-order-iir', (task, should) => { |
+ // Cascade 2 lowpass biquad filters and compare that with the equivalent |
+ // 4th order IIR filter. |
- var nyquist = sampleRate / 2; |
+ let nyquist = sampleRate / 2; |
// Compute the coefficients of a lowpass filter. |
- // First some preliminary stuff. Compute the coefficients of the biquad. This is used to |
- // figure out how frames to use in the test. |
- var biquadType = "lowpass"; |
- var biquadCutoff = 350; |
- var biquadQ = 5; |
- var biquadGain = 1; |
+ // First some preliminary stuff. Compute the coefficients of the |
+ // biquad. This is used to figure out how frames to use in the test. |
+ let biquadType = 'lowpass'; |
+ let biquadCutoff = 350; |
+ let biquadQ = 5; |
+ let biquadGain = 1; |
- var coef = createFilter(biquadType, |
- biquadCutoff / nyquist, |
- biquadQ, |
- biquadGain); |
+ let coef = createFilter( |
+ biquadType, biquadCutoff / nyquist, biquadQ, biquadGain); |
// Cascade the biquads together to create an equivalent IIR filter. |
- var cascade = cascadeBiquads(coef, coef); |
- |
- // Since we're cascading two identical biquads, the root of denominator of the IIR filter is |
- // repeated, so the root of the denominator with the largest magnitude occurs twice. The |
- // impulse response of the IIR filter will be roughly c*(r*r)^n at time n, where r is the |
- // root of largest magnitude. This approximation gets better as n increases. We can use |
- // this to get a rough idea of when the response has died down to a small value. |
- |
- // This is the value we will use to determine how many frames to render. Rendering too many |
- // is a waste of time and also makes it hard to compare the actual result to the expected |
- // because the magnitudes are so small that they could be mostly round-off noise. |
+ let cascade = cascadeBiquads(coef, coef); |
+ |
+ // Since we're cascading two identical biquads, the root of denominator |
+ // of the IIR filter is repeated, so the root of the denominator with |
+ // the largest magnitude occurs twice. The impulse response of the IIR |
+ // filter will be roughly c*(r*r)^n at time n, where r is the root of |
+ // largest magnitude. This approximation gets better as n increases. |
+ // We can use this to get a rough idea of when the response has died |
+ // down to a small value. |
+ |
+ // This is the value we will use to determine how many frames to render. |
+ // Rendering too many is a waste of time and also makes it hard to |
+ // compare the actual result to the expected because the magnitudes are |
+ // so small that they could be mostly round-off noise. |
// |
// Find magnitude of the root with largest magnitude |
- var rootMagnitude = largestRootMagnitude(coef.a1, coef.a2); |
+ let rootMagnitude = largestRootMagnitude(coef.a1, coef.a2); |
- // Find n such that |r|^(2*n) <= eps. That is, n = log(eps)/(2*log(r)). Somewhat |
- // arbitrarily choose eps = 1e-20; |
- var eps = 1e-20; |
- var framesForTest = Math.floor(Math.log(eps) / (2 * Math.log(rootMagnitude))); |
+ // Find n such that |r|^(2*n) <= eps. That is, n = log(eps)/(2*log(r)). |
+ // Somewhat arbitrarily choose eps = 1e-20; |
+ let eps = 1e-20; |
+ let framesForTest = |
+ Math.floor(Math.log(eps) / (2 * Math.log(rootMagnitude))); |
- // We're ready to create the graph for the test. The offline context has two channels: |
- // channel 0 is the expected (cascaded biquad) result and channel 1 is the actual IIR filter |
- // result. |
- var context = new OfflineAudioContext(2, framesForTest, sampleRate); |
+ // We're ready to create the graph for the test. The offline context |
+ // has two channels: channel 0 is the expected (cascaded biquad) result |
+ // and channel 1 is the actual IIR filter result. |
+ let context = new OfflineAudioContext(2, framesForTest, sampleRate); |
// Use a simple impulse with a large (arbitrary) amplitude as the source |
- var amplitude = 1; |
- var buffer = context.createBuffer(1, testFrames, sampleRate); |
+ let amplitude = 1; |
+ let buffer = context.createBuffer(1, testFrames, sampleRate); |
buffer.getChannelData(0)[0] = amplitude; |
- var source = context.createBufferSource(); |
+ let source = context.createBufferSource(); |
source.buffer = buffer; |
- // Create the two biquad filters. Doesn't really matter what, but for simplicity we choose |
- // identical lowpass filters with the same parameters. |
- var biquad1 = context.createBiquadFilter(); |
+ // Create the two biquad filters. Doesn't really matter what, but for |
+ // simplicity we choose identical lowpass filters with the same |
+ // parameters. |
+ let biquad1 = context.createBiquadFilter(); |
biquad1.type = biquadType; |
biquad1.frequency.value = biquadCutoff; |
biquad1.Q.value = biquadQ; |
- var biquad2 = context.createBiquadFilter(); |
+ let biquad2 = context.createBiquadFilter(); |
biquad2.type = biquadType; |
biquad2.frequency.value = biquadCutoff; |
biquad2.Q.value = biquadQ; |
- var iir = context.createIIRFilter(cascade.ff, cascade.fb); |
+ let iir = context.createIIRFilter(cascade.ff, cascade.fb); |
// Create the merger to get the signals into multiple channels |
- var merger = context.createChannelMerger(2); |
+ let merger = context.createChannelMerger(2); |
// Create the graph, filtering the source through two biquads. |
source.connect(biquad1); |
@@ -533,26 +542,28 @@ |
merger.connect(context.destination); |
// Now filter the source through the IIR filter. |
- var y = iirFilter(buffer.getChannelData(0), cascade.ff, cascade.fb); |
+ let y = iirFilter(buffer.getChannelData(0), cascade.ff, cascade.fb); |
// Rock and roll! |
source.start(); |
- context.startRendering().then(function(result) { |
- var expected = result.getChannelData(0); |
- var actual = result.getChannelData(1); |
- |
- should(actual, "4-th order IIRFilter (biquad ref)") |
- .beCloseToArray(expected, { |
- // Thresholds experimentally determined. |
- absoluteThreshold: 1.59e-7, |
- relativeThreshold: 2.11e-5, |
- }); |
- |
- var snr = 10*Math.log10(computeSNR(actual, expected)); |
- should(snr, "SNR of 4-th order IIRFilter (biquad ref)") |
- .beGreaterThanOrEqualTo(108.947); |
- }).then(() => task.done()); |
+ context.startRendering() |
+ .then(function(result) { |
+ let expected = result.getChannelData(0); |
+ let actual = result.getChannelData(1); |
+ |
+ should(actual, '4-th order IIRFilter (biquad ref)') |
+ .beCloseToArray(expected, { |
+ // Thresholds experimentally determined. |
+ absoluteThreshold: 1.59e-7, |
+ relativeThreshold: 2.11e-5, |
+ }); |
+ |
+ let snr = 10 * Math.log10(computeSNR(actual, expected)); |
+ should(snr, 'SNR of 4-th order IIRFilter (biquad ref)') |
+ .beGreaterThanOrEqualTo(108.947); |
+ }) |
+ .then(() => task.done()); |
}); |
audit.run(); |