| <!DOCTYPE html> |
| <html> |
| <head> |
| <title> |
| waveshaper.html |
| </title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../../resources/audit-util.js"></script> |
| <script src="../../resources/audit.js"></script> |
| </head> |
| <body> |
| <script id="layout-test-code"> |
| let audit = Audit.createTaskRunner(); |
| |
| let sampleRate = 44100; |
| let lengthInSeconds = 4; |
| let numberOfRenderFrames = sampleRate * lengthInSeconds; |
| let numberOfCurveFrames = 65536; |
| let inputBuffer; |
| let waveShapingCurve; |
| |
| let context; |
| |
| function generateInputBuffer() { |
| // Create mono input buffer. |
| let buffer = |
| context.createBuffer(1, numberOfRenderFrames, context.sampleRate); |
| let data = buffer.getChannelData(0); |
| |
| // Generate an input vector with values from -1 -> +1 over a duration of |
| // lengthInSeconds. This exercises the full nominal input range and will |
| // touch every point of the shaping curve. |
| for (let i = 0; i < numberOfRenderFrames; ++i) { |
| let x = i / numberOfRenderFrames; // 0 -> 1 |
| x = 2 * x - 1; // -1 -> +1 |
| data[i] = x; |
| } |
| |
| return buffer; |
| } |
| |
| // Generates a symmetric curve: Math.atan(5 * x) / (0.5 * Math.PI) |
| // (with x == 0 corresponding to the center of the array) |
| // This curve is arbitrary, but would be useful in the real-world. |
| // To some extent, the actual curve we choose is not important in this |
| // test, since the input vector walks through all possible curve values. |
| function generateWaveShapingCurve() { |
| let curve = new Float32Array(numberOfCurveFrames); |
| |
| let n = numberOfCurveFrames; |
| let n2 = n / 2; |
| |
| for (let i = 0; i < n; ++i) { |
| let x = (i - n2) / n2; |
| let y = Math.atan(5 * x) / (0.5 * Math.PI); |
| } |
| |
| return curve; |
| } |
| |
| function checkShapedCurve(buffer, should) { |
| let inputData = inputBuffer.getChannelData(0); |
| let outputData = buffer.getChannelData(0); |
| |
| let success = true; |
| |
| // Go through every sample and make sure it has been shaped exactly |
| // according to the shaping curve we gave it. |
| for (let i = 0; i < buffer.length; ++i) { |
| let input = inputData[i]; |
| |
| // Calculate an index based on input -1 -> +1 with 0 being at the |
| // center of the curve data. |
| let index = Math.floor(numberOfCurveFrames * 0.5 * (input + 1)); |
| |
| // Clip index to the input range of the curve. |
| // This takes care of input outside of nominal range -1 -> +1 |
| index = index < 0 ? 0 : index; |
| index = |
| index > numberOfCurveFrames - 1 ? numberOfCurveFrames - 1 : index; |
| |
| let expectedOutput = waveShapingCurve[index]; |
| |
| let output = outputData[i]; |
| |
| if (output != expectedOutput) { |
| success = false; |
| break; |
| } |
| } |
| |
| should( |
| success, 'WaveShaperNode applied non-linear distortion correctly') |
| .beTrue(); |
| } |
| |
| audit.define('test', function(task, should) { |
| // Create offline audio context. |
| context = new OfflineAudioContext(1, numberOfRenderFrames, sampleRate); |
| |
| // source -> waveshaper -> destination |
| let source = context.createBufferSource(); |
| let waveshaper = context.createWaveShaper(); |
| source.connect(waveshaper); |
| waveshaper.connect(context.destination); |
| |
| // Create an input test vector. |
| inputBuffer = generateInputBuffer(); |
| source.buffer = inputBuffer; |
| |
| // We'll apply non-linear distortion according to this shaping curve. |
| waveShapingCurve = generateWaveShapingCurve(); |
| waveshaper.curve = waveShapingCurve; |
| |
| source.start(0); |
| |
| context.startRendering() |
| .then(buffer => checkShapedCurve(buffer, should)) |
| .then(task.done.bind(task)); |
| }); |
| |
| audit.run(); |
| </script> |
| </body> |
| </html> |