//-----------------------------------------------------------------------------
// Project     : VST SDK
//
// Category    : Examples
// Filename    : public.sdk/samples/vst/common/voiceprocessor.h
// Created by  : Steinberg, 02/2010
// Description :
//
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------

#pragma once

#include "base/source/fdebug.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivstevents.h"
#include <algorithm>

#ifdef DEBUG_LOG
#undef DEBUG_LOG
#endif

#define DEBUG_LOG DEVELOPMENT

#ifndef VOICEPROCESSOR_BLOCKSIZE
#define VOICEPROCESSOR_BLOCKSIZE 32
#endif

namespace Steinberg {
namespace Vst {

//-----------------------------------------------------------------------------
/** A Voice Processor class.

A virtual base class for a voice manager implementation.

The idea behind this class is to make it easier to support either single precision or double
precision samples (float or double) or different channel layouts.

Example:
\code{.cpp}
//------------------------------------------------------------------------
class MySynthProcessor : public AudioEffect
{
public:
    tresult PLUGIN_API setActive (TBool state);
    tresult PLUGIN_API process (ProcessData& data);

protected:
    VoiceProcessor* voiceProcessor;
};

//------------------------------------------------------------------------
tresult PLUGIN_API MySynthProcessor::setActive (TBool state)
{
    if (state)
    {
        if (processSetup.symbolicSampleSize == kSample32)
            voiceProcessor = new VoiceProcessorImplementation<float, Voice<float>, 2, MAX_VOICES, void> (processSetup.sampleRate, 0);
		else if (processSetup.symbolicSampleSize == kSample64)
            voiceProcessor = new VoiceProcessorImplementation<double, Voice<double>, 2, MAX_VOICES, void> (processSetup.sampleRate, 0);
		else
			return kInvalidArgument;
    }
    else
    {
        delete voiceProcessor;
        voiceProcessor = 0;
    }
    return kResultTrue;
}

//------------------------------------------------------------------------
tresult PLUGIN_API MySynthProcessor::process (ProcessData& data)
{
    return voiceProcessor->process (data);
}
\endcode

\sa Steinberg::Vst::VoiceProcessorImplementation
*/
//-----------------------------------------------------------------------------
class VoiceProcessor
{
public:
	VoiceProcessor () {}
	virtual ~VoiceProcessor () {}

	virtual tresult process (ProcessData& data) = 0;
	virtual void processEvent (Event evt) = 0;

	virtual void clearAllVoices ()
	{
		activeVoices = 0;
	}

	/** Returns the number of active voices. */
	int32 getActiveVoices () const { return activeVoices; }

	void clearOutputNeeded (bool val) { mClearOutputNeeded = val; }

protected:
	int32 activeVoices {0};
	bool mClearOutputNeeded {true};
};

//-----------------------------------------------------------------------------
/** A Simple Voice Processor Implementation supporting note expression events.

\param Precision must be either float or double
\param VoiceClass the voice class
\param numChannels number of channels
\param maxVoices number of maximum voices
\param GlobalParameterStorage a class holding global parameters

The VoiceClass must implement the following methods:
\code{.cpp}
int32 getNoteId () const;
void setGlobalParameterStorage (GlobalParameterStorage* globalParameters);
void setSampleRate (ParamValue sampleRate);
void setNoteExpressionValue (int32 index, ParamValue value);
void noteOn (int32 pitch, ParamValue velocity, float tuning, int32 sampleOffset, int32 noteId);
void noteOff (ParamValue velocity, int32 sampleOffset);
bool process (SamplePrecision* outputBuffers[numChannels], int32 numSamples);
void reset ()
\endcode

See \ref Steinberg::Vst::VoiceBase for an example base class.

This implementation does not support advanced features like voice stealing when maxVoices is
reached, etc ...
*/
//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
class VoiceProcessorImplementation : public VoiceProcessor
{
public:
	VoiceProcessorImplementation (float sampleRate, GlobalParameterStorage* globalParameters = 0);
	~VoiceProcessorImplementation () override;

	tresult process (ProcessData& data) override;
	void processEvent (Event evt) override;

	void clearAllVoices () override;
protected:
	VoiceClass* getVoice (int32 noteId);
	VoiceClass* findVoice (int32 noteId);
	VoiceClass voices[maxVoices];
};

//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
VoiceProcessorImplementation<
    Precision, VoiceClass, numChannels, maxVoices,
    GlobalParameterStorage>::VoiceProcessorImplementation (float sampleRate,
                                                           GlobalParameterStorage* globalParameters)
{
	for (int32 i = 0; i < maxVoices; i++)
	{
		voices[i].setGlobalParameterStorage (globalParameters);
		voices[i].setSampleRate (sampleRate);
		voices[i].reset ();
	}
}

//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
VoiceProcessorImplementation<Precision, VoiceClass, numChannels, maxVoices,
                             GlobalParameterStorage>::~VoiceProcessorImplementation ()
{
}

//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
VoiceClass* VoiceProcessorImplementation<Precision, VoiceClass, numChannels, maxVoices,
                                         GlobalParameterStorage>::getVoice (int32 noteId)
{
	VoiceClass* firstFreeVoice = 0;
	if (noteId != -1)
	{
		for (int32 i = 0; i < maxVoices; i++)
		{
			if (voices[i].getNoteId () == noteId)
			{
				return &voices[i];
			}
			else if (firstFreeVoice == 0 && voices[i].getNoteId () == -1)
			{
				firstFreeVoice = &voices[i];
			}
		}
	}
	return firstFreeVoice;
}

//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
VoiceClass* VoiceProcessorImplementation<Precision, VoiceClass, numChannels, maxVoices,
                                         GlobalParameterStorage>::findVoice (int32 noteId)
{
	if (noteId != -1)
	{
		for (int32 i = 0; i < maxVoices; i++)
		{
			if (voices[i].getNoteId () == noteId)
			{
				return &voices[i];
			}
		}
	}
	return 0;
}

//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
void VoiceProcessorImplementation<Precision, VoiceClass, numChannels, maxVoices,
                                  GlobalParameterStorage>::clearAllVoices ()
{
	for (int32 i = 0; i < maxVoices; i++)
	{
		if (voices[i].getNoteId () != -1)
			voices[i].reset ();
	}
	VoiceProcessor::clearAllVoices ();
}

//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
void VoiceProcessorImplementation<Precision, VoiceClass, numChannels, maxVoices,
                                  GlobalParameterStorage>::processEvent (Event e)
{
	switch (e.type)
	{
		//--- --------------------
		case Event::kNoteOnEvent:
		{
			if (e.noteOn.noteId == -1)
				e.noteOn.noteId = e.noteOn.pitch;
			VoiceClass* voice = getVoice (e.noteOn.noteId);
			if (voice)
			{
				voice->noteOn (e.noteOn.pitch, e.noteOn.velocity, e.noteOn.tuning, e.sampleOffset,
				               e.noteOn.noteId);
				this->activeVoices++;
				// data.outputEvents->addEvent (e);
			}
			break;
		}
		//--- --------------------
		case Event::kNoteOffEvent:
		{
			if (e.noteOff.noteId == -1)
				e.noteOff.noteId = e.noteOff.pitch;
			VoiceClass* voice = findVoice (e.noteOff.noteId);
			if (voice)
			{
				voice->noteOff (e.noteOff.velocity, e.sampleOffset);
				// data.outputEvents->addEvent (e);
			}
#if DEBUG_LOG
			else
			{
				FDebugPrint ("Voice for kNoteOffEvent not found : %d\n", e.noteOff.noteId);
			}
#endif
			break;
		}
		//--- --------------------
		case Event::kNoteExpressionValueEvent:
		{
			VoiceClass* voice = findVoice (e.noteExpressionValue.noteId);
			if (voice)
			{
				voice->setNoteExpressionValue (e.noteExpressionValue.typeId,
				                               e.noteExpressionValue.value);
				// data.outputEvents->addEvent (e);
			}
#if DEBUG_LOG
			else
			{
				FDebugPrint ("Voice for kNoteExpressionValueEvent not found : %d\n",
				             e.noteExpressionValue.noteId);
			}
#endif
			break;
		}
	}
}

//-----------------------------------------------------------------------------
#if VOICEPROCESSOR_BLOCKSIZE <= 0 // voice processing happens in chunks of the block size
//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
tresult VoiceProcessorImplementation<Precision, VoiceClass, numChannels, maxVoices,
                                     GlobalParameterStorage>::process (ProcessData& data)
{
	if (mClearOutputNeeded)
		for (int32 i = 0; i < numChannels; i++)
			memset (data.outputs[0].channelBuffers32[i], 0, data.numSamples * sizeof (Precision));

	IEventList* inputEvents = data.inputEvents;
	if (inputEvents)
	{
		Event e;
		int32 numEvents = inputEvents->getEventCount ();
		for (int32 i = 0; i < numEvents; i++)
		{
			if (inputEvents->getEvent (i, e) == kResultTrue)
			{
				processEvent (e);
			}
		}
	}

	for (int32 i = 0; i < maxVoices; i++)
	{
		if (voices[i].getNoteId () != -1)
		{
			if (!voices[i].process ((Precision**)data.outputs[0].channelBuffers32, data.numSamples))
			{
				voices[i].reset ();
				this->activeVoices--;
			}
		}
	}
	return kResultTrue;
}

//-----------------------------------------------------------------------------
#else // voice processing happens in chunks of VOICEPROCESSOR_BLOCKSIZE samples
//-----------------------------------------------------------------------------
template <class Precision, class VoiceClass, int32 numChannels, int32 maxVoices,
          class GlobalParameterStorage>
tresult VoiceProcessorImplementation<Precision, VoiceClass, numChannels, maxVoices,
                                     GlobalParameterStorage>::process (ProcessData& data)
{
	const int32 kBlockSize = VOICEPROCESSOR_BLOCKSIZE;

	int32 numSamples = data.numSamples;
	int32 samplesProcessed = 0;

	IEventList* inputEvents = data.inputEvents;
	Event e = {};
	Event* eventPtr = nullptr;
	int32 eventIndex = 0;
	int32 numEvents = inputEvents ? inputEvents->getEventCount () : 0;

	// get the first event
	if (numEvents)
	{
		inputEvents->getEvent (0, e);
		eventPtr = &e;
	}

	// initialize audio output buffers
	Precision* buffers[numChannels];
	for (int32 i = 0; i < numChannels; i++)
	{
		buffers[i] = (Precision*)data.outputs[0].channelBuffers32[i];
		if (mClearOutputNeeded)
			memset (buffers[i], 0, data.numSamples * sizeof (Precision));
	}

	while (numSamples > 0)
	{
		int32 samplesToProcess = std::min<int32> (kBlockSize, numSamples);
		while (eventPtr != nullptr)
		{
			// if the event is not in the current processing block then adapt offset for next block
			if (e.sampleOffset > samplesToProcess)
			{
				e.sampleOffset -= samplesToProcess;
				break;
			}

			processEvent (e);

			// get next event
			eventIndex++;
			if (eventIndex < numEvents)
			{
				if (inputEvents->getEvent (eventIndex, e) == kResultTrue)
				{
					e.sampleOffset -= samplesProcessed;
				}
				else
				{
					eventPtr = nullptr;
				}
			}
			else
			{
				eventPtr = nullptr;
			}
		} // end while (event != 0)

		// now process the block
		for (int32 i = 0; i < maxVoices; i++)
		{
			if (voices[i].getNoteId () != -1)
			{
				if (!voices[i].process (buffers, samplesToProcess))
				{
					voices[i].reset ();
					this->activeVoices--;
				}
			}
		}

		// update the counters
		for (int32 i = 0; i < numChannels; i++)
			buffers[i] += samplesToProcess;
		numSamples -= samplesToProcess;
		samplesProcessed += samplesToProcess;

	} // end while (numSamples > 0)

	return kResultTrue;
}

#endif

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