| <!doctype html> |
| <html> |
| <head> |
| <title> |
| Test Sub-Sample Accurate Stitching of ABSNs |
| </title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/webaudio/resources/audit-util.js"></script> |
| <script src="/webaudio/resources/audit.js"></script> |
| </head> |
| <body> |
| <script> |
| let audit = Audit.createTaskRunner(); |
| |
| audit.define( |
| { |
| label: 'buffer-stitching-1', |
| description: 'Subsample buffer stitching, same rates' |
| }, |
| (task, should) => { |
| const sampleRate = 44100; |
| const bufferRate = 44100; |
| const bufferLength = 30; |
| |
| // Experimentally determined thresholds. DO NOT relax these values |
| // to far from these values to make the tests pass. |
| const errorThreshold = 9.0957e-5; |
| const snrThreshold = 85.580; |
| |
| // Informative message |
| should(sampleRate, 'Test 1: context.sampleRate') |
| .beEqualTo(sampleRate); |
| testBufferStitching(sampleRate, bufferRate, bufferLength) |
| .then(resultBuffer => { |
| const actual = resultBuffer.getChannelData(0); |
| const expected = resultBuffer.getChannelData(1); |
| should( |
| actual, |
| `Stitched sine-wave buffers at sample rate ${bufferRate}`) |
| .beCloseToArray( |
| expected, {absoluteThreshold: errorThreshold}); |
| const SNR = 10 * Math.log10(computeSNR(actual, expected)); |
| should(SNR, `SNR (${SNR} dB)`) |
| .beGreaterThanOrEqualTo(snrThreshold); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.define( |
| { |
| label: 'buffer-stitching-2', |
| description: 'Subsample buffer stitching, different rates' |
| }, |
| (task, should) => { |
| const sampleRate = 44100; |
| const bufferRate = 43800; |
| const bufferLength = 30; |
| |
| // Experimentally determined thresholds. DO NOT relax these values |
| // to far from these values to make the tests pass. |
| const errorThreshold = 3.8986e-3; |
| const snrThreshold = 65.737; |
| |
| // Informative message |
| should(sampleRate, 'Test 2: context.sampleRate') |
| .beEqualTo(sampleRate); |
| testBufferStitching(sampleRate, bufferRate, bufferLength) |
| .then(resultBuffer => { |
| const actual = resultBuffer.getChannelData(0); |
| const expected = resultBuffer.getChannelData(1); |
| should( |
| actual, |
| `Stitched sine-wave buffers at sample rate ${bufferRate}`) |
| .beCloseToArray( |
| expected, {absoluteThreshold: errorThreshold}); |
| const SNR = 10 * Math.log10(computeSNR(actual, expected)); |
| should(SNR, `SNR (${SNR} dB)`) |
| .beGreaterThanOrEqualTo(snrThreshold); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.run(); |
| |
| // Create graph to test stitching of consecutive ABSNs. The context rate |
| // is |sampleRate|, and the buffers have a fixed length of |bufferLength| |
| // and rate of |bufferRate|. The |bufferRate| should not be too different |
| // from |sampleRate| because of interpolation of the buffer to the context |
| // rate. |
| function testBufferStitching(sampleRate, bufferRate, bufferLength) { |
| // The context for testing. Channel 0 contains the output from |
| // stitching all the buffers together, and channel 1 contains the |
| // expected output. |
| const context = new OfflineAudioContext( |
| {numberOfChannels: 2, length: sampleRate, sampleRate: sampleRate}); |
| |
| const merger = new ChannelMergerNode( |
| context, {numberOfInputs: context.destination.channelCount}); |
| |
| merger.connect(context.destination); |
| |
| // The reference is a sine wave at 440 Hz. |
| const ref = new OscillatorNode(context, {frequency: 440, type: 'sine'}); |
| ref.connect(merger, 0, 1); |
| ref.start(); |
| |
| // The test signal is a bunch of short AudioBufferSources containing |
| // bits of a sine wave. |
| let waveSignal = new Float32Array(context.length); |
| const omega = 2 * Math.PI / bufferRate * ref.frequency.value; |
| for (let k = 0; k < context.length; ++k) { |
| waveSignal[k] = Math.sin(omega * k); |
| } |
| |
| // Slice the sine wave into many little buffers to be assigned to ABSNs |
| // that are started at the appropriate times to produce a final sine |
| // wave. |
| for (let k = 0; k < context.length; k += bufferLength) { |
| const buffer = |
| new AudioBuffer({length: bufferLength, sampleRate: bufferRate}); |
| buffer.copyToChannel(waveSignal.slice(k, k + bufferLength), 0); |
| |
| const src = new AudioBufferSourceNode(context, {buffer: buffer}); |
| src.connect(merger, 0, 0); |
| src.start(k / bufferRate); |
| } |
| |
| return context.startRendering(); |
| } |
| </script> |
| </body> |
| </html> |