| <!DOCTYPE html> |
| <html> |
| <head> |
| <title> |
| Test Basic Functionality of AudioBuffer.copyFromChannel and |
| AudioBuffer.copyToChannel |
| </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 id="layout-test-code"> |
| // Define utility routines. |
| |
| // Initialize the AudioBuffer |buffer| with a ramp signal on each channel. |
| // The ramp starts at channel number + 1. |
| function initializeAudioBufferRamp(buffer) { |
| for (let c = 0; c < buffer.numberOfChannels; ++c) { |
| let d = buffer.getChannelData(c); |
| for (let k = 0; k < d.length; ++k) { |
| d[k] = k + c + 1; |
| } |
| } |
| } |
| |
| // Create a Float32Array of length |length| and initialize the array to |
| // -1. |
| function createInitializedF32Array(length) { |
| let x = new Float32Array(length); |
| for (let k = 0; k < length; ++k) { |
| x[k] = -1; |
| } |
| return x; |
| } |
| |
| // Create a Float32Array of length |length| that is initialized to be a |
| // ramp starting at 1. |
| function createFloat32RampArray(length) { |
| let x = new Float32Array(length); |
| for (let k = 0; k < x.length; ++k) { |
| x[k] = k + 1; |
| } |
| |
| return x; |
| } |
| |
| // Test that the array |x| is a ramp starting at value |start| of length |
| // |length|, starting at |startIndex| in the array. |startIndex| is |
| // optional and defaults to 0. Any other values must be -1. |
| function shouldBeRamp( |
| should, testName, x, startValue, length, startIndex) { |
| let k; |
| let startingIndex = startIndex || 0; |
| let expected = Array(x.length); |
| |
| // Fill the expected array with the correct results. |
| |
| // The initial part (if any) must be -1. |
| for (k = 0; k < startingIndex; ++k) { |
| expected[k] = -1; |
| } |
| |
| // The second part should be a ramp starting with |startValue| |
| for (; k < startingIndex + length; ++k) { |
| expected[k] = startValue + k - startingIndex; |
| } |
| |
| // The last part (if any) should be -1. |
| for (; k < x.length; ++k) { |
| expected[k] = -1; |
| } |
| |
| should(x, testName, {numberOfArrayLog: 32}).beEqualToArray(expected); |
| } |
| |
| let audit = Audit.createTaskRunner(); |
| |
| let context = new AudioContext(); |
| // Temp array for testing exceptions for copyToChannel/copyFromChannel. |
| // The length is arbitrary. |
| let x = new Float32Array(8); |
| |
| // Number of frames in the AudioBuffer for testing. This is pretty |
| // arbitrary so choose a fairly small value. |
| let bufferLength = 16; |
| |
| // Number of channels in the AudioBuffer. Also arbitrary, but it should |
| // be greater than 1 for test coverage. |
| let numberOfChannels = 3; |
| |
| // AudioBuffer that will be used for testing copyFrom and copyTo. |
| let buffer = context.createBuffer( |
| numberOfChannels, bufferLength, context.sampleRate); |
| |
| let initialValues = Array(numberOfChannels); |
| |
| // Initialize things |
| audit.define('initialize', (task, should) => { |
| // Initialize to -1. |
| initialValues.fill(-1); |
| should(initialValues, 'Initialized values').beConstantValueOf(-1) |
| task.done(); |
| }); |
| |
| // Test that expected exceptions are signaled for copyFrom. |
| audit.define('copyFrom-exceptions', (task, should) => { |
| should( |
| AudioBuffer.prototype.copyFromChannel, |
| 'AudioBuffer.prototype.copyFromChannel') |
| .exist(); |
| |
| should( |
| () => { |
| buffer = context.createBuffer( |
| numberOfChannels, bufferLength, context.sampleRate); |
| }, |
| '0: buffer = context.createBuffer(' + numberOfChannels + ', ' + |
| bufferLength + ', context.sampleRate)') |
| .notThrow(); |
| should(() => { |
| buffer.copyFromChannel(null, 0); |
| }, '1: buffer.copyFromChannel(null, 0)').throw(TypeError); |
| should(() => { |
| buffer.copyFromChannel(context, 0); |
| }, '2: buffer.copyFromChannel(context, 0)').throw(TypeError); |
| should(() => { |
| buffer.copyFromChannel(x, -1); |
| }, '3: buffer.copyFromChannel(x, -1)').throw(DOMException, 'IndexSizeError'); |
| should( |
| () => { |
| buffer.copyFromChannel(x, numberOfChannels); |
| }, |
| '4: buffer.copyFromChannel(x, ' + numberOfChannels + ')') |
| .throw(DOMException, 'IndexSizeError'); |
| ; |
| should(() => { |
| buffer.copyFromChannel(x, 0, -1); |
| }, '5: buffer.copyFromChannel(x, 0, -1)').notThrow(); |
| should( |
| () => { |
| buffer.copyFromChannel(x, 0, bufferLength); |
| }, |
| '6: buffer.copyFromChannel(x, 0, ' + bufferLength + ')') |
| .notThrow(); |
| |
| should(() => { |
| buffer.copyFromChannel(x, 3); |
| }, '7: buffer.copyFromChannel(x, 3)').throw(DOMException, 'IndexSizeError'); |
| |
| // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` |
| // WebAssembly.Memory's size is in multiples of 64 KiB |
| const shared_buffer = new Float32Array(new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer); |
| should( |
| () => { |
| buffer.copyFromChannel(shared_buffer, 0); |
| }, |
| '8: buffer.copyFromChannel(SharedArrayBuffer view, 0)') |
| .throw(TypeError); |
| |
| should( |
| () => { |
| buffer.copyFromChannel(shared_buffer, 0, 0); |
| }, |
| '9: buffer.copyFromChannel(SharedArrayBuffer view, 0, 0)') |
| .throw(TypeError); |
| |
| task.done(); |
| }); |
| |
| // Test that expected exceptions are signaled for copyTo. |
| audit.define('copyTo-exceptions', (task, should) => { |
| should( |
| AudioBuffer.prototype.copyToChannel, |
| 'AudioBuffer.prototype.copyToChannel') |
| .exist(); |
| should(() => { |
| buffer.copyToChannel(null, 0); |
| }, '0: buffer.copyToChannel(null, 0)').throw(TypeError); |
| should(() => { |
| buffer.copyToChannel(context, 0); |
| }, '1: buffer.copyToChannel(context, 0)').throw(TypeError); |
| should(() => { |
| buffer.copyToChannel(x, -1); |
| }, '2: buffer.copyToChannel(x, -1)').throw(DOMException, 'IndexSizeError'); |
| should( |
| () => { |
| buffer.copyToChannel(x, numberOfChannels); |
| }, |
| '3: buffer.copyToChannel(x, ' + numberOfChannels + ')') |
| .throw(DOMException, 'IndexSizeError'); |
| should(() => { |
| buffer.copyToChannel(x, 0, -1); |
| }, '4: buffer.copyToChannel(x, 0, -1)').notThrow(); |
| should( |
| () => { |
| buffer.copyToChannel(x, 0, bufferLength); |
| }, |
| '5: buffer.copyToChannel(x, 0, ' + bufferLength + ')') |
| .notThrow(); |
| |
| should(() => { |
| buffer.copyToChannel(x, 3); |
| }, '6: buffer.copyToChannel(x, 3)').throw(DOMException, 'IndexSizeError'); |
| |
| // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` |
| // WebAssembly.Memory's size is in multiples of 64 KiB |
| const shared_buffer = new Float32Array(new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer); |
| should( |
| () => { |
| buffer.copyToChannel(shared_buffer, 0); |
| }, |
| '7: buffer.copyToChannel(SharedArrayBuffer view, 0)') |
| .throw(TypeError); |
| |
| should( |
| () => { |
| buffer.copyToChannel(shared_buffer, 0, 0); |
| }, |
| '8: buffer.copyToChannel(SharedArrayBuffer view, 0, 0)') |
| .throw(TypeError); |
| |
| task.done(); |
| }); |
| |
| // Test copyFromChannel |
| audit.define('copyFrom-validate', (task, should) => { |
| // Initialize the AudioBuffer to a ramp for testing copyFrom. |
| initializeAudioBufferRamp(buffer); |
| |
| // Test copyFrom operation with a short destination array, filling the |
| // destination completely. |
| for (let c = 0; c < numberOfChannels; ++c) { |
| let dst8 = createInitializedF32Array(8); |
| buffer.copyFromChannel(dst8, c); |
| shouldBeRamp( |
| should, 'buffer.copyFromChannel(dst8, ' + c + ')', dst8, c + 1, 8) |
| } |
| |
| // Test copyFrom operation with a short destination array using a |
| // non-zero start index that still fills the destination completely. |
| for (let c = 0; c < numberOfChannels; ++c) { |
| let dst8 = createInitializedF32Array(8); |
| buffer.copyFromChannel(dst8, c, 1); |
| shouldBeRamp( |
| should, 'buffer.copyFromChannel(dst8, ' + c + ', 1)', dst8, c + 2, |
| 8) |
| } |
| |
| // Test copyFrom operation with a short destination array using a |
| // non-zero start index that does not fill the destinatiom completely. |
| // The extra elements should be unchanged. |
| for (let c = 0; c < numberOfChannels; ++c) { |
| let dst8 = createInitializedF32Array(8); |
| let startInChannel = bufferLength - 5; |
| buffer.copyFromChannel(dst8, c, startInChannel); |
| shouldBeRamp( |
| should, |
| 'buffer.copyFromChannel(dst8, ' + c + ', ' + startInChannel + ')', |
| dst8, c + 1 + startInChannel, bufferLength - startInChannel); |
| } |
| |
| // Copy operation with the destination longer than the buffer, leaving |
| // the trailing elements of the destination untouched. |
| for (let c = 0; c < numberOfChannels; ++c) { |
| let dst26 = createInitializedF32Array(bufferLength + 10); |
| buffer.copyFromChannel(dst26, c); |
| shouldBeRamp( |
| should, 'buffer.copyFromChannel(dst26, ' + c + ')', dst26, c + 1, |
| bufferLength); |
| } |
| |
| task.done(); |
| }); |
| |
| // Test copyTo |
| audit.define('copyTo-validate', (task, should) => { |
| // Create a source consisting of a ramp starting at 1, longer than the |
| // AudioBuffer |
| let src = createFloat32RampArray(bufferLength + 10); |
| |
| // Test copyTo with AudioBuffer shorter than Float32Array. The |
| // AudioBuffer should be completely filled with the Float32Array. |
| should( |
| () => { |
| buffer = |
| createConstantBuffer(context, bufferLength, initialValues); |
| }, |
| 'buffer = createConstantBuffer(context, ' + bufferLength + ', [' + |
| initialValues + '])') |
| .notThrow(); |
| |
| for (let c = 0; c < numberOfChannels; ++c) { |
| buffer.copyToChannel(src, c); |
| shouldBeRamp( |
| should, 'buffer.copyToChannel(src, ' + c + ')', |
| buffer.getChannelData(c), 1, bufferLength); |
| } |
| |
| // Test copyTo with AudioBuffer longer than the Float32Array. The tail |
| // of the AudioBuffer should be unchanged. |
| buffer = createConstantBuffer(context, bufferLength, initialValues); |
| let src10 = createFloat32RampArray(10); |
| for (let c = 0; c < numberOfChannels; ++c) { |
| buffer.copyToChannel(src10, c); |
| shouldBeRamp( |
| should, 'buffer.copyToChannel(src10, ' + c + ')', |
| buffer.getChannelData(c), 1, 10); |
| } |
| |
| // Test copyTo with non-default startInChannel. Part of the AudioBuffer |
| // should filled with the beginning and end sections untouched. |
| buffer = createConstantBuffer(context, bufferLength, initialValues); |
| for (let c = 0; c < numberOfChannels; ++c) { |
| let startInChannel = 5; |
| buffer.copyToChannel(src10, c, startInChannel); |
| |
| shouldBeRamp( |
| should, |
| 'buffer.copyToChannel(src10, ' + c + ', ' + startInChannel + ')', |
| buffer.getChannelData(c), 1, src10.length, startInChannel); |
| } |
| task.done(); |
| }); |
| |
| audit.run(); |
| </script> |
| </body> |
| </html> |