summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface')
-rw-r--r--testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html112
-rw-r--r--testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html154
-rw-r--r--testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-overflow.html41
-rw-r--r--testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/osc-basic-waveform.html229
4 files changed, 536 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html b/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html
new file mode 100644
index 0000000000..bf50195a5b
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/ctor-oscillator.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>
+ Test Constructor: Oscillator
+ </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, 'OscillatorNode', context);
+ task.done();
+ });
+
+ audit.define('default constructor', (task, should) => {
+ let prefix = 'node0';
+ let node = testDefaultConstructor(should, 'OscillatorNode', context, {
+ prefix: prefix,
+ numberOfInputs: 0,
+ numberOfOutputs: 1,
+ channelCount: 2,
+ channelCountMode: 'max',
+ channelInterpretation: 'speakers'
+ });
+
+ testDefaultAttributes(
+ should, node, prefix,
+ [{name: 'type', value: 'sine'}, {name: 'frequency', value: 440}]);
+
+ task.done();
+ });
+
+ audit.define('test AudioNodeOptions', (task, should) => {
+ testAudioNodeOptions(should, context, 'OscillatorNode');
+ task.done();
+ });
+
+ audit.define('constructor options', (task, should) => {
+ let node;
+ let options = {type: 'sawtooth', detune: 7, frequency: 918};
+
+ should(
+ () => {
+ node = new OscillatorNode(context, options);
+ },
+ 'node1 = new OscillatorNode(c, ' + JSON.stringify(options) + ')')
+ .notThrow();
+
+ should(node.type, 'node1.type').beEqualTo(options.type);
+ should(node.detune.value, 'node1.detune.value')
+ .beEqualTo(options.detune);
+ should(node.frequency.value, 'node1.frequency.value')
+ .beEqualTo(options.frequency);
+
+ should(node.channelCount, 'node1.channelCount').beEqualTo(2);
+ should(node.channelCountMode, 'node1.channelCountMode')
+ .beEqualTo('max');
+ should(node.channelInterpretation, 'node1.channelInterpretation')
+ .beEqualTo('speakers');
+
+ // Test that type and periodicWave options work as described.
+ options = {
+ type: 'sine',
+ periodicWave: new PeriodicWave(context, {real: [1, 1]})
+ };
+ should(() => {
+ node = new OscillatorNode(context, options);
+ }, 'new OscillatorNode(c, ' + JSON.stringify(options) + ')').notThrow();
+
+ options = {type: 'custom'};
+ should(
+ () => {
+ node = new OscillatorNode(context, options);
+ },
+ 'new OscillatorNode(c, ' + JSON.stringify(options) + ')')
+ .throw(DOMException, 'InvalidStateError');
+
+ options = {
+ type: 'custom',
+ periodicWave: new PeriodicWave(context, {real: [1, 1]})
+ };
+ should(() => {
+ node = new OscillatorNode(context, options);
+ }, 'new OscillatorNode(c, ' + JSON.stringify(options) + ')').notThrow();
+
+ should(
+ () => {
+ node = new OscillatorNode(context, {periodicWave: null});
+ },
+ 'new OscillatorNode(c, {periodicWave: null}')
+ .throw(DOMException, 'TypeError');
+ task.done();
+ });
+
+ audit.run();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html b/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html
new file mode 100644
index 0000000000..81a1293d03
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html
@@ -0,0 +1,154 @@
+<!doctype html>
+<html>
+ <head>
+ <title>
+ Oscillator Detune Limits
+ </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/webaudio/resources/audit.js"></script>
+ </head>
+
+ <body>
+ <script>
+ const sampleRate = 44100;
+ const renderLengthSeconds = 0.125;
+
+ let audit = Audit.createTaskRunner();
+
+ audit.define(
+ {
+ label: 'detune limits',
+ description:
+ 'Oscillator with detune and frequency at Nyquist or above'
+ },
+ (task, should) => {
+ let context = new OfflineAudioContext(
+ 2, renderLengthSeconds * sampleRate, sampleRate);
+
+ let merger = new ChannelMergerNode(
+ context, {numberOfInputs: context.destination.channelCount});
+ merger.connect(context.destination);
+
+ // For test oscillator, set the oscillator frequency to -Nyquist and
+ // set detune to be a large number that would cause the detuned
+ // frequency to be way above Nyquist.
+ const oscFrequency = 1;
+ const detunedFrequency = sampleRate;
+ const detuneValue = Math.fround(1200 * Math.log2(detunedFrequency));
+
+ let testOsc = new OscillatorNode(
+ context, {frequency: oscFrequency, detune: detuneValue});
+ testOsc.connect(merger, 0, 1);
+
+ // For the reference oscillator, determine the computed oscillator
+ // frequency using the values above and set that as the oscillator
+ // frequency.
+ let computedFreq = oscFrequency * Math.pow(2, detuneValue / 1200);
+
+ let refOsc = new OscillatorNode(context, {frequency: computedFreq});
+ refOsc.connect(merger, 0, 0);
+
+ // Start 'em up and render
+ testOsc.start();
+ refOsc.start();
+
+ context.startRendering()
+ .then(renderedBuffer => {
+ let expected = renderedBuffer.getChannelData(0);
+ let actual = renderedBuffer.getChannelData(1);
+
+ // Let user know about the smaple rate so following messages
+ // make more sense.
+ should(context.sampleRate, 'Context sample rate')
+ .beEqualTo(context.sampleRate);
+
+ // Since the frequency is at Nyquist, the reference oscillator
+ // output should be zero.
+ should(
+ refOsc.frequency.value, 'Reference oscillator frequency')
+ .beGreaterThanOrEqualTo(context.sampleRate / 2);
+ should(
+ expected, `Osc(freq: ${refOsc.frequency.value}) output`)
+ .beConstantValueOf(0);
+ // The output from each oscillator should be the same.
+ should(
+ actual,
+ 'Osc(freq: ' + oscFrequency + ', detune: ' + detuneValue +
+ ') output')
+ .beCloseToArray(expected, {absoluteThreshold: 0});
+
+ })
+ .then(() => task.done());
+ });
+
+ audit.define(
+ {
+ label: 'detune automation',
+ description:
+ 'Oscillator output with detune automation should be zero ' +
+ 'above Nyquist'
+ },
+ (task, should) => {
+ let context = new OfflineAudioContext(
+ 1, renderLengthSeconds * sampleRate, sampleRate);
+
+ const baseFrequency = 1;
+ const rampEnd = renderLengthSeconds / 2;
+ const detuneEnd = 1e7;
+
+ let src = new OscillatorNode(context, {frequency: baseFrequency});
+ src.detune.linearRampToValueAtTime(detuneEnd, rampEnd);
+
+ src.connect(context.destination);
+
+ src.start();
+
+ context.startRendering()
+ .then(renderedBuffer => {
+ let audio = renderedBuffer.getChannelData(0);
+
+ // At some point, the computed oscillator frequency will go
+ // above Nyquist. Determine at what time this occurrs. The
+ // computed frequency is f * 2^(d/1200) where |f| is the
+ // oscillator frequency and |d| is the detune value. Thus,
+ // find |d| such that Nyquist = f*2^(d/1200). That is, d =
+ // 1200*log2(Nyquist/f)
+ let criticalDetune =
+ 1200 * Math.log2(context.sampleRate / 2 / baseFrequency);
+
+ // Now figure out at what point on the linear ramp does the
+ // detune value reach this critical value. For a linear ramp:
+ //
+ // v(t) = V0+(V1-V0)*(t-T0)/(T1-T0)
+ //
+ // Thus,
+ //
+ // t = ((T1-T0)*v(t) + T0*V1 - T1*V0)/(V1-V0)
+ //
+ // In this test, T0 = 0, V0 = 0, T1 = rampEnd, V1 =
+ // detuneEnd, and v(t) = criticalDetune
+ let criticalTime = (rampEnd * criticalDetune) / detuneEnd;
+ let criticalFrame =
+ Math.ceil(criticalTime * context.sampleRate);
+
+ should(
+ criticalFrame,
+ `Frame where detuned oscillator reaches Nyquist`)
+ .beEqualTo(criticalFrame);
+
+ should(
+ audio.slice(0, criticalFrame),
+ `osc[0:${criticalFrame - 1}]`)
+ .notBeConstantValueOf(0);
+
+ should(audio.slice(criticalFrame), `osc[${criticalFrame}:]`)
+ .beConstantValueOf(0);
+ })
+ .then(() => task.done());
+ });
+
+ audit.run();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-overflow.html b/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-overflow.html
new file mode 100644
index 0000000000..28c28bc1db
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-overflow.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test Osc.detune Overflow</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/webaudio/resources/audit.js"></script>
+ <script src="/webaudio/resources/audit-util.js"></script>
+ </head>
+
+ <body>
+ <script>
+ const sampleRate = 44100;
+ const renderLengthFrames = RENDER_QUANTUM_FRAMES;
+
+ let audit = Audit.createTaskRunner();
+
+ audit.define('detune overflow', async (task, should) => {
+ let context =
+ new OfflineAudioContext(1, renderLengthFrames, sampleRate);
+
+ // This value of frequency and detune results in a computed frequency of
+ // 440*2^(153600/1200) = 1.497e41. The frequency needs to be clamped to
+ // Nyquist. But a sine wave at Nyquist frequency is all zeroes. Verify
+ // the output is 0.
+ let osc = new OscillatorNode(context, {frequency: 440, detune: 153600});
+
+ osc.connect(context.destination);
+
+ let buffer = await context.startRendering();
+ let output = buffer.getChannelData(0);
+ should(output, 'Osc freq and detune outside nominal range')
+ .beConstantValueOf(0);
+
+ task.done();
+ });
+
+ audit.run();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/osc-basic-waveform.html b/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/osc-basic-waveform.html
new file mode 100644
index 0000000000..b34c96855f
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/osc-basic-waveform.html
@@ -0,0 +1,229 @@
+<!doctype html>
+<html>
+ <head>
+ <title>
+ Test Basic Oscillator Sine Wave Test
+ </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>
+ // Don't change the sample rate. The tests below depend on this sample
+ // rate to cover all the cases in Chrome's implementation. But the tests
+ // are general and apply to any browser.
+ const sampleRate = 44100;
+
+ // Only need a few samples for testing, so just use two renders.
+ const durationFrames = 2 * RENDER_QUANTUM_FRAMES;
+
+ let audit = Audit.createTaskRunner();
+
+ // The following tests verify that the oscillator produces the same
+ // results as the mathematical oscillators. We choose sine wave and a
+ // custom wave because we know they're bandlimited and won't change with
+ // the frequency.
+ //
+ // The tests for 1 and 2 Hz are intended to test Chrome's interpolation
+ // algorithm, but are still generally applicable to any browser.
+
+ audit.define(
+ {label: 'Test 0', description: 'Sine wave: 100 Hz'},
+ async (task, should) => {
+ let context = new OfflineAudioContext(
+ {length: durationFrames, sampleRate: sampleRate});
+
+ const freqHz = 100;
+
+ let src =
+ new OscillatorNode(context, {type: 'sine', frequency: freqHz});
+ src.connect(context.destination);
+
+ src.start();
+
+ let renderedBuffer = await context.startRendering();
+ checkResult(should, renderedBuffer, context, {
+ freqHz: freqHz,
+ a1: 0,
+ b1: 1,
+ prefix: 'Sine',
+ threshold: 1.8045e-6,
+ snrThreshold: 118.91
+ });
+ task.done();
+ });
+
+ audit.define(
+ {label: 'Test 1', description: 'Sine wave: -100 Hz'},
+ async (task, should) => {
+ let context = new OfflineAudioContext(
+ {length: durationFrames, sampleRate: sampleRate});
+
+ const freqHz = -100;
+
+ let src =
+ new OscillatorNode(context, {type: 'sine', frequency: freqHz});
+ src.connect(context.destination);
+
+ src.start();
+
+ let renderedBuffer = await context.startRendering();
+ checkResult(should, renderedBuffer, context, {
+ freqHz: freqHz,
+ a1: 0,
+ b1: 1,
+ prefix: 'Sine',
+ threshold: 4.7684e-7,
+ snrThreshold: 130.95
+ });
+ task.done();
+ });
+
+ audit.define(
+ {label: 'Test 2', description: 'Sine wave: 2 Hz'},
+ async (task, should) => {
+ let context = new OfflineAudioContext(
+ {length: durationFrames, sampleRate: sampleRate});
+
+ const freqHz = 2;
+
+ let src =
+ new OscillatorNode(context, {type: 'sine', frequency: freqHz});
+ src.connect(context.destination);
+
+ src.start();
+
+ let renderedBuffer = await context.startRendering();
+ checkResult(should, renderedBuffer, context, {
+ freqHz: freqHz,
+ a1: 0,
+ b1: 1,
+ prefix: 'Sine',
+ threshold: 1.4516e-7,
+ snrThreshold: 119.93
+ });
+ task.done();
+ });
+
+ audit.define(
+ {label: 'Test 3', description: 'Sine wave: 1 Hz'},
+ async (task, should) => {
+ let context = new OfflineAudioContext(
+ {length: durationFrames, sampleRate: sampleRate});
+
+ const freqHz = 1;
+
+ let src =
+ new OscillatorNode(context, {type: 'sine', frequency: freqHz});
+ src.connect(context.destination);
+
+ src.start();
+
+ let renderedBuffer = await context.startRendering();
+ checkResult(should, renderedBuffer, context, {
+ freqHz: freqHz,
+ a1: 0,
+ b1: 1,
+ prefix: 'Sine',
+ threshold: 1.4157e-7,
+ snrThreshold: 112.22
+ });
+ task.done();
+ });
+
+ audit.define(
+ {label: 'Test 4', description: 'Custom wave: 100 Hz'},
+ async (task, should) => {
+ let context = new OfflineAudioContext(
+ {length: durationFrames, sampleRate: sampleRate});
+
+ const freqHz = 100;
+
+ let wave = new PeriodicWave(
+ context,
+ {real: [0, 1], imag: [0, 1], disableNormalization: true});
+ let src = new OscillatorNode(
+ context,
+ {type: 'custom', frequency: freqHz, periodicWave: wave});
+ src.connect(context.destination);
+
+ src.start();
+
+ let renderedBuffer = await context.startRendering();
+ checkResult(should, renderedBuffer, context, {
+ freqHz: freqHz,
+ a1: 1,
+ b1: 1,
+ prefix: 'Custom',
+ threshold: 1.8478e-6,
+ snrThreshold: 122.43
+ });
+ task.done();
+ });
+
+ audit.define(
+ {label: 'Test 5', description: 'Custom wave: 1 Hz'},
+ async (task, should) => {
+ let context = new OfflineAudioContext(
+ {length: durationFrames, sampleRate: sampleRate});
+
+ const freqHz = 1;
+
+ let wave = new PeriodicWave(
+ context,
+ {real: [0, 1], imag: [0, 1], disableNormalization: true});
+ let src = new OscillatorNode(
+ context,
+ {type: 'custom', frequency: freqHz, periodicWave: wave});
+ src.connect(context.destination);
+
+ src.start();
+
+ let renderedBuffer = await context.startRendering();
+ checkResult(should, renderedBuffer, context, {
+ freqHz: freqHz,
+ a1: 1,
+ b1: 1,
+ prefix: 'Custom',
+ threshold: 4.7684e-7,
+ snrThreshold: 138.76
+ });
+ task.done();
+ });
+
+ audit.run();
+
+ function waveForm(context, freqHz, a1, b1, nsamples) {
+ let buffer =
+ new AudioBuffer({length: nsamples, sampleRate: context.sampleRate});
+ let signal = buffer.getChannelData(0);
+ const omega = 2 * Math.PI * freqHz / context.sampleRate;
+ for (let k = 0; k < nsamples; ++k) {
+ signal[k] = a1 * Math.cos(omega * k) + b1 * Math.sin(omega * k);
+ }
+
+ return buffer;
+ }
+
+ function checkResult(should, renderedBuffer, context, options) {
+ let {freqHz, a1, b1, prefix, threshold, snrThreshold} = options;
+
+ let actual = renderedBuffer.getChannelData(0);
+
+ let expected =
+ waveForm(context, freqHz, a1, b1, actual.length).getChannelData(0);
+
+ should(actual, `${prefix}: ${freqHz} Hz`).beCloseToArray(expected, {
+ absoluteThreshold: threshold
+ });
+
+ let snr = 10 * Math.log10(computeSNR(actual, expected));
+
+ should(snr, `${prefix}: SNR (db)`).beGreaterThanOrEqualTo(snrThreshold);
+ }
+ </script>
+ </body>
+</html>