summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad-connection.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad-connection.html')
-rw-r--r--testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad-connection.html456
1 files changed, 456 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad-connection.html b/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad-connection.html
new file mode 100644
index 0000000000..ab9df8740f
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad-connection.html
@@ -0,0 +1,456 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test k-rate AudioParam Inputs for BiquadFilterNode</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/webaudio/resources/audit-util.js"></script>
+ <script src="/webaudio/resources/audit.js"></script>
+ </head>
+
+ <body>
+ <script>
+ // sampleRate and duration are fairly arbitrary. We use low values to
+ // limit the complexity of the test.
+ let sampleRate = 8192;
+ let testDuration = 0.5;
+
+ let audit = Audit.createTaskRunner();
+
+ audit.define(
+ {label: 'Frequency AudioParam', description: 'k-rate input works'},
+ async (task, should) => {
+ // Test frequency AudioParam using a lowpass filter whose bandwidth
+ // is initially larger than the oscillator frequency. Then automate
+ // the frequency to 0 so that the output of the filter is 0 (because
+ // the cutoff is 0).
+ let oscFrequency = 440;
+
+ let options = {
+ sampleRate: sampleRate,
+ paramName: 'frequency',
+ oscFrequency: oscFrequency,
+ testDuration: testDuration,
+ filterOptions: {type: 'lowpass', frequency: 0},
+ autoStart:
+ {method: 'setValueAtTime', args: [2 * oscFrequency, 0]},
+ autoEnd: {
+ method: 'linearRampToValueAtTime',
+ args: [0, testDuration / 4]
+ }
+ };
+
+ let buffer = await doTest(should, options);
+ let expected = buffer.getChannelData(0);
+ let actual = buffer.getChannelData(1);
+ let halfLength = expected.length / 2;
+
+ // Sanity check. The expected output should not be zero for
+ // the first half, but should be zero for the second half
+ // (because the filter bandwidth is exactly 0).
+ const prefix = 'Expected k-rate frequency with automation';
+
+ should(
+ expected.slice(0, halfLength),
+ `${prefix} output[0:${halfLength - 1}]`)
+ .notBeConstantValueOf(0);
+ should(
+ expected.slice(expected.length),
+ `${prefix} output[${halfLength}:]`)
+ .beConstantValueOf(0);
+
+ // Outputs should be the same. Break the message into two
+ // parts so we can see the expected outputs.
+ checkForSameOutput(should, options.paramName, actual, expected);
+
+ task.done();
+ });
+
+ audit.define(
+ {label: 'Q AudioParam', description: 'k-rate input works'},
+ async (task, should) => {
+ // Test Q AudioParam. Use a bandpass filter whose center frequency
+ // is fairly far from the oscillator frequency. Then start with a Q
+ // value of 0 (so everything goes through) and then increase Q to
+ // some large value such that the out-of-band signals are basically
+ // cutoff.
+ let frequency = 440;
+ let oscFrequency = 4 * frequency;
+
+ let options = {
+ sampleRate: sampleRate,
+ oscFrequency: oscFrequency,
+ testDuration: testDuration,
+ paramName: 'Q',
+ filterOptions: {type: 'bandpass', frequency: frequency, Q: 0},
+ autoStart: {method: 'setValueAtTime', args: [0, 0]},
+ autoEnd: {
+ method: 'linearRampToValueAtTime',
+ args: [100, testDuration / 4]
+ }
+ };
+
+ const buffer = await doTest(should, options);
+ let expected = buffer.getChannelData(0);
+ let actual = buffer.getChannelData(1);
+
+ // Outputs should be the same
+ checkForSameOutput(should, options.paramName, actual, expected);
+
+ task.done();
+ });
+
+ audit.define(
+ {label: 'Gain AudioParam', description: 'k-rate input works'},
+ async (task, should) => {
+ // Test gain AudioParam. Use a peaking filter with a large Q so the
+ // peak is narrow with a center frequency the same as the oscillator
+ // frequency. Start with a gain of 0 so everything goes through and
+ // then ramp the gain down to -100 so that the oscillator is
+ // filtered out.
+ let oscFrequency = 4 * 440;
+
+ let options = {
+ sampleRate: sampleRate,
+ oscFrequency: oscFrequency,
+ testDuration: testDuration,
+ paramName: 'gain',
+ filterOptions:
+ {type: 'peaking', frequency: oscFrequency, Q: 100, gain: 0},
+ autoStart: {method: 'setValueAtTime', args: [0, 0]},
+ autoEnd: {
+ method: 'linearRampToValueAtTime',
+ args: [-100, testDuration / 4]
+ }
+ };
+
+ const buffer = await doTest(should, options);
+ let expected = buffer.getChannelData(0);
+ let actual = buffer.getChannelData(1);
+
+ // Outputs should be the same
+ checkForSameOutput(should, options.paramName, actual, expected);
+
+ task.done();
+ });
+
+ audit.define(
+ {label: 'Detune AudioParam', description: 'k-rate input works'},
+ async (task, should) => {
+ // Test detune AudioParam. The basic idea is the same as the
+ // frequency test above, but insteda of automating the frequency, we
+ // automate the detune value so that initially the filter cutuff is
+ // unchanged and then changing the detune until the cutoff goes to 1
+ // Hz, which would cause the oscillator to be filtered out.
+ let oscFrequency = 440;
+ let filterFrequency = 5 * oscFrequency;
+
+ // For a detune value d, the computed frequency, fc, of the filter
+ // is fc = f*2^(d/1200), where f is the frequency of the filter. Or
+ // d = 1200*log2(fc/f). Compute the detune value to produce a final
+ // cutoff frequency of 1 Hz.
+ let detuneEnd = 1200 * Math.log2(1 / filterFrequency);
+
+ let options = {
+ sampleRate: sampleRate,
+ oscFrequency: oscFrequency,
+ testDuration: testDuration,
+ paramName: 'detune',
+ filterOptions: {
+ type: 'lowpass',
+ frequency: filterFrequency,
+ detune: 0,
+ gain: 0
+ },
+ autoStart: {method: 'setValueAtTime', args: [0, 0]},
+ autoEnd: {
+ method: 'linearRampToValueAtTime',
+ args: [detuneEnd, testDuration / 4]
+ }
+ };
+
+ const buffer = await doTest(should, options);
+ let expected = buffer.getChannelData(0);
+ let actual = buffer.getChannelData(1);
+
+ // Outputs should be the same
+ checkForSameOutput(should, options.paramName, actual, expected);
+
+ task.done();
+ });
+
+ audit.define('All k-rate inputs', async (task, should) => {
+ // Test the case where all AudioParams are set to k-rate with an input
+ // to each AudioParam. Similar to the above tests except all the params
+ // are k-rate.
+ let testFrames = testDuration * sampleRate;
+ let context = new OfflineAudioContext(
+ {numberOfChannels: 2, sampleRate: sampleRate, length: testFrames});
+
+ let merger = new ChannelMergerNode(
+ context, {numberOfInputs: context.destination.channelCount});
+ merger.connect(context.destination);
+
+ let src = new OscillatorNode(context);
+
+ // The peaking filter uses all four AudioParams, so this is the node to
+ // test.
+ let filterOptions =
+ {type: 'peaking', frequency: 0, detune: 0, gain: 0, Q: 0};
+ let refNode;
+ should(
+ () => refNode = new BiquadFilterNode(context, filterOptions),
+ `Create: refNode = new BiquadFilterNode(context, ${
+ JSON.stringify(filterOptions)})`)
+ .notThrow();
+
+ let tstNode;
+ should(
+ () => tstNode = new BiquadFilterNode(context, filterOptions),
+ `Create: tstNode = new BiquadFilterNode(context, ${
+ JSON.stringify(filterOptions)})`)
+ .notThrow();
+ ;
+
+ // Make all the AudioParams k-rate.
+ ['frequency', 'Q', 'gain', 'detune'].forEach(param => {
+ should(
+ () => refNode[param].automationRate = 'k-rate',
+ `Set rate: refNode[${param}].automationRate = 'k-rate'`)
+ .notThrow();
+ should(
+ () => tstNode[param].automationRate = 'k-rate',
+ `Set rate: tstNode[${param}].automationRate = 'k-rate'`)
+ .notThrow();
+ });
+
+ // One input for each AudioParam.
+ let mod = {};
+ ['frequency', 'Q', 'gain', 'detune'].forEach(param => {
+ should(
+ () => mod[param] = new ConstantSourceNode(context, {offset: 0}),
+ `Create: mod[${
+ param}] = new ConstantSourceNode(context, {offset: 0})`)
+ .notThrow();
+ ;
+ should(
+ () => mod[param].offset.automationRate = 'a-rate',
+ `Set rate: mod[${param}].offset.automationRate = 'a-rate'`)
+ .notThrow();
+ });
+
+ // Set up automations for refNode. We want to start the filter with
+ // parameters that let the oscillator signal through more or less
+ // untouched. Then change the filter parameters to filter out the
+ // oscillator. What happens in between doesn't reall matter for this
+ // test. Hence, set the initial parameters with a center frequency well
+ // above the oscillator and a Q and gain of 0 to pass everthing.
+ [['frequency', [4 * src.frequency.value, 0]], ['Q', [0, 0]],
+ ['gain', [0, 0]], ['detune', [4 * 1200, 0]]]
+ .forEach(param => {
+ should(
+ () => refNode[param[0]].setValueAtTime(...param[1]),
+ `Automate 0: refNode.${param[0]}.setValueAtTime(${
+ param[1][0]}, ${param[1][1]})`)
+ .notThrow();
+ should(
+ () => mod[param[0]].offset.setValueAtTime(...param[1]),
+ `Automate 0: mod[${param[0]}].offset.setValueAtTime(${
+ param[1][0]}, ${param[1][1]})`)
+ .notThrow();
+ });
+
+ // Now move the filter frequency to the oscillator frequency with a high
+ // Q and very low gain to remove the oscillator signal.
+ [['frequency', [src.frequency.value, testDuration / 4]],
+ ['Q', [40, testDuration / 4]], ['gain', [-100, testDuration / 4]], [
+ 'detune', [0, testDuration / 4]
+ ]].forEach(param => {
+ should(
+ () => refNode[param[0]].linearRampToValueAtTime(...param[1]),
+ `Automate 1: refNode[${param[0]}].linearRampToValueAtTime(${
+ param[1][0]}, ${param[1][1]})`)
+ .notThrow();
+ should(
+ () => mod[param[0]].offset.linearRampToValueAtTime(...param[1]),
+ `Automate 1: mod[${param[0]}].offset.linearRampToValueAtTime(${
+ param[1][0]}, ${param[1][1]})`)
+ .notThrow();
+ });
+
+ // Connect everything
+ src.connect(refNode).connect(merger, 0, 0);
+ src.connect(tstNode).connect(merger, 0, 1);
+
+ src.start();
+ for (let param in mod) {
+ should(
+ () => mod[param].connect(tstNode[param]),
+ `Connect: mod[${param}].connect(tstNode.${param})`)
+ .notThrow();
+ }
+
+ for (let param in mod) {
+ should(() => mod[param].start(), `Start: mod[${param}].start()`)
+ .notThrow();
+ }
+
+ const buffer = await context.startRendering();
+ let expected = buffer.getChannelData(0);
+ let actual = buffer.getChannelData(1);
+
+ // Sanity check that the output isn't all zeroes.
+ should(actual, 'All k-rate AudioParams').notBeConstantValueOf(0);
+ should(actual, 'All k-rate AudioParams').beCloseToArray(expected, {
+ absoluteThreshold: 0
+ });
+
+ task.done();
+ });
+
+ audit.run();
+
+ async function doTest(should, options) {
+ // Test that a k-rate AudioParam with an input reads the input value and
+ // is actually k-rate.
+ //
+ // A refNode is created with an automation timeline. This is the
+ // expected output.
+ //
+ // The testNode is the same, but it has a node connected to the k-rate
+ // AudioParam. The input to the node is an a-rate ConstantSourceNode
+ // whose output is automated in exactly the same was as the refNode. If
+ // the test passes, the outputs of the two nodes MUST match exactly.
+
+ // The options argument MUST contain the following members:
+ // sampleRate - the sample rate for the offline context
+ // testDuration - duration of the offline context, in sec.
+ // paramName - the name of the AudioParam to be tested
+ // oscFrequency - frequency of oscillator source
+ // filterOptions - options used to construct the BiquadFilterNode
+ // autoStart - information about how to start the automation
+ // autoEnd - information about how to end the automation
+ //
+ // The autoStart and autoEnd options are themselves dictionaries with
+ // the following required members:
+ // method - name of the automation method to be applied
+ // args - array of arguments to be supplied to the method.
+ let {
+ sampleRate,
+ paramName,
+ oscFrequency,
+ autoStart,
+ autoEnd,
+ testDuration,
+ filterOptions
+ } = options;
+
+ let testFrames = testDuration * sampleRate;
+ let context = new OfflineAudioContext(
+ {numberOfChannels: 2, sampleRate: sampleRate, length: testFrames});
+
+ let merger = new ChannelMergerNode(
+ context, {numberOfInputs: context.destination.channelCount});
+ merger.connect(context.destination);
+
+ // Any calls to |should| are meant to be informational so we can see
+ // what nodes are created and the automations used.
+ let src;
+
+ // Create the source.
+ should(
+ () => {
+ src = new OscillatorNode(context, {frequency: oscFrequency});
+ },
+ `${paramName}: new OscillatorNode(context, {frequency: ${
+ oscFrequency}})`)
+ .notThrow();
+
+ // The refNode automates the AudioParam with k-rate automations, no
+ // inputs.
+ let refNode;
+ should(
+ () => {
+ refNode = new BiquadFilterNode(context, filterOptions);
+ },
+ `Reference BiquadFilterNode(c, ${JSON.stringify(filterOptions)})`)
+ .notThrow();
+
+ refNode[paramName].automationRate = 'k-rate';
+
+ // Set up automations for the reference node.
+ should(
+ () => {
+ refNode[paramName][autoStart.method](...autoStart.args);
+ },
+ `refNode.${paramName}.${autoStart.method}(${autoStart.args})`)
+ .notThrow();
+ should(
+ () => {
+ refNode[paramName][autoEnd.method](...autoEnd.args);
+ },
+ `refNode.${paramName}.${autoEnd.method}.(${autoEnd.args})`)
+ .notThrow();
+
+ // The tstNode does the same automation, but it comes from the input
+ // connected to the AudioParam.
+ let tstNode;
+ should(
+ () => {
+ tstNode = new BiquadFilterNode(context, filterOptions);
+ },
+ `Test BiquadFilterNode(context, ${JSON.stringify(filterOptions)})`)
+ .notThrow();
+ tstNode[paramName].automationRate = 'k-rate';
+
+ // Create the input to the AudioParam of the test node. The output of
+ // this node MUST have the same set of automations as the reference
+ // node, and MUST be a-rate to make sure we're handling k-rate inputs
+ // correctly.
+ let mod = new ConstantSourceNode(context);
+ mod.offset.automationRate = 'a-rate';
+ should(
+ () => {
+ mod.offset[autoStart.method](...autoStart.args);
+ },
+ `${paramName}: mod.offset.${autoStart.method}(${autoStart.args})`)
+ .notThrow();
+ should(
+ () => {
+ mod.offset[autoEnd.method](...autoEnd.args);
+ },
+ `${paramName}: mod.offset.${autoEnd.method}(${autoEnd.args})`)
+ .notThrow();
+
+ // Create graph
+ mod.connect(tstNode[paramName]);
+ src.connect(refNode).connect(merger, 0, 0);
+ src.connect(tstNode).connect(merger, 0, 1);
+
+ // Run!
+ src.start();
+ mod.start();
+ return context.startRendering();
+ }
+
+ function checkForSameOutput(should, paramName, actual, expected) {
+ let halfLength = expected.length / 2;
+
+ // Outputs should be the same. We break the check into halves so we can
+ // see the expected outputs. Mostly for a simple visual check that the
+ // output from the second half is small because the tests generally try
+ // to filter out the signal so that the last half of the output is
+ // small.
+ should(
+ actual.slice(0, halfLength),
+ `k-rate ${paramName} with input: output[0,${halfLength}]`)
+ .beCloseToArray(
+ expected.slice(0, halfLength), {absoluteThreshold: 0});
+ should(
+ actual.slice(halfLength),
+ `k-rate ${paramName} with input: output[${halfLength}:]`)
+ .beCloseToArray(expected.slice(halfLength), {absoluteThreshold: 0});
+ }
+ </script>
+ </body>
+</html>