
/*-------------------------------------------------*/
/* fireworks.c - graphical MIDI note visualisation */
/*          ® 1998 by Christian Buchner            */
/*-------------------------------------------------*/

#include "fireworks.h"
#include "fireworks_protos.h"

#include "linear_protos.h"

extern UBYTE randomarray33[256];

/*-----------------------------------*/
/* Linear fireworks (straight lines) */
/*-----------------------------------*/

struct LinData
{
	struct Globals *glob;
	struct Prefs *pref;
	
	ULONG curtime;
	
	WORD centx, centy;
	WORD divx, divy;
	
	struct MinList CrackerList;
	
	WORD fxtab[128], fytab[128];
};

#define SCALE 1024

/* the firecrackers */

struct LinearCracker
{
	struct Node		cr_node;
	UBYTE			cr_chn;			/* 0 - 15  */
	UBYTE			cr_note;		/* 0 - 127 */
	UBYTE			cr_vel;			/* 0 - 127 */
	
	LONG			cr_starttime;		/* time stamp of note on */
	LONG			cr_stoptime;		/* time stamp of note off */
	UWORD			cr_savedlifetime;	/* lifetime when ray hit border */
	
	WORD			cr_fx;			/* -SCALE to SCALE equals -1 to -1 */
	WORD			cr_fy;			/* -SCALE to SCALE equals -1 to -1 */
	
	WORD			cr_sparkx;		/* for painting sparks */
	WORD			cr_sparky;
};

APTR Lin_InitFireworks(struct Globals *glob, struct Prefs *pref)
{
	struct LinData *ld = NULL;
	UWORD i;
	
	if (ld = AllocVec(sizeof(struct LinData), MEMF_ANY|MEMF_CLEAR))
	{
		ld->glob = glob;
		ld->pref = pref;
		
		NewList((struct List*)&ld->CrackerList);
		
		/* pre-calculate angles of all possible 128 notes */
		for (i = 0 ; i < 128 ; i++)
		{
			float angle = PI - (float)i/127 * PI;
			
			ld->fxtab[i] = SCALE * cos(angle);
			ld->fytab[i] = SCALE * sin(angle);
		}
		
		Lin_RethinkWindow((APTR)ld);
		
		GetTimeDelta();			/* initialize delta counter */
		ld->curtime = 0;		/* start at time counter 0 */
	}
	
	return((APTR)ld);
}

void Lin_RethinkWindow(APTR data)
{
	struct LinData *ld = (struct LinData*) data;
	struct Globals *glob = ld->glob;
	
	/* Calculate ray scaling factors */
	ld->divx  = SCALE * MAXCOORD / (glob->ww);
	ld->divy  = SCALE * MAXCOORD / (glob->wh);
	ld->centx = CENTER_X * (glob->ww) / MAXCOORD;
	ld->centy = CENTER_Y * (glob->wh) / MAXCOORD;
}

void Lin_TimePassed(APTR data)
{
	struct LinData *ld = (struct LinData*) data;
	
	ld->curtime += GetTimeDelta();
}

BOOL Lin_IsIdle(APTR data)
{
	struct LinData *ld = (struct LinData*) data;
	
	if (IsListEmpty((struct List*)&ld->CrackerList))
		return(TRUE);
	else
		return(FALSE);
}

void Lin_DrawFireworks(APTR data, UWORD Mask)
{
	struct LinData *ld = (struct LinData*) data;
	struct LinearCracker *cr, *ncr;
	struct Globals *glob = ld->glob;
	struct Prefs *pref = ld->pref;
	
	UWORD lifetime;
	UWORD deathtime;
	WORD sx1, sy1;
	WORD sx2, sy2;
	LONG lastpen = -1, newpen;
	
	struct Layer *SaveLayer = glob->PaintRP.Layer;
		
	for ( cr = (struct LinearCracker*) ld->CrackerList.mlh_Head ;
		  ncr = (struct LinearCracker*) cr->cr_node.ln_Succ ;
		  cr = ncr )
	{
		/* get the ray's lifetime information */
		
		if (cr->cr_savedlifetime == 0xffff)
			if ((ld->curtime - cr->cr_starttime) < 0x10000)
				lifetime = (UWORD)(ld->curtime - cr->cr_starttime);
			else
				lifetime = 0xffff;
		else
			lifetime = cr->cr_savedlifetime;
		
		/* calculate ray starting points */
		sx1 = ld->centx + cr->cr_fx *  lifetime / ld->divx;
		sy1 = ld->centy - cr->cr_fy *  lifetime / ld->divy;
		
		if (cr->cr_savedlifetime == 0xffff)
		{
			if ((sx1 >= glob->ww) || (sy1 >= glob->wh) || (sx1 < 0) || (sy1 < 0))
			{
				/* Start of ray hit window borders */
				/* save the time this happened to prevent */
				/* the ray from growing longer than */
				/* graphic's clipping can handle safely */
				cr->cr_savedlifetime = lifetime;
			}
		}
		
		/* has ray already been stopped by a note off? */
		if (cr->cr_stoptime == -1)
		{
			/* no, still emmitting from the centre */
			sx2 = ld->centx;
			sy2 = ld->centy;
			deathtime = 0;
		}
		else
		{
			/* get the time when the note off occured */
			if ((ld->curtime - cr->cr_stoptime) < 0x10000)
				deathtime = (UWORD)(ld->curtime - cr->cr_stoptime);
			else
				deathtime = 0xffff;
			
			/* is the ray just a dot? */
			if (lifetime == deathtime)
			{
				sx2 = sx1;
				sy2 = sy1;
			}
			else
			{
				/* calculate ray ending points */
				sy2 = ld->centy - cr->cr_fy * deathtime / ld->divy;
				sx2 = ld->centx + cr->cr_fx * deathtime / ld->divx;
			}
		}
		
		/* see if the ray has disappeared or exceeded */
		/* its maximum time since the note was released */
		if ((sx2 >= glob->ww) || (sy2 >= glob->wh) || (sx2 < 0) || (sy2 < 0) ||
			deathtime  == 0xffff )
		{
			Remove((struct Node*)cr);
			FreePooled(glob->NotePool,cr,sizeof(struct LinearCracker));
		}
		else
		{
			/* display this channel ? */
			if ((1<<cr->cr_chn) & Mask)
			{
				/* change pen if necessary */
				newpen = glob->PenArray[Channelpens+cr->cr_chn];
				if (lastpen != newpen)
				{
					SetAPen(&glob->PaintRP, newpen);
					lastpen = newpen;
				}
				
				/* draw the ray */
				Move(&glob->PaintRP, sx1, sy1);
				Draw(&glob->PaintRP, sx2, sy2);
				
				if (pref->Flags & PREFF_DOUBLE)
				{
					WORD dx, dy;
					dx = sx2 - sx1; if (dx<0) dx=-dx;
					dy = sy2 - sy1; if (dy<0) dy=-dy;
					
					if (dy >= dx)
					{
						Move(&glob->PaintRP, sx1+1, sy1);
						Draw(&glob->PaintRP, sx2+1, sy2);
					}
					else
					{
						Move(&glob->PaintRP, sx1, sy1+1);
						Draw(&glob->PaintRP, sx2, sy2+1);
					}
				}
				if (pref->Flags & PREFF_SPARKS)
				{
					cr->cr_sparkx = sx1;
					cr->cr_sparky = sy1;
				}
			}
		}
	}
	
	if (pref->Flags & PREFF_SPARKS)
	{
		SetAPen(&glob->PaintRP, glob->PenArray[Sparkpen]);
		
		for ( cr = (struct LinearCracker*) ld->CrackerList.mlh_Head ;
			  ncr = (struct LinearCracker*) cr->cr_node.ln_Succ ;
			  cr = ncr )
		{
			/* display this channel ? */
			if ((1<<cr->cr_chn) & Mask)
			{
				if (cr->cr_savedlifetime == 0xffff)
				{
					UWORD margin = (pref->Flags & PREFF_DOUBLE) ? 2 : 1;
					UBYTE rnd = (ld->curtime>>6) + cr->cr_starttime + cr->cr_note;
					
					if (randomarray33[rnd])
					{
						sx1 = cr->cr_sparkx;
						sy1 = cr->cr_sparky;
						
						if ((sx1 >= margin) && (sy1 >= margin) &&
							(sx1 < (glob->ww - margin)) &&
							(sy1 < (glob->wh - margin)) )
							glob->PaintRP.Layer = NULL;
						else
							glob->PaintRP.Layer = SaveLayer;
						
						RectFill(&glob->PaintRP, sx1-margin, sy1-margin, sx1+margin, sy1+margin);
					}
				}
			}
		}
	}
	
	glob->PaintRP.Layer = SaveLayer;
}

void Lin_NoteOn(APTR data, UBYTE chn, UBYTE note, UBYTE vel, LONG reltime)
{
	struct LinData *ld = (struct LinData*) data;
	struct Globals *glob = ld->glob;
	struct Prefs *pref = ld->pref;
	struct LinearCracker *cr;
	
	if (cr = AllocPooled(glob->NotePool, sizeof(struct LinearCracker)))
	{
		cr->cr_node.ln_Pri = chn;
		
		cr->cr_chn  = chn;
		cr->cr_note = note;
		cr->cr_vel  = vel;
		
		cr->cr_starttime = ld->curtime - reltime;
		cr->cr_stoptime  = -1;
		cr->cr_savedlifetime = 0xffff;
		
		cr->cr_fx = ld->fxtab[note] * cr->cr_vel / 127 * pref->Sensitivity / 100;
		cr->cr_fy = ld->fytab[note] * cr->cr_vel / 127 * pref->Sensitivity / 100;
		
		/* sorted by channel num to speed up painting */
		Enqueue((struct List*)&ld->CrackerList, (struct Node*)cr);
	}
}

void Lin_NoteOff(APTR data, UBYTE chn, UBYTE note, LONG reltime)
{
	struct LinData *ld = (struct LinData*) data;
	struct LinearCracker *cr, *ncr;
	
	for ( cr = (struct LinearCracker*) ld->CrackerList.mlh_Head ;
		  ncr = (struct LinearCracker*) cr->cr_node.ln_Succ ;
		  cr = ncr )
	{
		if ( (cr->cr_chn  == chn ) &&
			 (cr->cr_note == note) &&
			 (cr->cr_stoptime == -1) )
		{
			cr->cr_stoptime = ld->curtime - reltime;
			break;
		}
	}
}

void Lin_ReleaseNotes(APTR data, LONG reltime)
{
	struct LinData *ld = (struct LinData*) data;
	struct LinearCracker *cr, *ncr;
	
	for ( cr = (struct LinearCracker*) ld->CrackerList.mlh_Head ;
		  ncr = (struct LinearCracker*) cr->cr_node.ln_Succ ;
		  cr = ncr )
	{
		if (cr->cr_stoptime == -1) cr->cr_stoptime = ld->curtime - reltime;
	}
}

void Lin_FreeNoteData(APTR data)
{
	struct LinData *ld = (struct LinData*) data;
	struct Globals *glob = ld->glob;
	struct LinearCracker *cr, *ncr;
	
	for ( cr = (struct LinearCracker*) ld->CrackerList.mlh_Head ;
		  ncr = (struct LinearCracker*) cr->cr_node.ln_Succ ;
		  cr = ncr )
	{
		Remove((struct Node*)cr);
		FreePooled(glob->NotePool,cr,sizeof(struct LinearCracker));
	}
}

void Lin_ExitFireworks(APTR data)
{
	struct LinData *ld = (struct LinData*) data;
	
	if (ld)
	{
		Lin_FreeNoteData(data);
		
		FreeVec(ld);
	}
}
