| <!doctype html> |
| <html> |
| <head> |
| <title>k-rate AudioParams with inputs for AudioBufferSourceNode</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/webaudio/resources/audit.js"></script> |
| <script src="/webaudio/resources/audit-util.js"></script> |
| </head> |
| |
| <body> |
| <script> |
| let audit = Audit.createTaskRunner(); |
| |
| // Fairly abitrary sampleRate and somewhat duration |
| const sampleRate = 8000; |
| const testDuration = 0.25; |
| |
| [['playbackRate', [1, 0], [2, testDuration]], |
| ['detune', [-1200, 0], [1200, testDuration]]] |
| .forEach(param => { |
| audit.define( |
| {label: param[0], description: `AudioBufferSource ${param[0]}`}, |
| async (task, should) => { |
| await doTest(should, { |
| prefix: task.label, |
| paramName: param[0], |
| startValue: param[1], |
| endValue: param[2] |
| }); |
| task.done(); |
| }); |
| }); |
| |
| audit.run(); |
| |
| async function doTest(should, options) { |
| // Test k-rate automation of AudioBufferSourceNode with connected |
| // input. |
| // |
| // A reference source node is created with an automation on the |
| // selected AudioParam. For simplicity, we just use a linear ramp from |
| // the minValue to the maxValue of the AudioParam. |
| // |
| // The test node has an input signal connected to the AudioParam. This |
| // input signal is created to match the automation on the reference |
| // node. |
| // |
| // Finally, the output from the two nodes must be identical if k-rate |
| // inputs are working correctly. |
| // |
| // Options parameter is a dictionary with the following required |
| // members: |
| // prefix - prefix to use for the messages. |
| // paramName - Name of the AudioParam to be tested |
| |
| let {prefix, paramName, startValue, endValue} = options; |
| |
| let context = new OfflineAudioContext({ |
| numberOfChannels: 2, |
| sampleRate: sampleRate, |
| length: testDuration * sampleRate |
| }); |
| |
| let merger = new ChannelMergerNode( |
| context, {numberOfInputs: context.destination.channelCount}); |
| merger.connect(context.destination); |
| |
| // Linear ramp to use for the buffer sources |
| let ramp = createLinearRampBuffer(context, context.length); |
| |
| // Create the reference and test nodes. |
| let refNode; |
| let tstNode; |
| |
| const nodeOptions = {buffer: ramp}; |
| |
| should( |
| () => refNode = new AudioBufferSourceNode(context, nodeOptions), |
| `${prefix}: refNode = new AudioBufferSourceNode(context, ${ |
| JSON.stringify(nodeOptions)})`) |
| .notThrow(); |
| |
| should( |
| () => tstNode = new AudioBufferSourceNode(context, nodeOptions), |
| `${prefix}: tstNode = new AudioBufferSourceNode(context, ${ |
| JSON.stringify(nodeOptions)})`) |
| .notThrow(); |
| |
| |
| // Automate the AudioParam of the reference node with a linear ramp |
| should( |
| () => refNode[paramName].setValueAtTime(...startValue), |
| `${prefix}: refNode[${paramName}].setValueAtTime(${ |
| startValue[0]}, ${startValue[1]})`) |
| .notThrow(); |
| |
| should( |
| () => refNode[paramName].linearRampToValueAtTime(...endValue), |
| `${prefix}: refNode[${paramName}].linearRampToValueAtTime(${ |
| endValue[0]}, ${endValue[1]})`) |
| .notThrow(); |
| |
| |
| // Create the input node and automate it so that it's output when added |
| // to the intrinsic value of the AudioParam we get the same values as |
| // the automations on the reference node. |
| |
| // Compute the start and end values based on the defaultValue of the |
| // param and the desired startValue and endValue. The input is added to |
| // the intrinsic value of the AudioParam, so we need to account for |
| // that. |
| |
| let mod; |
| should( |
| () => mod = new ConstantSourceNode(context, {offset: 0}), |
| `${prefix}: mod = new ConstantSourceNode(context, {offset: 0})`) |
| .notThrow(); |
| |
| let modStart = startValue[0] - refNode[paramName].defaultValue; |
| let modEnd = endValue[0] - refNode[paramName].defaultValue; |
| should( |
| () => mod.offset.setValueAtTime(modStart, startValue[1]), |
| `${prefix}: mod.offset.setValueAtTime(${modStart}, ${ |
| startValue[1]})`) |
| .notThrow(); |
| should( |
| () => mod.offset.linearRampToValueAtTime(modEnd, endValue[1]), |
| `${prefix}: mod.offset.linearRampToValueAtTime(${modEnd}, ${ |
| endValue[1]})`) |
| .notThrow(); |
| |
| // Connect up everything. |
| should( |
| () => mod.connect(tstNode[paramName]), |
| `${prefix}: mod.connect(tstNode[${paramName}])`) |
| .notThrow(); |
| |
| refNode.connect(merger, 0, 0); |
| tstNode.connect(merger, 0, 1); |
| |
| // Go! |
| refNode.start(); |
| tstNode.start(); |
| mod.start(); |
| |
| const buffer = await context.startRendering(); |
| let expected = buffer.getChannelData(0); |
| let actual = buffer.getChannelData(1); |
| |
| // Quick sanity check that output isn't zero. This means we messed up |
| // the connections or automations or the buffer source. |
| should(expected, `Expected k-rate ${paramName} AudioParam with input`) |
| .notBeConstantValueOf(0); |
| should(actual, `Actual k-rate ${paramName} AudioParam with input`) |
| .notBeConstantValueOf(0); |
| |
| // The expected and actual results must be EXACTLY the same. |
| should(actual, `k-rate ${paramName} AudioParam with input`) |
| .beCloseToArray(expected, {absoluteThreshold: 0}); |
| } |
| </script> |
| </body> |
| </html> |