| <!DOCTYPE html> |
| <html> |
| <head> |
| <title> |
| audiobuffersource-loop-comprehensive.html |
| </title> |
| <script src="../../imported/w3c/web-platform-tests/resources/testharness.js"></script> |
| <script src="../../resources/testharnessreport.js"></script> |
| <script src="../resources/audit-util.js"></script> |
| <script src="../resources/audit.js"></script> |
| <script src="../resources/audiobuffersource-testing.js"></script> |
| </head> |
| <body> |
| <script id="layout-test-code"> |
| let audit = Audit.createTaskRunner(); |
| |
| // The following test cases assume an AudioBuffer of length 8 whose PCM |
| // data is a linear ramp, 0, 1, 2, 3,... |description| is optional and |
| // will be computed from the other parameters. |offsetFrame| is optional |
| // and defaults to 0. |
| |
| let tests = [ |
| |
| { |
| description: |
| 'loop whole buffer by default with loopStart == loopEnd == 0', |
| loopStartFrame: 0, |
| loopEndFrame: 0, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7] |
| }, |
| |
| { |
| description: 'loop whole buffer explicitly', |
| loopStartFrame: 0, |
| loopEndFrame: 8, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7] |
| }, |
| |
| { |
| description: 'loop from middle to end of buffer', |
| loopStartFrame: 4, |
| loopEndFrame: 8, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7] |
| }, |
| |
| { |
| description: 'loop from start to middle of buffer', |
| loopStartFrame: 0, |
| loopEndFrame: 4, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] |
| }, |
| |
| { |
| loopStartFrame: 4, |
| loopEndFrame: 6, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5] |
| }, |
| |
| { |
| loopStartFrame: 3, |
| loopEndFrame: 7, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 6, 3, 4, 5, 6, 3, 4, 5, 6, 3] |
| }, |
| |
| { |
| loopStartFrame: 4, |
| loopEndFrame: 6, |
| renderFrames: 16, |
| playbackRate: 0.5, |
| expected: |
| [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 4, 4.5, 5, 5.5] |
| }, |
| |
| { |
| loopStartFrame: 4, |
| loopEndFrame: 6, |
| renderFrames: 16, |
| playbackRate: 1.5, |
| expected: |
| [0, 1.5, 3, 4.5, 4, 5.5, 5, 4.5, 4, 5.5, 5, 4.5, 4, 5.5, 5, 4.5] |
| }, |
| |
| // Offset past loop end, so playback starts at loop start |
| { |
| loopStartFrame: 2, |
| loopEndFrame: 5, |
| renderFrames: 16, |
| playbackRate: 1, |
| offsetFrame: 6, |
| expected: [2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2] |
| }, |
| |
| // Offset before loop start, so start at offset and continue |
| { |
| loopStartFrame: 3, |
| loopEndFrame: 6, |
| renderFrames: 16, |
| playbackRate: 1, |
| offsetFrame: 1, |
| expected: [1, 2, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4] |
| }, |
| |
| // Offset between loop start and loop end, so start at offset and |
| // continue |
| { |
| loopStartFrame: 3, |
| loopEndFrame: 6, |
| renderFrames: 16, |
| playbackRate: 1, |
| offsetFrame: 4, |
| expected: [4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4] |
| }, |
| |
| { |
| description: 'illegal playbackRate of 47 greater than loop length', |
| loopStartFrame: 4, |
| loopEndFrame: 6, |
| renderFrames: 16, |
| playbackRate: 47, |
| expected: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| }, |
| |
| // Try illegal loop-points - they should be ignored and we'll loop the |
| // whole buffer. |
| |
| { |
| description: 'illegal loop: loopStartFrame > loopEndFrame', |
| loopStartFrame: 7, |
| loopEndFrame: 3, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7] |
| }, |
| |
| { |
| description: 'illegal loop: loopStartFrame == loopEndFrame', |
| loopStartFrame: 3, |
| loopEndFrame: 3, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7] |
| }, |
| |
| { |
| description: 'illegal loop: loopStartFrame < 0', |
| loopStartFrame: -8, |
| loopEndFrame: 3, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7] |
| }, |
| |
| { |
| description: 'illegal loop: loopEndFrame > bufferLength', |
| loopStartFrame: 0, |
| loopEndFrame: 30000, |
| renderFrames: 16, |
| playbackRate: 1, |
| expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7] |
| }, |
| |
| // Start a loop with a duration longer than the buffer. The output |
| // should be the data from frame 1 to 6, and then looping from 3 to 5 |
| // until 20 frames have been played. |
| { |
| description: 'loop from 3 -> 6 with offset 1 for 20 frames', |
| loopStartFrame: 3, |
| loopEndFrame: 6, |
| playbackRate: 1, |
| offsetFrame: 1, |
| renderFrames: 30, |
| durationFrames: 20, |
| expected: [ |
| 1, 2, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, |
| 4, 5, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 |
| ] |
| }, |
| |
| // Start a loop with a duration less than the length of the looping |
| // frames. The output should be the data from frame 1 to 3, and then |
| // stopping because duration = 3 |
| { |
| description: 'loop from 3 -> 8 with offset 1 for 3 frames', |
| loopStartFrame: 3, |
| loopEndFrame: 8, |
| playbackRate: 1, |
| offsetFrame: 1, |
| durationFrames: 3, |
| renderFrames: 30, |
| expected: [ |
| 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 |
| ] |
| }, |
| |
| // Start a loop with a duration less than the length of the looping |
| // frames. The output should be the data from frame 1 to 3, and then |
| // stopping because duration = 3 |
| { |
| description: 'loop from 3 -> 8 with offset 7 for 3 frames', |
| loopStartFrame: 3, |
| loopEndFrame: 8, |
| playbackRate: 1, |
| offsetFrame: 7, |
| durationFrames: 3, |
| renderFrames: 30, |
| expected: [ |
| 7, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 |
| ] |
| } |
| |
| ]; |
| |
| let sampleRate = 32768; |
| let buffer; |
| let bufferFrameLength = 8; |
| let testSpacingFrames = 32; |
| let testSpacingSeconds = testSpacingFrames / sampleRate; |
| let totalRenderLengthFrames = tests.length * testSpacingFrames; |
| |
| function runLoopTest(context, testNumber, test, should) { |
| let source = context.createBufferSource(); |
| |
| source.buffer = buffer; |
| source.playbackRate.value = test.playbackRate; |
| source.loop = true; |
| source.loopStart = test.loopStartFrame / context.sampleRate; |
| source.loopEnd = test.loopEndFrame / context.sampleRate; |
| |
| let offset = |
| test.offsetFrame ? test.offsetFrame / context.sampleRate : 0; |
| |
| source.connect(context.destination); |
| |
| // Render each test one after the other, spaced apart by |
| // testSpacingSeconds. |
| let startTime = testNumber * testSpacingSeconds; |
| |
| // If durationFrames is given, run the test for the specified duration. |
| if (test.durationFrames) { |
| if (!test.renderFrames) { |
| throw( |
| 'renderFrames is required for test ' + testNumber + ': ' + |
| test.description); |
| } else { |
| if (test.durationFrames > testSpacingFrames || |
| test.durationFrames < 0) { |
| throw( |
| 'Test ' + testNumber + ': durationFrames (' + |
| test.durationFrames + ') outside the range [0, ' + |
| testSpacingFrames + ']'); |
| } |
| source.start( |
| startTime, offset, test.durationFrames / context.sampleRate); |
| } |
| } else if (test.renderFrames) { |
| let duration = test.renderFrames / context.sampleRate; |
| if (test.renderFrames > testSpacingFrames || test.renderFrames < 0) { |
| throw( |
| 'Test ' + testNumber + ': renderFrames (' + test.renderFrames + |
| ') outside the range [0, ' + testSpacingFrames + ']'); |
| } |
| source.start(startTime, offset); |
| source.stop(startTime + duration); |
| } else { |
| throw( |
| 'Test ' + testNumber + |
| ' must specify renderFrames and possibly durationFrames'); |
| } |
| } |
| |
| audit.define('AudioBufferSource looping test', function(task, should) { |
| // Create offline audio context. |
| let context = |
| new OfflineAudioContext(1, totalRenderLengthFrames, sampleRate); |
| buffer = createTestBuffer(context, bufferFrameLength); |
| |
| should(function() { |
| for (let i = 0; i < tests.length; ++i) |
| runLoopTest(context, i, tests[i], should); |
| }, 'Generate ' + tests.length + ' test cases').notThrow(); |
| |
| context.startRendering().then(function(audioBuffer) { |
| checkAllTests(audioBuffer, should); |
| task.done(); |
| }); |
| }); |
| |
| audit.run(); |
| </script> |
| </body> |
| </html> |