// 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.

/*

  Random number generator from Oskari Tammelin's Noise.cpp Noise generator
  FSM did the logarithmic volume slider mathematic formula

*/

/*
Revision history:
  1.0	First release.
  */

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

#define miCOMMAND_STRING "About..."
#define miMACHINE_NAME "cheapo fixer pro"
#define miSHORT_NAME "ch.fixer"
#define miMACHINE_AUTHOR "Mikko Apo (apo@iki.fi)"
#define miMAX_TRACKS		0
#define miMIN_TRACKS		0
#define miNUMGLOBALPARAMETERS 3
#define miNUMTRACKPARAMETERS 0
#define miNUMATTRIBUTES 0
#define miVERSION "1.0"

//	Parameters

CMachineParameter const paraThreshold = 
{ pt_word, "Threshold","Threshold [0 - 32768]",0,32768,0xffff,MPF_STATE,0 };

CMachineParameter const paraLevel = 
{ pt_byte, "Noise Level","Whitenoise Level",0,0x7f,0xff,MPF_STATE,0x20 };

// 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, infinite

CMachineParameter const paraZombie = 
{ pt_byte, "Re-animate","Ticks to keep the signal chain on.",0,13,0xff,MPF_STATE,6};


// List of all parameters, track parameters last

CMachineParameter const *pParameters[] = 
{ &paraThreshold,&paraLevel, &paraZombie};

#pragma pack(1)

class gvals
{
public:
	word threshold;
	byte level;
	byte zombie;
};

#pragma pack()

// Machine's info

CMachineInfo const MacInfo = 
{
	MT_EFFECT,MI_VERSION,MIF_DOES_INPUT_MIXING,miMIN_TRACKS,miMAX_TRACKS,
	miNUMGLOBALPARAMETERS,miNUMTRACKPARAMETERS,pParameters,miNUMATTRIBUTES,NULL,
#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);
	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:

	float valThreshold;
	int RandStat;
	float amp;
	int valLevel;
	long valZombie;
	bool alive;
	long zombiecounter;
};


DLL_EXPORTS

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

void mi::Command(int const i)
{
	static char txt[500];
	switch(i)
	{
	case 0:
		pCB->MessageBox(miMACHINE_NAME"\n\nBuild date: "__DATE__"\nVersion: "miVERSION"\nCoded by: "miMACHINE_AUTHOR"\nThanks to Oskari and FSM for code snippets.\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:
		if(value)
		{
		  sprintf(txt,"%d",value);
		} else
		{
			return("0.1");
		}
		break;
	case 1:
			sprintf(txt,"%.2fdB",20*(log10((((valThreshold)?valThreshold:0.1)*pow(2.0,(1-value/127.0)*(-16))/32768))));
		break;
	case 2:
// 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, infinite
		switch(value)
		{
		case 0: return("off");
		case 1:	return("1 tick");
		case 2:	return("2 ticks");
		case 3:	return("4 ticks");
		case 4:	return("8 ticks");
		case 5:	return("16 ticks");
		case 6:	return("32 ticks");
		case 7:	return("64 ticks");
		case 8:	return("128 ticks");
		case 9: return("256 ticks");
		case 10: return("512 ticks");
		case 11: return("1024 ticks");
		case 12: return("infinitely");
		case 13: return("wake up");
		}
		break;
	}
	return txt;
}

void mi::MDKInit(CMachineDataInput * const pi)
{
	valThreshold = (float)paraThreshold.DefValue;
	zombiecounter=valZombie = 32*pMasterInfo->SamplesPerTick;
	alive=false;
	valLevel = paraLevel.DefValue;
	RandStat = 0x16BA2118;	// initialize random generator
	amp=(float)((pow(2.0,(1-valLevel/127.0)*(-16))*valThreshold)/0x7fff);
}

void mi::Tick()
{
	if (gval.zombie != paraZombie.NoValue)
	{
		long tmp;
// 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, infinite
		switch(gval.zombie)
		{
		case 0:tmp=0;break;
		case 1:tmp=1;break;
		case 2:tmp=2;break;
		case 3:tmp=4;break;
		case 4:tmp=8;break;
		case 5:tmp=16;break;
		case 6:tmp=32;break;
		case 7:tmp=64;break;
		case 8:tmp=128;break;
		case 9:tmp=256;break;
		case 10:tmp=512;break;
		case 11:tmp=1024;break;
		case 12:
			zombiecounter=valZombie=0x3fffffff;
			break;
		case 13:
			alive=true;
			zombiecounter=valZombie;
			break;
		}
		if(gval.zombie<12)
		{
			tmp*=pMasterInfo->SamplesPerTick;
		    zombiecounter+=tmp-valZombie;
			if(zombiecounter<0)
			{
 // we've been keeping the signal up, but the new timeout is shorter
				alive=false;
			}
			valZombie=tmp;
		}
	}

	bool changed=false;
	if (gval.threshold != paraThreshold.NoValue)
	{
		valThreshold=gval.threshold;
		changed=true;
	}
	if (gval.level != paraLevel.NoValue)
	{
		valLevel=gval.level;
		changed=true;
	}
	if(changed)
	{
	if(valThreshold)
	{
		amp = (float) ((pow(2.0,(1-valLevel/127.0)*(-16))*valThreshold)/0x7fff);
	} else
	{
		amp = (float) ((pow(2.0,(1-valLevel/127.0)*(-16))*0.1)/0x7fff);
	}
	}
} 


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

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

	if(mode==WM_WRITE)
	{
		if(alive) // wake up the dead signal
		{
			zombiecounter-=numsamples;
			do 
			{
				RandStat = ((RandStat * 1103515245 + 12345) & 0xffff) - 0x8000;
				*psamples= amp*RandStat;
				psamples++;
			} while(--numsamples);
			if(zombiecounter<0) // we don't loop next time
			{
				alive=false;
			}
			return (true); // we messed with the signal
		}
		return false; // we didn't do anything to the signal
	}

	alive=true;
	zombiecounter=valZombie;
	do 
	{
		if(fabs(*psamples)<=valThreshold)
		{
			RandStat = ((RandStat * 1103515245 + 12345) & 0xffff) - 0x8000;
			*psamples= amp*RandStat;
		}
		psamples++;
	} while(--numsamples);

	return true;
}

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

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

	if(mode==WM_WRITE)
	{
		if(alive) // wake up the dead signal
		{
			zombiecounter-=numsamples;
			do 
			{
				RandStat = ((RandStat * 1103515245 + 12345) & 0xffff) - 0x8000;
				psamples[0]= amp*RandStat;
				RandStat = ((RandStat * 1103515245 + 12345) & 0xffff) - 0x8000;
				psamples[1]= amp*RandStat;
				psamples+=2;
			} while(--numsamples);
			if(zombiecounter<0) // we don't loop next time
			{
				alive=false;
			}
			return (true); // we messed with the signal
		}
		return false; // we didn't do anything to the signal
	}

	alive=true;
	zombiecounter=valZombie;
	do 
	{
		if(fabs(*psamples)<=valThreshold)
		{
			RandStat = ((RandStat * 1103515245 + 12345) & 0xffff) - 0x8000;
			*psamples= amp*RandStat;
		}
		psamples++;
		if(fabs(*psamples)<=valThreshold)
		{
			RandStat = ((RandStat * 1103515245 + 12345) & 0xffff) - 0x8000;
			*psamples= amp*RandStat;
		}
		psamples++;
	} while(--numsamples);

	return true;
}

