diff options
Diffstat (limited to 'WebCore/platform/audio')
-rw-r--r-- | WebCore/platform/audio/Cone.cpp | 84 | ||||
-rw-r--r-- | WebCore/platform/audio/Cone.h | 63 | ||||
-rw-r--r-- | WebCore/platform/audio/FFTConvolver.cpp | 108 | ||||
-rw-r--r-- | WebCore/platform/audio/FFTConvolver.h | 71 | ||||
-rw-r--r-- | WebCore/platform/audio/Reverb.cpp | 227 | ||||
-rw-r--r-- | WebCore/platform/audio/Reverb.h | 66 | ||||
-rw-r--r-- | WebCore/platform/audio/ReverbAccumulationBuffer.cpp | 117 | ||||
-rw-r--r-- | WebCore/platform/audio/ReverbAccumulationBuffer.h | 67 | ||||
-rw-r--r-- | WebCore/platform/audio/ReverbConvolver.cpp | 228 | ||||
-rw-r--r-- | WebCore/platform/audio/ReverbConvolver.h | 97 | ||||
-rw-r--r-- | WebCore/platform/audio/ReverbConvolverStage.cpp | 163 | ||||
-rw-r--r-- | WebCore/platform/audio/ReverbConvolverStage.h | 83 | ||||
-rw-r--r-- | WebCore/platform/audio/ReverbInputBuffer.cpp | 89 | ||||
-rw-r--r-- | WebCore/platform/audio/ReverbInputBuffer.h | 64 |
14 files changed, 1527 insertions, 0 deletions
diff --git a/WebCore/platform/audio/Cone.cpp b/WebCore/platform/audio/Cone.cpp new file mode 100644 index 0000000..91813ab --- /dev/null +++ b/WebCore/platform/audio/Cone.cpp @@ -0,0 +1,84 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "Cone.h" + +namespace WebCore { + +ConeEffect::ConeEffect() + : m_innerAngle(360.0) + , m_outerAngle(360.0) + , m_outerGain(0.0) +{ +} + +double ConeEffect::gain(Vector3 sourcePosition, Vector3 sourceOrientation, Vector3 listenerPosition) +{ + if (sourceOrientation.isZero() || ((m_innerAngle == 360.0) && (m_outerAngle == 360.0))) + return 1.0; // no cone specified - unity gain + + // Normalized source-listener vector + Vector3 sourceToListener = listenerPosition - sourcePosition; + sourceToListener.normalize(); + + Vector3 normalizedSourceOrientation = sourceOrientation; + normalizedSourceOrientation.normalize(); + + // Angle between the source orientation vector and the source-listener vector + double dotProduct = dot(sourceToListener, normalizedSourceOrientation); + double angle = 180.0 * acos(dotProduct) / M_PI; + double absAngle = fabs(angle); + + // Divide by 2.0 here since API is entire angle (not half-angle) + double absInnerAngle = fabs(m_innerAngle) / 2.0; + double absOuterAngle = fabs(m_outerAngle) / 2.0; + double gain = 1.0; + + if (absAngle <= absInnerAngle) + // No attenuation + gain = 1.0; + else if (absAngle >= absOuterAngle) + // Max attenuation + gain = m_outerGain; + else { + // Between inner and outer cones + // inner -> outer, x goes from 0 -> 1 + double x = (absAngle - absInnerAngle) / (absOuterAngle - absInnerAngle); + gain = (1.0 - x) + m_outerGain * x; + } + + return gain; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/platform/audio/Cone.h b/WebCore/platform/audio/Cone.h new file mode 100644 index 0000000..9936f28 --- /dev/null +++ b/WebCore/platform/audio/Cone.h @@ -0,0 +1,63 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef Cone_h +#define Cone_h + +#include <wtf/Vector3.h> + +namespace WebCore { + +// Cone gain is defined according to the OpenAL specification + +class ConeEffect { +public: + ConeEffect(); + + // Returns scalar gain for the given source/listener positions/orientations + double gain(Vector3 sourcePosition, Vector3 sourceOrientation, Vector3 listenerPosition); + + // Angles in degrees + void setInnerAngle(double innerAngle) { m_innerAngle = innerAngle; } + double innerAngle() const { return m_innerAngle; } + + void setOuterAngle(double outerAngle) { m_outerAngle = outerAngle; } + double outerAngle() const { return m_outerAngle; } + + void setOuterGain(double outerGain) { m_outerGain = outerGain; } + double outerGain() const { return m_outerGain; } + +protected: + double m_innerAngle; + double m_outerAngle; + double m_outerGain; +}; + +} // namespace WebCore + +#endif // Cone_h diff --git a/WebCore/platform/audio/FFTConvolver.cpp b/WebCore/platform/audio/FFTConvolver.cpp new file mode 100644 index 0000000..b0211fd --- /dev/null +++ b/WebCore/platform/audio/FFTConvolver.cpp @@ -0,0 +1,108 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "FFTConvolver.h" + +#include "Accelerate.h" + +namespace WebCore { + +FFTConvolver::FFTConvolver(size_t fftSize) + : m_frame(fftSize) + , m_readWriteIndex(0) + , m_inputBuffer(fftSize) // 2nd half of buffer is always zeroed + , m_outputBuffer(fftSize) + , m_lastOverlapBuffer(fftSize / 2) +{ +} + +void FFTConvolver::process(FFTFrame* fftKernel, float* sourceP, float* destP, size_t framesToProcess) +{ + // FIXME: make so framesToProcess is not required to fit evenly into fftSize/2 + + // Copy samples to input buffer (note contraint above!) + float* inputP = m_inputBuffer.data(); + + // Sanity check + bool isCopyGood1 = sourceP && inputP && m_readWriteIndex + framesToProcess <= m_inputBuffer.size(); + ASSERT(isCopyGood1); + if (!isCopyGood1) + return; + + memcpy(inputP + m_readWriteIndex, sourceP, sizeof(float) * framesToProcess); + + // Copy samples from output buffer + float* outputP = m_outputBuffer.data(); + + // Sanity check + bool isCopyGood2 = destP && outputP && m_readWriteIndex + framesToProcess <= m_outputBuffer.size(); + ASSERT(isCopyGood2); + if (!isCopyGood2) + return; + + memcpy(destP, outputP + m_readWriteIndex, sizeof(float) * framesToProcess); + m_readWriteIndex += framesToProcess; + + + // Check if it's time to perform the next FFT + size_t halfSize = fftSize() / 2; + if (m_readWriteIndex == halfSize) { + // The input buffer is now filled (get frequency-domain version) + m_frame.doFFT(m_inputBuffer.data()); + m_frame.multiply(*fftKernel); + m_frame.doInverseFFT(m_outputBuffer.data()); + + // Overlap-add 1st half from previous time + vadd(m_outputBuffer.data(), 1, m_lastOverlapBuffer.data(), 1, m_outputBuffer.data(), 1, halfSize); + + // Finally, save 2nd half of result + bool isCopyGood3 = m_outputBuffer.size() == 2 * halfSize && m_lastOverlapBuffer.size() == halfSize; + ASSERT(isCopyGood3); + if (!isCopyGood3) + return; + + memcpy(m_lastOverlapBuffer.data(), m_outputBuffer.data() + halfSize, sizeof(float) * halfSize); + + // Reset index back to start for next time + m_readWriteIndex = 0; + } +} + +void FFTConvolver::reset() +{ + m_lastOverlapBuffer.zero(); + m_readWriteIndex = 0; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/platform/audio/FFTConvolver.h b/WebCore/platform/audio/FFTConvolver.h new file mode 100644 index 0000000..0eec7c1 --- /dev/null +++ b/WebCore/platform/audio/FFTConvolver.h @@ -0,0 +1,71 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef FFTConvolver_h +#define FFTConvolver_h + +#include "AudioFloatArray.h" +#include "FFTFrame.h" + +namespace WebCore { + +class FFTConvolver { +public: + // fftSize must be a power of two + FFTConvolver(size_t fftSize); + + // For now, with multiple calls to Process(), framesToProcess MUST add up EXACTLY to fftSize / 2 + // + // FIXME: Later, we can do more sophisticated buffering to relax this requirement... + // + // The input to output latency is equal to fftSize / 2 + // + // Processing in-place is allowed... + void process(FFTFrame* fftKernel, float* sourceP, float* destP, size_t framesToProcess); + + void reset(); + + size_t fftSize() const { return m_frame.fftSize(); } + +private: + FFTFrame m_frame; + + // Buffer input until we get fftSize / 2 samples then do an FFT + size_t m_readWriteIndex; + AudioFloatArray m_inputBuffer; + + // Stores output which we read a little at a time + AudioFloatArray m_outputBuffer; + + // Saves the 2nd half of the FFT buffer, so we can do an overlap-add with the 1st half of the next one + AudioFloatArray m_lastOverlapBuffer; +}; + +} // namespace WebCore + +#endif // FFTConvolver_h diff --git a/WebCore/platform/audio/Reverb.cpp b/WebCore/platform/audio/Reverb.cpp new file mode 100644 index 0000000..886a553 --- /dev/null +++ b/WebCore/platform/audio/Reverb.cpp @@ -0,0 +1,227 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "Reverb.h" + +#include "AudioBus.h" +#include "AudioFileReader.h" +#include "ReverbConvolver.h" +#include <math.h> +#include <wtf/MathExtras.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +#if OS(DARWIN) +using namespace std; +#endif + +namespace WebCore { + +// Empirical gain calibration tested across many impulse responses to ensure perceived volume is same as dry (unprocessed) signal +const double GainCalibration = -58.0; + +// A minimum power value to when normalizing a silent (or very quiet) impulse response +const double MinPower = 0.000125; + +static double calculateNormalizationScale(AudioBus* response) +{ + // Normalize by RMS power + size_t numberOfChannels = response->numberOfChannels(); + size_t frameSize = response->frameSize(); + + double power = 0.0; + + for (size_t i = 0; i < numberOfChannels; ++i) { + int n = frameSize; + float* p = response->channel(i)->data(); + + while (n--) { + float sample = *p++; + power += sample * sample; + } + } + + power = sqrt(power / (numberOfChannels * frameSize)); + + // Protect against accidental overload + if (isinf(power) || isnan(power) || power < MinPower) + power = MinPower; + + double scale = 1.0 / power; + + scale *= pow(10.0, GainCalibration * 0.05); // calibrate to make perceived volume same as unprocessed + + // True-stereo compensation + if (response->numberOfChannels() == 4) + scale *= 0.5; + + return scale; +} + +Reverb::Reverb(AudioBus* impulseResponse, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads) +{ + double scale = calculateNormalizationScale(impulseResponse); + if (scale) + impulseResponse->scale(scale); + + initialize(impulseResponse, renderSliceSize, maxFFTSize, numberOfChannels, useBackgroundThreads); + + // Undo scaling since this shouldn't be a destructive operation on impulseResponse + if (scale) + impulseResponse->scale(1.0 / scale); +} + +void Reverb::initialize(AudioBus* impulseResponseBuffer, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads) +{ + m_impulseResponseLength = impulseResponseBuffer->frameSize(); + + // The reverb can handle a mono impulse response and still do stereo processing + size_t numResponseChannels = impulseResponseBuffer->numberOfChannels(); + m_convolvers.reserveCapacity(numberOfChannels); + + int convolverRenderPhase = 0; + for (size_t i = 0; i < numResponseChannels; ++i) { + AudioChannel* channel = impulseResponseBuffer->channel(i); + + ReverbConvolver* convolver = new ReverbConvolver(channel, renderSliceSize, maxFFTSize, convolverRenderPhase, useBackgroundThreads); + m_convolvers.append(convolver); + + convolverRenderPhase += renderSliceSize; + } + + // For "True" stereo processing we allocate a temporary buffer to avoid repeatedly allocating it in the process() method. + // It can be bad to allocate memory in a real-time thread. + if (numResponseChannels == 4) + m_tempBuffer = new AudioBus(2, MaxFrameSize); +} + +void Reverb::process(AudioBus* sourceBus, AudioBus* destinationBus, size_t framesToProcess) +{ + // Do a fairly comprehensive sanity check. + // If these conditions are satisfied, all of the source and destination pointers will be valid for the various matrixing cases. + bool isSafeToProcess = sourceBus && destinationBus && sourceBus->numberOfChannels() > 0 && destinationBus->numberOfChannels() > 0 + && framesToProcess <= MaxFrameSize && framesToProcess <= sourceBus->frameSize() && framesToProcess <= destinationBus->frameSize(); + + ASSERT(isSafeToProcess); + if (!isSafeToProcess) + return; + + // For now only handle mono or stereo output + if (destinationBus->numberOfChannels() > 2) { + destinationBus->zero(); + return; + } + + AudioChannel* destinationChannelL = destinationBus->channel(0); + AudioChannel* sourceChannelL = sourceBus->channel(0); + + // Handle input -> output matrixing... + size_t numInputChannels = sourceBus->numberOfChannels(); + size_t numOutputChannels = destinationBus->numberOfChannels(); + size_t numReverbChannels = m_convolvers.size(); + + if (numInputChannels == 2 && numReverbChannels == 2 && numOutputChannels == 2) { + // 2 -> 2 -> 2 + AudioChannel* sourceChannelR = sourceBus->channel(1); + AudioChannel* destinationChannelR = destinationBus->channel(1); + m_convolvers[0]->process(sourceChannelL, destinationChannelL, framesToProcess); + m_convolvers[1]->process(sourceChannelR, destinationChannelR, framesToProcess); + } else if (numInputChannels == 1 && numOutputChannels == 2 && numReverbChannels == 2) { + // 1 -> 2 -> 2 + for (int i = 0; i < 2; ++i) { + AudioChannel* destinationChannel = destinationBus->channel(i); + m_convolvers[i]->process(sourceChannelL, destinationChannel, framesToProcess); + } + } else if (numInputChannels == 1 && numReverbChannels == 1 && numOutputChannels == 2) { + // 1 -> 1 -> 2 + m_convolvers[0]->process(sourceChannelL, destinationChannelL, framesToProcess); + + // simply copy L -> R + AudioChannel* destinationChannelR = destinationBus->channel(1); + bool isCopySafe = destinationChannelL->data() && destinationChannelR->data() && destinationChannelL->frameSize() >= framesToProcess && destinationChannelR->frameSize() >= framesToProcess; + ASSERT(isCopySafe); + if (!isCopySafe) + return; + memcpy(destinationChannelR->data(), destinationChannelL->data(), sizeof(float) * framesToProcess); + } else if (numInputChannels == 1 && numReverbChannels == 1 && numOutputChannels == 1) { + // 1 -> 1 -> 1 + m_convolvers[0]->process(sourceChannelL, destinationChannelL, framesToProcess); + } else if (numInputChannels == 2 && numReverbChannels == 4 && numOutputChannels == 2) { + // 2 -> 4 -> 2 ("True" stereo) + AudioChannel* sourceChannelR = sourceBus->channel(1); + AudioChannel* destinationChannelR = destinationBus->channel(1); + + AudioChannel* tempChannelL = m_tempBuffer->channel(0); + AudioChannel* tempChannelR = m_tempBuffer->channel(1); + + // Process left virtual source + m_convolvers[0]->process(sourceChannelL, destinationChannelL, framesToProcess); + m_convolvers[1]->process(sourceChannelL, destinationChannelR, framesToProcess); + + // Process right virtual source + m_convolvers[2]->process(sourceChannelR, tempChannelL, framesToProcess); + m_convolvers[3]->process(sourceChannelR, tempChannelR, framesToProcess); + + destinationBus->sumFrom(*m_tempBuffer); + } else if (numInputChannels == 1 && numReverbChannels == 4 && numOutputChannels == 2) { + // 1 -> 4 -> 2 (Processing mono with "True" stereo impulse response) + // This is an inefficient use of a four-channel impulse response, but we should handle the case. + AudioChannel* destinationChannelR = destinationBus->channel(1); + + AudioChannel* tempChannelL = m_tempBuffer->channel(0); + AudioChannel* tempChannelR = m_tempBuffer->channel(1); + + // Process left virtual source + m_convolvers[0]->process(sourceChannelL, destinationChannelL, framesToProcess); + m_convolvers[1]->process(sourceChannelL, destinationChannelR, framesToProcess); + + // Process right virtual source + m_convolvers[2]->process(sourceChannelL, tempChannelL, framesToProcess); + m_convolvers[3]->process(sourceChannelL, tempChannelR, framesToProcess); + + destinationBus->sumFrom(*m_tempBuffer); + } else { + // Handle gracefully any unexpected / unsupported matrixing + // FIXME: add code for 5.1 support... + destinationBus->zero(); + } +} + +void Reverb::reset() +{ + for (size_t i = 0; i < m_convolvers.size(); ++i) + m_convolvers[i]->reset(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/platform/audio/Reverb.h b/WebCore/platform/audio/Reverb.h new file mode 100644 index 0000000..26f5f8e --- /dev/null +++ b/WebCore/platform/audio/Reverb.h @@ -0,0 +1,66 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef Reverb_h +#define Reverb_h + +#include "ReverbConvolver.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class AudioBus; + +// Multi-channel convolution reverb with channel matrixing - one or more ReverbConvolver objects are used internally. + +class Reverb { +public: + enum { MaxFrameSize = 256 }; + + // renderSliceSize is a rendering hint, so the FFTs can be optimized to not all occur at the same time (very bad when rendering on a real-time thread). + Reverb(AudioBus* impulseResponseBuffer, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads); + + void process(AudioBus* sourceBus, AudioBus* destinationBus, size_t framesToProcess); + void reset(); + + unsigned impulseResponseLength() const { return m_impulseResponseLength; } + +private: + void initialize(AudioBus* impulseResponseBuffer, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads); + + size_t m_impulseResponseLength; + + Vector<OwnPtr<ReverbConvolver> > m_convolvers; + + // For "True" stereo processing + OwnPtr<AudioBus> m_tempBuffer; +}; + +} // namespace WebCore + +#endif // Reverb_h diff --git a/WebCore/platform/audio/ReverbAccumulationBuffer.cpp b/WebCore/platform/audio/ReverbAccumulationBuffer.cpp new file mode 100644 index 0000000..7b1c63b --- /dev/null +++ b/WebCore/platform/audio/ReverbAccumulationBuffer.cpp @@ -0,0 +1,117 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "ReverbAccumulationBuffer.h" + +#include "Accelerate.h" + +namespace WebCore { + +ReverbAccumulationBuffer::ReverbAccumulationBuffer(size_t length) + : m_buffer(length) + , m_readIndex(0) + , m_readTimeFrame(0) +{ +} + +void ReverbAccumulationBuffer::readAndClear(float* destination, size_t numberOfFrames) +{ + size_t bufferLength = m_buffer.size(); + bool isCopySafe = m_readIndex <= bufferLength && numberOfFrames <= bufferLength; + + ASSERT(isCopySafe); + if (!isCopySafe) + return; + + size_t framesAvailable = bufferLength - m_readIndex; + size_t numberOfFrames1 = std::min(numberOfFrames, framesAvailable); + size_t numberOfFrames2 = numberOfFrames - numberOfFrames1; + + float* source = m_buffer.data(); + memcpy(destination, source + m_readIndex, sizeof(float) * numberOfFrames1); + memset(source + m_readIndex, 0, sizeof(float) * numberOfFrames1); + + // Handle wrap-around if necessary + if (numberOfFrames2 > 0) { + memcpy(destination + numberOfFrames1, source, sizeof(float) * numberOfFrames2); + memset(source, 0, sizeof(float) * numberOfFrames2); + } + + m_readIndex = (m_readIndex + numberOfFrames) % bufferLength; + m_readTimeFrame += numberOfFrames; +} + +void ReverbAccumulationBuffer::updateReadIndex(int* readIndex, size_t numberOfFrames) const +{ + // Update caller's readIndex + *readIndex = (*readIndex + numberOfFrames) % m_buffer.size(); +} + +int ReverbAccumulationBuffer::accumulate(float* source, size_t numberOfFrames, int* readIndex, size_t delayFrames) +{ + size_t bufferLength = m_buffer.size(); + + size_t writeIndex = (*readIndex + delayFrames) % bufferLength; + + // Update caller's readIndex + *readIndex = (*readIndex + numberOfFrames) % bufferLength; + + size_t framesAvailable = bufferLength - writeIndex; + size_t numberOfFrames1 = std::min(numberOfFrames, framesAvailable); + size_t numberOfFrames2 = numberOfFrames - numberOfFrames1; + + float* destination = m_buffer.data(); + + bool isSafe = writeIndex <= bufferLength && numberOfFrames1 + writeIndex <= bufferLength && numberOfFrames2 <= bufferLength; + ASSERT(isSafe); + if (!isSafe) + return 0; + + vadd(source, 1, destination + writeIndex, 1, destination + writeIndex, 1, numberOfFrames1); + + // Handle wrap-around if necessary + if (numberOfFrames2 > 0) + vadd(source + numberOfFrames1, 1, destination, 1, destination, 1, numberOfFrames2); + + return writeIndex; +} + +void ReverbAccumulationBuffer::reset() +{ + m_buffer.zero(); + m_readIndex = 0; + m_readTimeFrame = 0; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/platform/audio/ReverbAccumulationBuffer.h b/WebCore/platform/audio/ReverbAccumulationBuffer.h new file mode 100644 index 0000000..44a0773 --- /dev/null +++ b/WebCore/platform/audio/ReverbAccumulationBuffer.h @@ -0,0 +1,67 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef ReverbAccumulationBuffer_h +#define ReverbAccumulationBuffer_h + +#include "AudioFloatArray.h" + +namespace WebCore { + +// ReverbAccumulationBuffer is a circular delay buffer with one client reading from it and multiple clients +// writing/accumulating to it at different delay offsets from the read position. The read operation will zero the memory +// just read from the buffer, so it will be ready for accumulation the next time around. +class ReverbAccumulationBuffer { +public: + ReverbAccumulationBuffer(size_t length); + + // This will read from, then clear-out numberOfFrames + void readAndClear(float* destination, size_t numberOfFrames); + + // Each ReverbConvolverStage will accumulate its output at the appropriate delay from the read position. + // We need to pass in and update readIndex here, since each ReverbConvolverStage may be running in + // a different thread than the realtime thread calling ReadAndClear() and maintaining m_readIndex + // Returns the writeIndex where the accumulation took place + int accumulate(float* source, size_t numberOfFrames, int* readIndex, size_t delayFrames); + + size_t readIndex() const { return m_readIndex; } + void updateReadIndex(int* readIndex, size_t numberOfFrames) const; + + size_t readTimeFrame() const { return m_readTimeFrame; } + + void reset(); + +private: + AudioFloatArray m_buffer; + size_t m_readIndex; + size_t m_readTimeFrame; // for debugging (frame on continuous timeline) +}; + +} // namespace WebCore + +#endif // ReverbAccumulationBuffer_h diff --git a/WebCore/platform/audio/ReverbConvolver.cpp b/WebCore/platform/audio/ReverbConvolver.cpp new file mode 100644 index 0000000..719e586 --- /dev/null +++ b/WebCore/platform/audio/ReverbConvolver.cpp @@ -0,0 +1,228 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "ReverbConvolver.h" + +#include "Accelerate.h" +#include "AudioBus.h" + +namespace WebCore { + +const int InputBufferSize = 8 * 16384; + +// We only process the leading portion of the impulse response in the real-time thread. We don't exceed this length. +// It turns out then, that the background thread has about 278msec of scheduling slop. +// Empirically, this has been found to be a good compromise between giving enough time for scheduling slop, +// while still minimizing the amount of processing done in the primary (high-priority) thread. +// This was found to be a good value on Mac OS X, and may work well on other platforms as well, assuming +// the very rough scheduling latencies are similar on these time-scales. Of course, this code may need to be +// tuned for individual platforms if this assumption is found to be incorrect. +const size_t RealtimeFrameLimit = 8192 + 4096; // ~278msec @ 44.1KHz + +const size_t MinFFTSize = 256; +const size_t MaxRealtimeFFTSize = 2048; + +static void* backgroundThreadEntry(void* threadData) +{ + ReverbConvolver* reverbConvolver = static_cast<ReverbConvolver*>(threadData); + reverbConvolver->backgroundThreadEntry(); + return 0; +} + +ReverbConvolver::ReverbConvolver(AudioChannel* impulseResponse, size_t renderSliceSize, size_t maxFFTSize, size_t convolverRenderPhase, bool useBackgroundThreads) + : m_impulseResponseLength(impulseResponse->frameSize()) + , m_accumulationBuffer(impulseResponse->frameSize() + renderSliceSize) + , m_inputBuffer(InputBufferSize) + , m_renderSliceSize(renderSliceSize) + , m_minFFTSize(MinFFTSize) // First stage will have this size - successive stages will double in size each time + , m_maxFFTSize(maxFFTSize) // until we hit m_maxFFTSize + , m_useBackgroundThreads(useBackgroundThreads) + , m_backgroundThread(0) + , m_wantsToExit(false) + , m_moreInputBuffered(false) +{ + // If we are using background threads then don't exceed this FFT size for the + // stages which run in the real-time thread. This avoids having only one or two + // large stages (size 16384 or so) at the end which take a lot of time every several + // processing slices. This way we amortize the cost over more processing slices. + m_maxRealtimeFFTSize = MaxRealtimeFFTSize; + + // For the moment, a good way to know if we have real-time constraint is to check if we're using background threads. + // Otherwise, assume we're being run from a command-line tool. + bool hasRealtimeConstraint = useBackgroundThreads; + + float* response = impulseResponse->data(); + size_t totalResponseLength = impulseResponse->frameSize(); + + // Because we're not using direct-convolution in the leading portion, the reverb has an overall latency of half the first-stage FFT size + size_t reverbTotalLatency = m_minFFTSize / 2; + + size_t stageOffset = 0; + int i = 0; + size_t fftSize = m_minFFTSize; + while (stageOffset < totalResponseLength) { + size_t stageSize = fftSize / 2; + + // For the last stage, it's possible that stageOffset is such that we're straddling the end + // of the impulse response buffer (if we use stageSize), so reduce the last stage's length... + if (stageSize + stageOffset > totalResponseLength) + stageSize = totalResponseLength - stageOffset; + + // This "staggers" the time when each FFT happens so they don't all happen at the same time + int renderPhase = convolverRenderPhase + i * renderSliceSize; + + OwnPtr<ReverbConvolverStage> stage(new ReverbConvolverStage(response, totalResponseLength, reverbTotalLatency, stageOffset, stageSize, fftSize, renderPhase, renderSliceSize, &m_accumulationBuffer)); + + bool isBackgroundStage = false; + + if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) { + m_backgroundStages.append(stage.release()); + isBackgroundStage = true; + } else + m_stages.append(stage.release()); + + stageOffset += stageSize; + ++i; + + // Figure out next FFT size + fftSize *= 2; + if (hasRealtimeConstraint && !isBackgroundStage && fftSize > m_maxRealtimeFFTSize) + fftSize = m_maxRealtimeFFTSize; + if (fftSize > m_maxFFTSize) + fftSize = m_maxFFTSize; + } + + // Start up background thread + // FIXME: would be better to up the thread priority here. It doesn't need to be real-time, but higher than the default... + if (this->useBackgroundThreads() && m_backgroundStages.size() > 0) + m_backgroundThread = createThread(WebCore::backgroundThreadEntry, this, "convolution background thread"); +} + +ReverbConvolver::~ReverbConvolver() +{ + // Wait for background thread to stop + if (useBackgroundThreads() && m_backgroundThread) { + m_wantsToExit = true; + + // Wake up thread so it can return + { + MutexLocker locker(m_backgroundThreadLock); + m_moreInputBuffered = true; + m_backgroundThreadCondition.signal(); + } + + waitForThreadCompletion(m_backgroundThread, 0); + } +} + +void ReverbConvolver::backgroundThreadEntry() +{ + while (!m_wantsToExit) { + // Wait for realtime thread to give us more input + m_moreInputBuffered = false; + { + MutexLocker locker(m_backgroundThreadLock); + while (!m_moreInputBuffered && !m_wantsToExit) + m_backgroundThreadCondition.wait(m_backgroundThreadLock); + } + + // Process all of the stages until their read indices reach the input buffer's write index + int writeIndex = m_inputBuffer.writeIndex(); + + // Even though it doesn't seem like every stage needs to maintain its own version of readIndex + // we do this in case we want to run in more than one background thread. + int readIndex; + + while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME: do better to detect buffer overrun... + // The ReverbConvolverStages need to process in amounts which evenly divide half the FFT size + const int SliceSize = MinFFTSize / 2; + + // Accumulate contributions from each stage + for (size_t i = 0; i < m_backgroundStages.size(); ++i) + m_backgroundStages[i]->processInBackground(this, SliceSize); + } + } +} + +void ReverbConvolver::process(AudioChannel* sourceChannel, AudioChannel* destinationChannel, size_t framesToProcess) +{ + bool isSafe = sourceChannel && destinationChannel && sourceChannel->frameSize() >= framesToProcess && destinationChannel->frameSize() >= framesToProcess; + ASSERT(isSafe); + if (!isSafe) + return; + + float* source = sourceChannel->data(); + float* destination = destinationChannel->data(); + bool isDataSafe = source && destination; + ASSERT(isDataSafe); + if (!isDataSafe) + return; + + // Feed input buffer (read by all threads) + m_inputBuffer.write(source, framesToProcess); + + // Accumulate contributions from each stage + for (size_t i = 0; i < m_stages.size(); ++i) + m_stages[i]->process(source, framesToProcess); + + // Finally read from accumulation buffer + m_accumulationBuffer.readAndClear(destination, framesToProcess); + + // Now that we've buffered more input, wake up our background thread. + + // Not using a MutexLocker looks strange, but we use a tryLock() instead because this is run on the real-time + // thread where it is a disaster for the lock to be contended (causes audio glitching). It's OK if we fail to + // signal from time to time, since we'll get to it the next time we're called. We're called repeatedly + // and frequently (around every 3ms). The background thread is processing well into the future and has a considerable amount of + // leeway here... + if (m_backgroundThreadLock.tryLock()) { + m_moreInputBuffered = true; + m_backgroundThreadCondition.signal(); + m_backgroundThreadLock.unlock(); + } +} + +void ReverbConvolver::reset() +{ + for (size_t i = 0; i < m_stages.size(); ++i) + m_stages[i]->reset(); + + for (size_t i = 0; i < m_backgroundStages.size(); ++i) + m_backgroundStages[i]->reset(); + + m_accumulationBuffer.reset(); + m_inputBuffer.reset(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/platform/audio/ReverbConvolver.h b/WebCore/platform/audio/ReverbConvolver.h new file mode 100644 index 0000000..34f77d3 --- /dev/null +++ b/WebCore/platform/audio/ReverbConvolver.h @@ -0,0 +1,97 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef ReverbConvolver_h +#define ReverbConvolver_h + +#include "AudioFloatArray.h" +#include "FFTConvolver.h" +#include "ReverbAccumulationBuffer.h" +#include "ReverbConvolverStage.h" +#include "ReverbInputBuffer.h" +#include <wtf/OwnPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class AudioChannel; + +class ReverbConvolver { +public: + // maxFFTSize can be adjusted (from say 2048 to 32768) depending on how much precision is necessary. + // For certain tweaky de-convolving applications the phase errors add up quickly and lead to non-sensical results with + // larger FFT sizes and single-precision floats. In these cases 2048 is a good size. + // If not doing multi-threaded convolution, then should not go > 8192. + ReverbConvolver(AudioChannel* impulseResponse, size_t renderSliceSize, size_t maxFFTSize, size_t convolverRenderPhase, bool useBackgroundThreads); + ~ReverbConvolver(); + + void process(AudioChannel* sourceChannel, AudioChannel* destinationChannel, size_t framesToProcess); + void reset(); + + size_t impulseResponseLength() const { return m_impulseResponseLength; } + + ReverbInputBuffer* inputBuffer() { return &m_inputBuffer; } + + bool useBackgroundThreads() const { return m_useBackgroundThreads; } + void backgroundThreadEntry(); + +private: + Vector<OwnPtr<ReverbConvolverStage> > m_stages; + Vector<OwnPtr<ReverbConvolverStage> > m_backgroundStages; + size_t m_impulseResponseLength; + + ReverbAccumulationBuffer m_accumulationBuffer; + + // One or more background threads read from this input buffer which is fed from the realtime thread. + ReverbInputBuffer m_inputBuffer; + + // We're given a rendering hint, so the FFTs can be optimized to not all occur at the same time + // (very bad when rendering on a real-time thread). + size_t m_renderSliceSize; + + // First stage will be of size m_minFFTSize. Each next stage will be twice as big until we hit m_maxFFTSize. + size_t m_minFFTSize; + size_t m_maxFFTSize; + + // But don't exceed this size in the real-time thread (if we're doing background processing). + size_t m_maxRealtimeFFTSize; + + // Background thread and synchronization + bool m_useBackgroundThreads; + ThreadIdentifier m_backgroundThread; + bool m_wantsToExit; + bool m_moreInputBuffered; + mutable Mutex m_backgroundThreadLock; + mutable ThreadCondition m_backgroundThreadCondition; +}; + +} // namespace WebCore + +#endif // ReverbConvolver_h diff --git a/WebCore/platform/audio/ReverbConvolverStage.cpp b/WebCore/platform/audio/ReverbConvolverStage.cpp new file mode 100644 index 0000000..8606502 --- /dev/null +++ b/WebCore/platform/audio/ReverbConvolverStage.cpp @@ -0,0 +1,163 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "ReverbConvolverStage.h" + +#include "Accelerate.h" +#include "ReverbAccumulationBuffer.h" +#include "ReverbConvolver.h" +#include "ReverbInputBuffer.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +ReverbConvolverStage::ReverbConvolverStage(float* impulseResponse, size_t responseLength, size_t reverbTotalLatency, size_t stageOffset, size_t stageLength, + size_t fftSize, size_t renderPhase, size_t renderSliceSize, ReverbAccumulationBuffer* accumulationBuffer) + : m_fftKernel(fftSize) + , m_accumulationBuffer(accumulationBuffer) + , m_accumulationReadIndex(0) + , m_inputReadIndex(0) + , m_impulseResponseLength(responseLength) +{ + ASSERT(impulseResponse); + ASSERT(accumulationBuffer); + + m_fftKernel.doPaddedFFT(impulseResponse + stageOffset, stageLength); + m_convolver = new FFTConvolver(fftSize); + m_temporaryBuffer.allocate(renderSliceSize); + + // The convolution stage at offset stageOffset needs to have a corresponding delay to cancel out the offset. + size_t totalDelay = stageOffset + reverbTotalLatency; + + // But, the FFT convolution itself incurs fftSize / 2 latency, so subtract this out... + size_t halfSize = fftSize / 2; + ASSERT(totalDelay >= halfSize); + if (totalDelay >= halfSize) + totalDelay -= halfSize; + + // We divide up the total delay, into pre and post delay sections so that we can schedule at exactly the moment when the FFT will happen. + // This is coordinated with the other stages, so they don't all do their FFTs at the same time... + int maxPreDelayLength = std::min(halfSize, totalDelay); + m_preDelayLength = totalDelay > 0 ? renderPhase % maxPreDelayLength : 0; + if (m_preDelayLength > totalDelay) + m_preDelayLength = 0; + + m_postDelayLength = totalDelay - m_preDelayLength; + m_preReadWriteIndex = 0; + m_framesProcessed = 0; // total frames processed so far + + m_preDelayBuffer.allocate(m_preDelayLength < fftSize ? fftSize : m_preDelayLength); +} + +void ReverbConvolverStage::processInBackground(ReverbConvolver* convolver, size_t framesToProcess) +{ + ReverbInputBuffer* inputBuffer = convolver->inputBuffer(); + float* source = inputBuffer->directReadFrom(&m_inputReadIndex, framesToProcess); + process(source, framesToProcess); +} + +void ReverbConvolverStage::process(float* source, size_t framesToProcess) +{ + ASSERT(source); + if (!source) + return; + + // Deal with pre-delay stream : note special handling of zero delay. + + float* preDelayedSource; + float* temporaryBuffer; + bool isTemporaryBufferSafe = false; + if (m_preDelayLength > 0) { + // Handles both the read case (call to process() ) and the write case (memcpy() ) + bool isPreDelaySafe = m_preReadWriteIndex + framesToProcess <= m_preDelayBuffer.size(); + ASSERT(isPreDelaySafe); + if (!isPreDelaySafe) + return; + + isTemporaryBufferSafe = framesToProcess <= m_temporaryBuffer.size(); + + preDelayedSource = m_preDelayBuffer.data() + m_preReadWriteIndex; + temporaryBuffer = m_temporaryBuffer.data(); + } else { + // Zero delay + preDelayedSource = source; + temporaryBuffer = m_preDelayBuffer.data(); + + isTemporaryBufferSafe = framesToProcess <= m_preDelayBuffer.size(); + } + + ASSERT(isTemporaryBufferSafe); + if (!isTemporaryBufferSafe) + return; + + int writeIndex = 0; + + if (m_framesProcessed < m_preDelayLength) { + // For the first m_preDelayLength frames don't process the convolver, instead simply buffer in the pre-delay. + // But while buffering the pre-delay, we still need to update our index. + m_accumulationBuffer->updateReadIndex(&m_accumulationReadIndex, framesToProcess); + } else { + // Now, run the convolution (into the delay buffer). + // An expensive FFT will happen every fftSize / 2 frames. + // We process in-place here... + m_convolver->process(&m_fftKernel, preDelayedSource, temporaryBuffer, framesToProcess); + + // Now accumulate into reverb's accumulation buffer. + writeIndex = m_accumulationBuffer->accumulate(temporaryBuffer, framesToProcess, &m_accumulationReadIndex, m_postDelayLength); + } + + // Finally copy input to pre-delay. + if (m_preDelayLength > 0) { + memcpy(preDelayedSource, source, sizeof(float) * framesToProcess); + m_preReadWriteIndex += framesToProcess; + + ASSERT(m_preReadWriteIndex <= m_preDelayLength); + if (m_preReadWriteIndex >= m_preDelayLength) + m_preReadWriteIndex = 0; + } + + m_framesProcessed += framesToProcess; +} + +void ReverbConvolverStage::reset() +{ + m_convolver->reset(); + m_preDelayBuffer.zero(); + m_accumulationReadIndex = 0; + m_inputReadIndex = 0; + m_framesProcessed = 0; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/platform/audio/ReverbConvolverStage.h b/WebCore/platform/audio/ReverbConvolverStage.h new file mode 100644 index 0000000..88351af --- /dev/null +++ b/WebCore/platform/audio/ReverbConvolverStage.h @@ -0,0 +1,83 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef ReverbConvolverStage_h +#define ReverbConvolverStage_h + +#include "AudioFloatArray.h" +#include "FFTFrame.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class ReverbAccumulationBuffer; +class ReverbConvolver; +class FFTConvolver; + +// A ReverbConvolverStage represents the convolution associated with a sub-section of a large impulse response. +// It incorporates a delay line to account for the offset of the sub-section within the larger impulse response. +class ReverbConvolverStage { +public: + // renderPhase is useful to know so that we can manipulate the pre versus post delay so that stages will perform + // their heavy work (FFT processing) on different slices to balance the load in a real-time thread. + ReverbConvolverStage(float* impulseResponse, size_t responseLength, size_t reverbTotalLatency, size_t stageOffset, size_t stageLength, + size_t fftSize, size_t renderPhase, size_t renderSliceSize, ReverbAccumulationBuffer* accumulationBuffer); + + // WARNING: framesToProcess must be such that it evenly divides the delay buffer size (stage_offset). + void process(float* source, size_t framesToProcess); + + void processInBackground(ReverbConvolver* convolver, size_t framesToProcess); + + void reset(); + + // Useful for background processing + int inputReadIndex() const { return m_inputReadIndex; } + +private: + FFTFrame m_fftKernel; + OwnPtr<FFTConvolver> m_convolver; + + AudioFloatArray m_preDelayBuffer; + + ReverbAccumulationBuffer* m_accumulationBuffer; + int m_accumulationReadIndex; + int m_inputReadIndex; + + size_t m_preDelayLength; + size_t m_postDelayLength; + size_t m_preReadWriteIndex; + size_t m_framesProcessed; + + AudioFloatArray m_temporaryBuffer; + + size_t m_impulseResponseLength; +}; + +} // namespace WebCore + +#endif // ReverbConvolverStage_h diff --git a/WebCore/platform/audio/ReverbInputBuffer.cpp b/WebCore/platform/audio/ReverbInputBuffer.cpp new file mode 100644 index 0000000..f270f6f --- /dev/null +++ b/WebCore/platform/audio/ReverbInputBuffer.cpp @@ -0,0 +1,89 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "ReverbInputBuffer.h" + +namespace WebCore { + +ReverbInputBuffer::ReverbInputBuffer(size_t length) + : m_buffer(length) + , m_writeIndex(0) +{ +} + +void ReverbInputBuffer::write(float* sourceP, size_t numberOfFrames) +{ + size_t bufferLength = m_buffer.size(); + bool isCopySafe = m_writeIndex + numberOfFrames <= bufferLength; + ASSERT(isCopySafe); + if (!isCopySafe) + return; + + memcpy(m_buffer.data() + m_writeIndex, sourceP, sizeof(float) * numberOfFrames); + + m_writeIndex += numberOfFrames; + ASSERT(m_writeIndex <= bufferLength); + + if (m_writeIndex >= bufferLength) + m_writeIndex = 0; +} + +float* ReverbInputBuffer::directReadFrom(int* readIndex, size_t numberOfFrames) +{ + size_t bufferLength = m_buffer.size(); + bool isPointerGood = readIndex && *readIndex >= 0 && *readIndex + numberOfFrames <= bufferLength; + ASSERT(isPointerGood); + if (!isPointerGood) { + // Should never happen in practice but return pointer to start of buffer (avoid crash) + if (readIndex) + *readIndex = 0; + return m_buffer.data(); + } + + float* sourceP = m_buffer.data(); + float* p = sourceP + *readIndex; + + // Update readIndex + *readIndex = (*readIndex + numberOfFrames) % bufferLength; + + return p; +} + +void ReverbInputBuffer::reset() +{ + m_buffer.zero(); + m_writeIndex = 0; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/platform/audio/ReverbInputBuffer.h b/WebCore/platform/audio/ReverbInputBuffer.h new file mode 100644 index 0000000..aa9cf41 --- /dev/null +++ b/WebCore/platform/audio/ReverbInputBuffer.h @@ -0,0 +1,64 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef ReverbInputBuffer_h +#define ReverbInputBuffer_h + +#include "AudioFloatArray.h" + +namespace WebCore { + +// ReverbInputBuffer is used to buffer input samples for deferred processing by the background threads. +class ReverbInputBuffer { +public: + ReverbInputBuffer(size_t length); + + // The realtime audio thread keeps writing samples here. + // The assumption is that the buffer's length is evenly divisible by numberOfFrames (for nearly all cases this will be fine). + // FIXME: remove numberOfFrames restriction... + void write(float* sourceP, size_t numberOfFrames); + + // Background threads can call this to check if there's anything to read... + size_t writeIndex() const { return m_writeIndex; } + + // The individual background threads read here (and hope that they can keep up with the buffer writing). + // readIndex is updated with the next readIndex to read from... + // The assumption is that the buffer's length is evenly divisible by numberOfFrames. + // FIXME: remove numberOfFrames restriction... + float* directReadFrom(int* readIndex, size_t numberOfFrames); + + void reset(); + +private: + AudioFloatArray m_buffer; + size_t m_writeIndex; +}; + +} // namespace WebCore + +#endif // ReverbInputBuffer_h |