152 lines
6.4 KiB
HTML
152 lines
6.4 KiB
HTML
<!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
|
|
// the 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.
|
|
//
|
|
// Any major change of operating system or CPU architecture might affect
|
|
// this value significantly. See: https://crbug.com/1339291
|
|
const minRequiredSNR = 68.40;
|
|
|
|
// 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>
|