// Copyright (C) Mikko Apo (apo@iki.fi)
// The following code may be used to write free software
// if credit is given to the original author.
// Using it for anything else is not allowed without permission
// from the author.


#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <string.h>
#include "../mdk.h"

#define miCOMMAND_STRING "About..."
#define miMACHINE_NAME "cheapo stereo xfade"
#define miSHORT_NAME "ch.st.xfade"
#define miMACHINE_AUTHOR "Mikko Apo (apo@iki.fi)"
#define miMAX_TRACKS		0
#define miMIN_TRACKS		0
#define miNUMGLOBALPARAMETERS	5
#define miNUMTRACKPARAMETERS	0
#define miNUMATTRIBUTES	1
#define miVERSION "1.0"

//	Parameters

CMachineParameter const paraMode = 
{ pt_byte, "Mode","Mode: 0 Separate, 1 Link, 2 Mirror, 3 Left Master, 4 Right Master",0,4,0xff,MPF_STATE,0 };

CMachineParameter const paraLXFade = 
{ pt_byte, "Left X-Fade","Left X-Fade",0,0xfe,0xff,MPF_STATE,0};

CMachineParameter const paraRXFade = 
{ pt_byte, "Right X-Fade","Right X-Fade",0,0xfe,0xff,MPF_STATE,0xfe };

CMachineParameter const paraInertia = 
{ pt_word, "Inertia","Inertia length",0,0xfffe,0xffff,MPF_STATE,1 };

CMachineParameter const paraInertiaUnit = 
{ pt_byte, "Inertia Unit","Inertia Unit: 0=tick (default), 1 ticks/256, 2 samples, 3=ms, 4=seconds",0,4,0xff,MPF_STATE,0 };

// List of all parameters, track parameters last

CMachineParameter const *pParameters[] = 
{ &paraMode,&paraLXFade,&paraRXFade,&paraInertia,&paraInertiaUnit };

// Attributes

CMachineAttribute const attrModeChange = { "Mode change: Normal inertia" ,0,1,0 };

// List of all attributes

CMachineAttribute const *pAttributes[] =
{ &attrModeChange };

#pragma pack(1)

class gvals
{
public:
	byte mode;
	byte lxfade;
	byte rxfade;
	word inertia;
	byte inertiaunit;
};

class avals
{
public:
	int maxdelay;
};

#pragma pack()

// Machine's info

CMachineInfo const MacInfo = 
{
	MT_EFFECT,MI_VERSION,MIF_DOES_INPUT_MIXING,miMIN_TRACKS,miMAX_TRACKS,
	miNUMGLOBALPARAMETERS,miNUMTRACKPARAMETERS,pParameters,miNUMATTRIBUTES,pAttributes,
#ifdef _DEBUG
	miMACHINE_NAME" [DEBUG]"
#else
	miMACHINE_NAME
#endif
	,miSHORT_NAME,miMACHINE_AUTHOR,miCOMMAND_STRING
};


class miex : public CMDKMachineInterfaceEx
{

};

class mi : public CMDKMachineInterface
{
public:
	mi();

	virtual void Command(int const i);
	virtual void Tick();
	virtual char const *DescribeValue(int const param, int const value);

	virtual void MDKInit(CMachineDataInput * const pi);
	virtual bool MDKWork(float *psamples, int numsamples, int const mode) {return false;}
	virtual bool MDKWorkStereo(float *psamples, int numsamples, int const mode);
	virtual void MDKSave(CMachineDataOutput * const po) { }

	public:
	virtual CMDKMachineInterfaceEx *GetEx() { return &ex; }
	virtual void OutputModeChanged(bool stereo) {}

	public:
	miex ex;
	gvals gval;
	avals aval;

private:

	unsigned long calculate_length(byte type, word len);

	float l_l_amp,l_r_amp,r_l_amp,r_r_amp;
	int valInertia,valInertiaUnit;
	bool l_inertia,r_inertia,first;	// inertia
	float l_amp_inc,l_amp_target; //
	float r_amp_inc,r_amp_target;  //
	unsigned int l_counter,r_counter; //
	int valMode;
	float valLFade,valRFade;
};


DLL_EXPORTS

mi::mi()
{
	GlobalVals = &gval;
	AttrVals = (int *)&aval;
}

void mi::Command(int const i)
{
	switch(i)
	{
	case 0:
		pCB->MessageBox(miMACHINE_NAME"\n\nBuild date: "__DATE__"\nVersion: "miVERSION"\nCoded by: "miMACHINE_AUTHOR"\nThanks to Zephod for showing how it's done.\n\nCheck out http://www.iki.fi/apo/buzz/\nfor more buzz stuff.\n\nExcellent skin made by Hymax.");
		break;
	}
}

char const *mi::DescribeValue(int const param, int const value)
{
	static char txt[100];

	switch(param)
	{
	case 0:
		switch(value)
		{
		case 0: return("Separate");
		case 1: return("Link");
		case 2: return("Mirror");
		case 3: return("Left Master");
		case 4: return("Right Master");
		}
		break;
	case 1:
	case 2:
		sprintf(txt,"%2.1f%%:%2.1f%%",100.0*(1-((float)value)/0xfe),(float)((100.0*value)/0xfe));
		break;
	case 3:
		sprintf(txt,"%d %s",value,DescribeValue(4,valInertiaUnit));
		break;
	case 4:
		switch(value)
		{
		case 0: return("ticks");
		case 1: return("ticks/256");
		case 2:	return("samples");
		case 3:	return("ms");
		case 4: return("secs");
		}
		break;
	}

	return txt;
}

void mi::MDKInit(CMachineDataInput * const pi)
{
	valMode=paraMode.DefValue;
	l_r_amp=valLFade=((float)paraLXFade.DefValue)/0xfe;
	l_l_amp=(float)(1.0 - l_r_amp);
	r_r_amp=valRFade=((float)paraLXFade.DefValue)/0xfe;
	r_l_amp=(float)(1.0 - r_r_amp);
	l_inertia=r_inertia=false;
	valInertia=paraInertia.DefValue;
	valInertiaUnit=paraInertiaUnit.DefValue;
	first=true;
	SetOutputMode(true);
}


unsigned long mi::calculate_length(byte type, word len)
{
	unsigned long length;
	switch(type)
	{
	case 0:
		length=len*pMasterInfo->SamplesPerTick;
		break;
	case 1:
		length=(len*pMasterInfo->SamplesPerTick)/256;
		break;
	case 2:
		length=len;
		break;
	case 3:
		length=(len*pMasterInfo->SamplesPerSec)/1000;
		break;
	case 4:
		length=len*pMasterInfo->SamplesPerSec;
		break;
	}
	return length;
}


void mi::Tick()
{

  int tmpMode;

  // inertia parameters

  if (gval.inertia != paraInertia.NoValue)
  {
	valInertia=gval.inertia;
  }
  if (gval.inertiaunit != paraInertiaUnit.NoValue)
  {
	valInertiaUnit=gval.inertiaunit;
  }

  if (gval.mode != paraMode.NoValue)
  {
	  tmpMode=gval.mode;
  } else
  {
	  tmpMode=valMode;
  }

  // right fader parameters

  if (gval.rxfade != paraRXFade.NoValue)
  {
	r_counter=calculate_length(valInertiaUnit,valInertia);
	valRFade=((float)gval.rxfade)/0xfe;
	switch(tmpMode)
	{
	case 0: // separate
		r_inertia=true;
		r_amp_target=valRFade;
		r_amp_inc=(r_amp_target-r_r_amp)/r_counter;
		break;
	case 1: // linked
	case 4: // right master
		r_inertia=true;
		r_amp_target=valRFade;
		r_amp_inc=(r_amp_target-r_r_amp)/r_counter;

		l_counter=r_counter;
		l_amp_target=r_amp_target;
		l_inertia=true;
		l_amp_inc=(l_amp_target-l_r_amp)/l_counter;
		break;
	case 3: // left master
		break;
	case 2: // mirror
		r_inertia=true;
		r_amp_target=valRFade;
		r_amp_inc=(r_amp_target-r_r_amp)/r_counter;

		l_counter=r_counter;
		l_amp_target=(float)(1.0-r_amp_target);
		l_inertia=true;
		l_amp_inc=(l_amp_target-l_r_amp)/l_counter;
		break;
	}
  }

  // left fader parameters

  if (gval.lxfade != paraLXFade.NoValue)
  {
	l_counter=calculate_length(valInertiaUnit,valInertia);
	valLFade=((float)gval.lxfade)/0xfe;
	switch(tmpMode)
	{
	case 0: // separate
		l_inertia=true;
		l_amp_target=valLFade;
		l_amp_inc=(l_amp_target-l_r_amp)/l_counter;
		break;
	case 1: // linked
	case 3: // left master
		l_inertia=true;
		l_amp_target=valLFade;
		l_amp_inc=(l_amp_target-l_r_amp)/l_counter;

		r_counter=l_counter;
		r_amp_target=l_amp_target;
		r_inertia=true;
		r_amp_inc=(r_amp_target-r_r_amp)/r_counter;
		break;
	case 4: // right master
		break;
	case 2: // mirror
		l_inertia=true;
		l_amp_target=valLFade;
		l_amp_inc=(l_amp_target-l_r_amp)/l_counter;

		r_counter=l_counter;
		r_amp_target=(float)(1.0-l_amp_target);
		r_inertia=true;
		r_amp_inc=(r_amp_target-r_r_amp)/r_counter;
		break;
	}
  }

  // mode parameters

  if (gval.mode != paraMode.NoValue)
  {
	if(valMode!=gval.mode)
	{
		// set the beginning values for the mode to the defaults
		switch(gval.mode)
		{
		case 0: // separate
		case 1: // linked
		case 2: // mirror
			l_amp_target=valLFade;
			r_amp_target=valRFade;
			break;
		case 3: // left master
			l_amp_target=valLFade;
			r_amp_target=valLFade;
			break;
		case 4: // right master
			l_amp_target=valRFade;
			r_amp_target=valRFade;
			break;
		}

		// make a fade to the new parameter.

		if(aval.maxdelay)
		{
			r_counter=l_counter=calculate_length(valInertiaUnit,valInertia);
		} else
		{
			r_counter=l_counter=calculate_length(0,1)/2;
		}

		r_inertia=true;
		r_amp_inc=(r_amp_target-r_r_amp)/r_counter;
		l_inertia=true;
		l_amp_inc=(l_amp_target-l_r_amp)/l_counter;
	}
	valMode=gval.mode;
  }

  // bypasses inertia on the first tick() to prevent the machine warmup effect
  // sets inertia to 0 so that the amp values are effective immediatly
  if(first)
  {
	r_inertia=true;
	r_counter=0;
	l_inertia=true;
	l_counter=0;
    first=false;
  }
 
} 


bool mi::MDKWorkStereo(float *psamples, int numsamples, int const mode)
{
	float l,r;
	if ((mode==WM_WRITE)||(mode==WM_NOIO))
	{
		return false;
	}

	if (mode == WM_READ)		// <thru>
		return true;

	do 
	{
		if(l_inertia)
		{
			l_r_amp+=l_amp_inc;
			if(!(l_counter--))
			{
				l_inertia=false;
				l_r_amp=l_amp_target;
			}
			l_l_amp=(float)(1.0-l_r_amp);
		}
		if(r_inertia)
		{
			r_r_amp+=r_amp_inc;
			if(!(r_counter--))
			{
				r_inertia=false;
				r_r_amp=r_amp_target;
			}
			r_l_amp=(float)(1.0-r_r_amp);
		}
		l=psamples[0];
		r=psamples[1];
		psamples[0]=l_l_amp*l+r_l_amp*r;
		psamples[1]=l_r_amp*l+r_r_amp*r;
		psamples+=2;
	} while(--numsamples);

	return true;
}
