// 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 COMMAND_STRING "About..."
#define MACHINE_NAME "cheapo negative"
#define SHORT_NAME "ch.neg"
#define MACHINE_AUTHOR "Mikko Apo (apo@iki.fi)"
#define MAX_TRACKS		0
#define MIN_TRACKS		0
#define NUMGLOBALPARAMETERS 5
#define NUMTRACKPARAMETERS 0
#define NUMATTRIBUTES 0
#define __VERSION__ "1.0"

//	Parameters

CMachineParameter const paraMode = 
{ pt_byte, "Stereo Mode","Stereo Control Mode: 0 Separate channels, 1 Left master, right slave",0,1,0xff,MPF_STATE,0 };

CMachineParameter const paraLGain = 
{ pt_byte, "Left Gain","Left Gain",0,0xfe,0xff,MPF_STATE,0xfe };

CMachineParameter const paraRGain = 
{ pt_byte, "Right Gain","Right Gain",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,&paraLGain,&paraRGain,&paraInertia,&paraInertiaUnit };

#pragma pack(1)

class gvals
{
public:
	byte mode;
	byte lgain;
	byte rgain;
	word inertia;
	byte inertiaunit;
};

#pragma pack()

// Machine's info

CMachineInfo const MacInfo = 
{
	MT_EFFECT,MI_VERSION,MIF_DOES_INPUT_MIXING,MIN_TRACKS,MAX_TRACKS,
	NUMGLOBALPARAMETERS,NUMTRACKPARAMETERS,pParameters,NUMATTRIBUTES,NULL,
#ifdef _DEBUG
	MACHINE_NAME" [DEBUG]"
#else
	MACHINE_NAME
#endif
	,SHORT_NAME,MACHINE_AUTHOR,COMMAND_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);
	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;

private:

	unsigned long calculate_length(byte type, word len);

	float l_amp,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;
};


DLL_EXPORTS

mi::mi()
{
	GlobalVals = &gval;
}

void mi::Command(int const i)
{
	switch(i)
	{
	case 0:
		pCB->MessageBox(MACHINE_NAME"\n\nBuild date: "__DATE__"\nVersion: "__VERSION__"\nCoded by: "MACHINE_AUTHOR"\nThanks to #buzzdev for support.\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:
		return((value)?"Right slave":"Separate");
	case 1:
	case 2:
		sprintf(txt,"%.2f",((float)(value-0x7f))/0x7f);
		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_amp=((float)(paraLGain.DefValue-0x7f))/0x7f;
	r_amp=((float)(paraRGain.DefValue-0x7f))/0x7f;
	l_inertia=r_inertia=false;
	valInertia=paraInertia.DefValue;
	valInertiaUnit=paraInertiaUnit.DefValue;
	first=true;
}

void mi::OutputModeChanged(bool stereo)
{
	if(r_inertia)
	{
		r_amp=r_amp_target;
		r_inertia=false;
	}
}

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()
{
  if (gval.mode != paraMode.NoValue)
  {
	if(valMode!=gval.mode)
	{
		if(r_inertia)
		{
			r_amp=r_amp_target;
			r_inertia=false;
		}
	}
	valMode=gval.mode;
  }
  if (gval.inertia != paraInertia.NoValue)
  {
	valInertia=gval.inertia;
  }
  if (gval.inertiaunit != paraInertiaUnit.NoValue)
  {
	valInertiaUnit=gval.inertiaunit;
  }

  if(first)
  {
	if (gval.lgain != paraLGain.NoValue)
	{
		l_amp=((float)(gval.lgain-0x7f))/0x7f;
	}
	if (gval.rgain != paraRGain.NoValue)
	{
		r_amp=((float)(gval.rgain-0x7f))/0x7f;
	}
  }
  else
  {
	if (gval.lgain != paraLGain.NoValue)
	{
		l_inertia=true;
		l_counter=calculate_length(valInertiaUnit,valInertia);
		l_amp_target=((float)(gval.lgain-0x7f))/0x7f;
		l_amp_inc=(l_amp_target-l_amp)/l_counter;
	}
	if (gval.rgain != paraRGain.NoValue)
	{
		r_inertia=true;
		r_counter=calculate_length(valInertiaUnit,valInertia);
		r_amp_target=((float)(gval.rgain-0x7f))/0x7f;
		r_amp_inc=(r_amp_target-r_amp)/r_counter;
	}
  }
 
  first=false;
} 


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

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

	if(valMode)
	{
	do 
	{
		if(l_inertia)
		{
			l_amp+=l_amp_inc;
			if(!(l_counter--))
			{
				l_inertia=false;
				l_amp=l_amp_target;
			}
		}
		psamples[0]*=l_amp;
		psamples[1]*=l_amp;
		psamples+=2;
	} while(--numsamples);
	} else
	{

	do 
	{
		if(l_inertia)
		{
			l_amp+=l_amp_inc;
			if(!(l_counter--))
			{
				l_inertia=false;
				l_amp=l_amp_target;
			}
		}
		psamples[0]*=l_amp;
		if(r_inertia)
		{
			r_amp+=r_amp_inc;
			if(!(r_counter--))
			{
				r_inertia=false;
				r_amp=r_amp_target;
			}
		}
		psamples[1]*=r_amp;
		psamples+=2;
	} while(--numsamples);
	}

	return true;
}

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

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

	do 
	{
		if(l_inertia)
		{
			l_amp+=l_amp_inc;
			if(!(l_counter--))
			{
				l_inertia=false;
				l_amp=l_amp_target;
			}
		}
		psamples[0]*=l_amp;
		psamples++;
	} while(--numsamples);

	return true;
}
