241 lines
9.7 KiB
C++
241 lines
9.7 KiB
C++
/*
|
|
* Copyright (C) 2010, Google Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#if ENABLE(WEB_AUDIO)
|
|
|
|
#include "DelayDSPKernel.h"
|
|
|
|
#include "AudioUtilities.h"
|
|
#include "VectorMath.h"
|
|
#include <algorithm>
|
|
|
|
namespace WebCore {
|
|
|
|
static size_t bufferLengthForDelay(double maxDelayTime, double sampleRate)
|
|
{
|
|
// Compute the length of the buffer needed to handle a max delay of |maxDelayTime|. Add an additional render quantum frame size so we can
|
|
// vectorize the delay processing. The extra space is needed so that writes to the buffer won't overlap reads from the buffer.
|
|
return AudioUtilities::renderQuantumSize + AudioUtilities::timeToSampleFrame(maxDelayTime, sampleRate, AudioUtilities::SampleFrameRounding::Up);
|
|
}
|
|
|
|
// Returns (a - b) if a is greater than b, 0 otherwise.
|
|
template<typename T> static inline size_t positiveSubtract(T a, T b)
|
|
{
|
|
return a <= b ? 0 : static_cast<size_t>(a - b);
|
|
}
|
|
|
|
static void copyToCircularBuffer(float* buffer, size_t writeIndex, size_t bufferLength, const float* source, size_t framesToProcess)
|
|
{
|
|
// The algorithm below depends on this being true because we don't expect to have to fill the entire buffer more than once.
|
|
RELEASE_ASSERT(bufferLength >= framesToProcess);
|
|
|
|
// Copy |framesToProcess| values from |source| to the circular buffer that starts at |buffer| of length |bufferLength|. The
|
|
// copy starts at index |writeIndex| into the buffer.
|
|
auto* writePointer = &buffer[writeIndex];
|
|
size_t remainder = positiveSubtract(bufferLength, writeIndex);
|
|
|
|
// Copy the frames over, carefully handling the case where we need to wrap around to the beginning of the buffer.
|
|
memcpy(writePointer, source, sizeof(*writePointer) * std::min(framesToProcess, remainder));
|
|
memcpy(buffer, source + remainder, sizeof(*buffer) * positiveSubtract(framesToProcess, remainder));
|
|
}
|
|
|
|
DelayDSPKernel::DelayDSPKernel(DelayProcessor* processor)
|
|
: AudioDSPKernel(processor)
|
|
, m_delayTimes(AudioUtilities::renderQuantumSize)
|
|
, m_tempBuffer(AudioUtilities::renderQuantumSize)
|
|
{
|
|
ASSERT(processor && processor->sampleRate() > 0);
|
|
if (!(processor && processor->sampleRate() > 0))
|
|
return;
|
|
|
|
m_maxDelayTime = processor->maxDelayTime();
|
|
ASSERT(m_maxDelayTime >= 0);
|
|
if (m_maxDelayTime < 0)
|
|
return;
|
|
|
|
m_buffer.resize(bufferLengthForDelay(m_maxDelayTime, processor->sampleRate()));
|
|
}
|
|
|
|
DelayDSPKernel::DelayDSPKernel(double maxDelayTime, float sampleRate)
|
|
: AudioDSPKernel(sampleRate)
|
|
, m_maxDelayTime(maxDelayTime)
|
|
, m_tempBuffer(AudioUtilities::renderQuantumSize)
|
|
{
|
|
ASSERT(maxDelayTime > 0.0);
|
|
if (maxDelayTime <= 0.0)
|
|
return;
|
|
|
|
size_t bufferLength = bufferLengthForDelay(maxDelayTime, sampleRate);
|
|
ASSERT(bufferLength);
|
|
if (!bufferLength)
|
|
return;
|
|
|
|
m_buffer.resize(bufferLength);
|
|
}
|
|
|
|
void DelayDSPKernel::process(const float* source, float* destination, size_t framesToProcess)
|
|
{
|
|
ASSERT(m_buffer.size());
|
|
ASSERT(source && destination);
|
|
if (UNLIKELY(m_buffer.isEmpty() || !source || !destination))
|
|
return;
|
|
|
|
bool sampleAccurate = delayProcessor() && delayProcessor()->delayTime().hasSampleAccurateValues();
|
|
bool shouldUseARate = delayProcessor() && delayProcessor()->delayTime().automationRate() == AutomationRate::ARate;
|
|
if (sampleAccurate && shouldUseARate)
|
|
processARate(source, destination, framesToProcess);
|
|
else
|
|
processKRate(source, destination, framesToProcess);
|
|
}
|
|
|
|
void DelayDSPKernel::processARate(const float* source, float* destination, size_t framesToProcess)
|
|
{
|
|
size_t bufferLength = m_buffer.size();
|
|
auto* buffer = m_buffer.data();
|
|
|
|
delayProcessor()->delayTime().calculateSampleAccurateValues(m_delayTimes.data(), framesToProcess);
|
|
|
|
copyToCircularBuffer(buffer, m_writeIndex, bufferLength, source, framesToProcess);
|
|
|
|
for (unsigned i = 0; i < framesToProcess; ++i) {
|
|
double delayTime = std::clamp<double>(m_delayTimes[i], 0.0, maxDelayTime());
|
|
double desiredDelayFrames = delayTime * sampleRate();
|
|
|
|
double readPosition = m_writeIndex + bufferLength - desiredDelayFrames;
|
|
if (readPosition >= bufferLength)
|
|
readPosition -= bufferLength;
|
|
|
|
// Linearly interpolate in-between delay times.
|
|
size_t readIndex1 = static_cast<size_t>(readPosition);
|
|
size_t readIndex2 = (readIndex1 + 1) % bufferLength;
|
|
float interpolationFactor = readPosition - readIndex1;
|
|
|
|
m_writeIndex = (m_writeIndex + 1) % bufferLength;
|
|
|
|
float sample1 = buffer[readIndex1];
|
|
float sample2 = buffer[readIndex2];
|
|
destination[i] = sample1 + interpolationFactor * (sample2 - sample1);
|
|
}
|
|
}
|
|
|
|
// Optimized version of processARate() when the delayTime is constant.
|
|
void DelayDSPKernel::processKRate(const float* source, float* destination, size_t framesToProcess)
|
|
{
|
|
size_t bufferLength = m_buffer.size();
|
|
auto* buffer = m_buffer.data();
|
|
|
|
double delayTime = delayProcessor() ? delayProcessor()->delayTime().finalValue() : m_desiredDelayFrames / sampleRate();
|
|
// Make sure the delay time is in a valid range.
|
|
delayTime = std::clamp(delayTime, 0.0, maxDelayTime());
|
|
double desiredDelayFrames = delayTime * sampleRate();
|
|
|
|
double readPosition = m_writeIndex + bufferLength - desiredDelayFrames;
|
|
if (readPosition >= bufferLength)
|
|
readPosition -= bufferLength;
|
|
|
|
// Linearly interpolate in-between delay times. |readIndex1| and |readIndex2| are the indices of the frames to be used
|
|
// for interpolation.
|
|
size_t readIndex1 = static_cast<size_t>(readPosition);
|
|
float interpolationFactor = readPosition - readIndex1;
|
|
auto* bufferEnd = &buffer[bufferLength];
|
|
ASSERT(static_cast<unsigned>(bufferLength) >= framesToProcess);
|
|
|
|
// sample1 and sample2 hold the current and next samples in the buffer. These are used for interoplating the delay value.
|
|
// To reduce memory usage and an extra memcpy, sample1 can be the same as destination.
|
|
// VectorMath::interpolate() below has an optimization in the case where the input buffer is the same as the output one.
|
|
auto* sample1 = destination;
|
|
|
|
// Copy data from the source into the buffer, starting at the write index. The buffer is circular, so carefully handle
|
|
// the wrapping of the write pointer.
|
|
copyToCircularBuffer(buffer, m_writeIndex, bufferLength, source, framesToProcess);
|
|
m_writeIndex = (m_writeIndex + framesToProcess) % bufferLength;
|
|
|
|
// Now copy out the samples from the buffer, starting at the read pointer, carefully handling wrapping of the read pointer.
|
|
auto* readPointer = &buffer[readIndex1];
|
|
|
|
size_t remainder = positiveSubtract(bufferEnd, readPointer);
|
|
memcpy(sample1, readPointer, sizeof(*sample1) * std::min(framesToProcess, remainder));
|
|
memcpy(sample1 + remainder, buffer, sizeof(*sample1) * positiveSubtract(framesToProcess, remainder));
|
|
|
|
// If interpolationFactor is 0, we don't need to do any interpolation and sample1 contains the desired values.
|
|
if (!interpolationFactor)
|
|
return;
|
|
|
|
ASSERT(framesToProcess <= m_tempBuffer.size());
|
|
|
|
size_t readIndex2 = (readIndex1 + 1) % bufferLength;
|
|
auto* sample2 = m_tempBuffer.data();
|
|
|
|
readPointer = &buffer[readIndex2];
|
|
remainder = positiveSubtract(bufferEnd, readPointer);
|
|
memcpy(sample2, readPointer, sizeof(*sample2) * std::min(framesToProcess, remainder));
|
|
memcpy(sample2 + remainder, buffer, sizeof(*sample2) * positiveSubtract(framesToProcess, remainder));
|
|
|
|
// Interpolate samples.
|
|
// destination[k] = sample1[k] + interpolationFactor * (sample2[k] - sample1[k]);
|
|
VectorMath::interpolate(sample1, sample2, interpolationFactor, destination, framesToProcess);
|
|
}
|
|
|
|
void DelayDSPKernel::processOnlyAudioParams(size_t framesToProcess)
|
|
{
|
|
if (!delayProcessor())
|
|
return;
|
|
|
|
float values[AudioUtilities::renderQuantumSize];
|
|
ASSERT(framesToProcess <= AudioUtilities::renderQuantumSize);
|
|
|
|
delayProcessor()->delayTime().calculateSampleAccurateValues(values, framesToProcess);
|
|
}
|
|
|
|
void DelayDSPKernel::reset()
|
|
{
|
|
m_buffer.zero();
|
|
}
|
|
|
|
double DelayDSPKernel::tailTime() const
|
|
{
|
|
return m_maxDelayTime;
|
|
}
|
|
|
|
double DelayDSPKernel::latencyTime() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool DelayDSPKernel::requiresTailProcessing() const
|
|
{
|
|
// Always return true even if the tail time and latency might both
|
|
// be zero. This is for simplicity; most interesting delay nodes
|
|
// have non-zero delay times anyway. And it's ok to return true. It
|
|
// just means the node lives a little longer than strictly
|
|
// necessary.
|
|
return true;
|
|
}
|
|
|
|
} // namespace WebCore
|
|
|
|
#endif // ENABLE(WEB_AUDIO)
|