[Webkit-unassigned] [Bug 34912] audio engine: add ReverbConvolver class
bugzilla-daemon at webkit.org
bugzilla-daemon at webkit.org
Fri Mar 12 04:43:35 PST 2010
https://bugs.webkit.org/show_bug.cgi?id=34912
Jeremy Orlow <jorlow at chromium.org> changed:
What |Removed |Added
----------------------------------------------------------------------------
Attachment #48676|review? |review-
Flag| |
--- Comment #4 from Jeremy Orlow <jorlow at chromium.org> 2010-03-12 04:43:35 PST ---
(From update of attachment 48676)
I'll take a closer look once these comments are addressed.
> diff --git a/WebCore/platform/audio/ReverbConvolver.cpp b/WebCore/platform/audio/ReverbConvolver.cpp
> new file mode 100644
> index 0000000..0594c8c
> --- /dev/null
> +++ b/WebCore/platform/audio/ReverbConvolver.cpp
> @@ -0,0 +1,476 @@
> +/*
> + * 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"
> +#include "ReverbConvolver.h"
> +
> +#include "Accelerate.h"
> +#include "AudioBus.h"
> +
> +namespace WebCore {
> +
> +const int kInputBufferSize = 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
> +const size_t kRealtimeFrameLimit = 8192 + 4096; // ~278msec @ 44.1KHz
How was this measured? On what platforms? How will we make sure that this
stays close to reality? (I.e. can we make a layout test that verifies this or
something?)
> +
> +const size_t kMinFFTSize = 256;
> +const size_t kMaxRealtimeFFTSize = 2048;
I believe the preferred style is LikeThis. I.e. no k prefix. It's unfortunate
that there are so many examples of the k prefix (and other styles, IIRC).
> +
> +static void* BackgroundThreadDispatch(void* p)
p is not a very good variable name.
> +{
> + ReverbConvolver* This = static_cast<ReverbConvolver*>(p);
> + This->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(kInputBufferSize)
> + , m_renderSliceSize(renderSliceSize)
> + , m_useBackgroundThreads(useBackgroundThreads)
> + , m_wantsToExit(false)
> +{
> + m_minFFTSize = kMinFFTSize; // First stage will have this size
> + // successive stages will double in size each time
This comment could have all just gone on the same line. WebKit does not share
Chromium's 80 col limit.
> +
> + m_maxFFTSize = maxFFTSize; // until we hit |m_maxFFTSize|
> +
> + // 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 = kMaxRealtimeFFTSize;
> +
> + // 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;
Is any such "command line tool" intended to be upstreamed? If not, this
support should be removed.
> + float* response = impulseResponse->data();
> + size_t totalResponseLength = impulseResponse->frameSize();
> +
> + ReverbAccumulationBuffer* accumBufferP = 0;
> +
This is an example of where you bleed vertical space when you probably don't
need to.
> + accumBufferP = &m_accumulationBuffer;
> +
> + // Because we're not using direct-convolution first 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;
> +
Ditto.
> + 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...
> +
> + // May have to reduce length of last stage (clip to very end of response)
> + 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;
> +
> + ReverbConvolverStage* stage = new ReverbConvolverStage(response,
> + totalResponseLength,
> + reverbTotalLatency,
> + stageOffset,
> + stageSize,
> + fftSize,
> + renderPhase,
> + renderSliceSize,
> + accumBufferP);
This should all be on one line. It should use a ::create function. The
::create function should return a PassOwnPtr<>. Avoid manual memory management
at all costs.
> +
> + bool isBackgroundStage = false;
> +
> + if (stageOffset <= kRealtimeFrameLimit)
> + m_stages.append(stage);
> + else {
> + if (this->useBackgroundThreads()) {
> + m_backgroundStages.append(stage);
> + isBackgroundStage = true;
> + } else
> + m_stages.append(stage);
> + }
> +
> + 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)
> + pthread_create(&m_backgroundThread, 0, BackgroundThreadDispatch, this);
> + else
> + m_backgroundThread = 0;
> +}
> +
> +ReverbConvolver::~ReverbConvolver()
> +{
> + // Wait for background thread to stop
> + if (useBackgroundThreads() && m_backgroundThread) {
> + m_wantsToExit = true;
> + pthread_join(m_backgroundThread, 0);
> + }
> +
> + for (size_t i = 0; i < m_stages.size(); ++i)
> + delete m_stages[i];
> +
> + for (size_t i = 0; i < m_backgroundStages.size(); ++i)
> + delete m_backgroundStages[i];
> +}
> +
> +void ReverbConvolver::backgroundThreadEntry()
> +{
> + while (!m_wantsToExit) {
> + // Check to see if there's any more input to consume
> + 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
Add a period to the end of this.
> + int readIndex;
> +
> + while ((readIndex = m_backgroundStages[0]->inputReadIndex()) != writeIndex) { // FIXME : do better to detect buffer overrun...
> + // FIXME : remove hard-coded value
> + const int kSliceSize = 128;
> +
> + // Accumulate contributions from each stage
> + for (size_t i = 0; i < m_backgroundStages.size(); ++i)
> + m_backgroundStages[i]->processInBackground(this, kSliceSize);
> + }
> +
> + // Sleep 10ms
> + usleep(10000); // FIXME : this really isn't ideal - could use wait/signal
> + }
> +}
> +
> +size_t ReverbConvolver::impulseResponseLength()
> +{
> + return m_impulseResponseLength;
> +}
> +
> +void ReverbConvolver::process(float* sourceP,
> + float* destP,
> + size_t framesToProcess)
All on the same line.
> +{
> + // Feed input buffer (read by all threads)
> + m_inputBuffer.write(sourceP, framesToProcess);
> +
> + // Accumulate contributions from each stage
> + for (size_t i = 0; i < m_stages.size(); ++i)
> + m_stages[i]->process(sourceP, framesToProcess);
> +
> + // Finally read from accumulation buffer
> + m_accumulationBuffer.readAndClear(destP, framesToProcess);
> +}
> +
> +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();
> +}
> +
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +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)
I'm not sure, but I believe this should be on the same line...even though the
initializing list below is supposed to be split.
> + : m_fftKernel(fftSize)
> + , m_accumulationBuffer(accumulationBuffer)
> + , m_accumulationReadIndex(0)
> + , m_inputReadIndex(0)
> + , m_accumulationReadTimeFrame(0)
> + , m_impulseResponseLength(responseLength)
> +{
> + m_fftKernel.doPaddedFFT(impulseResponse + stageOffset, stageLength);
> +
> + m_convolver = new FFTConvolver(fftSize);
> +
> + m_tempBuffer.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;
> + if (totalDelay >= halfSize)
> + totalDelay -= halfSize;
> +
> + // FIXME : DEAL with case when total delay is less than fftSize/2....
> +
> + // 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 m = (halfSize <= totalDelay) ? halfSize : totalDelay;
> + m_preDelayLength = (totalDelay > 0) ? (renderPhase % m) : 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);
> +}
> +
> +ReverbConvolverStage::~ReverbConvolverStage()
> +{
> + delete m_convolver;
Avoid manual memory management. Use stuff like OwnPtrs.
> +}
> +
> +void ReverbConvolverStage::processInBackground(ReverbConvolver* convolver,
> + size_t framesToProcess)
> +{
> + ReverbInputBuffer& inputBuffer = convolver->inputBuffer();
> +
> + float* sourceP = inputBuffer.directReadFrom(&m_inputReadIndex, framesToProcess);
> +
> + process(sourceP, framesToProcess);
> +}
> +
> +void ReverbConvolverStage::process(float* sourceP,
> + size_t framesToProcess)
> +{
> + //
> + // FIXME : do sanity check on framesToProcess versus delay buffer size
> + //
> +
> + // Get pointer to pre-delay stream : note special handling of zero delay
> + float* preDelayedSourceP = sourceP;
> + float* preDelayBufferP = m_preDelayBuffer;
> + float* tempP = preDelayBufferP;
> + if (m_preDelayLength > 0) {
> + preDelayedSourceP = preDelayBufferP + m_preReadWriteIndex;
> + tempP = m_tempBuffer;
> + }
> +
> + 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
> +
> + // 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,
> + preDelayedSourceP,
> + tempP,
> + framesToProcess);
> +
> + // Now accumulate into reverb's accumulation buffer
> + // FIXME : really need to have locking mechanism here!!
> + writeIndex = m_accumulationBuffer->accumulate(tempP,
> + framesToProcess,
> + &m_accumulationReadIndex,
> + m_postDelayLength);
> + }
> +
> + // Finally copy input to pre-delay
> + if (m_preDelayLength > 0) {
> + memcpy(preDelayedSourceP, sourceP, sizeof(float) * framesToProcess);
> + m_preReadWriteIndex += framesToProcess;
> +
> + if (m_preReadWriteIndex >= m_preDelayLength)
> + m_preReadWriteIndex = 0; // should only be <=
> + }
> +
> +#if 0
Typically we like to avoid dead code in the tree like this. Has this perhaps
served its purpose, so we can get rid of it?
> + // TESTING - sanity check
> + int timelineReadFrame = m_accumulationBuffer->readTimeFrame();
> + int timelineWriteFrame = m_accumulationReadTimeFrame + m_postDelayLength;
> +
> + // printf("%p %p: ReverbConvolverStage::process(%d) : (%d \t %d)\n", pthread_self(), this, m_fftKernel.fftSize(), timelineWriteFrame, timelineReadFrame);
> +
> + if (timelineReadFrame > timelineWriteFrame)
> + printf("%p %p: ReverbConvolverStage::process(%d) : (%d \t %d)\n", pthread_self(), this, m_fftKernel.fftSize(), timelineWriteFrame, timelineReadFrame);
> +
> + if (timelineWriteFrame > timelineReadFrame + m_impulseResponseLength)
> + printf("%p %p: ReverbConvolverStage::process(%d) : (%d \t %d)\n", pthread_self(), this, m_fftKernel.fftSize(), timelineWriteFrame, timelineReadFrame);
> +#endif
> +
> + m_accumulationReadTimeFrame += framesToProcess;
> + m_framesProcessed += framesToProcess;
> +}
> +
> +void ReverbConvolverStage::reset()
> +{
> + m_convolver->reset();
> + m_preDelayBuffer.zero();
> + m_accumulationReadIndex = 0;
> + m_inputReadIndex = 0;
> + m_framesProcessed = 0;
> + m_accumulationReadTimeFrame = 0;
> +}
> +
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +ReverbAccumulationBuffer::ReverbAccumulationBuffer(size_t length)
> + : m_buffer(length)
> + , m_length(length)
> + , m_readIndex(0)
> + , m_readTimeFrame(0)
> +{
> +}
> +
> +void ReverbAccumulationBuffer::readAndClear(float* destP, size_t nframes)
> +{
> + assert(m_readIndex <= m_length);
> + size_t framesAvailable = m_length - m_readIndex;
> + size_t nframes1 = (nframes <= framesAvailable) ? nframes : framesAvailable;
> + size_t nframes2 = nframes - nframes1;
> +
> + float* sourceP = m_buffer;
> + memcpy(destP, sourceP + m_readIndex, sizeof(float) * nframes1);
> + memset(sourceP + m_readIndex, 0, sizeof(float) * nframes1);
> +
> + // Handle wrap-around if necessary
> + if (nframes2 > 0) {
> + memcpy(destP + nframes1, sourceP, sizeof(float) * nframes2);
> + memset(sourceP, 0, sizeof(float) * nframes2);
> + }
> +
> + m_readIndex = (m_readIndex + nframes) % m_length;
> + m_readTimeFrame += nframes;
> +}
> +
> +void ReverbAccumulationBuffer::updateReadIndex(int* readIndex, size_t nframes)
> +{
> + // Update caller's |readIndex|
> + *readIndex = (*readIndex + nframes) % m_length;
> +}
> +
> +int ReverbAccumulationBuffer::accumulate(float* sourceP,
> + size_t nframes,
> + int* readIndex,
> + size_t delayFrames)
> +{
> + size_t writeIndex = (*readIndex + delayFrames) % m_length;
> +
> + // Update caller's |readIndex|
> + *readIndex = (*readIndex + nframes) % m_length;
> +
> + assert(writeIndex <= m_length);
> + size_t framesAvailable = m_length - writeIndex;
> + size_t nframes1 = (nframes <= framesAvailable) ? nframes : framesAvailable;
> + size_t nframes2 = nframes - nframes1;
> +
> + float* destP = m_buffer;
> +
> + vadd(sourceP,
> + 1,
> + destP + writeIndex,
> + 1,
> + destP + writeIndex,
> + 1,
> + nframes1);
> +
> + // Handle wrap-around if necessary
> + if (nframes2 > 0) {
> + vadd(sourceP + nframes1,
> + 1,
> + destP,
> + 1,
> + destP,
> + 1,
> + nframes2);
Don't split
> + }
> +
> + return writeIndex;
> +}
> +
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This isn't really WebKit's style.
> +
> +ReverbInputBuffer::ReverbInputBuffer(size_t length)
> + : m_buffer(length)
> + , m_length(length)
> + , m_writeIndex(0)
> +{
> +}
> +
> +void ReverbInputBuffer::write(float* sourceP, size_t nframes)
> +{
> + memcpy(m_buffer.data() + m_writeIndex, sourceP, sizeof(float) * nframes);
> +
> + m_writeIndex += nframes;
> + assert(m_writeIndex <= m_length);
> +
> + if (m_writeIndex >= m_length)
> + m_writeIndex = 0;
> +}
> +
> +float* ReverbInputBuffer::directReadFrom(int* index, size_t nframes)
> +{
> + assert(*index >= 0 && *index + nframes <= m_length);
> + float* sourceP = m_buffer;
> + float* p = sourceP + *index;
> +
> + // Update index
> + *index = (*index + nframes) % m_length;
> +
> + return p;
> +}
> +
> +} // namespace WebCore
> diff --git a/WebCore/platform/audio/ReverbConvolver.h b/WebCore/platform/audio/ReverbConvolver.h
> new file mode 100644
> index 0000000..e37ccf9
> --- /dev/null
> +++ b/WebCore/platform/audio/ReverbConvolver.h
> @@ -0,0 +1,226 @@
> +/*
> + * 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 <pthread.h>
There's thread abstractions in wtf. Please use those.
> +#include <wtf/Vector.h>
> +
> +namespace WebCore {
> +
> +class AudioChannel;
> +class ReverbConvolver;
> +
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +// 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 {
Each class should have its own .h file and .cpp file.
> +public:
> + ReverbAccumulationBuffer(size_t length);
> +
> + // This will read from, then clear-out |nframes|
> + void readAndClear(float* destP, size_t nframes);
> +
> + // 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* sourceP,
> + size_t nframes,
> + int* readIndex,
> + size_t delayFrames);
Don't split things onto multiple lines like this unless you have a _really_
good reason.
> +
> + size_t readIndex() const { return m_readIndex; }
> + size_t readTimeFrame() const { return m_readTimeFrame; }
> +
> + void updateReadIndex(int* readIndex, size_t nframes);
> +
> + void reset()
> + {
> + m_buffer.zero();
> + m_readIndex = 0;
> + m_readTimeFrame = 0;
> + }
> +
> +private:
> + AudioFloatArray m_buffer;
> + size_t m_length;
> + size_t m_readIndex;
> + size_t m_readTimeFrame; // for debugging (frame on continuous timeline)
> +};
> +
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +// 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 |nframes| (for nearly all cases this will be fine)
> + // FIXME : remove |nframes| restriction...
Use FIXME: not FIXME :. Here and lots of places.
> + void write(float* sourceP, size_t nframes);
> +
> + // Background threads can call this to check if there's anything to read...
> + // FIXME : create better system to check for buffer overruns / error conditions...
> + size_t writeIndex() const { return m_writeIndex; }
> +
> + // The individual background threads read here (and hope that they can keep up with the buffer writing)
> + // |index| is updated with the next index to read from...
> + // The assumption is that the buffer's length is evenly divisible by |nframes|
> + // FIXME : remove |nframes| restriction...
> + float* directReadFrom(int* index, size_t nframes);
> +
> + void reset()
> + {
> + m_buffer.zero();
> + m_writeIndex = 0;
> + }
> +
> +private:
> + AudioFloatArray m_buffer;
> + size_t m_length;
> + size_t m_writeIndex;
> +};
> +
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +// 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);
> +
> + virtual ~ReverbConvolverStage();
> +
> + // WARNING: |framesToProcess| must be such that it evenly divides the delay buffer size (stage_offset)
> + void process(float* sourceP, 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;
> + FFTConvolver* m_convolver;
> +
> + AudioFloatArray m_preDelayBuffer;
> +
> + ReverbAccumulationBuffer* m_accumulationBuffer;
> + int m_accumulationReadIndex;
> + int m_inputReadIndex;
> +
> + int m_accumulationReadTimeFrame; // for testing (frame on continuous timeline)
> +
> + size_t m_preDelayLength;
> + size_t m_postDelayLength;
> + size_t m_preReadWriteIndex;
> + size_t m_framesProcessed;
> +
> + AudioFloatArray m_tempBuffer;
> +
> + size_t m_impulseResponseLength;
> +};
> +
> +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +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);
> +
> + virtual ~ReverbConvolver();
> +
> + void process(float* sourceP,
> + float* destP,
> + size_t framesToProcess);
> +
> + void reset();
> +
> + size_t impulseResponseLength();
> +
> + void backgroundThreadEntry();
> + ReverbInputBuffer& inputBuffer() { return m_inputBuffer; }
> +
> + bool useBackgroundThreads() const { return m_useBackgroundThreads; }
> +
> +private:
> + Vector<ReverbConvolverStage*> m_stages;
> + Vector<ReverbConvolverStage*> m_backgroundStages;
> + size_t m_impulseResponseLength;
> +
> + ReverbAccumulationBuffer m_accumulationBuffer;
> +
> + // For multithreading
> + 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;
> +
> + // FIXME : use thread abstraction (assuming pthreads here)
> + bool m_useBackgroundThreads;
> + pthread_t m_backgroundThread;
> + bool m_wantsToExit;
> +};
> +
> +} // namespace WebCore
> +
> +#endif // ReverbConvolver_h
--
Configure bugmail: https://bugs.webkit.org/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
You are the assignee for the bug.
More information about the webkit-unassigned
mailing list