//	Granulator.h - Audio granulator effect.
//	----------------------------------------------------------------------------
//	This file is part of NiallGran, a VST plugin.
//	Copyright (c) 2013 Niall Moody.
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#ifndef GRANULATOR_H_
#define GRANULATOR_H_

#include "c0xHeaders.h"

///	Audio granulator effect.
class Granulator
{
  public:
	///	Constructor.
	Granulator();
	///	Destructor.
	~Granulator();

	///	Sets the maximum number of samples in a block.
	/*!
		Must be called before you call processBlock().
	 */
	void setMaxBlockSize(int size);

	///	Processes the input audio, writes the granulated output to the passed-in buffer.
	/*!
		\param buffer Contains the input audio, which should be overwritten
		with the plugin's output. NOTE: this is an INTERLEAVED stereo array.
		\param numSamples The number of samples in this buffer.

		Assumes a stereo input/output.
	 */
	void processBlock(float *buffer, int numSamples);

	///	Sets the density of the grains (0->1).
	void setDensity(float val);
	///	Sets the duration of the grains (0.001->2 seconds).
	void setDuration(float val);
	///	Sets the pitch of the grains (0.25->4).
	void setPitch(float val);
	///	Sets the feedback level.
	void setFeedback(float val);
	///	Sets the amount of output to mix to the delayline (0->1).
	void setMix(float val);
	///	Sets the output level (0->2).
	void setLevel(float val);
  private:
	///	Helper method. Calculates the next onset.
	void calcNextOnset(bool forceUpdate);

	///	Important constants.
	enum
	{
		/*!
			With the max grain length being 2s, this should give us enough space
			that the read pointers never catch up with/get overtaken by the
			write pointer (which would cause clicks/glitches). That is assuming
			we start the sped up/slowed down grains at the right point in the
			delay line, of course.
		 */
		DelayLineSize = (8 * 44100),
		MaxGrainLength = (2 * 44100),
		MaxGrains = 32
	};

	///	For simplicity. Represents a stereo sample buffer, DelayLineSize samples long.
	typedef array<array<float, Granulator::DelayLineSize>, 2> DelayLineArray;
	///	Ditto. For the buffer holding the grain envelope.
	typedef array<float, Granulator::DelayLineSize> EnvelopeArray;

	///	Represents a single grain.
	class Grain
	{
	  public:
		///	Constructor.
		Grain();
		///	Destructor.
		~Grain();

		///	Lets the grain know the location of the 2 global arrays.
		/*!
			(global in the sense that all the grains in the granulator share the
			same delayline, not in the strict c++ sense)

			Must be called before you attempt any audio processing with the
			grain.
		 */
		void setArrays(DelayLineArray *globalDelayLine,
					   EnvelopeArray *globalEnvelope);

		///	Starts the grain playing.
		/*!
			\param delayLineStartPos The sample position within the delayline
			from which the grain should start playing.
			\param bufferStartPos The sample position within the current audio
			buffer at which the grain should start playing.
			\param grainLength The length of the grain in samples.
			\param speed The speed at which the grain should play (0.25->4).
		 */
		void activate(int delayLineStartPos,
					  int bufferStartPos,
					  float grainLength,
					  float speed);
		///	Returns true if the grain is currently playing.
		bool getActive() const {return active;};

		///	Writes the grain's audio to the passed-in buffer.
		/*!
			\param buffer The buffer to write the audio to. NOTE: this is an
			INTERLEAVED stereo buffer.
			\param numSamples The number of samples in this block.
			\param level The amount to scale the grain's audio by. This is a
			sample array so we can slew the level when it changes.
		 */
		void getBlock(float *buffer, int numSamples, float *level);
	  private:
		///	The grain's pointer to the global delayline.
		DelayLineArray *delayLine;
		///	The grain's pointer to the global grain envelope.
		EnvelopeArray *envelope;

		///	True if the grain is currently active.
		bool active;

		///	The grain's position within its specified number of samples.
		/*!
			In activate(), this is set to the grainLength value and decremented
			by inc for every sample output. When it reaches 0 the grain stops
			playing.
		 */
		float grainPos;
		///	The length of the grain in samples.
		float length;
		///	The grain's position within the delayline.
		float delayLinePos;
		///	The number of samples to advance in the delayLine for each sample we output.
		float inc;

		///	Temporary variable only valid immediately after activate() has been called.
		int startPos;
	};

	///	The density of the grains.
	float density;
	///	The duration of the grains.
	float duration;
	///	The pitch of the grains.
	float pitch;
	///	The feedback level.
	float feedback;
	///	The amount of mix to apply.
	float mix;
	///	The output level.
	float level;

	///	The grains.
	Grain grains[MaxGrains];
	///	The envelope for the grains.
	EnvelopeArray grainEnv;

	///	The delayline.
	DelayLineArray delayLine;
	///	The write position for the delayLine.
	int writePos;

	///	Used to calculate when to activate a new grain.
	int nextOnset;

	///	Array which holds the overall level values, to be passed to the grains.
	float *levelArray;
	///	The last level value.
	float lastLevel;
};

#endif
