1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
|
<!DOCTYPE html>
<html>
<head>
<title>
Test Convolver Channel Outputs for Response with 1 channel
</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">
// Test various convolver configurations when the convolver response has
// one channel (mono).
//
// Fairly arbitrary sample rate, except that we want the rate to be a
// power of two so that 1/sampleRate is exactly respresentable as a
// single-precision float.
let sampleRate = 8192;
// A fairly arbitrary number of frames, except the number of frames should
// be more than a few render quanta.
let renderFrames = 10 * 128;
let audit = Audit.createTaskRunner();
// Convolver response
let response;
audit.define(
{
label: 'initialize',
description: 'Convolver response with one channel'
},
(task, should) => {
// Convolver response
should(
() => {
response = new AudioBuffer(
{numberOfChannels: 1, length: 2, sampleRate: sampleRate});
response.getChannelData(0)[1] = 1;
},
'new AudioBuffer({numberOfChannels: 1, length: 2, sampleRate: ' +
sampleRate + '})')
.notThrow();
task.done();
});
audit.define(
{label: '1-channel input', description: 'produces 1-channel output'},
(task, should) => {
// Create a 3-channel context: channel 0 = convolver under test,
// channel 1: test that convolver output is not stereo, channel 2:
// expected output. The context MUST be discrete so that the
// channels don't get mixed in some unexpected way.
let context = new OfflineAudioContext(3, renderFrames, sampleRate);
context.destination.channelInterpretation = 'discrete';
let src = new OscillatorNode(context);
let conv = new ConvolverNode(
context, {disableNormalization: true, buffer: response});
// Splitter node to verify that the output of the convolver is mono.
// channelInterpretation must be 'discrete' so we don't do any
// mixing of the input to the node.
let splitter = new ChannelSplitterNode(
context,
{numberOfOutputs: 2, channelInterpretation: 'discrete'});
// Final merger to feed all of the individual channels into the
// destination.
let merger = new ChannelMergerNode(context, {numberOfInputs: 3});
src.connect(conv).connect(splitter);
splitter.connect(merger, 0, 0);
splitter.connect(merger, 1, 1);
// The convolver response is a 1-sample delay. Use a delay node to
// implement this.
let delay =
new DelayNode(context, {delayTime: 1 / context.sampleRate});
src.connect(delay);
delay.connect(merger, 0, 2);
merger.connect(context.destination);
src.start();
context.startRendering()
.then(audioBuffer => {
// Extract out the three channels
let actual = audioBuffer.getChannelData(0);
let c1 = audioBuffer.getChannelData(1);
let expected = audioBuffer.getChannelData(2);
// c1 is expected to be zero.
should(c1, '1: Channel 1').beConstantValueOf(0);
// The expected and actual results should be identical
should(actual, 'Convolver output')
.beCloseToArray(expected, {absoluteThreshold: 4.1724e-7});
})
.then(() => task.done());
});
audit.define(
{label: '2-channel input', description: 'produces 2-channel output'},
(task, should) => {
downMixTest({numberOfInputs: 2, prefix: '2'}, should)
.then(() => task.done());
});
audit.define(
{
label: '3-channel input',
description: '3->2 downmix producing 2-channel output'
},
(task, should) => {
downMixTest({numberOfInputs: 3, prefix: '3'}, should)
.then(() => task.done());
});
audit.define(
{
label: '4-channel input',
description: '4->2 downmix producing 2-channel output'
},
(task, should) => {
downMixTest({numberOfInputs: 4, prefix: '4'}, should)
.then(() => task.done());
});
audit.define(
{
label: '5.1-channel input',
description: '5.1->2 downmix producing 2-channel output'
},
(task, should) => {
downMixTest({numberOfInputs: 6, prefix: '5.1'}, should)
.then(() => task.done());
});
function downMixTest(options, should) {
// Create an 4-channel offline context. The first two channels are for
// the stereo output of the convolver and the next two channels are for
// the reference stereo signal.
let context = new OfflineAudioContext(4, renderFrames, sampleRate);
context.destination.channelInterpretation = 'discrete';
// Create oscillators for use as the input. The type and frequency is
// arbitrary except that oscillators must be different.
let src = new Array(options.numberOfInputs);
for (let k = 0; k < src.length; ++k) {
src[k] = new OscillatorNode(
context, {type: 'square', frequency: 440 + 220 * k});
}
// Merger to combine the oscillators into one output stream.
let srcMerger =
new ChannelMergerNode(context, {numberOfInputs: src.length});
for (let k = 0; k < src.length; ++k) {
src[k].connect(srcMerger, 0, k);
}
// Convolver under test.
let conv = new ConvolverNode(
context, {disableNormalization: true, buffer: response});
srcMerger.connect(conv);
// Splitter to get individual channels of the convolver output so we can
// feed them (eventually) to the context in the right set of channels.
let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
conv.connect(splitter);
// Reference graph consists of a delay node to simulate the response of
// the convolver. (The convolver response is designed this way.)
let delay = new DelayNode(context, {delayTime: 1 / context.sampleRate});
// Gain node to mix the sources to stereo in the desired way. (Could be
// done in the delay node, but let's keep the mixing separated from the
// functionality.)
let gainMixer = new GainNode(
context, {channelCount: 2, channelCountMode: 'explicit'});
srcMerger.connect(gainMixer);
// Splitter to extract the channels of the reference signal.
let refSplitter =
new ChannelSplitterNode(context, {numberOfOutputs: 2});
gainMixer.connect(delay).connect(refSplitter);
// Final merger to bring back the individual channels from the convolver
// and the reference in the right order for the destination.
let finalMerger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
// First two channels are for the convolver output, and the next two are
// for the reference.
splitter.connect(finalMerger, 0, 0);
splitter.connect(finalMerger, 1, 1);
refSplitter.connect(finalMerger, 0, 2);
refSplitter.connect(finalMerger, 1, 3);
finalMerger.connect(context.destination);
// Start the sources at last.
for (let k = 0; k < src.length; ++k) {
src[k].start();
}
return context.startRendering().then(audioBuffer => {
// Extract the various channels out
let actual0 = audioBuffer.getChannelData(0);
let actual1 = audioBuffer.getChannelData(1);
let expected0 = audioBuffer.getChannelData(2);
let expected1 = audioBuffer.getChannelData(3);
// Verify that each output channel of the convolver matches
// the delayed signal from the reference
should(actual0, options.prefix + ': Channel 0')
.beEqualToArray(expected0);
should(actual1, options.prefix + ': Channel 1')
.beEqualToArray(expected1);
});
}
audit.run();
</script>
</body>
</html>
|