// 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 amp"
#define miSHORT_NAME "ch.amp"
#define miMACHINE_AUTHOR "Mikko Apo (apo@iki.fi)"
#define miMAX_TRACKS		0
#define miMIN_TRACKS		0
#define miNUMGLOBALPARAMETERS	4
#define miNUMTRACKPARAMETERS	0
#define miNUMATTRIBUTES	7
#define miVERSION "1.0"

//	Parameters

CMachineParameter const paraMaxGain = 
{ pt_word, "Max Gain","Max Gain in dB [0=-96.0dB, 960=0.0dB, 1280=+32.0dB]",0,1280,0xffff,MPF_STATE,960};

CMachineParameter const paraGain = 
{ pt_word, "Gain","Gain in %",0,1000,0xffff,MPF_STATE,1000};

CMachineParameter const paraMute = 
{ pt_switch, "Mute","",0,1,SWITCH_NO,MPF_STATE,0};

CMachineParameter const paraInertia = 
{ pt_byte, "Inertia","Inertia",0,0xfe,0xff,MPF_STATE,10 };

// List of all parameters, track parameters last

CMachineParameter const *pParameters[] = 
{ &paraMaxGain,&paraGain,&paraMute,&paraInertia};

// Attributes

CMachineAttribute const attrMuteInertia = { "Inertia controlled Mute [0=no]" ,0,1,0 };
CMachineAttribute const attrMuteIn = { "Mute start fade in [ms]" ,0,1000,50 };
CMachineAttribute const attrMuteOut = { "Mute end fade out [ms]" ,0,1000,50 };
CMachineAttribute const attrMinDB = { "M.Gain neg. dB [min]" ,0,128,96 };
CMachineAttribute const attrMaxDB = { "M.Gain pos. dB [max]" ,0,128,32 };
CMachineAttribute const attrMinPercent = { "Min Gain %" ,0,1000,0 };
CMachineAttribute const attrMaxPercent = { "Max Gain %" ,0,1000,100 };

// List of all attributes

CMachineAttribute const *pAttributes[] =
{ &attrMuteInertia,&attrMuteIn,&attrMuteOut,&attrMinDB,&attrMaxDB,&attrMinPercent,&attrMaxPercent };

#pragma pack(1)

class gvals
{
public:
	word maxgain;
	word gain;
	byte mute;
	byte inertia;
};

class avals
{
public:
	int muteinertia;
	int mutein;
	int muteout;
	int dbmin;
	int dbmax;
	int percentmin;
	int percentmax;
};

#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();

	public:
	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) { }

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

	virtual void AttributesChanged();

	public:
	miex ex;
	gvals gval;
	avals aval;

private:

	double valMaxGain;
	float valGain;
	bool valMute;
	long valInertia;
	long valMuteInertia,valMuteIn,valMuteOut,valDBMin,valDBMax,valPercentMin,valPercentMax;
	double amp_current;
	double amp_dest,amp_inc;	
	long inc_counter;
	bool inertia;
	bool firsttime;
};


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"\n\nCheck out http://www.iki.fi/apo/buzz/\nfor more buzz stuff.\n\nExcellent skin made by Hymax.");
		break;
	}
}

void mi::AttributesChanged()
{
	long tmp;
	valMuteInertia=aval.muteinertia;
	valMuteIn=(int)((aval.mutein*pMasterInfo->SamplesPerSec)/1000);
	valMuteOut=(int)((aval.muteout*pMasterInfo->SamplesPerSec)/1000);
	valDBMin=aval.dbmin;
	valDBMax=aval.dbmax;
	valPercentMin=aval.percentmin;
	valPercentMax=aval.percentmax;
	if(valPercentMin>valPercentMax)
	{
		tmp=valPercentMin;
		valPercentMin=valPercentMax;
		valPercentMax=tmp;
	}
	if(valPercentMin==valPercentMax)
	{
		valPercentMin=attrMinPercent.DefValue;
		valPercentMax=attrMaxPercent.DefValue;
	}
	if(!valDBMin&&!valDBMax)
	{
		valDBMin=attrMinDB.DefValue;
		valDBMax=attrMaxDB.DefValue;
	}

}

float inertiatime(int value)
{
	double tmp;
	tmp=(float)(value/10.0);
#define vr(x,y,z) if(value>(x)) tmp=(float)((y)+(z)*(value-(x)))
	vr(80,8,1);
	vr(136,64,2);
	vr(232,256,64);
	vr(236,512,128);
	vr(240,1024,256);
	vr(244,2048,512);
	vr(248,4096,1024);
#undef vr
	return (float)tmp;
}

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

	if(!param) sprintf(txt,"%-2.1f dB",(float) (value*(valDBMin+valDBMax)/1280.0-valDBMin) );
	if(param==1) sprintf(txt,"%.1f %%",value*(aval.percentmax-aval.percentmin)/1000.0+aval.percentmin);
	if(param==2) sprintf(txt,"%s",value?"on":"off");
	if(param==3) sprintf(txt,"%.1f ticks",inertiatime(value));

	return txt;
}

#define calc_maxgain(value) pow(10, ( (value*(valDBMin+valDBMax)/1280.0-valDBMin) * 0.05))
#define calc_gain(value) (float)(((value)*(aval.percentmax-aval.percentmin)/1000.0+aval.percentmin)/100.0)

void mi::MDKInit(CMachineDataInput * const pi)
{
	valMaxGain=calc_maxgain(paraMaxGain.DefValue);
	valGain=calc_gain(paraGain.DefValue);
	valMute=(paraMute.DefValue)?true:false;
	valInertia=(int)(inertiatime(paraInertia.DefValue)*pMasterInfo->SamplesPerTick);
	AttributesChanged();
	amp_current=1.0;
	inertia=false;
	firsttime=true;
}


void mi::Tick()
{
  bool gainchanged=false,mutechanged=false;
  if (gval.maxgain != paraMaxGain.NoValue)
  {
	valMaxGain=calc_maxgain(gval.maxgain);
	gainchanged=true;
  }
  if (gval.gain != paraGain.NoValue)
  {
	valGain=calc_gain(gval.gain);
	gainchanged=true;
  }
  if (gval.mute != paraMute.NoValue)
  {
	bool tmpmute=(gval.mute)?true:false;
	if(tmpmute&&!valMute||!tmpmute&&valMute)
	{
		mutechanged=true;
	}
	valMute=tmpmute;
  }
  if(gval.inertia != paraInertia.NoValue)
  {
	  valInertia=(int)(inertiatime(gval.inertia)*pMasterInfo->SamplesPerTick);
  }
  if(mutechanged)
  {
	  if(valMute)
	  {
		  amp_dest=0.0;
		  inc_counter=valMuteInertia?valInertia:valMuteIn;
	  } else
	  {
		  amp_dest=valGain*valMaxGain;
		  inc_counter=valMuteInertia?valInertia:valMuteOut;
	  }
  } else if(gainchanged)
  {
	  if(valMute)
	  {
		  gainchanged=false;
	  } else
	  {
	    amp_dest=valGain*valMaxGain;
	    inc_counter=valInertia;
	  }
  }
  if(firsttime)
  {
	  amp_current=1.0;
	  inertia=false;
	  firsttime=false;
  } else
  {
	if(mutechanged||gainchanged)
	{
		  amp_inc=(amp_dest-amp_current)/inc_counter;
		  inertia=true;
	  }
  }
} 

// update inertia counters
#define upd_inertia	if(inertia) { \
			inc_counter-=numsamples; \
			if(inc_counter>0){ \
			  amp_current=amp_current+amp_inc*numsamples; \
			} else { \
				inertia=false; \
				amp_current=amp_dest; \
			} \
		}

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

	if (mode==WM_WRITE) // no sound coming in
	{
		// if there's inertia, update counters
		upd_inertia;
		return false;
	}

	if (mode == WM_READ)		// <thru>
	{
		// if there's inertia, update counters
		upd_inertia;
		return true;
	}

	// no sound coming out if amp_current = 0 and there's no inertia
	if(amp_current=0.0&&!inertia) return false;

	do 
	{
		if(inertia)
		{
			inc_counter--;
			if(inc_counter>0)
			{
			  amp_current=amp_current+amp_inc;
			} else
			{
				inertia=false;
				amp_current=amp_dest;
			}
		}
		psamples[0]=(float)(amp_current*psamples[0]);
		psamples[1]=(float)(amp_current*psamples[1]);
		psamples+=2;
	} while(--numsamples);

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

	if (mode==WM_WRITE) // no sound coming in
	{
		// if there's inertia, update counters
		upd_inertia;
		return false;
	}

	if (mode == WM_READ)		// <thru>
	{
		// if there's inertia, update counters
		upd_inertia;
		return true;
	}

	// no sound coming out if amp_current = 0 and there's no inertia
	if(amp_current==0.0&&!inertia) return false;

	do 
	{
		if(inertia)
		{
			inc_counter--;
			if(inc_counter>0)
			{
			  amp_current+=amp_inc;
			} else
			{
				inertia=false;
				amp_current=amp_dest;
			}
		}
		psamples[0]=(float)(psamples[0]*amp_current);
		psamples++;
	} while(--numsamples);

	return true;
}

#undef upd_inertia