| <!DOCTYPE html> |
| <html> |
| <head> |
| <title> |
| Test Clamping of Automations |
| </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> |
| </head> |
| <body> |
| <script id="layout-test-code"> |
| // Some arbitrary sample rate for the offline context. |
| let sampleRate = 48000; |
| |
| // Duration of test (fairly arbitrary). |
| let renderDuration = 1; |
| let renderFrames = renderDuration * sampleRate; |
| |
| let audit = Audit.createTaskRunner(); |
| |
| audit.define('clamp', (task, should) => { |
| // Test clamping of automations. Most AudioParam limits are essentially |
| // unbounded, so clamping doesn't happen. For most other AudioParams, |
| // the behavior is sufficiently complicated with complicated outputs |
| // that testing them is hard. However the output behavior of the |
| // frequency parameter for a BiquadFilter is relatively simple. Use |
| // that as the test. |
| let context = new OfflineAudioContext(1, renderFrames, sampleRate); |
| |
| let source = context.createBufferSource(); |
| source.buffer = createConstantBuffer(context, 1, 1); |
| source.loop = true; |
| |
| let filter = context.createBiquadFilter(); |
| filter.type = 'lowpass'; |
| |
| source.connect(filter); |
| filter.connect(context.destination); |
| |
| let V0 = 880; |
| let T0 = 0; |
| filter.frequency.setValueAtTime(V0, T0); |
| |
| let V1 = -1000; |
| let T1 = renderDuration / 4; |
| filter.frequency.linearRampToValueAtTime(V1, T1); |
| |
| let V2 = 880; |
| let T2 = renderDuration / 2; |
| filter.frequency.linearRampToValueAtTime(V2, T2); |
| |
| source.start(); |
| |
| context.startRendering() |
| .then(function(buffer) { |
| let result = buffer.getChannelData(0); |
| |
| // When the cutoff frequency of a lowpass filter is 0, nothing |
| // gets through. Hence the output of the filter between the |
| // clamping period should be exactly zero. This tests passes if |
| // the output is 0 during the expected range. |
| // |
| // Compute when the frequency value of the biquad goes to 0. In |
| // general, t = (T0*V1 -T1*V0)/(V1-V0) (using the notation from |
| // the spec.) |
| let clampStartTime = solveLinearRamp(0, V0, T0, V1, T1); |
| let clampEndTime = solveLinearRamp(0, V1, T1, V2, T2); |
| |
| let clampStartFrame = Math.ceil(clampStartTime * sampleRate); |
| let clampEndFrame = Math.floor(clampEndTime * sampleRate); |
| |
| let clampedSignal = |
| result.slice(clampStartFrame, clampEndFrame + 1); |
| let expectedSignal = new Float32Array(clampedSignal.length); |
| expectedSignal.fill(0); |
| |
| // Output should be zero. |
| should( |
| clampedSignal, |
| 'Clamped signal in frame range [' + clampStartFrame + ', ' + |
| clampEndFrame + ']') |
| .beCloseToArray(expectedSignal, 0); |
| |
| // Find the actual clamp range based on the output values. |
| let actualClampStart = result.findIndex(x => x === 0); |
| let actualClampEnd = actualClampStart + |
| result.slice(actualClampStart).findIndex(x => x != 0); |
| |
| // Verify that the expected clamping range is a subset of the |
| // actual range. |
| should(actualClampStart, 'Actual Clamp start') |
| .beLessThanOrEqualTo(clampStartFrame); |
| should(actualClampEnd, 'Actual Clamp end') |
| .beGreaterThanOrEqualTo(clampEndFrame); |
| |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.run(); |
| |
| function solveLinearRamp(v, v0, t0, v1, t1) { |
| // Solve the linear ramp equation for the time t at which the ramp |
| // reaches the value v. The linear ramp equation (from the spec) is |
| // |
| // v(t) = v0 + (v1 - v0) * (t - t0)/(t1 - t0) |
| // |
| // Find t such that |
| // |
| // v = v0 + (v1 - v0) * (t - t0)/(t1 - t0) |
| // |
| // Then |
| // |
| // t = (t0 * v1 - t1 * v0 + (t1 - t0) * v) / (v1 - v0) |
| // |
| return (t0 * v1 - t1 * v0 + (t1 - t0) * v) / (v1 - v0); |
| } |
| </script> |
| </body> |
| </html> |