diff options
Diffstat (limited to 'testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html')
-rw-r--r-- | testing/web-platform/tests/webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html | 154 |
1 files changed, 154 insertions, 0 deletions
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> |