blob: c41f4c908056aba804bc691c335e528fd1e2f44f [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<title>
Basic GainNode Functionality
</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">
// Tests that GainNode is properly scaling the gain. We'll render 11
// notes, starting at a gain of 1.0, decreasing in gain by 0.1. The 11th
// note will be of gain 0.0, so it should be silent (at the end in the
// rendered output).
let audit = Audit.createTaskRunner();
// Use a power of two to eliminate any round-off when converting frame to
// time.
let sampleRate = 32768;
// Make sure the buffer duration and spacing are all exact frame lengths
// so that the note spacing is also on frame boundaries to eliminate
// sub-sample accurate start of a ABSN.
let bufferDurationSeconds = Math.floor(0.125 * sampleRate) / sampleRate;
let numberOfNotes = 11;
// Leave about 20ms of silence, being sure this is an exact frame
// duration.
let noteSilence = Math.floor(0.020 * sampleRate) / sampleRate;
let noteSpacing = bufferDurationSeconds + noteSilence;
let lengthInSeconds = numberOfNotes * noteSpacing;
let context = 0;
let sinWaveBuffer = 0;
// Create a stereo AudioBuffer of duration |lengthInSeconds| consisting of
// a pure sine wave with the given |frequency|. Both channels contain the
// same data.
function createSinWaveBuffer(lengthInSeconds, frequency) {
let audioBuffer =
context.createBuffer(2, lengthInSeconds * sampleRate, sampleRate);
let n = audioBuffer.length;
let channelL = audioBuffer.getChannelData(0);
let channelR = audioBuffer.getChannelData(1);
for (let i = 0; i < n; ++i) {
channelL[i] = Math.sin(frequency * 2.0 * Math.PI * i / sampleRate);
channelR[i] = channelL[i];
}
return audioBuffer;
}
function playNote(time, gain, merger) {
let source = context.createBufferSource();
source.buffer = sinWaveBuffer;
let gainNode = context.createGain();
gainNode.gain.value = gain;
let sourceSplitter = context.createChannelSplitter(2);
let gainSplitter = context.createChannelSplitter(2);
// Split the stereo channels from the source output and the gain output
// and merge them into the desired channels of the merger.
source.connect(gainNode).connect(gainSplitter);
source.connect(sourceSplitter);
gainSplitter.connect(merger, 0, 0);
gainSplitter.connect(merger, 1, 1);
sourceSplitter.connect(merger, 0, 2);
sourceSplitter.connect(merger, 1, 3);
source.start(time);
}
audit.define(
{label: 'create context', description: 'Create context for test'},
function(task, should) {
// Create offline audio context.
context = new OfflineAudioContext(
4, sampleRate * lengthInSeconds, sampleRate);
task.done();
});
audit.define(
{label: 'test', description: 'GainNode functionality'},
function(task, should) {
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
// Create a buffer for a short "note".
sinWaveBuffer = createSinWaveBuffer(bufferDurationSeconds, 880.0);
let startTimes = [];
let gainValues = [];
// Render 11 notes, starting at a gain of 1.0, decreasing in gain by
// 0.1. The last note will be of gain 0.0, so shouldn't be
// perceptible in the rendered output.
for (let i = 0; i < numberOfNotes; ++i) {
let time = i * noteSpacing;
let gain = 1.0 - i / (numberOfNotes - 1);
startTimes.push(time);
gainValues.push(gain);
playNote(time, gain, merger);
}
context.startRendering()
.then(buffer => {
let actual0 = buffer.getChannelData(0);
let actual1 = buffer.getChannelData(1);
let reference0 = buffer.getChannelData(2);
let reference1 = buffer.getChannelData(3);
// It's ok to a frame too long since the sine pulses are
// followed by silence.
let bufferDurationFrames =
Math.ceil(bufferDurationSeconds * context.sampleRate);
// Apply the gains to the reference signal.
for (let k = 0; k < startTimes.length; ++k) {
// It's ok to be a frame early because the sine pulses are
// preceded by silence.
let startFrame =
Math.floor(startTimes[k] * context.sampleRate);
let gain = gainValues[k];
for (let n = 0; n < bufferDurationFrames; ++n) {
reference0[startFrame + n] *= gain;
reference1[startFrame + n] *= gain;
}
}
// Verify the channels are clsoe to the reference.
should(actual0, 'Left output from gain node')
.beCloseToArray(
reference0, {relativeThreshold: 1.1877e-7});
should(actual1, 'Right output from gain node')
.beCloseToArray(
reference1, {relativeThreshold: 1.1877e-7});
// Test the SNR too for both channels.
let snr0 = 10 * Math.log10(computeSNR(actual0, reference0));
let snr1 = 10 * Math.log10(computeSNR(actual1, reference1));
should(snr0, 'Left SNR (in dB)')
.beGreaterThanOrEqualTo(148.71);
should(snr1, 'Right SNR (in dB)')
.beGreaterThanOrEqualTo(148.71);
})
.then(() => task.done());
;
});
audit.run();
</script>
</body>
</html>