//------------------------------------------------------------------------
// Project     : VST SDK
//
// Category    : Examples
// Filename    : public.sdk/samples/vst/multiple_programchanges/source/plug.cpp
// Created by  : Steinberg, 02/2016
// Description : Plug-in Example for VST SDK 3.x using Multiple ProgramChange parameters
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses. 
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------

#include "plug.h"
#include "plugcids.h" // for class ids
#include "plugparamids.h"

#include "public.sdk/source/vst/vstaudioprocessoralgo.h"

#include "pluginterfaces/base/futils.h"
#include "pluginterfaces/base/ibstream.h"
#include "pluginterfaces/vst/ivstevents.h"
#include "pluginterfaces/vst/ivstparameterchanges.h"

#include "base/source/fstreamer.h"

#include <cstdio>

namespace Steinberg {
namespace Vst {

//------------------------------------------------------------------------
// Plug Implementation
//------------------------------------------------------------------------
Plug::Plug ()
{
	// register its editor class (the same than used in plugentry.cpp)
	setControllerClass (PlugControllerUID);
}

//------------------------------------------------------------------------
tresult PLUGIN_API Plug::initialize (FUnknown* context)
{
	//---always initialize the parent-------
	tresult result = AudioEffect::initialize (context);
	// if everything Ok, continue
	if (result != kResultOk)
	{
		return result;
	}

	//---create Audio In/Out busses------
	// we want a stereo Input and a Stereo Output
	addAudioInput (STR16 ("Stereo In"), SpeakerArr::kStereo);
	addAudioOutput (STR16 ("Stereo Out"), SpeakerArr::kStereo);

	addEventInput (STR16 ("Event In"), kNumSlots);

	return kResultOk;
}

//------------------------------------------------------------------------
tresult PLUGIN_API Plug::process (ProcessData& data)
{
	//---1) Read inputs parameter changes-----------
	if (IParameterChanges* paramChanges = data.inputParameterChanges)
	{
		int32 numParamsChanged = paramChanges->getParameterCount ();
		// for each parameter which are some changes in this audio block:
		for (int32 i = 0; i < numParamsChanged; i++)
		{
			if (IParamValueQueue* paramQueue = paramChanges->getParameterData (i))
			{
				int32 offsetSamples;
				double value;
				int32 numPoints = paramQueue->getPointCount ();
				switch (paramQueue->getParameterId ())
				{
					case kBypassId:
						if (paramQueue->getPoint (numPoints - 1, offsetSamples, value) ==
						    kResultTrue)
						{
							bBypass = (value > 0.5f);
						}
						break;

					case kGainId:
						if (paramQueue->getPoint (numPoints - 1, offsetSamples, value) ==
						    kResultTrue)
						{
							mCurrentGainValue = static_cast<float> (value);
						}
						break;
					case kProgramCountId:
						if (paramQueue->getPoint (numPoints - 1, offsetSamples, value) ==
						    kResultTrue)
						{
							mProgramCount = FromNormalized<ParamValue> (
							                    value, kMaxProgramCount - kMinProgramCount) +
							                kMinProgramCount;
						}
						break;
					default:
						if (paramQueue->getParameterId () >= kProgramStartId &&
						    paramQueue->getParameterId () < kProgramEndId)
						{
							if (paramQueue->getPoint (numPoints - 1, offsetSamples, value) ==
							    kResultTrue)
							{
								int32 idx = paramQueue->getParameterId () - kProgramStartId;
								// here we get the last set program
								mCurrentProgram[idx] =
								    FromNormalized<ParamValue> (value, kNumProgs - 1);
							}
						}
						break;
				}
			}
		}
	}

	//--- ----------------------------------
	//---3) Process Audio---------------------
	//--- ----------------------------------
	if (data.numInputs == 0 || data.numOutputs == 0)
	{
		// nothing to do
		return kResultOk;
	}

	// (simplification) we suppose in this example that we have the same input channel count than
	// the output
	int32 numChannels = data.inputs[0].numChannels;

	//---get audio buffers----------------
	float** in = data.inputs[0].channelBuffers32;
	float** out = data.outputs[0].channelBuffers32;

	// check if all channel are silent then process silent
	if (data.inputs[0].silenceFlags == getChannelMask (data.inputs[0].numChannels))
	{
		// mark output silence too
		data.outputs[0].silenceFlags = data.inputs[0].silenceFlags;

		int32 sampleFrames = data.numSamples;

		// the plug-in has to be sure that if it sets the flags silence that the output buffer are
		// clear
		for (int32 i = 0; i < numChannels; i++)
		{
			// do not need to be cleared if the buffers are the same (in this case input buffer are
			// already cleared by the host)
			if (in[i] != out[i])
			{
				memset (out[i], 0, sampleFrames * sizeof (float));
			}
		}

		// nothing to do at this point
		return kResultOk;
	}

	// mark our outputs has not silent
	data.outputs[0].silenceFlags = 0;

	//---in bypass mode outputs should be like inputs-----
	if (bBypass)
	{
		int32 sampleFrames = data.numSamples;
		for (int32 i = 0; i < numChannels; i++)
		{
			// do not need to be copied if the buffers are the same
			if (in[i] != out[i])
			{
				memcpy (out[i], in[i], sampleFrames * sizeof (float));
			}
		}
	}
	else
	{
		// here we use the current program as gain value...
		float gain = mCurrentGainValue;

		// in real plug-in it would be better to do dezippering to avoid jump (click) in gain value
		for (int32 i = 0; i < numChannels; i++)
		{
			int32 sampleFrames = data.numSamples;
			float* ptrIn = in[i];
			float* ptrOut = out[i];
			float tmp;
			while (--sampleFrames >= 0)
			{
				// apply gain
				tmp = (*ptrIn++) * gain;
				(*ptrOut++) = tmp;
			}
		}
	}

	return kResultOk;
}

//------------------------------------------------------------------------
tresult PLUGIN_API Plug::setState (IBStream* state)
{
	// called when we load a preset, the model has to be reloaded

	if (!state)
		return kResultFalse;

	IBStreamer streamer (state, kLittleEndian);

	// read the bypass
	int32 savedBypass = 0;
	if (streamer.readInt32 (savedBypass) == false)
		return kResultFalse;

	// read number of program
	int32 numPrograms = 0;
	if (streamer.readInt32 (numPrograms) == false)
		return kResultFalse;

	int32 savedProgram[kNumSlots] {0};
	// read the program
	for (int32 i = 0; i < numPrograms; i++)
	{
		if (i < kNumSlots)
		{
			// read the program
			if (streamer.readInt32 (savedProgram[i]) == false)
				return kResultFalse;
		}
		else
		{
			int32 savedProgram = 0;
			if (streamer.readInt32 (savedProgram) == false)
				return kResultFalse;
		}
	}

	// read the Gain param
	float savedGain = 0.f;
	if (streamer.readFloat (savedGain) == false)
		return kResultFalse;

	bBypass = savedBypass > 0;
	for (int32 i = 0; i < kNumSlots; i++)
		mCurrentProgram[i] = savedProgram[i];

	mCurrentGainValue = savedGain;

	// read the Program count param
	uint32 savedProgramCount = 0;
	if (streamer.readInt32u (savedProgramCount) == true)
		mProgramCount = savedProgramCount;
	else
		mProgramCount = 128;

	return kResultOk;
}

//------------------------------------------------------------------------
tresult PLUGIN_API Plug::getState (IBStream* state)
{
	// here we need to save the model

	if (!state)
		return kResultFalse;

	IBStreamer streamer (state, kLittleEndian);

	streamer.writeInt32 (bBypass ? 1 : 0);

	// write number of program
	streamer.writeInt32 (kNumSlots);

	for (int32 i : mCurrentProgram)
		streamer.writeInt32 (i);

	streamer.writeFloat (mCurrentGainValue);

	streamer.writeInt32u (mProgramCount);

	return kResultOk;
}

//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
