blob: f6f94d6d8b21cedf5636993f27c4832c72fa31a0 [file] [log] [blame]
/*
* Copyright 2016 The Chromium Authors. All rights reserved.
* Copyright (C) 2020, Apple 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 "config.h"
#if ENABLE(WEB_AUDIO)
#include "IIRFilter.h"
#include "AudioArray.h"
#include "AudioUtilities.h"
#include "VectorMath.h"
#include <algorithm>
#include <complex>
#include <wtf/MathExtras.h>
namespace WebCore {
// The length of the memory buffers for the IIR filter. This MUST be a power of
// two and must be greater than the possible length of the filter coefficients.
constexpr int bufferLength = 32;
static_assert(bufferLength >= IIRFilter::maxOrder + 1, "Internal IIR buffer length must be greater than maximum IIR Filter order.");
static std::complex<double> evaluatePolynomial(const double* coefficients, std::complex<double> z, int order)
{
// Use Horner's method to evaluate the polynomial P(z) = sum(coef[k]*z^k, k, 0, order);
std::complex<double> result = 0;
for (int k = order; k >= 0; --k)
result = result * z + std::complex<double>(coefficients[k]);
return result;
}
IIRFilter::IIRFilter(const Vector<double>& feedforward, const Vector<double>& feedback)
: m_feedforward(feedforward)
, m_feedback(feedback)
{
m_xBuffer.fill(0, bufferLength);
m_yBuffer.fill(0, bufferLength);
}
void IIRFilter::reset()
{
m_xBuffer.fill(0);
m_yBuffer.fill(0);
m_bufferIndex = 0;
}
void IIRFilter::process(const float* source, float* destination, size_t framesToProcess)
{
// Compute
//
// y[n] = sum(b[k] * x[n - k], k = 0, M) - sum(a[k] * y[n - k], k = 1, N)
//
// where b[k] are the feedforward coefficients and a[k] are the feedback
// coefficients of the filter.
// This is a Direct Form I implementation of an IIR Filter. Should we
// consider doing a different implementation such as Transposed Direct Form
// II?
const double* feedback = m_feedback.data();
const double* feedforward = m_feedforward.data();
ASSERT(feedback);
ASSERT(feedforward);
// Sanity check to see if the feedback coefficients have been scaled appropriately. It must be EXACTLY 1!
ASSERT(feedback[0] == 1);
int feedbackLength = m_feedback.size();
int feedforwardLength = m_feedforward.size();
int minLength = std::min(feedbackLength, feedforwardLength);
double* xBuffer = m_xBuffer.data();
double* yBuffer = m_yBuffer.data();
for (size_t n = 0; n < framesToProcess; ++n) {
// To help minimize roundoff, we compute using double's, even though the
// filter coefficients only have single precision values.
double yn = feedforward[0] * source[n];
// Run both the feedforward and feedback terms together, when possible.
for (int k = 1; k < minLength; ++k) {
int m = (m_bufferIndex - k) & (bufferLength - 1);
yn += feedforward[k] * xBuffer[m];
yn -= feedback[k] * yBuffer[m];
}
// Handle any remaining feedforward or feedback terms.
for (int k = minLength; k < feedforwardLength; ++k)
yn += feedforward[k] * xBuffer[(m_bufferIndex - k) & (bufferLength - 1)];
for (int k = minLength; k < feedbackLength; ++k)
yn -= feedback[k] * yBuffer[(m_bufferIndex - k) & (bufferLength - 1)];
// Save the current input and output values in the memory buffers for the
// next output.
m_xBuffer[m_bufferIndex] = source[n];
m_yBuffer[m_bufferIndex] = yn;
m_bufferIndex = (m_bufferIndex + 1) & (bufferLength - 1);
destination[n] = yn;
}
}
void IIRFilter::getFrequencyResponse(unsigned length, const float* frequency, float* magResponse, float* phaseResponse)
{
// Evaluate the z-transform of the filter at the given normalized frequencies
// from 0 to 1. (One corresponds to the Nyquist frequency.)
//
// The z-tranform of the filter is
//
// H(z) = sum(b[k]*z^(-k), k, 0, M) / sum(a[k]*z^(-k), k, 0, N);
//
// The desired frequency response is H(exp(j*omega)), where omega is in [0,
// 1).
//
// Let P(x) = sum(c[k]*x^k, k, 0, P) be a polynomial of order P. Then each of
// the sums in H(z) is equivalent to evaluating a polynomial at the point
// 1/z.
for (unsigned k = 0; k < length; ++k) {
if (frequency[k] < 0 || frequency[k] > 1) {
// Out-of-bounds frequencies should return NaN.
magResponse[k] = std::nanf("");
phaseResponse[k] = std::nanf("");
} else {
// zRecip = 1/z = exp(-j*frequency)
double omega = -piDouble * frequency[k];
auto zRecip = std::complex<double>(cos(omega), sin(omega));
auto numerator = evaluatePolynomial(m_feedforward.data(), zRecip, m_feedforward.size() - 1);
auto denominator = evaluatePolynomial(m_feedback.data(), zRecip, m_feedback.size() - 1);
auto response = numerator / denominator;
magResponse[k] = static_cast<float>(abs(response));
phaseResponse[k] = static_cast<float>(atan2(imag(response), real(response)));
}
}
}
double IIRFilter::tailTime(double sampleRate, bool isFilterStable)
{
// The maximum tail time. This is somewhat arbitrary, but we're assuming that
// no one is going to expect the IIRFilter to produce an output after this
// much time after the inputs have stopped.
constexpr double maxTailTime = 10;
// If the maximum amplitude of the impulse response is less than this, we
// assume that we've reached the tail of the response. Currently, this means
// that the impulse is less than 1 bit of a 16-bit PCM value.
constexpr float maxTailAmplitude = 1 / 32768.0;
// If filter is not stable, just return max tail. Since the filter is not
// stable, the impulse response won't converge to zero, so we don't need to
// find the impulse response to find the actual tail time.
if (!isFilterStable || !sampleRate)
return maxTailTime;
// How to compute the tail time? We're going to filter an impulse
// for |maxTailTime| seconds, in blocks of kRenderQuantumFrames at
// a time. The maximum magnitude of this block is saved. After all
// of the samples have been computed, find the last block with a
// maximum magnitude greater than |kMaxTaileAmplitude|. That block
// index + 1 will be the tail time. We don't need to be
// super-accurate in computing the tail time since we process on
// blocks, block accuracy is good enough, and the value just needs
// to be larger than the "real" tail time, so we don't prematurely
// zero out the output of the node.
// Number of render quanta needed to reach the max tail time.
int numberOfBlocks = std::ceil(sampleRate * maxTailTime / AudioUtilities::renderQuantumSize);
RELEASE_ASSERT(numberOfBlocks);
// Input and output buffers for filtering.
AudioFloatArray input(AudioUtilities::renderQuantumSize);
AudioFloatArray output(AudioUtilities::renderQuantumSize);
// Array to hold the max magnitudes
AudioFloatArray magnitudes(numberOfBlocks);
// Create the impulse input signal.
input[0] = 1;
// Process the first block and get the max magnitude of the output.
process(input.data(), output.data(), AudioUtilities::renderQuantumSize);
magnitudes[0] = VectorMath::maximumMagnitude(output.data(), AudioUtilities::renderQuantumSize);
// Process the rest of the signal, getting the max magnitude of the
// output for each block.
input[0] = 0;
for (int k = 1; k < numberOfBlocks; ++k) {
process(input.data(), output.data(), AudioUtilities::renderQuantumSize);
magnitudes[k] = VectorMath::maximumMagnitude(output.data(), AudioUtilities::renderQuantumSize);
}
// Done computing the impulse response; reset the state so the actual node
// starts in the expected initial state.
reset();
// Find the last block with amplitude greater than the threshold.
int index = numberOfBlocks - 1;
for (int k = index; k >= 0; --k) {
if (magnitudes[k] > maxTailAmplitude) {
index = k;
break;
}
}
// The magnitude first become lower than the threshold at the next block.
// Compute the corresponding time value value; that's the tail time.
return (index + 1) * AudioUtilities::renderQuantumSize / sampleRate;
}
} // namespace WebCore
#endif // ENABLE(WEB_AUDIO)