diff options
Diffstat (limited to 'testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface')
11 files changed, 837 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/ctor-delay.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/ctor-delay.html new file mode 100644 index 0000000000..e7ccefc655 --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/ctor-delay.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Test Constructor: Delay + </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> + <script src="/webaudio/resources/audionodeoptions.js"></script> + </head> + <body> + <script id="layout-test-code"> + let context; + + let audit = Audit.createTaskRunner(); + + audit.define('initialize', (task, should) => { + context = initializeContext(should); + task.done(); + }); + + audit.define('invalid constructor', (task, should) => { + testInvalidConstructor(should, 'DelayNode', context); + task.done(); + }); + + audit.define('default constructor', (task, should) => { + let prefix = 'node0'; + let node = testDefaultConstructor(should, 'DelayNode', context, { + prefix: prefix, + numberOfInputs: 1, + numberOfOutputs: 1, + channelCount: 2, + channelCountMode: 'max', + channelInterpretation: 'speakers' + }); + + testDefaultAttributes( + should, node, prefix, [{name: 'delayTime', value: 0}]); + + task.done(); + }); + + audit.define('test AudioNodeOptions', (task, should) => { + testAudioNodeOptions(should, context, 'DelayNode'); + task.done(); + }); + + audit.define('constructor options', (task, should) => { + let node; + let options = { + delayTime: 0.5, + maxDelayTime: 1.5, + }; + + should( + () => { + node = new DelayNode(context, options); + }, + 'node1 = new DelayNode(c, ' + JSON.stringify(options) + ')') + .notThrow(); + + should(node.delayTime.value, 'node1.delayTime.value') + .beEqualTo(options.delayTime); + should(node.delayTime.maxValue, 'node1.delayTime.maxValue') + .beEqualTo(options.maxDelayTime); + + task.done(); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delay-test.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delay-test.html new file mode 100644 index 0000000000..6277c253ec --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delay-test.html @@ -0,0 +1,61 @@ +<!doctype html> +<html> + <head> + <title>Test DelayNode Delay</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> + let audit = Audit.createTaskRunner(); + + audit.define( + {label: 'test0', description: 'Test delay of 3 frames'}, + async (task, should) => { + // Only need a few outputs samples. The sample rate is arbitrary. + const context = + new OfflineAudioContext(1, RENDER_QUANTUM_FRAMES, 8192); + let src; + let delay; + + should( + () => { + src = new ConstantSourceNode(context); + delay = new DelayNode(context); + }, + 'Creating ConstantSourceNode(context) and DelayNode(context)') + .notThrow(); + + // The number of frames to delay for the DelayNode. Should be a + // whole number, but is otherwise arbitrary. + const delayFrames = 3; + + should(() => { + delay.delayTime.value = delayFrames / context.sampleRate; + }, `Setting delayTime to ${delayFrames} frames`).notThrow(); + + src.connect(delay).connect(context.destination); + + src.start(); + + let buffer = await context.startRendering(); + let output = buffer.getChannelData(0); + + // Verify output was delayed the correct number of frames. + should(output.slice(0, delayFrames), `output[0:${delayFrames - 1}]`) + .beConstantValueOf(0); + should( + output.slice(delayFrames), + `output[${delayFrames}:${output.length - 1}]`) + .beConstantValueOf(1); + + task.done(); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-channel-count-1.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-channel-count-1.html new file mode 100644 index 0000000000..dd964ef9e3 --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-channel-count-1.html @@ -0,0 +1,104 @@ +<!DOCTYPE html> +<title>Test that DelayNode output channelCount matches that of the delayed input</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +// See https://github.com/WebAudio/web-audio-api/issues/25 + +// sampleRate is a power of two so that delay times are exact in base-2 +// floating point arithmetic. +const SAMPLE_RATE = 32768; +// Arbitrary delay time in frames (but this is assumed a multiple of block +// size below): +const DELAY_FRAMES = 3 * 128; +// Implementations may apply interpolation to input samples, which can spread +// the effect of input with larger channel counts over neighbouring blocks. +// This test ignores enough neighbouring blocks to ignore the effects of +// filter radius of up to this number of frames: +const INTERPOLATION_GRACE = 128; +// Number of frames of DelayNode output that are known to be stereo: +const STEREO_FRAMES = 128; +// The delay will be increased at this frame to switch DelayNode output back +// to mono. +const MONO_OUTPUT_START_FRAME = + DELAY_FRAMES + INTERPOLATION_GRACE + STEREO_FRAMES; +// Number of frames of output that are known to be mono after the known stereo +// and interpolation grace. +const MONO_FRAMES = 128; +// Total length allows for interpolation after effects of stereo input are +// finished and one block to test return to mono output: +const TOTAL_LENGTH = + MONO_OUTPUT_START_FRAME + INTERPOLATION_GRACE + MONO_FRAMES; +// maxDelayTime, is a multiple of block size, because the Gecko implementation +// once had a bug with delayTime = maxDelayTime in this situation: +const MAX_DELAY_FRAMES = TOTAL_LENGTH + INTERPOLATION_GRACE; + +promise_test(() => { + let context = new OfflineAudioContext({numberOfChannels: 1, + length: TOTAL_LENGTH, + sampleRate: SAMPLE_RATE}); + + // Only channel 1 of the splitter is connected to the destination. + let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2}); + splitter.connect(context.destination, 1); + + // A gain node has channelCountMode "max" and channelInterpretation + // "speakers", and so will up-mix a mono input when there is stereo input. + let gain = new GainNode(context); + gain.connect(splitter); + + // The delay node initially outputs a single channel of silence, when it + // does not have enough signal in its history to output what it has + // previously received. After the delay period, it will then output the + // stereo signal it received. + let delay = + new DelayNode(context, + {maxDelayTime: MAX_DELAY_FRAMES / context.sampleRate, + delayTime: DELAY_FRAMES / context.sampleRate}); + // Schedule an increase in the delay to return to mono silent output from + // the unfilled portion of the DelayNode's buffer. + delay.delayTime.setValueAtTime(MAX_DELAY_FRAMES / context.sampleRate, + MONO_OUTPUT_START_FRAME / context.sampleRate); + delay.connect(gain); + + let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2}); + stereoMerger.connect(delay); + + let leftOffset = 0.125; + let rightOffset = 0.5; + let leftSource = new ConstantSourceNode(context, {offset: leftOffset}); + let rightSource = new ConstantSourceNode(context, {offset: rightOffset}); + leftSource.start(); + rightSource.start(); + leftSource.connect(stereoMerger, 0, 0); + rightSource.connect(stereoMerger, 0, 1); + // Connect a mono source directly to the gain, so that even stereo silence + // will be detected in channel 1 of the gain output because it will cause + // the mono source to be up-mixed. + let monoOffset = 0.25 + let monoSource = new ConstantSourceNode(context, {offset: monoOffset}); + monoSource.start(); + monoSource.connect(gain); + + return context.startRendering(). + then((buffer) => { + let output = buffer.getChannelData(0); + + function assert_samples_equal(startIndex, length, expected, description) + { + for (let i = startIndex; i < startIndex + length; ++i) { + assert_equals(output[i], expected, description + ` at ${i}`); + } + } + + assert_samples_equal(0, DELAY_FRAMES - INTERPOLATION_GRACE, + 0, "Initial mono"); + assert_samples_equal(DELAY_FRAMES + INTERPOLATION_GRACE, STEREO_FRAMES, + monoOffset + rightOffset, "Stereo"); + assert_samples_equal(MONO_OUTPUT_START_FRAME + INTERPOLATION_GRACE, + MONO_FRAMES, + 0, "Final mono"); + }); +}); + +</script> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-default-delay.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-default-delay.html new file mode 100644 index 0000000000..ef526c96ff --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-default-delay.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> + <head> + <title> + delaynode-max-default-delay.html + </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> + <script src="/webaudio/resources/delay-testing.js"></script> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + audit.define( + { + label: 'test', + description: 'DelayNode with delay set to default maximum delay' + }, + function(task, should) { + + // Create offline audio context. + let context = new OfflineAudioContext( + 1, sampleRate * renderLengthSeconds, sampleRate); + let toneBuffer = createToneBuffer( + context, 20, 20 * toneLengthSeconds, sampleRate); // 20Hz tone + + let bufferSource = context.createBufferSource(); + bufferSource.buffer = toneBuffer; + + let delay = context.createDelay(); + delayTimeSeconds = 1; + delay.delayTime.value = delayTimeSeconds; + + bufferSource.connect(delay); + delay.connect(context.destination); + bufferSource.start(0); + + context.startRendering() + .then(buffer => checkDelayedResult(buffer, toneBuffer, should)) + .then(() => task.done()); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-nondefault-delay.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-nondefault-delay.html new file mode 100644 index 0000000000..3be07255e1 --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-max-nondefault-delay.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> + <head> + <title> + delaynode-max-nondefault-delay.html + </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> + <script src="/webaudio/resources/delay-testing.js"></script> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + audit.define( + { + label: 'test', + description: 'DelayNode with delay set to non-default maximum delay' + }, + function(task, should) { + + // Create offline audio context. + let context = new OfflineAudioContext( + 1, sampleRate * renderLengthSeconds, sampleRate); + let toneBuffer = createToneBuffer( + context, 20, 20 * toneLengthSeconds, sampleRate); // 20Hz tone + + let bufferSource = context.createBufferSource(); + bufferSource.buffer = toneBuffer; + + let maxDelay = 1.5; + let delay = context.createDelay(maxDelay); + delayTimeSeconds = maxDelay; + delay.delayTime.value = delayTimeSeconds; + + bufferSource.connect(delay); + delay.connect(context.destination); + bufferSource.start(0); + + context.startRendering() + .then(buffer => checkDelayedResult(buffer, toneBuffer, should)) + .then(() => task.done()); + ; + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelay.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelay.html new file mode 100644 index 0000000000..a43ceeb7be --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelay.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html> + <head> + <title> + delaynode-maxdelay.html + </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> + <script src="/webaudio/resources/delay-testing.js"></script> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + audit.define( + { + label: 'test', + description: + 'Basic functionality of DelayNode with a non-default max delay time' + }, + function(task, should) { + + // Create offline audio context. + let context = new OfflineAudioContext( + 1, sampleRate * renderLengthSeconds, sampleRate); + let toneBuffer = createToneBuffer( + context, 20, 20 * toneLengthSeconds, sampleRate); // 20Hz tone + + let bufferSource = context.createBufferSource(); + bufferSource.buffer = toneBuffer; + + // Create a delay node with an explicit max delay time (greater than + // the default of 1 second). + let delay = context.createDelay(2); + // Set the delay time to a value greater than the default max delay + // so we can verify the delay is working for this case. + delayTimeSeconds = 1.5; + delay.delayTime.value = delayTimeSeconds; + + bufferSource.connect(delay); + delay.connect(context.destination); + bufferSource.start(0); + + context.startRendering() + .then(buffer => checkDelayedResult(buffer, toneBuffer, should)) + .then(() => task.done()); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelaylimit.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelaylimit.html new file mode 100644 index 0000000000..caf2f85dfd --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-maxdelaylimit.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<html> + <head> + <title> + delaynode-maxdelaylimit.html + </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> + <script src="/webaudio/resources/delay-testing.js"></script> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + audit.define( + { + label: 'test', + description: + 'Tests attribute and maximum allowed delay of DelayNode' + }, + function(task, should) { + + // Create offline audio context. + let context = new OfflineAudioContext( + 1, sampleRate * renderLengthSeconds, sampleRate); + let toneBuffer = createToneBuffer( + context, 20, 20 * toneLengthSeconds, sampleRate); // 20Hz tone + + let bufferSource = context.createBufferSource(); + bufferSource.buffer = toneBuffer; + + window.context = context; + should(() => context.createDelay(180), + 'Setting Delay length to 180 seconds or more') + .throw(DOMException, 'NotSupportedError'); + should(() => context.createDelay(0), + 'Setting Delay length to 0 seconds') + .throw(DOMException, 'NotSupportedError'); + should(() => context.createDelay(-1), + 'Setting Delay length to negative') + .throw(DOMException, 'NotSupportedError'); + should(() => context.createDelay(NaN), + 'Setting Delay length to NaN') + .throw(TypeError); + + let delay = context.createDelay(179); + delay.delayTime.value = delayTimeSeconds; + window.delay = delay; + should( + delay.delayTime.value, + 'delay.delayTime.value = ' + delayTimeSeconds) + .beEqualTo(delayTimeSeconds); + + bufferSource.connect(delay); + delay.connect(context.destination); + bufferSource.start(0); + + context.startRendering() + .then(buffer => checkDelayedResult(buffer, toneBuffer, should)) + .then(() => task.done()); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-scheduling.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-scheduling.html new file mode 100644 index 0000000000..af6c54950a --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-scheduling.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> + <head> + <title> + delaynode-scheduling.html + </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> + <script src="/webaudio/resources/delay-testing.js"></script> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + audit.define( + { + label: 'test', + description: + 'DelayNode delayTime parameter can be scheduled at a given time' + }, + function(task, should) { + + // Create offline audio context. + let context = new OfflineAudioContext( + 1, sampleRate * renderLengthSeconds, sampleRate); + let toneBuffer = createToneBuffer( + context, 20, 20 * toneLengthSeconds, sampleRate); // 20Hz tone + + let bufferSource = context.createBufferSource(); + bufferSource.buffer = toneBuffer; + + let delay = context.createDelay(); + + // Schedule delay time at time zero. + delay.delayTime.setValueAtTime(delayTimeSeconds, 0); + + bufferSource.connect(delay); + delay.connect(context.destination); + bufferSource.start(0); + + context.startRendering() + .then(buffer => checkDelayedResult(buffer, toneBuffer, should)) + .then(() => task.done()); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode.html new file mode 100644 index 0000000000..da508e439f --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html> + <head> + <title> + delaynode.html + </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> + <script src="/webaudio/resources/delay-testing.js"></script> + </head> + <body> + <script id="layout-test-code"> + let audit = Audit.createTaskRunner(); + + audit.define( + { + label: 'test', + description: 'Tests attribute and basic functionality of DelayNode' + }, + function(task, should) { + + // Create offline audio context. + let context = new OfflineAudioContext( + 1, sampleRate * renderLengthSeconds, sampleRate); + let toneBuffer = createToneBuffer( + context, 20, 20 * toneLengthSeconds, sampleRate); // 20Hz tone + + let bufferSource = context.createBufferSource(); + bufferSource.buffer = toneBuffer; + + let delay = context.createDelay(); + + window.delay = delay; + should(delay.numberOfInputs, 'delay.numberOfInputs').beEqualTo(1); + should(delay.numberOfOutputs, 'delay.numberOfOutputs').beEqualTo(1); + should(delay.delayTime.defaultValue, 'delay.delayTime.defaultValue') + .beEqualTo(0.0); + should(delay.delayTime.value, 'delay.delayTime.value') + .beEqualTo(0.0); + + delay.delayTime.value = delayTimeSeconds; + should( + delay.delayTime.value, + 'delay.delayTime.value = ' + delayTimeSeconds) + .beEqualTo(delayTimeSeconds); + + bufferSource.connect(delay); + delay.connect(context.destination); + bufferSource.start(0); + + context.startRendering() + .then(buffer => checkDelayedResult(buffer, toneBuffer, should)) + .then(task.done.bind(task)); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/maxdelay-rounding.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/maxdelay-rounding.html new file mode 100644 index 0000000000..84d9f18138 --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/maxdelay-rounding.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Test DelayNode when maxDelayTime requires rounding + </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 id="layout-test-code"> + let sampleRate = 44100; + let inputLengthSeconds = 1; + let renderLengthSeconds = 2; + + // Delay for one second plus 0.4 of a sample frame, to test that + // DelayNode is properly rounding up when calculating its buffer + // size (crbug.com/1065110). + let delayTimeSeconds = 1 + 0.4 / sampleRate; + + let audit = Audit.createTaskRunner(); + + audit.define( + { + label: 'maxdelay-rounding', + description: 'Test DelayNode when maxDelayTime requires rounding', + }, + (task, should) => { + let context = new OfflineAudioContext({ + numberOfChannels: 1, + length: sampleRate * renderLengthSeconds, + sampleRate: sampleRate, + }); + + // Create a constant source to use as input. + let src = new ConstantSourceNode(context); + + // Create a DelayNode to delay for delayTimeSeconds. + let delay = new DelayNode(context, { + maxDelayTime: delayTimeSeconds, + delayTime: delayTimeSeconds, + }); + + src.connect(delay).connect(context.destination); + + src.start(); + context.startRendering() + .then(renderedBuffer => { + let renderedData = renderedBuffer.getChannelData(0); + + // The first delayTimeSeconds of output should be silent. + let expectedSilentFrames = Math.floor( + delayTimeSeconds * sampleRate); + + should( + renderedData.slice(0, expectedSilentFrames), + `output[0:${expectedSilentFrames - 1}]`) + .beConstantValueOf(0); + + // The rest should be non-silent: that is, there should + // be at least one non-zero sample. (Any reasonable + // interpolation algorithm will make all these samples + // non-zero, but I don't think that's guaranteed by the + // spec, so we use a conservative test for now.) + should( + renderedData.slice(expectedSilentFrames), + `output[${expectedSilentFrames}:]`) + .notBeConstantValueOf(0); + }) + .then(() => task.done()); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/no-dezippering.html b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/no-dezippering.html new file mode 100644 index 0000000000..ccca103a3b --- /dev/null +++ b/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/no-dezippering.html @@ -0,0 +1,184 @@ +<!DOCTYPE html> +<html> + <head> + <title> + Test DelayNode Has No Dezippering + </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 id="layout-test-code"> + // The sample rate must be a power of two to avoid any round-off errors in + // computing when to suspend a context on a rendering quantum boundary. + // Otherwise this is pretty arbitrary. + let sampleRate = 16384; + + let audit = Audit.createTaskRunner(); + + audit.define( + {label: 'test0', description: 'Test DelayNode has no dezippering'}, + (task, should) => { + let context = new OfflineAudioContext(1, sampleRate, sampleRate); + + // Simple integer ramp for testing delay node + let buffer = new AudioBuffer( + {length: context.length, sampleRate: context.sampleRate}); + let rampData = buffer.getChannelData(0); + for (let k = 0; k < rampData.length; ++k) { + rampData[k] = k + 1; + } + + // |delay0Frame| is the initial delay in frames. |delay1Frame| is + // the new delay in frames. These must be integers. + let delay0Frame = 64; + let delay1Frame = 16; + + let src = new AudioBufferSourceNode(context, {buffer: buffer}); + let delay = new DelayNode( + context, {delayTime: delay0Frame / context.sampleRate}); + + src.connect(delay).connect(context.destination); + + // After a render quantum, change the delay to |delay1Frame|. + context.suspend(RENDER_QUANTUM_FRAMES / context.sampleRate) + .then(() => { + delay.delayTime.value = delay1Frame / context.sampleRate; + }) + .then(() => context.resume()); + + src.start(); + context.startRendering() + .then(renderedBuffer => { + let renderedData = renderedBuffer.getChannelData(0); + + // The first |delay0Frame| frames should be zero. + should( + renderedData.slice(0, delay0Frame), + 'output[0:' + (delay0Frame - 1) + ']') + .beConstantValueOf(0); + + // Now we have the ramp should show up from the delay. + let ramp0 = + new Float32Array(RENDER_QUANTUM_FRAMES - delay0Frame); + for (let k = 0; k < ramp0.length; ++k) { + ramp0[k] = rampData[k]; + } + + should( + renderedData.slice(delay0Frame, RENDER_QUANTUM_FRAMES), + 'output[' + delay0Frame + ':' + + (RENDER_QUANTUM_FRAMES - 1) + ']') + .beEqualToArray(ramp0); + + // After one rendering quantum, the delay is changed to + // |delay1Frame|. + let ramp1 = + new Float32Array(context.length - RENDER_QUANTUM_FRAMES); + for (let k = 0; k < ramp1.length; ++k) { + // ramp1[k] = 1 + k + RENDER_QUANTUM_FRAMES - delay1Frame; + ramp1[k] = + rampData[k + RENDER_QUANTUM_FRAMES - delay1Frame]; + } + should( + renderedData.slice(RENDER_QUANTUM_FRAMES), + 'output[' + RENDER_QUANTUM_FRAMES + ':]') + .beEqualToArray(ramp1); + }) + .then(() => task.done()); + }); + + audit.define( + {label: 'test1', description: 'Test value setter and setValueAtTime'}, + (task, should) => { + testWithAutomation(should, {prefix: '', threshold: 6.5819e-5}) + .then(() => task.done()); + }); + + audit.define( + {label: 'test2', description: 'Test value setter and modulation'}, + (task, should) => { + testWithAutomation(should, { + prefix: 'With modulation: ', + modulator: true + }).then(() => task.done()); + }); + + // Compare .value setter with setValueAtTime, Optionally allow modulation + // of |delayTime|. + function testWithAutomation(should, options) { + let prefix = options.prefix; + // Channel 0 is the output of delay node using the setter and channel 1 + // is the output using setValueAtTime. + let context = new OfflineAudioContext(2, sampleRate, sampleRate); + + let merger = new ChannelMergerNode( + context, {numberOfInputs: context.destination.channelCount}); + merger.connect(context.destination); + + let src = new OscillatorNode(context); + + // |delay0Frame| is the initial delay value in frames. |delay1Frame| is + // the new delay in frames. The values here are constrained only by the + // constraints for a DelayNode. These are pretty arbitrary except we + // wanted them to be fractional so as not be on a frame boundary to + // test interpolation compared with |setValueAtTime()|.. + let delay0Frame = 3.1; + let delay1Frame = 47.2; + + let delayTest = new DelayNode( + context, {delayTime: delay0Frame / context.sampleRate}); + let delayRef = new DelayNode( + context, {delayTime: delay0Frame / context.sampleRate}); + + src.connect(delayTest).connect(merger, 0, 0); + src.connect(delayRef).connect(merger, 0, 1); + + if (options.modulator) { + // Fairly arbitrary modulation of the delay time, with a peak + // variation of 10 ms. + let mod = new OscillatorNode(context, {frequency: 1000}); + let modGain = new GainNode(context, {gain: .01}); + mod.connect(modGain); + modGain.connect(delayTest.delayTime); + modGain.connect(delayRef.delayTime); + mod.start(); + } + + // The time at which the delay time of |delayTest| node will be + // changed. This MUST be on a render quantum boundary, but is + // otherwise arbitrary. + let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate; + + // Schedule the delay change on |delayRef| and also apply the value + // setter for |delayTest| at |changeTime|. + delayRef.delayTime.setValueAtTime( + delay1Frame / context.sampleRate, changeTime); + context.suspend(changeTime) + .then(() => { + delayTest.delayTime.value = delay1Frame / context.sampleRate; + }) + .then(() => context.resume()); + + src.start(); + + return context.startRendering().then(renderedBuffer => { + let actual = renderedBuffer.getChannelData(0); + let expected = renderedBuffer.getChannelData(1); + + let match = should(actual, prefix + '.value setter output') + .beCloseToArray( + expected, {absoluteThreshold: options.threshold}); + should( + match, + prefix + '.value setter output matches setValueAtTime output') + .beTrue(); + }); + } + + audit.run(); + </script> + </body> +</html> |