//-----------------------------------------------------------------------------
// Project     : VST SDK
//
// Category    : Helpers
// Filename    : public.sdk/source/vst/vstbypassprocessor.cpp
// Created by  : Steinberg, 04/2015
// Description : Example of bypass support Implementation
//
//-----------------------------------------------------------------------------
// LICENSE
// (c) 2018, Steinberg Media Technologies GmbH, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//   * Redistributions of source code must retain the above copyright notice, 
//     this list of conditions and the following disclaimer.
//   * 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.
//   * Neither the name of the Steinberg Media Technologies 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT OWNER OR 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 "vstbypassprocessor.h"
#include "vstspeakerarray.h"

#include <cstdlib>

namespace Steinberg {
namespace Vst {

//------------------------------------------------------------------------
static bool delay (int32 sampleFrames, float* inStream, float* outStream, float* delayBuffer,
                   int32 bufferSize, int32 bufferInPos, int32 bufferOutPos)
{
	// delay inStream
	int32 remain, inFrames, outFrames;
	float* bufIn;
	float* bufOut;

	remain = sampleFrames;
	while (remain > 0)
	{
		bufIn = delayBuffer + bufferInPos;
		bufOut = delayBuffer + bufferOutPos;

		if (bufferInPos > bufferOutPos)
			inFrames = bufferSize - bufferInPos;
		else
			inFrames = bufferOutPos - bufferInPos;

		outFrames = bufferSize - bufferOutPos;

		if (inFrames > remain)
			inFrames = remain;

		if (outFrames > inFrames)
			outFrames = inFrames;

		// order important for in-place processing!
		memcpy (bufIn, inStream, inFrames * sizeof (float)); // copy to buffer
		memcpy (outStream, bufOut, outFrames * sizeof (float)); // copy from buffer

		inStream += inFrames;
		outStream += outFrames;

		bufferInPos += inFrames;
		if (bufferInPos >= bufferSize)
			bufferInPos -= bufferSize;
		bufferOutPos += outFrames;
		if (bufferOutPos >= bufferSize)
			bufferOutPos -= bufferSize;

		if (inFrames > outFrames)
		{
			// still some output to copy
			bufOut = delayBuffer + bufferOutPos;
			outFrames = inFrames - outFrames;

			memcpy (outStream, bufOut, outFrames * sizeof (float)); // copy from buffer

			outStream += outFrames;

			bufferOutPos += outFrames;
			if (bufferOutPos >= bufferSize)
				bufferOutPos -= bufferSize;
		}

		remain -= inFrames;
	}

	return true;
}

//------------------------------------------------------------------------
// AudioBuffer Implementation
//------------------------------------------------------------------------
AudioBuffer::AudioBuffer () : mBuffer (nullptr), mMaxSamples (0)
{
}

//------------------------------------------------------------------------
AudioBuffer::~AudioBuffer ()
{
	resize (0);
}

//------------------------------------------------------------------------
void AudioBuffer::resize (int32 _maxSamples)
{
	if (mMaxSamples != _maxSamples)
	{
		mMaxSamples = _maxSamples;
		if (mMaxSamples <= 0)
		{
			if (mBuffer)
			{
				free (mBuffer);
				mBuffer = nullptr;
			}
		}
		else
		{
			if (mBuffer)
				mBuffer = (float*)realloc (mBuffer, mMaxSamples * sizeof (float));
			else
				mBuffer = (float*)malloc (mMaxSamples * sizeof (float));
		}
	}
}

//------------------------------------------------------------------------
void AudioBuffer::clear (int32 numSamples)
{
	if (mBuffer)
	{
		int32 count = numSamples < mMaxSamples ? numSamples : mMaxSamples;
		memset (mBuffer, 0, count * sizeof (float));
	}
}

//------------------------------------------------------------------------
//------------------------------------------------------------------------
// BypassProcessor Implementation
//------------------------------------------------------------------------
BypassProcessor::BypassProcessor () : mActive (false), mMainIOBypass (false)
{
	for (int32 i = 0; i < kMaxChannelsSupported; i++)
	{
		mInputPinLookup[i] = -1;
		mDelays[i] = nullptr;
	}
}

//------------------------------------------------------------------------
BypassProcessor::~BypassProcessor ()
{
	reset ();
}

//------------------------------------------------------------------------
void BypassProcessor::setup (IAudioProcessor& audioProcessor, ProcessSetup& processSetup,
                             int32 delaySamples)
{
	reset ();

	SpeakerArrangement inputArr = 0;
	bool hasInput = audioProcessor.getBusArrangement (kInput, 0, inputArr) == kResultOk;

	SpeakerArrangement outputArr = 0;
	bool hasOutput = audioProcessor.getBusArrangement (kOutput, 0, outputArr) == kResultOk;

	mMainIOBypass = hasInput && hasOutput;
	if (!mMainIOBypass)
		return;

	// create lookup table (in <- out) and delays...
	SpeakerArray inArray (inputArr);
	SpeakerArray outArray (outputArr);

	// security check (todo)
	if (outArray.total () >= kMaxChannelsSupported)
		return;

	for (int32 i = 0; i < outArray.total (); i++)
	{
		if (outArray.at (i) == Vst::kSpeakerL)
		{
			if (inArray.total () == 1 && inArray.at (0) == Vst::kSpeakerM)
			{
				mInputPinLookup[i] = 0;
			}
			else
				mInputPinLookup[i] = inArray.getSpeakerIndex (outArray.at (i));
		}
		else
			mInputPinLookup[i] = inArray.getSpeakerIndex (outArray.at (i));

		mDelays[i] = new Delay (processSetup.maxSamplesPerBlock, delaySamples);
		mDelays[i]->flush ();
	}
}

//------------------------------------------------------------------------
void BypassProcessor::reset ()
{
	mMainIOBypass = false;

	for (int32 i = 0; i < kMaxChannelsSupported; i++)
	{
		mInputPinLookup[i] = -1;
		if (mDelays[i])
		{
			delete mDelays[i];
			mDelays[i] = nullptr;
		}
	}
}

//------------------------------------------------------------------------
void BypassProcessor::setActive (bool state)
{
	if (mActive == state)
		return;

	mActive = state;

	// flush delays when turning on
	if (state && mMainIOBypass)
		for (int32 i = 0; i < kMaxChannelsSupported; i++)
		{
			if (!mDelays[i])
				break;
			mDelays[i]->flush ();
		}
}

//------------------------------------------------------------------------
void BypassProcessor::process (ProcessData& data)
{
	// flush
	if (data.numInputs == 0 || data.numOutputs == 0)
		return;

	if (mMainIOBypass)
	{
		AudioBusBuffers& inBus = data.inputs[0];
		AudioBusBuffers& outBus = data.outputs[0];

		for (int32 channel = 0; channel < outBus.numChannels; channel++)
		{
			float* src = nullptr;
			bool silent = true;
			float* dst = outBus.channelBuffers32[channel];
			if (!dst)
				continue;

			int inputChannel = mInputPinLookup[channel];
			if (inputChannel != -1)
			{
				silent = (inBus.silenceFlags & (1ll << inputChannel)) != 0;
				src = inBus.channelBuffers32[inputChannel];
			}

			if (mDelays[channel]->process (src, dst, data.numSamples, silent))
			{
				outBus.silenceFlags |= (1ll << channel);
			}
			else
			{
				outBus.silenceFlags = 0;
			}
		}
	}

	// clear all other outputs
	for (int32 outBusIndex = mMainIOBypass ? 1 : 0; outBusIndex < data.numOutputs; outBusIndex++)
	{
		AudioBusBuffers& outBus = data.outputs[outBusIndex];
		if (!outBus.channelBuffers32)
			continue;

		for (int32 channel = 0; channel < outBus.numChannels; channel++)
		{
			float* dst = outBus.channelBuffers32[channel];
			if (dst)
			{
				memset (dst, 0, data.numSamples * sizeof (float));
				outBus.silenceFlags |= 1ll << channel;
			}
		}
	}
}

//------------------------------------------------------------------------
void BypassProcessor::Delay::flush ()
{
	mDelayBuffer.clearAll ();

	mInPos = mOutPos = 0;
	if (hasDelay ())
		mOutPos = getBufferSamples () - mDelaySamples; // must be != inPos
}

//------------------------------------------------------------------------
bool BypassProcessor::Delay::process (float* src, float* dst, int32 numSamples, bool silentIn)
{
	bool silentOut = false;

	if (hasDelay () && src)
	{
		int32 bufferSize = getBufferSamples ();
		delay (numSamples, src, dst, mDelayBuffer, bufferSize, mInPos, mOutPos);

		// update inPos, outPos
		mInPos += numSamples;
		if (mInPos >= bufferSize)
			mInPos -= bufferSize;
		mOutPos += numSamples;
		if (mOutPos >= bufferSize)
			mOutPos -= bufferSize;
	}
	else
	{
		if (src != dst)
		{
			if (src && !silentIn)
			{
				memcpy (dst, src, numSamples * sizeof (float));
			}
			else
			{
				memset (dst, 0, numSamples * sizeof (float));
				silentOut = true;
			}
		}
		else
		{
			silentOut = silentIn;
		}
	}
	return silentOut;
}

//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
