blob: 930728dc369e434c832ff23cb7cc3cacc8fc4baa [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<title>
Test Convolver on Real-time Context
</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>
<script src="/webaudio/resources/convolution-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
const audit = Audit.createTaskRunner();
// Choose a length that is larger enough to cause multiple threads to be
// used in the convolver. For browsers that don't support this, this
// value doesn't matter.
const pulseLength = 16384;
// The computed SNR should be at least this large. This value depends on
// teh platform and browser. Don't set this value to be to much lower
// than this. It probably indicates a fairly inaccurate convolver or
// constant source node automations that should be fixed instead.
const minRequiredSNR = 83;
// To test the real-time convolver, we convolve two square pulses together
// to produce a triangular pulse. To verify the result is correct we
// compare it against a constant source node configured to generate the
// expected ramp.
audit.define(
{label: 'test', description: 'Test convolver with real-time context'},
(task, should) => {
// Use a power of two for the sample rate to eliminate round-off in
// computing times from frames.
const context = new AudioContext({sampleRate: 16384});
// Square pulse for the convolver impulse response.
const squarePulse = new AudioBuffer(
{length: pulseLength, sampleRate: context.sampleRate});
squarePulse.getChannelData(0).fill(1);
const convolver = new ConvolverNode(
context, {buffer: squarePulse, disableNormalization: true});
// Square pulse for testing
const srcSquare = new ConstantSourceNode(context, {offset: 0});
srcSquare.connect(convolver);
// Reference ramp. Automations on this constant source node will
// generate the desired ramp.
const srcRamp = new ConstantSourceNode(context, {offset: 0});
// Use these gain nodes to compute the difference between the
// convolver output and the expected ramp to create the error
// signal.
const inverter = new GainNode(context, {gain: -1});
const sum = new GainNode(context, {gain: 1});
convolver.connect(sum);
srcRamp.connect(inverter).connect(sum);
// Square the error signal using this gain node.
const squarer = new GainNode(context, {gain: 0});
sum.connect(squarer);
sum.connect(squarer.gain);
// Merge the error signal and the square source so we can integrate
// the error signal to find an SNR.
const merger = new ChannelMergerNode(context, {numberOfInputs: 2});
squarer.connect(merger, 0, 0);
srcSquare.connect(merger, 0, 1);
// For simplicity, use a ScriptProcessor to integrate the error
// signal. The square pulse signal is used as a gate over which the
// integration is done. When the pulse ends, the SNR is computed
// and the test ends.
// |doSum| is used to determine when to integrate and when it
// becomes false, it signals the end of integration.
let doSum = false;
// |signalSum| is the energy in the square pulse. |errorSum| is the
// energy in the error signal.
let signalSum = 0;
let errorSum = 0;
let spn = context.createScriptProcessor(0, 2, 1);
spn.onaudioprocess = (event) => {
// Sum the values on the first channel when the second channel is
// not zero. When the second channel goes from non-zero to 0,
// dump the value out and terminate the test.
let c0 = event.inputBuffer.getChannelData(0);
let c1 = event.inputBuffer.getChannelData(1);
for (let k = 0; k < c1.length; ++k) {
if (c1[k] == 0) {
if (doSum) {
doSum = false;
// Square wave is now silent and we were integration, so we
// can stop now and verify the SNR.
should(10 * Math.log10(signalSum / errorSum), 'SNR')
.beGreaterThanOrEqualTo(minRequiredSNR);
spn.onaudioprocess = null;
task.done();
}
} else {
// Signal is non-zero so sum up the values.
doSum = true;
errorSum += c0[k];
signalSum += c1[k] * c1[k];
}
}
};
merger.connect(spn).connect(context.destination);
// Schedule everything to start a bit in the futurefrom now, and end
// pulseLength frames later.
let now = context.currentTime;
// |startFrame| is the number of frames to schedule ahead for
// testing.
const startFrame = 512;
const startTime = startFrame / context.sampleRate;
const pulseDuration = pulseLength / context.sampleRate;
// Create a square pulse in the constant source node.
srcSquare.offset.setValueAtTime(1, now + startTime);
srcSquare.offset.setValueAtTime(0, now + startTime + pulseDuration);
// Create the reference ramp.
srcRamp.offset.setValueAtTime(1, now + startTime);
srcRamp.offset.linearRampToValueAtTime(
pulseLength,
now + startTime + pulseDuration - 1 / context.sampleRate);
srcRamp.offset.linearRampToValueAtTime(
0,
now + startTime + 2 * pulseDuration - 1 / context.sampleRate);
// Start the ramps!
srcRamp.start();
srcSquare.start();
});
audit.run();
</script>
</body>
</html>