summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html')
-rw-r--r--dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html169
1 files changed, 169 insertions, 0 deletions
diff --git a/dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html b/dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html
new file mode 100644
index 0000000000..bede517b2e
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html
@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<title>Test up-mixing in ConvolverNode after ChannelInterpretation change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// This test is not in wpt because it requires that multiple changes to the
+// nodes in an AudioContext during a single event will be processed by the
+// audio thread in a single transaction. Gecko provides that, but this is not
+// currently required by the Web Audio API.
+
+const EPSILON = Math.pow(2, -23);
+// sampleRate is a power of two so that delay times are exact in base-2
+// floating point arithmetic.
+const SAMPLE_RATE = 32768;
+// Length of initial mono signal in frames, if the test has an initial mono
+// signal. This is more than one block to ensure that at least one block
+// will be mono, even if interpolation in the DelayNode means that stereo is
+// output one block earlier than if frames are delayed without interpolation.
+const MONO_FRAMES = 256;
+// Length of response buffer. This is greater than 1 to ensure that the
+// convolver has stereo output at least one block after stereo input is
+// disconnected.
+const RESPONSE_FRAMES = 2;
+
+function test_interpretation_change(t, initialInterpretation, initialMonoFrames)
+{
+ let context = new AudioContext({sampleRate: SAMPLE_RATE});
+
+ // Three independent signals. These are constant so that results are
+ // independent of the timing of the `ended` event.
+ let monoOffset = 0.25
+ let monoSource = new ConstantSourceNode(context, {offset: monoOffset});
+ let leftOffset = 0.125;
+ let rightOffset = 0.5;
+ let leftSource = new ConstantSourceNode(context, {offset: leftOffset});
+ let rightSource = new ConstantSourceNode(context, {offset: rightOffset});
+ monoSource.start();
+ leftSource.start();
+ rightSource.start();
+
+ let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2});
+ leftSource.connect(stereoMerger, 0, 0);
+ rightSource.connect(stereoMerger, 0, 1);
+
+ // The DelayNode initially has a single channel of silence, and so the
+ // output of the delay node is first mono silence (if there is a non-zero
+ // initialMonoFrames), then stereo. In Gecko, this triggers a convolver
+ // configuration that is different for different channelInterpretations.
+ let delay =
+ new DelayNode(context,
+ {maxDelayTime: MONO_FRAMES / context.sampleRate,
+ delayTime: initialMonoFrames / context.sampleRate});
+ stereoMerger.connect(delay);
+
+ // Two convolvers with the same impulse response. The test convolver will
+ // process a mix of stereo and mono signals. The reference convolver will
+ // always process stereo, including the up-mixed mono signal.
+ let response = new AudioBuffer({numberOfChannels: 1,
+ length: RESPONSE_FRAMES,
+ sampleRate: context.sampleRate});
+ response.getChannelData(0)[response.length - 1] = 1;
+
+ let testConvolver = new ConvolverNode(context,
+ {disableNormalization: true,
+ buffer: response});
+ testConvolver.channelInterpretation = initialInterpretation;
+ let referenceConvolver = new ConvolverNode(context,
+ {disableNormalization: true,
+ buffer: response});
+ // No need to set referenceConvolver.channelInterpretation because
+ // input is always stereo, due to up-mixing at gain node.
+ let referenceMixer = new GainNode(context);
+ referenceMixer.channelCount = 2;
+ referenceMixer.channelCountMode = "explicit";
+ referenceMixer.channelInterpretation = initialInterpretation;
+ referenceMixer.connect(referenceConvolver);
+
+ delay.connect(testConvolver);
+ delay.connect(referenceMixer);
+
+ monoSource.connect(testConvolver);
+ monoSource.connect(referenceMixer);
+
+ // A timer sends 'ended' when the convolvers are known to be processing
+ // stereo.
+ let timer = new ConstantSourceNode(context);
+ timer.start();
+ timer.stop((initialMonoFrames + 1) / context.sampleRate);
+
+ timer.onended = t.step_func(() => {
+ let changedInterpretation =
+ initialInterpretation == "speakers" ? "discrete" : "speakers";
+
+ // Switch channelInterpretation in test and reference paths.
+ testConvolver.channelInterpretation = changedInterpretation;
+ referenceMixer.channelInterpretation = changedInterpretation;
+
+ // Disconnect the stereo input from both test and reference convolvers.
+ // The disconnected convolvers will continue to output stereo for at least
+ // one frame. The test convolver will up-mix its mono input into its two
+ // buffers.
+ delay.disconnect();
+
+ // Capture the outputs in a script processor.
+ //
+ // The first two channels contain signal where some up-mixing occurs
+ // internally to the test convolver.
+ //
+ // The last two channels are expected to contain the same signal, but
+ // up-mixing was performed at a GainNode prior to convolution.
+ //
+ // Two stereo splitters will collect test and reference outputs.
+ let testSplitter =
+ new ChannelSplitterNode(context, {numberOfOutputs: 2});
+ let referenceSplitter =
+ new ChannelSplitterNode(context, {numberOfOutputs: 2});
+ testConvolver.connect(testSplitter);
+ referenceConvolver.connect(referenceSplitter);
+
+ let outputMerger = new ChannelMergerNode(context, {numberOfInputs: 4});
+ testSplitter.connect(outputMerger, 0, 0);
+ testSplitter.connect(outputMerger, 1, 1);
+ referenceSplitter.connect(outputMerger, 0, 2);
+ referenceSplitter.connect(outputMerger, 1, 3);
+
+ let processor = context.createScriptProcessor(256, 4, 0);
+ outputMerger.connect(processor);
+
+ processor.onaudioprocess = t.step_func_done((e) => {
+ e.target.onaudioprocess = null;
+ outputMerger.disconnect();
+
+ // The test convolver output is stereo for the first block.
+ let length = 128;
+
+ let buffer = e.inputBuffer;
+ let maxDiff = -1.0;
+ let frameIndex = 0;
+ let channelIndex = 0;
+ for (let c = 0; c < 2; ++c) {
+ let testOutput = buffer.getChannelData(0 + c);
+ let referenceOutput = buffer.getChannelData(2 + c);
+ for (var i = 0; i < length; ++i) {
+ var diff = Math.abs(testOutput[i] - referenceOutput[i]);
+ if (diff > maxDiff) {
+ maxDiff = diff;
+ frameIndex = i;
+ channelIndex = c;
+ }
+ }
+ }
+ assert_approx_equals(buffer.getChannelData(0 + channelIndex)[frameIndex],
+ buffer.getChannelData(2 + channelIndex)[frameIndex],
+ EPSILON,
+ `output at ${frameIndex} ` +
+ `in channel ${channelIndex}` );
+ });
+ });
+}
+
+async_test((t) => test_interpretation_change(t, "speakers", MONO_FRAMES),
+ "speakers to discrete, initially mono");
+async_test((t) => test_interpretation_change(t, "discrete", MONO_FRAMES),
+ "discrete to speakers");
+// Gecko uses a separate path for "speakers" initial up-mixing when the
+// convolver's first input is stereo, so test that separately.
+async_test((t) => test_interpretation_change(t, "speakers", 0),
+ "speakers to discrete, initially stereo");
+</script>