Commit f18e6ccd authored by Raymond Toy's avatar Raymond Toy Committed by Commit Bot

Remove Dezippering from the DelayNode

Remove dezippering from delayTime.  The value will now change
immediately instead of gradually changing from the old to new
value.

Chromium Feature: https://www.chromestatus.com/features/5287995770929152
Intent to ship: https://groups.google.com/a/chromium.org/d/msg/blink-dev/YKYRrh0nWMo/aGzd3049AgAJ

Bug: 550533
Test: Delay/dezipper.html
Change-Id: Id3ce76401d4342205c5d67079f6684514ae7e2a2
Reviewed-on: https://chromium-review.googlesource.com/609443
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Reviewed-by: default avatarHongchan Choi <hongchan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#532682}
parent d86c522b
<!DOCTYPE html>
<html>
<head>
<title>
Test DelayNode Has No Dezippering
</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">
// The sample rate must be a power of two to avoid any round-off errors in
// computing when to suspend a context on a rendering quantum boundary.
// Otherwise this is pretty arbitrary.
let sampleRate = 16384;
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test0', description: 'Test DelayNode has no dezippering'},
(task, should) => {
let context = new OfflineAudioContext(1, sampleRate, sampleRate);
// Simple integer ramp for testing delay node
let buffer = new AudioBuffer(
{length: context.length, sampleRate: context.sampleRate});
let rampData = buffer.getChannelData(0);
for (let k = 0; k < rampData.length; ++k) {
rampData[k] = k + 1;
}
// |delay0Frame| is the initial delay in frames. |delay1Frame| is
// the new delay in frames. These must be integers.
let delay0Frame = 64;
let delay1Frame = 16;
let src = new AudioBufferSourceNode(context, {buffer: buffer});
let delay = new DelayNode(
context, {delayTime: delay0Frame / context.sampleRate});
src.connect(delay).connect(context.destination);
// After a render quantum, change the delay to |delay1Frame|.
context.suspend(RENDER_QUANTUM_FRAMES / context.sampleRate)
.then(() => {
delay.delayTime.value = delay1Frame / context.sampleRate;
})
.then(() => context.resume());
src.start();
context.startRendering()
.then(renderedBuffer => {
let renderedData = renderedBuffer.getChannelData(0);
// The first |delay0Frame| frames should be zero.
should(
renderedData.slice(0, delay0Frame),
'output[0:' + (delay0Frame - 1) + ']')
.beConstantValueOf(0);
// Now we have the ramp should show up from the delay.
let ramp0 =
new Float32Array(RENDER_QUANTUM_FRAMES - delay0Frame);
for (let k = 0; k < ramp0.length; ++k) {
ramp0[k] = rampData[k];
}
should(
renderedData.slice(delay0Frame, RENDER_QUANTUM_FRAMES),
'output[' + delay0Frame + ':' +
(RENDER_QUANTUM_FRAMES - 1) + ']')
.beEqualToArray(ramp0);
// After one rendering quantum, the delay is changed to
// |delay1Frame|.
let ramp1 =
new Float32Array(context.length - RENDER_QUANTUM_FRAMES);
for (let k = 0; k < ramp1.length; ++k) {
// ramp1[k] = 1 + k + RENDER_QUANTUM_FRAMES - delay1Frame;
ramp1[k] =
rampData[k + RENDER_QUANTUM_FRAMES - delay1Frame];
}
should(
renderedData.slice(RENDER_QUANTUM_FRAMES),
'output[' + RENDER_QUANTUM_FRAMES + ':]')
.beEqualToArray(ramp1);
})
.then(() => task.done());
});
audit.define(
{label: 'test1', description: 'Test value setter and setValueAtTime'},
(task, should) => {
testWithAutomation(should, {prefix: ''}).then(() => task.done());
});
audit.define(
{label: 'test2', description: 'Test value setter and modulation'},
(task, should) => {
testWithAutomation(should, {
prefix: 'With modulation: ',
modulator: true
}).then(() => task.done());
});
// Compare .value setter with setValueAtTime, Optionally allow modulation
// of |delayTime|.
function testWithAutomation(should, options) {
let prefix = options.prefix;
// Channel 0 is the output of delay node using the setter and channel 1
// is the output using setValueAtTime.
let context = new OfflineAudioContext(2, sampleRate, sampleRate);
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
let src = new OscillatorNode(context);
// |delay0Frame| is the initial delay value in frames. |delay1Frame| is
// the new delay in frames. The values here are constrained only by the
// constraints for a DelayNode. These are pretty arbitrary except we
// wanted them to be fractional so as not be on a frame boundary to
// test interpolation compared with |setValueAtTime()|..
let delay0Frame = 3.1;
let delay1Frame = 47.2;
let delayTest = new DelayNode(
context, {delayTime: delay0Frame / context.sampleRate});
let delayRef = new DelayNode(
context, {delayTime: delay0Frame / context.sampleRate});
src.connect(delayTest).connect(merger, 0, 0);
src.connect(delayRef).connect(merger, 0, 1);
if (options.modulator) {
// Fairly arbitrary modulation of the delay time, with a peak
// variation of 10 ms.
let mod = new OscillatorNode(context, {frequency: 1000});
let modGain = new GainNode(context, {gain: .01});
mod.connect(modGain);
modGain.connect(delayTest.delayTime);
modGain.connect(delayRef.delayTime);
mod.start();
}
// The time at which the delay time of |delayTest| node will be
// changed. This MUST be on a render quantum boundary, but is
// otherwise arbitrary.
let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate;
// Schedule the delay change on |delayRef| and also apply the value
// setter for |delayTest| at |changeTime|.
delayRef.delayTime.setValueAtTime(
delay1Frame / context.sampleRate, changeTime);
context.suspend(changeTime)
.then(() => {
delayTest.delayTime.value = delay1Frame / context.sampleRate;
})
.then(() => context.resume());
src.start();
return context.startRendering().then(renderedBuffer => {
let actual = renderedBuffer.getChannelData(0);
let expected = renderedBuffer.getChannelData(1);
let match = should(actual, prefix + '.value setter output')
.beEqualToArray(expected);
should(
match,
prefix + '.value setter output matches setValueAtTime output')
.beTrue();
});
}
audit.run();
</script>
</body>
</html>
...@@ -30,8 +30,6 @@ ...@@ -30,8 +30,6 @@
namespace blink { namespace blink {
const float kSmoothingTimeConstant = 0.020f; // 20ms
DelayDSPKernel::DelayDSPKernel(DelayProcessor* processor) DelayDSPKernel::DelayDSPKernel(DelayProcessor* processor)
: AudioDelayDSPKernel(processor, AudioUtilities::kRenderQuantumFrames) { : AudioDelayDSPKernel(processor, AudioUtilities::kRenderQuantumFrames) {
DCHECK(processor); DCHECK(processor);
...@@ -48,9 +46,6 @@ DelayDSPKernel::DelayDSPKernel(DelayProcessor* processor) ...@@ -48,9 +46,6 @@ DelayDSPKernel::DelayDSPKernel(DelayProcessor* processor)
buffer_.Allocate( buffer_.Allocate(
BufferLengthForDelay(max_delay_time_, processor->SampleRate())); BufferLengthForDelay(max_delay_time_, processor->SampleRate()));
buffer_.Zero(); buffer_.Zero();
smoothing_rate_ = AudioUtilities::DiscreteTimeConstantForSampleRate(
kSmoothingTimeConstant, processor->SampleRate());
} }
bool DelayDSPKernel::HasSampleAccurateValues() { bool DelayDSPKernel::HasSampleAccurateValues() {
......
...@@ -31,21 +31,17 @@ ...@@ -31,21 +31,17 @@
namespace blink { namespace blink {
const float kSmoothingTimeConstant = 0.020f; // 20ms
AudioDelayDSPKernel::AudioDelayDSPKernel(AudioDSPKernelProcessor* processor, AudioDelayDSPKernel::AudioDelayDSPKernel(AudioDSPKernelProcessor* processor,
size_t processing_size_in_frames) size_t processing_size_in_frames)
: AudioDSPKernel(processor), : AudioDSPKernel(processor),
write_index_(0), write_index_(0),
first_time_(true),
delay_times_(processing_size_in_frames) {} delay_times_(processing_size_in_frames) {}
AudioDelayDSPKernel::AudioDelayDSPKernel(double max_delay_time, AudioDelayDSPKernel::AudioDelayDSPKernel(double max_delay_time,
float sample_rate) float sample_rate)
: AudioDSPKernel(sample_rate), : AudioDSPKernel(sample_rate),
max_delay_time_(max_delay_time), max_delay_time_(max_delay_time),
write_index_(0), write_index_(0) {
first_time_(true) {
DCHECK_GT(max_delay_time, 0.0); DCHECK_GT(max_delay_time, 0.0);
DCHECK(!std::isnan(max_delay_time)); DCHECK(!std::isnan(max_delay_time));
if (max_delay_time <= 0.0 || std::isnan(max_delay_time)) if (max_delay_time <= 0.0 || std::isnan(max_delay_time))
...@@ -58,9 +54,6 @@ AudioDelayDSPKernel::AudioDelayDSPKernel(double max_delay_time, ...@@ -58,9 +54,6 @@ AudioDelayDSPKernel::AudioDelayDSPKernel(double max_delay_time,
buffer_.Allocate(buffer_length); buffer_.Allocate(buffer_length);
buffer_.Zero(); buffer_.Zero();
smoothing_rate_ = AudioUtilities::DiscreteTimeConstantForSampleRate(
kSmoothingTimeConstant, sample_rate);
} }
size_t AudioDelayDSPKernel::BufferLengthForDelay(double max_delay_time, size_t AudioDelayDSPKernel::BufferLengthForDelay(double max_delay_time,
...@@ -112,11 +105,6 @@ void AudioDelayDSPKernel::Process(const float* source, ...@@ -112,11 +105,6 @@ void AudioDelayDSPKernel::Process(const float* source,
// Make sure the delay time is in a valid range. // Make sure the delay time is in a valid range.
delay_time = clampTo(delay_time, 0.0, max_time); delay_time = clampTo(delay_time, 0.0, max_time);
if (first_time_) {
current_delay_time_ = delay_time;
first_time_ = false;
}
} }
for (unsigned i = 0; i < frames_to_process; ++i) { for (unsigned i = 0; i < frames_to_process; ++i) {
...@@ -126,14 +114,9 @@ void AudioDelayDSPKernel::Process(const float* source, ...@@ -126,14 +114,9 @@ void AudioDelayDSPKernel::Process(const float* source,
delay_time = max_time; delay_time = max_time;
else else
delay_time = clampTo(delay_time, 0.0, max_time); delay_time = clampTo(delay_time, 0.0, max_time);
current_delay_time_ = delay_time;
} else {
// Approach desired delay time.
current_delay_time_ +=
(delay_time - current_delay_time_) * smoothing_rate_;
} }
double desired_delay_frames = current_delay_time_ * sample_rate; double desired_delay_frames = delay_time * sample_rate;
double read_position = write_index_ + buffer_length - desired_delay_frames; double read_position = write_index_ + buffer_length - desired_delay_frames;
if (read_position >= buffer_length) if (read_position >= buffer_length)
...@@ -159,7 +142,6 @@ void AudioDelayDSPKernel::Process(const float* source, ...@@ -159,7 +142,6 @@ void AudioDelayDSPKernel::Process(const float* source,
} }
void AudioDelayDSPKernel::Reset() { void AudioDelayDSPKernel::Reset() {
first_time_ = true;
buffer_.Zero(); buffer_.Zero();
} }
......
...@@ -61,9 +61,6 @@ class PLATFORM_EXPORT AudioDelayDSPKernel : public AudioDSPKernel { ...@@ -61,9 +61,6 @@ class PLATFORM_EXPORT AudioDelayDSPKernel : public AudioDSPKernel {
AudioFloatArray buffer_; AudioFloatArray buffer_;
double max_delay_time_; double max_delay_time_;
int write_index_; int write_index_;
double current_delay_time_;
double smoothing_rate_;
bool first_time_;
double desired_delay_frames_; double desired_delay_frames_;
AudioFloatArray delay_times_; AudioFloatArray delay_times_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment