1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
// 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]);
}
}
});
}
|