blob: 73535484027bb54ef0d586c064ac2485e064a169 [file] [log] [blame]
/*
* Copyright (C) 2010, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include <limits.h>
#include "third_party/blink/renderer/modules/webaudio/biquad_dsp_kernel.h"
#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
static bool hasConstantValues(float* values, int frames_to_process) {
// TODO(rtoy): Use SIMD to optimize this. This would speed up
// processing by a factor of 4 because we can process 4 floats at a
// time.
float value = values[0];
for (int k = 1; k < frames_to_process; ++k) {
if (values[k] != value) {
return false;
}
}
return true;
}
void BiquadDSPKernel::UpdateCoefficientsIfNecessary(int frames_to_process) {
if (GetBiquadProcessor()->FilterCoefficientsDirty()) {
float cutoff_frequency[audio_utilities::kRenderQuantumFrames];
float q[audio_utilities::kRenderQuantumFrames];
float gain[audio_utilities::kRenderQuantumFrames];
float detune[audio_utilities::kRenderQuantumFrames]; // in Cents
SECURITY_CHECK(static_cast<unsigned>(frames_to_process) <=
audio_utilities::kRenderQuantumFrames);
if (GetBiquadProcessor()->HasSampleAccurateValues() &&
GetBiquadProcessor()->IsAudioRate()) {
GetBiquadProcessor()->Parameter1().CalculateSampleAccurateValues(
cutoff_frequency, frames_to_process);
GetBiquadProcessor()->Parameter2().CalculateSampleAccurateValues(
q, frames_to_process);
GetBiquadProcessor()->Parameter3().CalculateSampleAccurateValues(
gain, frames_to_process);
GetBiquadProcessor()->Parameter4().CalculateSampleAccurateValues(
detune, frames_to_process);
// If all the values are actually constant for this render (or the
// automation rate is "k-rate" for all of the AudioParams), we don't need
// to compute filter coefficients for each frame since they would be the
// same as the first.
bool isConstant =
hasConstantValues(cutoff_frequency, frames_to_process) &&
hasConstantValues(q, frames_to_process) &&
hasConstantValues(gain, frames_to_process) &&
hasConstantValues(detune, frames_to_process);
UpdateCoefficients(isConstant ? 1 : frames_to_process, cutoff_frequency,
q, gain, detune);
} else {
cutoff_frequency[0] = GetBiquadProcessor()->Parameter1().FinalValue();
q[0] = GetBiquadProcessor()->Parameter2().FinalValue();
gain[0] = GetBiquadProcessor()->Parameter3().FinalValue();
detune[0] = GetBiquadProcessor()->Parameter4().FinalValue();
UpdateCoefficients(1, cutoff_frequency, q, gain, detune);
}
}
}
void BiquadDSPKernel::UpdateCoefficients(int number_of_frames,
const float* cutoff_frequency,
const float* q,
const float* gain,
const float* detune) {
// Convert from Hertz to normalized frequency 0 -> 1.
double nyquist = this->Nyquist();
biquad_.SetHasSampleAccurateValues(number_of_frames > 1);
for (int k = 0; k < number_of_frames; ++k) {
double normalized_frequency = cutoff_frequency[k] / nyquist;
// Offset frequency by detune.
if (detune[k]) {
// Detune multiplies the frequency by 2^(detune[k] / 1200).
normalized_frequency *= exp2(detune[k] / 1200);
}
// Configure the biquad with the new filter parameters for the appropriate
// type of filter.
switch (GetBiquadProcessor()->GetType()) {
case BiquadProcessor::FilterType::kLowPass:
biquad_.SetLowpassParams(k, normalized_frequency, q[k]);
break;
case BiquadProcessor::FilterType::kHighPass:
biquad_.SetHighpassParams(k, normalized_frequency, q[k]);
break;
case BiquadProcessor::FilterType::kBandPass:
biquad_.SetBandpassParams(k, normalized_frequency, q[k]);
break;
case BiquadProcessor::FilterType::kLowShelf:
biquad_.SetLowShelfParams(k, normalized_frequency, gain[k]);
break;
case BiquadProcessor::FilterType::kHighShelf:
biquad_.SetHighShelfParams(k, normalized_frequency, gain[k]);
break;
case BiquadProcessor::FilterType::kPeaking:
biquad_.SetPeakingParams(k, normalized_frequency, q[k], gain[k]);
break;
case BiquadProcessor::FilterType::kNotch:
biquad_.SetNotchParams(k, normalized_frequency, q[k]);
break;
case BiquadProcessor::FilterType::kAllpass:
biquad_.SetAllpassParams(k, normalized_frequency, q[k]);
break;
}
}
UpdateTailTime(number_of_frames - 1);
}
void BiquadDSPKernel::UpdateTailTime(int coef_index) {
// A reasonable upper limit for the tail time. While it's easy to
// create biquad filters whose tail time can be much larger than
// this, limit the maximum to this value so that we don't keep such
// nodes alive "forever".
// TODO: What is a reasonable upper limit?
const double kMaxTailTime = 30;
double sample_rate = SampleRate();
double tail =
biquad_.TailFrame(coef_index, kMaxTailTime * sample_rate) / sample_rate;
tail_time_ = clampTo(tail, 0.0, kMaxTailTime);
}
void BiquadDSPKernel::Process(const float* source,
float* destination,
uint32_t frames_to_process) {
DCHECK(source);
DCHECK(destination);
DCHECK(GetBiquadProcessor());
// Recompute filter coefficients if any of the parameters have changed.
// FIXME: as an optimization, implement a way that a Biquad object can simply
// copy its internal filter coefficients from another Biquad object. Then
// re-factor this code to only run for the first BiquadDSPKernel of each
// BiquadProcessor.
// The audio thread can't block on this lock; skip updating the coefficients
// for this block if necessary. We'll get them the next time around.
{
MutexTryLocker try_locker(process_lock_);
if (try_locker.Locked())
UpdateCoefficientsIfNecessary(frames_to_process);
}
biquad_.Process(source, destination, frames_to_process);
}
void BiquadDSPKernel::GetFrequencyResponse(BiquadDSPKernel& kernel,
int n_frequencies,
const float* frequency_hz,
float* mag_response,
float* phase_response) {
// Only allow on the main thread because we don't want the audio thread to be
// updating |kernel| while we're computing the response.
DCHECK(IsMainThread());
DCHECK_GE(n_frequencies, 0);
DCHECK(frequency_hz);
DCHECK(mag_response);
DCHECK(phase_response);
Vector<float> frequency(n_frequencies);
double nyquist = kernel.Nyquist();
// Convert from frequency in Hz to normalized frequency (0 -> 1),
// with 1 equal to the Nyquist frequency.
for (int k = 0; k < n_frequencies; ++k)
frequency[k] = frequency_hz[k] / nyquist;
kernel.biquad_.GetFrequencyResponse(n_frequencies, frequency.data(),
mag_response, phase_response);
}
bool BiquadDSPKernel::RequiresTailProcessing() const {
// Always return true even if the tail time and latency might both
// be zero. This is for simplicity and because TailTime() is 0
// basically only when the filter response H(z) = 0 or H(z) = 1. And
// it's ok to return true. It just means the node lives a little
// longer than strictly necessary.
return true;
}
double BiquadDSPKernel::TailTime() const {
return tail_time_;
}
double BiquadDSPKernel::LatencyTime() const {
return 0;
}
} // namespace blink