| <!DOCTYPE html> |
| <html> |
| <head> |
| <title> |
| Test StereoPannerNode Has No Dezippering |
| </title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../../resources/audit-util.js"></script> |
| <script src="../../resources/audit.js"></script> |
| </head> |
| <body> |
| <script id="layout-test-code"> |
| // Arbitrary sample rate except that it should be a power of two to |
| // eliminate any round-off in computing frame boundaries. |
| let sampleRate = 16384; |
| |
| let audit = Audit.createTaskRunner(); |
| |
| audit.define( |
| { |
| label: 'test mono input', |
| description: 'Test StereoPanner with mono input has no dezippering' |
| }, |
| (task, should) => { |
| let context = new OfflineAudioContext(2, sampleRate, sampleRate); |
| let src = new ConstantSourceNode(context, {offset: 1}); |
| let p = new StereoPannerNode(context, {pan: -1}); |
| |
| src.connect(p).connect(context.destination); |
| src.start(); |
| |
| // Frame at which to change pan value. |
| let panFrame = 256; |
| context.suspend(panFrame / context.sampleRate) |
| .then(() => p.pan.value = 1) |
| .then(() => context.resume()); |
| |
| context.startRendering() |
| .then(renderedBuffer => { |
| let c0 = renderedBuffer.getChannelData(0); |
| let c1 = renderedBuffer.getChannelData(1); |
| |
| // The first part should be full left. |
| should( |
| c0.slice(0, panFrame), 'Mono: Left channel, pan = -1: ') |
| .beConstantValueOf(1); |
| should( |
| c1.slice(0, panFrame), 'Mono: Right channel, pan = -1:') |
| .beConstantValueOf(0); |
| |
| // The second part should be full right, but due to roundoff, |
| // the left channel won't be exactly zero. Compare the left |
| // channel against zero with a threshold instead. |
| let tail = c0.slice(panFrame); |
| let zero = new Float32Array(tail.length); |
| |
| should(c0.slice(panFrame), 'Mono: Left channel, pan = 1: ') |
| .beCloseToArray(zero, {absoluteThreshold: 6.1233e-17}); |
| should(c1.slice(panFrame), 'Mono: Right channel, pan = 1:') |
| .beConstantValueOf(1); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.define( |
| { |
| label: 'test stereo input', |
| description: |
| 'Test StereoPanner with stereo input has no dezippering' |
| }, |
| (task, should) => { |
| let context = new OfflineAudioContext(2, sampleRate, sampleRate); |
| |
| // Create stereo source from two constant source nodes. |
| let s0 = new ConstantSourceNode(context, {offset: 1}); |
| let s1 = new ConstantSourceNode(context, {offset: 2}); |
| let merger = new ChannelMergerNode(context, {numberOfInputs: 2}); |
| |
| s0.connect(merger, 0, 0); |
| s1.connect(merger, 0, 1); |
| |
| let p = new StereoPannerNode(context, {pan: -1}); |
| |
| merger.connect(p).connect(context.destination); |
| s0.start(); |
| s1.start(); |
| |
| // Frame at which to change pan value. |
| let panFrame = 256; |
| context.suspend(panFrame / context.sampleRate) |
| .then(() => p.pan.value = 1) |
| .then(() => context.resume()); |
| |
| context.startRendering() |
| .then(renderedBuffer => { |
| let c0 = renderedBuffer.getChannelData(0); |
| let c1 = renderedBuffer.getChannelData(1); |
| |
| // The first part should be full left. |
| should( |
| c0.slice(0, panFrame), 'Stereo: Left channel, pan = -1: ') |
| .beConstantValueOf(3); |
| should( |
| c1.slice(0, panFrame), 'Stereo: Right channel, pan = -1:') |
| .beConstantValueOf(0); |
| |
| // The second part should be full right, but due to roundoff, |
| // the left channel won't be exactly zero. Compare the left |
| // channel against zero with a threshold instead. |
| let tail = c0.slice(panFrame); |
| let zero = new Float32Array(tail.length); |
| |
| should(c0.slice(panFrame), 'Stereo: Left channel, pan = 1: ') |
| .beCloseToArray(zero, {absoluteThreshold: 6.1233e-17}); |
| should(c1.slice(panFrame), 'Stereo: Right channel, pan = 1:') |
| .beConstantValueOf(3); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.define( |
| { |
| label: 'test mono input setValue', |
| description: 'Test StereoPanner with mono input value setter ' + |
| 'vs setValueAtTime' |
| }, |
| (task, should) => { |
| let context = new OfflineAudioContext(4, sampleRate, sampleRate); |
| |
| let src = new OscillatorNode(context); |
| |
| src.start(); |
| testWithSetValue(context, src, should, { |
| prefix: 'Mono' |
| }).then(() => task.done()); |
| }); |
| |
| audit.define( |
| { |
| label: 'test stereo input setValue', |
| description: 'Test StereoPanner with mono input value setter ' + |
| ' vs setValueAtTime' |
| }, |
| (task, should) => { |
| let context = new OfflineAudioContext(4, sampleRate, sampleRate); |
| |
| let src0 = new OscillatorNode(context, {frequency: 800}); |
| let src1 = new OscillatorNode(context, {frequency: 250}); |
| let merger = new ChannelMergerNode(context, {numberOfChannels: 2}); |
| |
| src0.connect(merger, 0, 0); |
| src1.connect(merger, 0, 1); |
| |
| src0.start(); |
| src1.start(); |
| |
| testWithSetValue(context, merger, should, { |
| prefix: 'Stereo' |
| }).then(() => task.done()); |
| }); |
| |
| audit.define( |
| { |
| label: 'test mono input automation', |
| description: 'Test StereoPanner with mono input and automation' |
| }, |
| (task, should) => { |
| let context = new OfflineAudioContext(4, sampleRate, sampleRate); |
| |
| let src0 = new OscillatorNode(context, {frequency: 800}); |
| let src1 = new OscillatorNode(context, {frequency: 250}); |
| let merger = new ChannelMergerNode(context, {numberOfChannels: 2}); |
| |
| src0.connect(merger, 0, 0); |
| src1.connect(merger, 0, 1); |
| |
| src0.start(); |
| src1.start(); |
| |
| let mod = new OscillatorNode(context, {frequency: 100}); |
| mod.start(); |
| |
| testWithSetValue(context, merger, should, { |
| prefix: 'Modulated Stereo', |
| modulator: (testNode, refNode) => { |
| mod.connect(testNode.pan); |
| mod.connect(refNode.pan); |
| } |
| }).then(() => task.done()); |
| }); |
| |
| |
| function testWithSetValue(context, src, should, options) { |
| let merger = new ChannelMergerNode( |
| context, {numberOfInputs: context.destination.channelCount}); |
| merger.connect(context.destination); |
| |
| let pannerRef = new StereoPannerNode(context, {pan: -0.3}); |
| let pannerTest = |
| new StereoPannerNode(context, {pan: pannerRef.pan.value}); |
| |
| let refSplitter = |
| new ChannelSplitterNode(context, {numberOfOutputs: 2}); |
| let testSplitter = |
| new ChannelSplitterNode(context, {numberOfOutputs: 2}); |
| |
| pannerRef.connect(refSplitter); |
| pannerTest.connect(testSplitter); |
| |
| testSplitter.connect(merger, 0, 0); |
| testSplitter.connect(merger, 1, 1); |
| refSplitter.connect(merger, 0, 2); |
| refSplitter.connect(merger, 1, 3); |
| |
| src.connect(pannerRef); |
| src.connect(pannerTest); |
| |
| let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate; |
| // An arbitrary position, different from the default pan value. |
| let newPanPosition = .71; |
| |
| pannerRef.pan.setValueAtTime(newPanPosition, changeTime); |
| context.suspend(changeTime) |
| .then(() => pannerTest.pan.value = newPanPosition) |
| .then(() => context.resume()); |
| |
| if (options.modulator) { |
| options.modulator(pannerTest, pannerRef); |
| } |
| return context.startRendering().then(renderedBuffer => { |
| let actual = new Array(2); |
| let expected = new Array(2); |
| |
| actual[0] = renderedBuffer.getChannelData(0); |
| actual[1] = renderedBuffer.getChannelData(1); |
| expected[0] = renderedBuffer.getChannelData(2); |
| expected[1] = renderedBuffer.getChannelData(3); |
| |
| let label = ['Left', 'Right']; |
| |
| for (let k = 0; k < 2; ++k) { |
| let match = |
| should( |
| actual[k], |
| options.prefix + ' ' + label[k] + ' .value setter output') |
| .beCloseToArray(expected[k], {absoluteThreshold: 1.192094e-7}); |
| should( |
| match, |
| options.prefix + ' ' + label[k] + |
| ' .value setter output matches setValueAtTime output') |
| .beTrue(); |
| } |
| |
| }); |
| } |
| |
| audit.run(); |
| </script> |
| </body> |
| </html> |