| /* |
| * 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) |