/*
  ==============================================================================

    This file was auto-generated!

    It contains the basic framework code for an ARA playback renderer implementation.

  ==============================================================================
*/

%%araplaybackrenderer_headers%%

//==============================================================================
void %%araplaybackrenderer_class_name%%::prepareToPlay (double sampleRateIn, int maximumSamplesPerBlockIn, int numChannelsIn, juce::AudioProcessor::ProcessingPrecision, AlwaysNonRealtime alwaysNonRealtime)
{
    numChannels = numChannelsIn;
    sampleRate = sampleRateIn;
    maximumSamplesPerBlock = maximumSamplesPerBlockIn;
    useBufferedAudioSourceReader = alwaysNonRealtime == AlwaysNonRealtime::no;
}

void %%araplaybackrenderer_class_name%%::releaseResources()
{
}

//==============================================================================
bool %%araplaybackrenderer_class_name%%::processBlock (juce::AudioBuffer<float>& buffer,
                                                       juce::AudioProcessor::Realtime realtime,
                                                       const juce::AudioPlayHead::PositionInfo& positionInfo) noexcept
{
    const auto numSamples = buffer.getNumSamples();
    jassert (numSamples <= maximumSamplesPerBlock);
    jassert (numChannels == buffer.getNumChannels());
    jassert (realtime == juce::AudioProcessor::Realtime::no || useBufferedAudioSourceReader);
    const auto timeInSamples = positionInfo.getTimeInSamples().orFallback (0);
    const auto isPlaying = positionInfo.getIsPlaying();

    bool success = true;
    bool didRenderAnyRegion = false;

    if (isPlaying)
    {
        const auto blockRange = juce::Range<juce::int64>::withStartAndLength (timeInSamples, numSamples);

        for (const auto& playbackRegion : getPlaybackRegions())
        {
            // Evaluate region borders in song time, calculate sample range to render in song time.
            // Note that this example does not use head- or tailtime, so the includeHeadAndTail
            // parameter is set to false here - this might need to be adjusted in actual plug-ins.
            const auto playbackSampleRange = playbackRegion->getSampleRange (sampleRate,
                                                                             juce::ARAPlaybackRegion::IncludeHeadAndTail::no);
            auto renderRange = blockRange.getIntersectionWith (playbackSampleRange);

            if (renderRange.isEmpty())
                continue;

            // Evaluate region borders in modification/source time and calculate offset between
            // song and source samples, then clip song samples accordingly
            // (if an actual plug-in supports time stretching, this must be taken into account here).
            juce::Range<juce::int64> modificationSampleRange { playbackRegion->getStartInAudioModificationSamples(),
                                                               playbackRegion->getEndInAudioModificationSamples() };
            const auto modificationSampleOffset = modificationSampleRange.getStart() - playbackSampleRange.getStart();

            renderRange = renderRange.getIntersectionWith (modificationSampleRange.movedToStartAt (playbackSampleRange.getStart()));

            if (renderRange.isEmpty())
                continue;

            // Now calculate the samples in renderRange for this PlaybackRegion based on the ARA model
            // graph. If didRenderAnyRegion is true, add the region's output samples in renderRange to
            // the buffer. Otherwise the buffer needs to be initialised so the sample value must be
            // overwritten.
            const int numSamplesToRead = (int) renderRange.getLength();
            const int startInBuffer = (int) (renderRange.getStart() - blockRange.getStart());
            const auto startInSource = renderRange.getStart() + modificationSampleOffset;

            for (int c = 0; c < numChannels; ++c)
            {
                auto* channelData = buffer.getWritePointer (c);

                for (int i = 0; i < numSamplesToRead; ++i)
                {
                    // Calculate region output sample at index startInSource + i ...
                    float sample = 0.0f;

                    if (didRenderAnyRegion)
                        channelData[startInBuffer + i] += sample;
                    else
                        channelData[startInBuffer + i] = sample;
                }
            }

            // If rendering first region, clear any excess at start or end of the region.
            if (! didRenderAnyRegion)
            {
                if (startInBuffer != 0)
                    buffer.clear (0, startInBuffer);

                const int endInBuffer = startInBuffer + numSamples;
                const int remainingSamples = numSamples - endInBuffer;

                if (remainingSamples != 0)
                    buffer.clear (endInBuffer, remainingSamples);

                didRenderAnyRegion = true;
            }
        }
    }

    if (! didRenderAnyRegion)
        buffer.clear();

    return success;
}
