Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(86)

Unified Diff: third_party/WebKit/LayoutTests/webaudio/AudioNode/tail-processing.html

Issue 2839063003: Implement tail processing for AudioNodes (Closed)
Patch Set: Make declaration order consistent Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | third_party/WebKit/LayoutTests/webaudio/DynamicsCompressor/dynamicscompressor-clear-internal-state.html » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/WebKit/LayoutTests/webaudio/AudioNode/tail-processing.html
diff --git a/third_party/WebKit/LayoutTests/webaudio/AudioNode/tail-processing.html b/third_party/WebKit/LayoutTests/webaudio/AudioNode/tail-processing.html
new file mode 100644
index 0000000000000000000000000000000000000000..e04d781fc74282412f68abe42fa2e379759ceb10
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/webaudio/AudioNode/tail-processing.html
@@ -0,0 +1,328 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test Handling of Tail Processing</title>
+ <script src="../../resources/testharness.js"></script>
+ <script src="../../resources/testharnessreport.js"></script>
+ <script src="../resources/audit.js"></script>
+ <script src="../resources/audit-util.js"></script>
+ </head>
+
+ <body>
+ <script>
+ let audit = Audit.createTaskRunner();
+
+ // Fairly arbitrary but must be a power of two to eliminate roundoff when
+ // we compute times from sample frames
+ const sampleRate = 32768;
+
+ // Fairly arbitrary duration
+ const renderDuration = 0.25;
+ const renderFrames = renderDuration * sampleRate;
+
+ audit.define('hrtf-panner-tail', (task, should) => {
+ runTest('PannerNode', {panningModel: 'HRTF', distanceMode: 'linear'})
+ .then(renderedBuffer => {
+ let prefix = 'HRTF PannerNode';
+ let output = renderedBuffer.getChannelData(0);
+ let response = renderedBuffer.getChannelData(1);
+ let latencyFrame = findLatencyFrame(response);
+ let tailFrame = findTailFrame(response);
+
+ // The HRTF panner has both a latency component and a tail
+ // component. Make sure both are non-zero.
+ should(latencyFrame, `${prefix} latency frame (${latencyFrame})`)
+ .beGreaterThan(0);
+
+ should(tailFrame, `${prefix} tail frame (${tailFrame})`)
+ .beGreaterThan(0);
+
+ // Because of the latency, the output is zero at the beginning.
+ // Make sure this is true.
+ should(
+ output.slice(0, latencyFrame),
+ `${prefix} Latency output[0:` + (latencyFrame - 1) + ']')
+ .beConstantValueOf(0);
+
+ // Verify the rest of the output matches the expected values. The
+ // output should be non-zero from latencyFrame to tailFrame and
+ // zero after tailFrame.
+ verifyOutput(should, output, {
+ prefix: prefix,
+ startFrame: latencyFrame,
+ nonZeroEndFrame: Math.min(tailFrame, output.length),
+ zeroStartFrame: roundUp(tailFrame),
+ tailFrame: tailFrame,
+ reference: response
+ });
+ })
+ .then(() => task.done());
+ });
+
+ audit.define('biquad-tail', (task, should) => {
+ runTest('BiquadFilterNode', {Q: 20, frequency: 100})
+ .then(renderedBuffer => {
+ let prefix = 'BiquadFilter'
+ let output = renderedBuffer.getChannelData(0);
+ let response = renderedBuffer.getChannelData(1);
+ let tailFrame = findTailFrame(response);
+
+ should(tailFrame, `${prefix} tail frame (${tailFrame})`)
+ .beGreaterThan(0);
+
+ // Verify biquad output which should be non-zero up to tailFrame
+ // and zero afterwards. However, the actual output isn't after
+ // tailFrame because the internal biquad tail time uses an
+ // approximation. That's why zeroStartFrame is 128 frames after
+ // tailFrame.
+ verifyOutput(should, output, {
+ prefix: prefix,
+ startFrame: 0,
+ nonZeroEndFrame: Math.min(tailFrame + 128, output.length),
+ zeroStartFrame: 128 + roundUp(tailFrame),
+ tailFrame: tailFrame,
+ reference: response
+ });
+ })
+ .then(() => task.done());
+ });
+
+ audit.define('iir-tail', (task, should) => {
+ runTest('IIRFilterNode', {feedforward: [1], feedback: [1, -.99]})
+ .then(renderedBuffer => {
+ let prefix = 'IIRFilter';
+ let output = renderedBuffer.getChannelData(0);
+ let response = renderedBuffer.getChannelData(1);
+ let tailFrame = findTailFrame(response);
+
+ should(tailFrame, `${prefix} tail frame (${tailFrame})`)
+ .beGreaterThan(0);
+
+ verifyOutput(should, output, {
+ prefix: prefix,
+ startFrame: 0,
+ nonZeroEndFrame: Math.min(tailFrame + 128, output.length),
+ zeroStartFrame: 256 + roundUp(tailFrame),
+ tailFrame: tailFrame,
+ reference: response
+ });
+ })
+ .then(() => task.done());
+ });
+
+ audit.define('delay-tail', (task, should) => {
+ // For the test, make sure the delay is greater than one render
+ // quantum. If it's less we won't be able to tell if tail processing
+ // worked because the input signal is an impulse.
+ let delayFrames = RENDER_QUANTUM_FRAMES + 64;
+ runTest('DelayNode', {delayTime: delayFrames / sampleRate})
+ .then(renderedBuffer => {
+ let prefix = 'Delay';
+ let output = renderedBuffer.getChannelData(0);
+ let response = renderedBuffer.getChannelData(1);
+ let tailFrame = findTailFrame(response);
+
+ should(tailFrame, `${prefix} tail frame (${tailFrame})`)
+ .beGreaterThan(0);
+
+ // As a delay node with delay time greater than one render
+ // quantum, the first render quantum must be 0.
+ should(output.slice(0, RENDER_QUANTUM_FRAMES),
+ `${prefix} output[0:` + (RENDER_QUANTUM_FRAMES - 1) + ']')
+ .beConstantValueOf(0);
+
+ // The output of the delay node should be nonzero in the second
+ // render quantum and zero forever after.
+ verifyOutput(should, output, {
+ prefix: prefix,
+ startFrame: RENDER_QUANTUM_FRAMES,
+ nonZeroEndFrame: Math.min(tailFrame, output.length),
+ zeroStartFrame: roundUp(tailFrame),
+ tailFrame: tailFrame,
+ reference: response
+ });
+
+ })
+ .then(() => task.done());
+ });
+
+ audit.define('convolver-tail', (task, should) => {
+ // The convolver response. It needs to be longer than one render
+ // quantum to show the tail processing.
+ let response = new AudioBuffer({length: RENDER_QUANTUM_FRAMES + 64,
+ sampleRate: sampleRate});
+ // For simplicity, just make the response all ones.
+ response.getChannelData(0).fill(1);
+
+ runTest('ConvolverNode', {disableNormalization: true, buffer: response})
+ .then(renderedBuffer => {
+ let prefix = 'Convolver';
+ let output = renderedBuffer.getChannelData(0);
+ let response = renderedBuffer.getChannelData(1);
+ let tailFrame = findTailFrame(response);
+
+ should(tailFrame, `${prefix} tail frame (${tailFrame})`)
+ .beGreaterThan(0);
+
+ verifyOutput(should, output, {
+ prefix: prefix,
+ startFrame: 0,
+ nonZeroEndFrame: Math.min(tailFrame + 128, output.length),
+ zeroStartFrame: 128 + roundUp(tailFrame),
+ tailFrame: tailFrame,
+ reference: response
+ });
+ })
+ .then(() => task.done());
+ });
+
+ audit.define('dynamics-compressor-tail', (task, should) => {
+ runTest('DynamicsCompressorNode', {})
+ .then(renderedBuffer => {
+ let prefix = 'DyamicsCompressor';
+ let output = renderedBuffer.getChannelData(0);
+ let response = renderedBuffer.getChannelData(1);
+ let tailFrame = findTailFrame(response);
+
+ should(tailFrame, `${prefix} tail frame (${tailFrame})`)
+ .beGreaterThan(0);
+
+ let latencyFrame = roundDown(tailFrame - 1);
+ should(
+ output.slice(0, latencyFrame),
+ `${prefix} output[0:` + (latencyFrame - 1) + ']')
+ .beConstantValueOf(0);
+
+ verifyOutput(should, output, {
+ prefix: prefix,
+ startFrame: latencyFrame,
+ nonZeroEndFrame: Math.min(tailFrame, output.length),
+ zeroStartFrame: roundUp(tailFrame),
+ tailFrame: tailFrame,
+ reference: response
+ });
+
+ })
+ .then(() => task.done());
+ });
+
+ audit.define('waveshaper-tail', (task, should) => {
+ // Fairly arbitrary curve for the WaveShaper
+ let curve = Float32Array.from([-1, -.5, 0, 0.5, 1]);
+
+ runTest('WaveShaperNode', {curve: curve, oversample: '2x'})
+ .then(renderedBuffer => {
+ let prefix = 'WaveShaper';
+ let output = renderedBuffer.getChannelData(0);
+ let response = renderedBuffer.getChannelData(1);
+ let tailFrame = findTailFrame(response);
+
+ should(tailFrame, `${prefix} tail frame (${tailFrame})`)
+ .beGreaterThan(0);
+
+ verifyOutput(should, output, {
+ prefix: prefix,
+ startFrame: 0,
+ nonZeroEndFrame: Math.min(tailFrame, output.length),
+ zeroStartFrame: roundUp(tailFrame),
+ tailFrame: tailFrame,
+ reference: response
+ });
+ })
+ .then(() => task.done());
+ });
+
+ audit.run();
+
+ function runTest(nodeName, nodeOptions) {
+ // Two-channel output. Channel 0 is the test result; channel 1 is the
+ // impulse response that is used to figure out when the tail should
+ // start.
+ let context = new OfflineAudioContext(2, sampleRate, sampleRate);
+
+ // Merge channels for the destination.
+ let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
+ merger.connect(context.destination);
+
+ let src = new ConstantSourceNode(context, {offset: 1});
+
+ // Impulse for testing. We want a full buffer so as not to worry about
+ // the source disconnecting prematurely from the filter.
+ let b = new AudioBuffer(
+ {length: context.length, sampleRate: context.sampleRate});
+ b.getChannelData(0)[0] = 1;
+ let impulse = new AudioBufferSourceNode(context, {buffer: b});
+
+ let testNode = new window[nodeName](context, nodeOptions);
+ let refNode = new window[nodeName](context, nodeOptions);
+
+ src.connect(testNode).connect(merger, 0, 0);
+ impulse.connect(refNode).connect(merger, 0, 1);
+
+ src.start();
+ src.stop(1 / context.sampleRate);
+ impulse.start();
+
+ return context.startRendering();
+ }
+
+ function findTailFrame(response) {
+ let tailFrame = response.length;
+
+ for (let k = response.length - 1; k >= 0; --k) {
+ if (Math.abs(response[k]) > 1 / 32768) {
+ tailFrame = k + 1;
+ break;
+ }
+ }
+
+ return tailFrame;
+ }
+
+ function findLatencyFrame(response) {
+ for (let k = 0; k < response.length; ++k) {
+ if (response[k] != 0)
+ return k;
+ }
+
+ return response.length;
+ }
+
+ function verifyOutput(should, output, options) {
+ let prefix = options.prefix || '';
+ if (options.tailFrame && options.reference) {
+ should(
+ output.slice(0, options.tailFrame),
+ `${prefix} Tail output[0:` + (options.tailFrame - 1) + ']')
+ .beEqualToArray(options.reference.slice(0, options.tailFrame));
+ }
+
+ // Verify that |output| is non-zero between |startFrame| and
+ // |nonZeroEndFrame|.
+ for (let k = options.startFrame; k < options.nonZeroEndFrame;
+ k += 128) {
+ should(
+ output.slice(k, k + 128),
+ `${prefix} output[` + k + ':' + (k + 127) + ']')
+ .notBeConstantValueOf(0);
+ }
+
+ // Verify |output| is zero starting at frame |zeroStartFrame|, inclusive
+ if (options.zeroStartFrame < output.length) {
+ should(
+ output.slice(options.zeroStartFrame),
+ `${prefix} output[` + options.zeroStartFrame + ':]')
+ .beConstantValueOf(0);
+ }
+ }
+
+ function roundDown(frame) {
+ return 128 * Math.floor(frame / 128);
+ }
+
+ function roundUp(frame) {
+ return 128 * Math.ceil(frame / 128);
+ }
+ </script>
+ </body>
+</html>
« no previous file with comments | « no previous file | third_party/WebKit/LayoutTests/webaudio/DynamicsCompressor/dynamicscompressor-clear-internal-state.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698