blob: 6347242d046c8bd3b3504fa4f8f61aca29c1aa71 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<title>
Test Constructor: PeriodicWave
</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../resources/audit-util.js"></script>
<script src="../resources/audit.js"></script>
<script src="audionodeoptions.js"></script>
</head>
<body>
<script id="layout-test-code">
let context;
let audit = Audit.createTaskRunner();
audit.define('initialize', (task, should) => {
context = initializeContext(should);
task.done();
});
audit.define('invalid constructor', (task, should) => {
testInvalidConstructor(should, 'PeriodicWave', context);
task.done();
});
audit.define('default constructor', (task, should) => {
should(() => {
node = new PeriodicWave(context);
}, 'node = new PeriodicWave(context)').notThrow();
task.done();
});
audit.define('constructor with options', (task, should) => {
let node1;
let options = {real: [1, 1]};
should(
() => {
node1 = new PeriodicWave(context, options);
},
'node = new PeriodicWave(context, ' + JSON.stringify(options) + ')')
.notThrow();
should(node1 instanceof PeriodicWave, 'node1 instanceof PeriodicWave')
.beEqualTo(true);
let node2;
options = {imag: [1, 1]};
should(
() => {
node2 = new PeriodicWave(context, options);
},
'node2 = new PeriodicWave(context, ' + JSON.stringify(options) +
')')
.notThrow();
should(node2 instanceof PeriodicWave, 'node2 instanceof PeriodicWave')
.beEqualTo(true);
let node3;
options = {real: [1, 2], imag: [1, 1]};
should(
() => {
node3 = new PeriodicWave(context, options);
},
'node3 = new PeriodicWave(context, ' + JSON.stringify(options) +
')')
.notThrow();
should(node3 instanceof PeriodicWave, 'node3 instanceof PeriodicWave')
.beEqualTo(true);
task.done();
});
// The following test that the correct waveforms are produced when various
// possible PeriodicWave options are used. These are needed because it's
// the only way to tell if the various options were correctly applied.
// TODO(rtoy): These functionality tests should be moved out to a separate
// file.
audit.define('1: real periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {real: [0, 2]}, generateReference(Math.cos), 2.7143e-5)
.then(() => task.done());
});
audit.define('2: real periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {real: [0, 2], disableNormalization: false},
generateReference(Math.cos), 2.7143e-5)
.then(() => task.done());
});
audit.define('3: real periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {real: [0, 2], disableNormalization: true},
generateReference(x => 2 * Math.cos(x)), 5.4285e-5)
.then(() => task.done());
});
audit.define('1: imag periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {imag: [0, 2]}, generateReference(Math.sin), 2.7232e-5)
.then(() => task.done());
});
audit.define('2: imag periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {imag: [0, 2], disableNormalization: false},
generateReference(Math.sin), 2.7232e-5)
.then(() => task.done());
});
audit.define('3: imag periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {imag: [0, 2], disableNormalization: true},
generateReference(x => 2 * Math.sin(x)), 5.4464e-5)
.then(() => task.done());
});
audit.define('1: real/imag periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {
real: [0, 1],
imag: [0, 1],
},
generateReference(x => Math.SQRT1_2 * (Math.sin(x) + Math.cos(x))),
3.8371e-5)
.then(() => task.done());
});
audit.define('2: real/imag periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {real: [0, 1], imag: [0, 1], disableNormalization: false},
generateReference(x => Math.SQRT1_2 * (Math.sin(x) + Math.cos(x))),
2.7165e-5)
.then(() => task.done());
});
audit.define('3: real/imag periodicwave test', (task, should) => {
verifyPeriodicWaveOutput(
should, {real: [0, 1], imag: [0, 1], disableNormalization: true},
generateReference(x => Math.sin(x) + Math.cos(x)), 3.8416e-5)
.then(() => task.done());
});
// Returns a function that generates the expected reference array where
// the samples are generated by the function |gen|.
function generateReference(gen) {
return (length, freq, sampleRate) => {
let expected = new Float32Array(length);
let omega = 2 * Math.PI * freq / sampleRate;
for (let k = 0; k < length; ++k) {
expected[k] = gen(omega * k);
}
return expected;
};
}
// Verify that an oscillator constructed from the given periodic wave
// produces the expected result.
function verifyPeriodicWaveOutput(
should, waveOptions, expectedFunction, threshold) {
let node;
// Rather arbitrary sample rate and render length. Length doesn't have
// to be very long.
let sampleRate = 48000;
let renderLength = 0.25;
let testContext =
new OfflineAudioContext(1, renderLength * sampleRate, sampleRate);
let options = {
periodicWave: new PeriodicWave(testContext, waveOptions)
};
node = new OscillatorNode(testContext, options);
// Create the graph
node.connect(testContext.destination);
node.start();
return testContext.startRendering().then(function(resultBuffer) {
let actual = resultBuffer.getChannelData(0);
let expected = expectedFunction(
actual.length, node.frequency.value, testContext.sampleRate);
// Actual must match expected to within the (experimentally)
// determined threshold.
let message = '';
if (waveOptions.disableNormalization != undefined)
message =
'disableNormalization: ' + waveOptions.disableNormalization;
if (waveOptions.real) {
if (message.length > 0)
message += ', '
message += 'real: [' + waveOptions.real + ']';
}
if (waveOptions.imag) {
if (message.length > 0)
message += ', '
message += 'imag: [' + waveOptions.imag + ']';
}
should(actual, 'Oscillator with periodicWave {' + message + '}')
.beCloseToArray(expected, {absoluteThreshold: threshold});
});
}
// Verify that the default PeriodicWave produces a sine wave. Use a
// 2-channel context to verify this.
function sineWaveTest(should, waveFun, message) {
// Channel 0 is the output from the PeriodicWave, and channel 1 is the
// reference oscillator output.
let context = new OfflineAudioContext(2, 40000, 40000);
let oscRef =
new OscillatorNode(context, {type: 'sine', frequency: 440});
let wave = waveFun(context);
let oscTest =
new OscillatorNode(context, {frequency: 440, periodicWave: wave});
let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
oscRef.connect(merger, 0, 1);
oscTest.connect(merger, 0, 0);
merger.connect(context.destination);
oscRef.start();
oscTest.start();
return context.startRendering().then(output => {
// The output from the two channels MUST match exactly.
let actual = output.getChannelData(0);
let ref = output.getChannelData(1);
should(actual, message).beEqualToArray(ref);
});
}
audit.define('default wave', (task, should) => {
// Verify that the default PeriodicWave produces a sine wave.
sineWaveTest(
should, (context) => new PeriodicWave(context),
'new PeriodicWave(context) output')
.then(() => task.done());
});
audit.define('default wave (with dict)', (task, should) => {
// Verify that the default PeriodicWave produces a sine wave when the
// PeriodicWaveOptions dictionary is given, but real and imag members
// are not set.
sineWaveTest(
should, (context) => new PeriodicWave(context, {foo: 42}),
'new PeriodicWave(context, {foo: 42}) output')
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>