// 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]); } } }); }