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