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
|
<!DOCTYPE html>
<html>
<head>
<title>
Test DelayNode Has No Dezippering
</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">
// The sample rate must be a power of two to avoid any round-off errors in
// computing when to suspend a context on a rendering quantum boundary.
// Otherwise this is pretty arbitrary.
let sampleRate = 16384;
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test0', description: 'Test DelayNode has no dezippering'},
(task, should) => {
let context = new OfflineAudioContext(1, sampleRate, sampleRate);
// Simple integer ramp for testing delay node
let buffer = new AudioBuffer(
{length: context.length, sampleRate: context.sampleRate});
let rampData = buffer.getChannelData(0);
for (let k = 0; k < rampData.length; ++k) {
rampData[k] = k + 1;
}
// |delay0Frame| is the initial delay in frames. |delay1Frame| is
// the new delay in frames. These must be integers.
let delay0Frame = 64;
let delay1Frame = 16;
let src = new AudioBufferSourceNode(context, {buffer: buffer});
let delay = new DelayNode(
context, {delayTime: delay0Frame / context.sampleRate});
src.connect(delay).connect(context.destination);
// After a render quantum, change the delay to |delay1Frame|.
context.suspend(RENDER_QUANTUM_FRAMES / context.sampleRate)
.then(() => {
delay.delayTime.value = delay1Frame / context.sampleRate;
})
.then(() => context.resume());
src.start();
context.startRendering()
.then(renderedBuffer => {
let renderedData = renderedBuffer.getChannelData(0);
// The first |delay0Frame| frames should be zero.
should(
renderedData.slice(0, delay0Frame),
'output[0:' + (delay0Frame - 1) + ']')
.beConstantValueOf(0);
// Now we have the ramp should show up from the delay.
let ramp0 =
new Float32Array(RENDER_QUANTUM_FRAMES - delay0Frame);
for (let k = 0; k < ramp0.length; ++k) {
ramp0[k] = rampData[k];
}
should(
renderedData.slice(delay0Frame, RENDER_QUANTUM_FRAMES),
'output[' + delay0Frame + ':' +
(RENDER_QUANTUM_FRAMES - 1) + ']')
.beEqualToArray(ramp0);
// After one rendering quantum, the delay is changed to
// |delay1Frame|.
let ramp1 =
new Float32Array(context.length - RENDER_QUANTUM_FRAMES);
for (let k = 0; k < ramp1.length; ++k) {
// ramp1[k] = 1 + k + RENDER_QUANTUM_FRAMES - delay1Frame;
ramp1[k] =
rampData[k + RENDER_QUANTUM_FRAMES - delay1Frame];
}
should(
renderedData.slice(RENDER_QUANTUM_FRAMES),
'output[' + RENDER_QUANTUM_FRAMES + ':]')
.beEqualToArray(ramp1);
})
.then(() => task.done());
});
audit.define(
{label: 'test1', description: 'Test value setter and setValueAtTime'},
(task, should) => {
testWithAutomation(should, {prefix: ''}).then(() => task.done());
});
audit.define(
{label: 'test2', description: 'Test value setter and modulation'},
(task, should) => {
testWithAutomation(should, {
prefix: 'With modulation: ',
modulator: true
}).then(() => task.done());
});
// Compare .value setter with setValueAtTime, Optionally allow modulation
// of |delayTime|.
function testWithAutomation(should, options) {
let prefix = options.prefix;
// Channel 0 is the output of delay node using the setter and channel 1
// is the output using setValueAtTime.
let context = new OfflineAudioContext(2, sampleRate, sampleRate);
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
let src = new OscillatorNode(context);
// |delay0Frame| is the initial delay value in frames. |delay1Frame| is
// the new delay in frames. The values here are constrained only by the
// constraints for a DelayNode. These are pretty arbitrary except we
// wanted them to be fractional so as not be on a frame boundary to
// test interpolation compared with |setValueAtTime()|..
let delay0Frame = 3.1;
let delay1Frame = 47.2;
let delayTest = new DelayNode(
context, {delayTime: delay0Frame / context.sampleRate});
let delayRef = new DelayNode(
context, {delayTime: delay0Frame / context.sampleRate});
src.connect(delayTest).connect(merger, 0, 0);
src.connect(delayRef).connect(merger, 0, 1);
if (options.modulator) {
// Fairly arbitrary modulation of the delay time, with a peak
// variation of 10 ms.
let mod = new OscillatorNode(context, {frequency: 1000});
let modGain = new GainNode(context, {gain: .01});
mod.connect(modGain);
modGain.connect(delayTest.delayTime);
modGain.connect(delayRef.delayTime);
mod.start();
}
// The time at which the delay time of |delayTest| node will be
// changed. This MUST be on a render quantum boundary, but is
// otherwise arbitrary.
let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate;
// Schedule the delay change on |delayRef| and also apply the value
// setter for |delayTest| at |changeTime|.
delayRef.delayTime.setValueAtTime(
delay1Frame / context.sampleRate, changeTime);
context.suspend(changeTime)
.then(() => {
delayTest.delayTime.value = delay1Frame / context.sampleRate;
})
.then(() => context.resume());
src.start();
return context.startRendering().then(renderedBuffer => {
let actual = renderedBuffer.getChannelData(0);
let expected = renderedBuffer.getChannelData(1);
let match = should(actual, prefix + '.value setter output')
.beEqualToArray(expected);
should(
match,
prefix + '.value setter output matches setValueAtTime output')
.beTrue();
});
}
audit.run();
</script>
</body>
</html>
|