blob: bb193cf2dd2ba115e0820e7cae20b842ebe643a3 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<title>
Test OscillatorNode with Negative Frequency
</title>
<script src="../../imported/w3c/web-platform-tests/resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../resources/audit-util.js"></script>
<script src="../resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
// Some arbitrary sample rate for the offline context. But it MUST be
// at least twice the oscillator frequency that we're using for the
// test. (Currently 440 Hz.)
let sampleRate = 16000;
// A fairly arbitrary duration that should have at least 1-2 sample
// periods of the oscillator (at a nominal 440 Hz).
let renderDuration = 0.1;
let renderFrames = renderDuration * sampleRate;
let audit = Audit.createTaskRunner();
audit.define('sine', (task, should) => {
runTest(should, {
message: 'Sum of positive and negative frequency sine oscillators',
type: 'sine',
threshold: 1.5349e-6
}).then(() => task.done());
});
audit.define('square', (task, should) => {
runTest(should, {
message: 'Sum of positive and negative frequency square oscillators',
type: 'square',
threshold: 1.1496e-5
}).then(() => task.done());
});
audit.define('sawtooth', (task, should) => {
runTest(should, {
message:
'Sum of positive and negative frequency sawtooth oscillators',
type: 'sawtooth',
threshold: 1.1506e-5
}).then(() => task.done());
});
audit.define('triangle', (task, should) => {
runTest(should, {
message:
'Sum of positive and negative frequency triangle oscillators',
type: 'triangle',
threshold: 1.0133e-5
}).then(() => task.done());
});
audit.define('auto-sawtooth', (task, should) => {
runTest(should, {
message:
'Sum of positive and negative frequency-ramped sawtooth oscillators',
type: 'sawtooth',
automation: {
type: 'linearRampToValueAtTime',
startTime: 0,
endTime: renderDuration / 2,
startFrequency: 440,
endFrequency: sampleRate / 4
},
threshold: 1.2368e-6
}).then(() => task.done());
});
audit.define('periodic-wave', (task, should) => {
// Test negative frequencies for a custom oscillator. Two channels are
// needed for the context; one for the expected result, and one for the
// actual, as explained below.
let context = new OfflineAudioContext(2, renderFrames, sampleRate);
let oscPositive = context.createOscillator();
let oscNegative = context.createOscillator();
// The Fourier coefficients for our custom oscillator. The actual
// values not important. The waveform for our custom oscillator is
//
// x(t) = sum(real[k]*cos(2*%pi*f*k/Fs), k, 1)
// + sum(imag[k]*sin(2*%pi*f*k/Fs), k, 0)
//
// With a negative frequency we have
//
// x(t) = sum(real[k]*cos(2*%pi*(-f)*k/Fs), k, 1)
// + sum(imag[k]*sin(2*%pi*(-f)*k/Fs), k, 0)
//
// = sum(real[k]*cos(2*%pi*f*k/Fs), k, 1)
// + sum((-imag[k])*sin(2*%pi*f*k/Fs), k, 0)
//
// That is, when the frequency is inverted, it behaves as if the
// coefficients of the imaginary part are inverted.
//
// Thus, the test is to create two custom oscillators. The second
// osillator uses the same PeriodicWave as the first except the
// imaginary coefficients are inverted. This second oscillator also
// gets a negative frequency. The combination of the two results in an
// oscillator that is the same as the first with gain of 2.
let real = [0, 1, 1];
let imag = [0, 1, 1];
let wavePositive = context.createPeriodicWave(
Float32Array.from(real), Float32Array.from(imag));
let waveNegative = context.createPeriodicWave(
Float32Array.from(real), Float32Array.from(imag.map(x => -x)));
oscPositive.setPeriodicWave(wavePositive);
oscNegative.setPeriodicWave(waveNegative);
oscPositive.frequency.value = 440;
oscNegative.frequency.value = -oscPositive.frequency.value;
let merger = context.createChannelMerger(2);
let gain = context.createGain();
// As explained above, the expected result should be positive frequency
// oscillator but with a gain of 2.
gain.gain.value = 2;
oscPositive.connect(gain);
gain.connect(merger, 0, 0);
// Sum the positive and negative frequency oscillators by using the same
// input to the merger.
oscPositive.connect(merger, 0, 1);
oscNegative.connect(merger, 0, 1);
merger.connect(context.destination);
oscPositive.start();
oscNegative.start();
context.startRendering()
.then(function(buffer) {
let expected = buffer.getChannelData(0);
let actual = buffer.getChannelData(1);
should(
actual,
'Sum of positive and negative frequency custom oscillators')
.beCloseToArray(expected, {absoluteThreshold: 2.2352e-6});
})
.then(() => task.done());
});
audit.run();
function runTest(should, options) {
// To test if negative frequencies work, create two oscillators. One
// has a positive frequency and the other has a negative frequency.
// Sum the oscillator outputs; the output should be zero because all of
// the builtin oscillator types are odd functions of frequency.
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
let oscPositive = context.createOscillator();
let oscNegative = context.createOscillator();
oscPositive.type = options.type;
oscNegative.type = oscPositive.type;
if (options.automation) {
let {type, startTime, endTime, startFrequency, endFrequency} =
options.automation;
oscPositive.frequency.setValueAtTime(startFrequency, startTime);
oscPositive.frequency[type](endFrequency, endTime)
oscNegative.frequency.setValueAtTime(-startFrequency, startTime);
oscNegative.frequency[type](-endFrequency, endTime)
} else {
oscPositive.frequency.value = 440;
oscNegative.frequency.value = -oscPositive.frequency.value;
}
oscPositive.connect(context.destination);
oscNegative.connect(context.destination);
oscPositive.start();
oscNegative.start();
return context.startRendering().then(function(buffer) {
let result = buffer.getChannelData(0);
should(result, options.message)
.beCloseToArray(
new Float32Array(result.length),
{absoluteThreshold: options.threshold || 0});
});
}
</script>
</body>
</html>