<!DOCTYPE html>
<html>
<head>
<title>
Test Clamping of Automations
</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">
// 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>