Source code

Revision control

Copy as Markdown

Other Tools

// Test k-rate vs a-rate AudioParams.
//
// |options| describes how the testing of the AudioParam should be done:
//
// sourceNodeName: name of source node to use for testing; defaults to
// 'OscillatorNode'. If set to 'none', then no source node
// is created for testing and it is assumed that the AudioNode
// under test are sources and need to be started.
// verifyPieceWiseConstant: if true, verify that the k-rate output is
// piecewise constant for each render quantum.
// nodeName: name of the AudioNode to be tested
// nodeOptions: options to be used in the AudioNode constructor
//
// prefix: Prefix for all output messages (to make them unique for
// testharness)
//
// rateSettings: A vector of dictionaries specifying how to set the automation
// rate(s):
// name: Name of the AudioParam
// value: The automation rate for the AudioParam given by |name|.
//
// automations: A vector of dictionaries specifying how to automate each
// AudioParam:
// name: Name of the AudioParam
//
// methods: A vector of dictionaries specifying the automation methods to
// be used for testing:
// name: Automation method to call
// options: Arguments for the automation method
//
// Testing is somewhat rudimentary. We create two nodes of the same type. One
// node uses the default automation rates for each AudioParam (expecting them to
// be a-rate). The second node sets the automation rate of AudioParams to
// "k-rate". The set is speciified by |options.rateSettings|.
//
// For both of these nodes, the same set of automation methods (given by
// |options.automations|) is applied. A simple oscillator is connected to each
// node which in turn are connected to different channels of an offline context.
// Channel 0 is the k-rate node output; channel 1, the a-rate output; and
// channel 3, the difference between the outputs.
//
// Success is declared if the difference signal is not exactly zero. This means
// the the automations did different things, as expected.
//
// The promise from |startRendering| is returned.
function doTest(context, should, options) {
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
let src = null;
// Skip creating a source to drive the graph if |sourceNodeName| is 'none'.
// If |sourceNodeName| is given, use that, else default to OscillatorNode.
if (options.sourceNodeName !== 'none') {
src = new window[options.sourceNodeName || 'OscillatorNode'](context);
}
let kRateNode = new window[options.nodeName](context, options.nodeOptions);
let aRateNode = new window[options.nodeName](context, options.nodeOptions);
let inverter = new GainNode(context, {gain: -1});
// Set kRateNode filter to use k-rate params.
options.rateSettings.forEach(setting => {
kRateNode[setting.name].automationRate = setting.value;
// Mostly for documentation in the output. These should always
// pass.
should(
kRateNode[setting.name].automationRate,
`${options.prefix}: Setting ${
setting.name
}.automationRate to "${setting.value}"`)
.beEqualTo(setting.value);
});
// Run through all automations for each node separately. (Mostly to keep
// output of automations together.)
options.automations.forEach(param => {
param.methods.forEach(method => {
// Most for documentation in the output. These should never throw.
let message = `${param.name}.${method.name}(${method.options})`
should(() => {
kRateNode[param.name][method.name](...method.options);
}, options.prefix + ': k-rate node: ' + message).notThrow();
});
});
options.automations.forEach(param => {
param.methods.forEach(method => {
// Most for documentation in the output. These should never throw.
let message = `${param.name}.${method.name}(${method.options})`
should(() => {
aRateNode[param.name][method.name](...method.options);
}, options.prefix + ': a-rate node:' + message).notThrow();
});
});
// Connect the source, if specified.
if (src) {
src.connect(kRateNode);
src.connect(aRateNode);
}
// The k-rate result is channel 0, and the a-rate result is channel 1.
kRateNode.connect(merger, 0, 0);
aRateNode.connect(merger, 0, 1);
// Compute the difference between the a-rate and k-rate results and send
// that to channel 2.
kRateNode.connect(merger, 0, 2);
aRateNode.connect(inverter).connect(merger, 0, 2);
if (src) {
src.start();
} else {
// If there's no source, then assume the test nodes are sources and start
// them.
kRateNode.start();
aRateNode.start();
}
return context.startRendering().then(renderedBuffer => {
let kRateOutput = renderedBuffer.getChannelData(0);
let aRateOutput = renderedBuffer.getChannelData(1);
let diff = renderedBuffer.getChannelData(2);
// Some informative messages to print out values of the k-rate and
// a-rate outputs. These should always pass.
should(
kRateOutput, `${options.prefix}: Output of k-rate ${options.nodeName}`)
.beEqualToArray(kRateOutput);
should(
aRateOutput, `${options.prefix}: Output of a-rate ${options.nodeName}`)
.beEqualToArray(aRateOutput);
// The real test. If k-rate AudioParam is working correctly, the
// k-rate result MUST differ from the a-rate result.
should(
diff,
`${
options.prefix
}: Difference between a-rate and k-rate ${options.nodeName}`)
.notBeConstantValueOf(0);
if (options.verifyPieceWiseConstant) {
// Verify that the output from the k-rate parameter is step-wise
// constant.
for (let k = 0; k < kRateOutput.length; k += 128) {
should(
kRateOutput.slice(k, k + 128),
`${options.prefix} k-rate output [${k}: ${k + 127}]`)
.beConstantValueOf(kRateOutput[k]);
}
}
});
}