#pragma once



#pragma warning (disable: 4244)

#define MAKEUNIQUE // ignores punctuation if defined
#define BORINGCHANCE 0 // out of 16


extern HWND fgw;

struct compressor
{
	float curlevel;
	int holdtime;
	float minlevel;
	int clip;

	compressor()
	{
		minlevel=0.2f;
		holdtime=0;
		curlevel=minlevel;
	}

	void Run(stereo *in, stereo *out, int len)
	{
		for (int i=0;i<len;i++)
		{
			float lev=sqrtf((abs(in->l)+abs(in->r))/32768.f);
			if (lev>curlevel)
			{
				curlevel+=(lev-curlevel) * 0.002f; // accelerate up very fast
				holdtime=0;
			}
			else
			{
				holdtime++;
				if (holdtime>=44100/4 ) // wait 1/4 of a second
				{
					curlevel+=(lev-curlevel) * 0.0001f * (curlevel-minlevel); // go down very slow
					if (curlevel<minlevel) curlevel=minlevel;
				}
			}	
			float lev2=0.9f / (curlevel*curlevel);
			int nl = lev2*in->l;
			int nr = lev2*in->r;
			if (nl<-32767 || nl>32767) clip++;
			nl=bound(-32768,nl,32767);
			nr=bound(-32768,nr,32767);
			out->l=nl;
			out->r=nr;			
			in++;
			out++;
			
		}
	}
};


struct AudioTag
{
	char txt[32];
	int pos;
	int len;
};

struct Word
{
	char txt[32];
	int count;

	static int total;
	static Word *first;
	Word *next;

	Word();
	~Word();

	static Word *Add(char *t);
};


struct UniqueWord
{
	char txt[32];
	Word *word;
	AudioTag *tag;
	AudioTag *tag2;

	static int total;
	static UniqueWord *first;
	UniqueWord *next;

	UniqueWord();
	~UniqueWord();

	static UniqueWord *Add(char *t, AudioTag *tt, AudioTag *tt2);
};

struct UWList
{
	UniqueWord *uw,*w1,*w2;	
	UWList *next;
	
	static UWList *Add(UWList *head, UniqueWord *u, UniqueWord *a, UniqueWord *b);
};

struct WordPair
{
	Word *a,*b;
	UWList *follow;
	int countfollow;

	static int total;
	static WordPair *first;
	WordPair *next;

	WordPair();
	~WordPair();

	static WordPair *Add(UniqueWord *a,UniqueWord *b, UniqueWord *c);
	static WordPair *Find(UniqueWord *a,UniqueWord *b);

};
//extern float Random (float min, float max);


__inline int CLIP(int i)
{
	return bound(-32767,i,32767);
}

void RunMarkov(UniqueWord *&a,UniqueWord *&b,UniqueWord *&c);
void __cdecl Report3D(const char *pFormat,... );

struct Talkie 
{
	
	UniqueWord *starta, *startb, *startc;
	short *wavdata;
	FILE *wavfile;
	int wavdataofs;
	int wavlen;
	UniqueWord *cura,*curb,*curc;
	

	stereo delaybuf[44100];
	int delaypos;
	int delay;
	float wet,feedback,lowpass;
	//LPMIXWAVE mainwave;
	

	
	void ProcessWavFile(char *txt, bool sfl);

	void Render()	;

	~Talkie()
	{
		if (wavfile) fclose(wavfile);
		delete wavdata;
		while (Word::first) delete Word::first;	
		while (UniqueWord::first) delete UniqueWord::first;
		while (WordPair::first) delete WordPair::first;
	}

	union Params
	{
		struct
		{
			float level;
			float fxmix;
			float delaytime;
			float feedback;
			float lowpass;
			float timestretch;
			float pitch;
			float balance;
			float nojump;

		};
		float p[16];		

		float delrand;
		float tsrand;

		Params()
		{
			nojump=0;
			level=1.f;
			fxmix=0.5f;
			delaytime=250;
			feedback=0.2f;
			lowpass=0;
			timestretch=1;
			pitch=0.f;
			balance=0.f;
			tsrand=0;
			delrand=0;

		}
	};

	Params p;
	
	Talkie() 
	{				
		
		/*
		str[1].Set("FX Mix",6,0,1,0.5f);
		str[2].Set("Delay time",0,0,1000,250,1,0);
		str[3].Set("Feedback",0,0,1,0.2f);
		str[4].Set("Low pass",0,0,1);
		str[5].Set("TimeStretch",0,0,1,1);
		str[6].Set("Pitch",0,-24,24,0,0,0);
		*/

		
		wavfile=NULL;
		memset(delaybuf,0,sizeof(delaybuf));
		wet=feedback=lowpass=0;
		delay=50;
		delaypos=0;
		starta=NULL;
		startb=NULL;
		startc=NULL;
		wavdata=NULL;
		wavlen=0;
		//mainwave=NULL;

		
		
		
	}

	int ReadWavTags(char *wavname, AudioTag **tag, bool sfl);
	void FillSource(short *dst, int numout);
	void LoadMP3(char *fname, char *mem, int len);

	/*
	struct Grain
	{
		Talkie *par;
	
#define WINDOWDELTA (32768/64)

#define GRAINSIZE 512
#define OVERLAPSIZE 64
#define SOURCESIZE ((GRAINSIZE+OVERLAPSIZE)*4)
		short sourcebuf[SOURCESIZE];
		short grainbuf[GRAINSIZE+OVERLAPSIZE];
		short overlapbuf[OVERLAPSIZE];
		int sourceleft;
		int grainleft;
		float pitch,stretch;

		int lastptr;
		int lastlevel;
		int curptr;
		int curlevel;
		int curleft;

		int Run()
		{
			int s=0;
			if (lastlevel>0)
			{
				s=lastlevel * par->wavdata[lastptr++];
				lastlevel-=WINDOWDELTA;
			}		
			if (curptr) s+=curlevel * par->wavdata[curptr++];
			curleft--;
			if (curlevel<32768) curlevel+=WINDOWDELTA;
			return s;	
		}

		void GetNewGrain(float timestretch, float pitchshift)
		{
			if (sourceleft<SOURCESIZE)
			{
				if (sourceleft<0) sourceleft=0;
				memmove(sourcebuf,sourcebuf+SOURCESIZE-sourceleft,sourceleft*2);
				par->FillSource(sourcebuf+sourceleft,SOURCESIZE-sourceleft);
				sourceleft=SOURCESIZE;
			}
			// save overlap
			memcpy(overlapbuf,grainbuf+GRAINSIZE,sizeof(overlapbuf));
			// copy grain...
			int sourceused=GRAINSIZE*pitchshift;
			int sourcepos=0;
			int sinc=65536*pitchshift;
			short *dst=grainbuf;		
			for (int c1=0;c1<GRAINSIZE+OVERLAPSIZE;c1++)
			{
				*dst++=sourcebuf[sourcepos>>16];
				sourcepos+=sinc;
			}		
			// cross fade overlap
			int mul=65536;
			for (c1=0;c1<OVERLAPSIZE;c1++,mul-=65536/OVERLAPSIZE) grainbuf[c1]+= (overlapbuf[c1]-grainbuf[c1])*mul>>16;
			// increment source pointer
			sourceleft-=sourceused*bound(0,timestretch,1);

		}

		void Reset()
		{
			grainleft=0;
			sourceleft=0;
			stretch=pitch=1;				
			lastptr=0;
			lastlevel=0;
			curptr=0;
			curlevel=0;
			curleft=0;
			
		}
	};

	Grain grain[2];
	*/

	struct SoundSource
	{
		int curpos,lastpos;
		int length;
		int mix;
		Talkie *par;

		SoundSource()
		{
			mix=0;
			length=0;
			curpos=lastpos=0;
		}

		void SetNewSource(int pos, int len)
		{
			lastpos=lastpos+length;
			length=len;			
			curpos=pos;
			mix=1;
		}

		void GetSound(short *out, int len, float pos)
		{
			if (mix)
			{
				int cp=curpos+length*pos;
				int lp=lastpos+length*pos;
				for (int c1=0;c1<len;c1++)
				{
					*out++ = ((par->wavdata[cp++]*c1)+(par->wavdata[lp++]*(len-c1))) / len;
				}
				mix=0;
			}
			else
			{
				int cp=curpos+length*pos;
				memcpy(out,par->wavdata+cp,len*2);
			}
		}
	};

	SoundSource ss[2];
	float wordpos;
	float balance;	
	
#define GRAINSIZE 1000
	stereo overlapbuf[GRAINSIZE];

	void GetSoundGrain(float level, stereo *out, float stretch=1.f, float pitch=1.f)
	{
		// work out twice too much...
		if (pitch>8) pitch=8;
		static short tempbuf[GRAINSIZE*2*8+1];
		static short tempbuf2[GRAINSIZE*2*8+1];
		static stereo tempbuf3[GRAINSIZE*2];

		int newsource=0;

		if ((thedoc->pulsetrigger & 1) || (!cura || !curb || !curc))
		{		
			thedoc->pulsetrigger &= ~1;
			int r=rand() % WordPair::total;
			WordPair *uw = WordPair::first;
			for (int c1=0;c1<r && uw->next;c1++) uw=uw->next;
			curc=uw->follow->uw;
			cura=uw->follow->w1;
			curb=uw->follow->w2;
			
			wordpos=0.5f;
			newsource=2;			
		}

		if (wordpos<=0 || wordpos>=1)
		{
			wordpos=0;
			if (!cura || !curb || !curc)
			{
				cura=starta;
				curb=startb;
				curc=startc;
			}
			else
			{
				RunMarkov(cura,curb,curc);				
			}
			newsource=1;
			
		}
		if (newsource)
		{
			if (cura && cura->tag)
			{
				//SetWindowText(fgw,"ns");
				//Report3D(cura->txt); seems to crash it!
				//Report3D("\n");

				
				ss[0].SetNewSource(cura->tag->pos,cura->tag->len);
				ss[1].SetNewSource(cura->tag2->pos,cura->tag2->len);
				
				//SetWindowText(fgw,"rd");
				if (wavfile)
				{
					
					fseek(wavfile,cura->tag->pos*2,SEEK_SET);
					fread(wavdata+cura->tag->pos,2,cura->tag->len+32768,wavfile);					
					fseek(wavfile,cura->tag2->pos*2,SEEK_SET);
					fread(wavdata+cura->tag2->pos,2,cura->tag2->len+32768,wavfile);					
					
				}
			}
		}

//		SetWindowText(fgw,"ss");
		
		ss[0].GetSound(tempbuf,GRAINSIZE*2*pitch+1,wordpos);
		ss[1].GetSound(tempbuf2,GRAINSIZE*2*pitch+1,wordpos);
		thedoc->animframe[0] = (ss[0].curpos + wordpos * ss[0].length)*25.f/44100.f;
		thedoc->animframe[1] = (ss[0].curpos + wordpos * ss[0].length)*25.f/44100.f;

//		SetWindowText(fgw,"mix");

		float mixel = ss[1].length * balance + ss[0].length * (1-balance); // mixed length. we want to inc wordpos by len*stretch/mixel
		if (level>0.001f)
		{
			if (mixel<=0) wordpos=1; else wordpos+=(GRAINSIZE*stretch)/(mixel);
		}
		
		float balancer = balance*2;if (balancer>1) balancer=1;
		float balancel = 2-balance*2;if (balancel>1) balancel=1.f;
		
		level*=1.f/ GRAINSIZE;
		int pos=0;
		int ipitch=pitch*65536;
		// now mix with the overlap buf
		for (int c1=0;c1<GRAINSIZE;c1++)
		{
			tempbuf3[c1].r = tempbuf2[pos>>16] * balancer ;
			tempbuf3[c1].l  = tempbuf[pos>>16] * balancel;
			pos+=ipitch;
			out[c1].l = (tempbuf3[c1].l * c1 + overlapbuf[c1].l * (GRAINSIZE-c1)) * level;
			out[c1].r = (tempbuf3[c1].r * c1 + overlapbuf[c1].r * (GRAINSIZE-c1)) * level;
		}
		/* pulsetrigger noise 
		if (newsource==2)
		{
			for (int c1=0;c1<GRAINSIZE;c1++)
			{
				float c2=(GRAINSIZE-c1)/10.f;
				out[c1].l += (sin(c2*c2/1500.f)*10000*(GRAINSIZE-c1))/GRAINSIZE;
				out[c1].r += (cos(c2*c2/1500.f)*10000*(GRAINSIZE-c1))/GRAINSIZE;
			}
		}
		*/
		for (c1=0;c1<GRAINSIZE;c1++)
		{
			overlapbuf[c1].r = tempbuf2[pos>>16] * balancer;
			overlapbuf[c1].l = tempbuf[pos>>16] * balancel;
			pos+=ipitch;
		}

		if (wet>0.01f)
		{
			if (delay) delaypos%=delay;
			float q=1-lowpass;
			for (c1=0;c1<GRAINSIZE;c1++)
			{
				stereo s=out[c1];
				int nsl=delaybuf[delaypos].l*feedback;
				int nsr=delaybuf[delaypos].r*feedback;
				nsl+=s.l;
				nsr+=s.r;
				static int filtl=0;
				static int filtr=0;
				filtl+=(nsl-filtl)*q;
				filtr+=(nsr-filtr)*q;
				delaybuf[delaypos++].l=CLIP(filtl);
				delaybuf[delaypos++].r=CLIP(filtr);
				if (delaypos>=delay) delaypos=0;
				nsl = s.l+(filtl-s.l)*wet;			
				nsr = s.r+(filtr-s.r)*wet;			
				out[c1].l=nsl;				
				out[c1].r=nsr;				
			}
		}
	}


	
	void ResetSound()
	{
		
		ss[0].par=this;
		ss[1].par=this;
		/*
		grain[0].par=this;
		grain[1].par=this;
		grain[0].Reset();
		grain[1].Reset();
		*/
		wordpos=0;
		balance=0.f;
		cura=NULL;
		curb=NULL;
		curc=NULL;		
	
		grainleft=0;
	}

	stereo grainbuf[GRAINSIZE];
	int grainleft;
	float pitch,stretch;
	

	void ProcessSound(stereo *snd, int len)
	{
		
		if (thedoc->talkielevel && wavdata)
		{			
			int numout=len;

			static int row=0;
			row+=4;
			static int rev[8]={8,1,2,1,4,1,2,1};
			p.tsrand*=0.5f;
			if ((row&3)==0)
			{
				int prob = rev[(row/4)&7];
				if (Random(0,1) < prob/8.f * thedoc->pulseprob)
				{
					if (p.nojump==0) thedoc->pulsetrigger|=3;					
					else thedoc->pulsetrigger|=2;
					if (p.nojump<4) thedoc->pulsetrigger|=4;
					if (thedoc->pulsetrigger & 1)
					{					
						grainleft=0;
						p.delrand=Random(0,1);
						p.tsrand=Random(0,1);
					}
				}
			}
			
			

			while (numout>0)
			{
				if (grainleft<=0)
				{
//					SetWindowText(fgw,"gsg");
					GetSoundGrain(thedoc->talkielevel * 2,grainbuf,stretch,pitch);
//					SetWindowText(fgw,"gsg ok");
					grainleft=GRAINSIZE;
					//comp.Run(grainbuf,grainbuf,len);
				}

				int amt=min(grainleft,numout);
				stereo *src=grainbuf+GRAINSIZE-grainleft;
				float centa = 0.3f;
				float centa2 = 1-centa;

				float l1=/*thedoc->talkielevel * */centa;
				float l2=/*thedoc->talkielevel * */centa2;

				for (int c1=0;c1<amt;c1++)
				{
					stereo s=*src;
					snd->l=bound(-32767,snd->l+s.l*l1+s.r*l2,32767);
					snd->r=bound(-32767,snd->r+s.l*l2+s.r*l1,32767);
					snd++;
					src++;
				}
				grainleft-=amt;
				numout-=amt;
				//dst+=amt;
			}
			
		}		
	}

	

	
};

