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