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

#include <math.h>

#define FPS 25

#define MAXCOORD 1024

#define CENTER_X 512		/* window width  is 0...MAXCOORD */
#define CENTER_Y 980		/* window height is 0...MAXCOORD */

#define NUMPENS 17

UBYTE PenColors[NUMPENS][3] =
{
	0x00, 0x00, 0x00,			/* Black background */
	
	0x00, 0x00, 0xff,			/* Chan  1 */
	0x00, 0xff, 0x00,			/* Chan  2 */
	0x00, 0xff, 0xff,			/* Chan  3 */
	0xff, 0x00, 0x00,			/* Chan  4 */
	0xff, 0x00, 0xff,			/* Chan  5 */
	0xff, 0xff, 0x80,			/* Chan  6 */
	0x80, 0x80, 0xff,			/* Chan  7 */
	0x80, 0xff, 0x80,			/* Chan  8 */
	0x80, 0xff, 0xff,			/* Chan  9 */
	0xff, 0x80, 0x80,			/* Chan 10 */
	0xff, 0x80, 0xff,			/* Chan 11 */
	0xff, 0xff, 0x80,			/* Chan 12 */
	0xc0, 0xc0, 0xff,			/* Chan 13 */
	0xc0, 0xff, 0xc0,			/* Chan 14 */
	0xc0, 0xff, 0xff,			/* Chan 15 */
	0xff, 0xc0, 0xc0,			/* Chan 16 */
};

enum
{
	Backgroundpen = 0,
	Channelpens = 1
};


/* Library bases */

struct ExecBase			*SysBase;
struct DosLibrary		*DOSBase;
struct GfxBase			*GfxBase;
struct Library			*LayersBase;
struct IntuitionBase	*IntuitionBase;
struct Library 			*GadToolsBase;
struct Library			*UtilityBase;
struct Library			*CamdBase;
struct Library			*TimerBase;
BOOL					 FPBase;	/* pseudo floating point base */

/* Workbench or not? */

BOOL WBMode = FALSE;

/* Prototypes */

LONG ShellInterface(void);
LONG WBInterface(struct Process *MyProc);

BOOL WindowLayout(struct Screen *scr, WORD *ww, WORD *wh, WORD *minw, WORD *minh, WORD *maxw, WORD *maxh);

LONG Fireworks(UBYTE *linkname, LONG WinX, LONG WinY);
void MainLoop(struct Window *Window, WORD *ww, WORD *wh, struct MidiNode **midi, struct MidiLink **link, APTR NotePool, struct timerequest *treq, LONG *PenArray, struct BitMap **BGBitMap, struct BitMap **PaintBitMap, struct RastPort *PaintRP, struct Layer_Info **LInfo, struct Layer **PaintLayer);

BOOL InitOrUpdateGraphics(struct Window *Window, WORD ww, WORD wh, LONG *PenArray, struct BitMap **BGBitMap, struct BitMap **PaintBitMap, struct RastPort *PaintRP, struct Layer_Info **LInfo, struct Layer **PaintLayer);
void FreeGraphics(struct Window *Window, LONG *PenArray, struct BitMap **BGBitMap, struct BitMap **PaintBitMap, struct Layer_Info **LInfo, struct Layer **PaintLayer);

BOOL OpenLibs(void);
void CloseLibs(void);

struct timerequest *OpenTimer(void);
void CloseTimer(struct timerequest *treq);
ULONG GetTimeDelta(void);

LONG __stdargs Message(UBYTE *Msg,UBYTE *Options,...);
void __stdargs _XCEXIT(LONG lcode);

extern BOOL __stdargs __fpinit(void);
extern void __stdargs __fpterm(void);


/* CAM Library data and protos */

struct MidiNode *CreateMidi(Tag tag, ...);
BOOL SetMidiAttrs(struct MidiNode *mi, Tag tag, ...);
struct MidiLink *AddMidiLink(struct MidiNode *mi, LONG type, Tag tag, ...);
BOOL SetMidiLinkAttrs(struct MidiLink *mi, Tag tag, ...);


/* Some defines */

/* the opposite of noteon() */
#define noteoff(m) ( voicemsg(m,MS_NoteOff) || (voicemsg(m,MS_NoteOn) && (!(m)->mm_Data2)) )

#define offsetof(s, m)  (size_t)(&(((s *)0)->m))
#define MIN(a,b)    ((a) <= (b) ? (a) : (b))
#define elementsof(a)  ((sizeof(a) / sizeof(a[0])))


/* Intuition structures */

enum
{
	Menu_Ignore,
	Menu_Link,
	Menu_Save,
	Menu_About,
	Menu_Quit,
};


struct NewMenu VUNewMenu[]=
{
	/* nm_Type	nm_Label			nm_CommKey	nm_Flags			nm_MutualExclude	nm_UserData */
	NM_TITLE,	"Project",			NULL,		0,					0,					(APTR)Menu_Ignore,
	NM_ITEM,	"MIDI Link...",		"L",		0,					0,					(APTR)Menu_Link,
	NM_ITEM,	"Save Config",		"S",		0,					0,					(APTR)Menu_Save,
	NM_ITEM,	NM_BARLABEL,		NULL,		0,					0,					(APTR)Menu_Ignore,
	NM_ITEM,	"About",			"?",		0,					0,					(APTR)Menu_About,
	NM_ITEM,	NM_BARLABEL,		NULL,		0,					0,					(APTR)Menu_Ignore,
	NM_ITEM,	"Quit",				"Q",		0,					0,					(APTR)Menu_Quit,
	NM_END,		NULL,				NULL,		0,					0,					(APTR)Menu_Ignore,
};


/*--------------*/
/* Startup code */
/*--------------*/

LONG __saveds mymain(void)
{
	LONG ReturnCode;
	
	struct	Process *MyProc;
	
	SysBase = *((struct ExecBase**)(0x4));
	
	MyProc = (struct Process*) FindTask(NULL);
	
	if (!MyProc->pr_CLI)
	{
		WBMode = TRUE;
		ReturnCode = WBInterface(MyProc);
	}
	else
	{
		ReturnCode = ShellInterface();
	}
	
	return(ReturnCode);
}



/*---------------------*/
/* Workbench Interface */
/*---------------------*/

LONG WBInterface(struct Process *MyProc)
{
	struct WBStartup *wbmsg;
	
	LONG ReturnCode;
	
	WaitPort(&MyProc->pr_MsgPort);
	
	if (wbmsg = (struct WBStartup*)GetMsg(&MyProc->pr_MsgPort))
	{
		if (OpenLibs())
		{
			ReturnCode = Fireworks("out.0", 100, 200);
			
			CloseLibs();
		}
		Forbid();
		ReplyMsg((struct Message*)wbmsg);
	}
	return(ReturnCode);
}



/*-----------------*/
/* Shell Interface */
/*-----------------*/

LONG  ShellInterface(void)
{
	LONG ReturnCode = RETURN_ERROR;
	
	UBYTE *linkname = "out.0";
	
	LONG WinX = 100;
	LONG WinY = 200;
	
	if (OpenLibs())
	{
		/* CLI argument parsing */
		
		struct	ArgArray
		{
			UBYTE *aa_Link;
			ULONG *aa_WinX;
			ULONG *aa_WinY;
		} AA = {NULL, NULL, NULL};
		
		static UBYTE	*Template = "LINK/K,WINX/K/N,WINY/K/N";
		struct RDArgs *RDArgs;
		
		ReturnCode = RETURN_FAIL;
		
		if (RDArgs=ReadArgs(Template, (LONG *)&AA, 0))
		{
			if (AA.aa_Link) linkname = AA.aa_Link;
			if (AA.aa_WinX) WinX= *AA.aa_WinX;
			if (AA.aa_WinY) WinY= *AA.aa_WinY;
			
			ReturnCode = Fireworks(linkname, WinX, WinY);
			
			FreeArgs(RDArgs);
		}
		else
		{
			PrintFault(IoErr(),"vu");
		}
		CloseLibs();
	}
	return(ReturnCode);
}



/*----------------------------*/
/* MIDI/Window initialisation */
/*----------------------------*/

LONG Fireworks(UBYTE *linkname, LONG WinX, LONG WinY)
{
	LONG ReturnCode = RETURN_FAIL;
	
	struct MidiNode *midi;
	struct MidiLink *link;
	
	struct Screen *scr;
	APTR VisualInfo;
	struct Window *Window;
	struct Menu   *Menu;
	
	struct timerequest *treq;
	
	WORD ww, wh;
	WORD minw, minh;
	WORD maxw, maxh;
	
	struct BitMap *BGBitMap = NULL;
	struct BitMap *PaintBitMap = NULL;
	struct RastPort PaintRP;
	struct Layer_Info *LInfo = NULL;
	struct Layer *PaintLayer = NULL;
	UWORD i;
	LONG PenArray[NUMPENS];
	
	APTR NotePool;
	
	struct Task *MyTask;
	BYTE OldPri;
	
	for (i=0; i<NUMPENS; i++) PenArray[i] = -1L;
	
	if (!(midi = CreateMidi(
		MIDI_Name, "VU Meters",
		MIDI_RecvSignal, SIGBREAKB_CTRL_E,
		MIDI_MsgQueue,   500,
		MIDI_ErrFilter, CMEF_All,
		TAG_DONE)))
	{
		Message("Cannot create MIDI port!",NULL);
	}
	else
	{
		if (!(link = AddMidiLink(midi, MLTYPE_Receiver,
			MLINK_Name, "VU Meter Link",
			MLINK_Location, linkname,
			MLINK_EventMask, CMF_Note|CMF_Mode,
			MLINK_Comment,  "Fireworks [Input]",
			TAG_DONE)))
		{
			Message("Cannot create link to MIDI interface '%s'",NULL,linkname);
		}
		else
		{
			if (!(treq = OpenTimer()))
			{
				
			}
			else
			{
				if (scr = LockPubScreen(NULL))
				{
					if (!(VisualInfo = GetVisualInfo(scr, TAG_DONE)))
					{
						Message("No visual info!",NULL);
					}
					else
					{
						if (!(WindowLayout(scr, &ww, &wh, &minw, &minh, &maxw, &maxh )))
						{
							Message("Couldn't layout window!",NULL);
						}
						else
						{
							if (!(Window = OpenWindowTags( NULL,
									WA_PubScreen, scr,
									WA_Title, "Fireworks",
									WA_Left, WinX,
									WA_Top, WinY,
									WA_InnerWidth, (ULONG)ww,
									WA_InnerHeight, (ULONG)wh,
									WA_GimmeZeroZero, TRUE,
									WA_DepthGadget, TRUE,
									WA_SizeGadget, TRUE,
									WA_SizeBBottom, TRUE,
									WA_CloseGadget, TRUE,
									WA_DragBar, TRUE,
									WA_IDCMP, IDCMP_CLOSEWINDOW|IDCMP_NEWSIZE|IDCMP_MENUPICK|IDCMP_REFRESHWINDOW,
									WA_SimpleRefresh, TRUE,
									WA_NewLookMenus, TRUE,
									TAG_DONE ) ))
							{
								Message("Couldn't open window!",NULL);
							}
							else
							{
								ww = Window->Width  - Window->BorderLeft-Window->BorderRight ;
								wh = Window->Height - Window->BorderTop -Window->BorderBottom;
								
								Window->MinWidth  = minw + Window->BorderLeft+Window->BorderRight;
								Window->MinHeight = minh + Window->BorderTop +Window->BorderBottom;
								Window->MaxWidth  = maxw + Window->BorderLeft+Window->BorderRight;
								Window->MaxHeight = maxh + Window->BorderTop +Window->BorderBottom;
								
								if (!(InitOrUpdateGraphics(Window, ww, wh, PenArray, &BGBitMap, &PaintBitMap, &PaintRP, &LInfo, &PaintLayer)))
								{
									Message("Failed to initialize graphics!", NULL);
								}
								else
								{
									BltBitMapRastPort(BGBitMap, 0, 0, Window->RPort, 0, 0, ww, wh, 0xc0);
									
									if (!(Menu=(struct Menu *)CreateMenus(VUNewMenu, TAG_DONE)))
									{
										Message("Failed to create intuition menu.",NULL);
									}
									else
									{
										LayoutMenus(Menu, VisualInfo, GTMN_NewLookMenus, TRUE, TAG_DONE);
										SetMenuStrip(Window, Menu);
										
										if (!((NotePool = CreatePool(MEMF_ANY,8192,4096))))
										{
											Message("No memory for NotePool!", NULL);
										}
										else
										{
											MyTask = FindTask(NULL);
											OldPri = MyTask->tc_Node.ln_Pri;
											MyTask->tc_Node.ln_Pri = -10;
											
											MainLoop(Window, &ww, &wh, &midi, &link, NotePool, treq, PenArray, &BGBitMap, &PaintBitMap, &PaintRP, &LInfo, &PaintLayer);
											ReturnCode = RETURN_OK;
											
											MyTask->tc_Node.ln_Pri = OldPri;
											
											DeletePool(NotePool);
										}
										ClearMenuStrip(Window);
										
										FreeMenus(Menu);
									}
								}
								FreeGraphics(Window, PenArray, &BGBitMap, &PaintBitMap, &LInfo, &PaintLayer);
								
								CloseWindow(Window);
							}
						}
						FreeVisualInfo(VisualInfo);
					}
					UnlockPubScreen(NULL, scr);
				}
				CloseTimer(treq);
			}
			RemoveMidiLink(link);
		}
		DeleteMidi(midi);
	}
	return(ReturnCode);
}



BOOL WindowLayout(struct Screen *scr, WORD *ww, WORD *wh, WORD *minw, WORD *minh, WORD *maxw, WORD *maxh)
{
	BOOL Success = FALSE;
	
	*ww=256;
	*wh=256;
	
	*minw=*ww / 4;
	*minh=*wh / 4;
	
	*maxw=*ww * 4;
	*maxh=*wh * 4;
	
	Success=TRUE;
	
	return(Success);
}



/*-----------------*/
/* Main event loop */
/*-----------------*/

UWORD NoteArray[128];
UBYTE ChanUse[16];
struct MinList CrackerList;

#define SCALE 1024

/* the firecrackers */

struct Cracker
{
	struct Node		cr_node;
	UBYTE			cr_chn;			/* 0 - 15  */
	UBYTE			cr_note;		/* 0 - 127 */
	UBYTE			cr_vel;			/* 0 - 127 */
	
	LONG			cr_starttime;	/* how old is the note on (msec) */
	LONG			cr_savedtime;	/* time cracker reached borders  */
	LONG			cr_stoptime;	/* how old is the note off (msec)*/
	
	WORD			cr_fx;			/* -SCALE to SCALE equals -1 to -1 */
	WORD			cr_fy;			/* -SCALE to SCALE equals -1 to -1 */
};


void MainLoop(struct Window *Window, WORD *ww, WORD *wh, struct MidiNode **midi, struct MidiLink **link, APTR NotePool, struct timerequest *treq, LONG *PenArray, struct BitMap **BGBitMap, struct BitMap **PaintBitMap, struct RastPort *PaintRP, struct Layer_Info **LInfo, struct Layer **PaintLayer)
{
	UWORD Mask = 0xffff;
	
	BOOL Active;
	ULONG signals;
	ULONG gotsignals;
	struct IntuiMessage *imsg;
	ULONG Cl;
	UWORD Co;
	APTR IA;
	
	ULONG timersig = (1L << treq->tr_node.io_Message.mn_ReplyPort->mp_SigBit);
	BOOL TimerActive = FALSE;
	ULONG delta;
	
	struct Cracker *cr, *ncr;
	
	UBYTE Err;
	
	Active=TRUE;
	
	memset(NoteArray,0,sizeof(NoteArray));
	memset(ChanUse  ,0,sizeof(ChanUse  ));
	NewList((struct List*)&CrackerList);
	
	*ww = Window->Width  - Window->BorderLeft-Window->BorderRight ;
	*wh = Window->Height - Window->BorderTop -Window->BorderBottom;
	
	GetTimeDelta();		/* initialize delta counter */
	
	signals = (1L<<Window->UserPort->mp_SigBit) | SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E | timersig;
	
	treq->tr_node.io_Command = TR_ADDREQUEST;
	treq->tr_time.tv_secs  = 0;
	treq->tr_time.tv_micro = 1000000 / FPS;
	SendIO((struct IORequest*)treq);
	TimerActive = TRUE;
	
	while(Active)
	{
		gotsignals = Wait(signals);
		
		if (gotsignals & (1L<<Window->UserPort->mp_SigBit))
		{
			while(Active && (imsg=(struct IntuiMessage*)GetMsg(Window->UserPort)))
			{
				Cl=imsg->Class;
				Co=imsg->Code;
				IA=imsg->IAddress;
				ReplyMsg((struct Message*)imsg);
				
				if (Cl==IDCMP_CLOSEWINDOW)
				{
					Active=FALSE;
					break;
				}
				
				if (Cl==IDCMP_NEWSIZE)
				{
					*ww = Window->Width  - Window->BorderLeft-Window->BorderRight ;
					*wh = Window->Height - Window->BorderTop -Window->BorderBottom;
					
					if (!(InitOrUpdateGraphics(Window, *ww, *wh, PenArray, BGBitMap, PaintBitMap, PaintRP, LInfo, PaintLayer)))
					{
						Message("Failed to update graphics!", NULL);
					}
					
					EraseRect(Window->RPort, 0, 0, (*ww)-1, (*wh)-1);
				}
				
				if (Cl==IDCMP_REFRESHWINDOW)
				{
					BeginRefresh( Window );
					
					if (*PaintBitMap)
					{
						BltBitMapRastPort(*PaintBitMap, 0, 0, Window->RPort, 0, 0, *ww, *wh, 0xc0);
					}
					
				    EndRefresh( Window, TRUE );
				}
				
				if (Cl==IDCMP_MENUPICK)
				{
					struct MenuItem *n;
					ULONG pick;
					
					while( (Co != MENUNULL) && Active)
					{
						n = ItemAddress( Window->MenuStrip, (ULONG)Co );
						pick = (ULONG) GTMENUITEM_USERDATA( n );
						
						switch(pick)
						{
							case Menu_Link:
							{
								APTR listreq;
								
								if (!(listreq = AllocListRequest(
									LISTREQ_Window, Window,
									LISTREQ_TitleText, "Select input link",
									TAG_DONE )))
								{
									Message("Couldn't allocate a list requester.",NULL);
								}
								else
								{
									UBYTE linkname[32] = {0};
									
									strncpy(linkname, (*link)->ml_Location->mcl_Node.ln_Name, 32);
									linkname[31] = 0;
									
									if (SelectCluster( listreq, linkname, sizeof(linkname), TAG_DONE ))
									{
										if (strcmp((*link)->ml_Location->mcl_Node.ln_Name,linkname))
										{
											struct MidiLink *newlink;
											
											if (!(newlink = AddMidiLink(*midi, MLTYPE_Receiver,
												MLINK_Name, "VU Meter Link",
												MLINK_Location, linkname,
												MLINK_EventMask, CMF_Note|CMF_Mode,
												MLINK_Comment,  "Fireworks [Input]",
												TAG_DONE)))
											{
												Message("Cannot create link to MIDI interface '%s'",NULL,linkname);
											}
											else
											{
												RemoveMidiLink( *link );
												*link = newlink;
											}
										}
									}
									FreeListRequest(listreq);
								}
							}
							break;
							
							case Menu_Save:
							{
								Message("Save Config\nIsn't implemented... eeeh!","Damn");
							}
							break;
							
							case Menu_About:
							{
								Message("Fireworks\n© 1998 by Christian Buchner\nflowerp@eikon.e-technik.tu-muenchen.de","Cool");
							}
							break;
							
							case Menu_Quit:
							{
								Active = FALSE;
							}
							break;
						}
						
						Co = n->NextSelect;
					}
				}
			}
		}
		
		if (Active && (gotsignals & SIGBREAKF_CTRL_E))
		{
			MidiMsg msg;
			
			delta = GetTimeDelta();
			
			for ( cr = (struct Cracker*) CrackerList.mlh_Head ;
				  ncr = (struct Cracker*) cr->cr_node.ln_Succ ;
				  cr = ncr )
			{
				/* move the ray */
				
				cr->cr_starttime += delta;
				if (cr->cr_stoptime != -1) cr->cr_stoptime += delta;
			}
			
			while (GetMidi(*midi,&msg))
			{
				if (noteon(&msg))
				{
					UBYTE chn  = msg.mm_Status & MS_ChanBits;
					UWORD note = msg.mm_Data1;
					UWORD cmsk = 1<<chn;
					
					// if ((Mask & cmsk) && !((NoteArray[note] & Mask)))
					// {
					// }
					
					if (!(NoteArray[note] & cmsk))
					{
						/* Channel wird benutzt */
						ChanUse[chn]++;
						
						NoteArray[note] |= cmsk;
					}
					
					/* Note angeschlagen */
					
					if (cr = AllocPooled(NotePool, sizeof(struct Cracker)))
					{
						float angle;
						
						cr->cr_node.ln_Pri = chn;
						
						cr->cr_chn  = chn;
						cr->cr_note = note;
						cr->cr_vel  = msg.mm_Data2;
						
						cr->cr_starttime =  0;
						cr->cr_savedtime = -1;
						cr->cr_stoptime  = -1;
						
						angle = PI - (float)cr->cr_note/127 * PI;
						cr->cr_fx = SCALE * cos(angle) * cr->cr_vel / 127;
						cr->cr_fy = SCALE * sin(angle) * cr->cr_vel / 127;
						
						/* sorted by channel num to speed up painting */
						Enqueue((struct List*)&CrackerList, (struct Node*)cr);
					}
				}
				else
				{
					if (noteoff(&msg))
					{
						UBYTE chn  = msg.mm_Status & MS_ChanBits;
						UWORD note = msg.mm_Data1;
						UWORD cmsk = 1<<chn;
						
						if (NoteArray[note] & cmsk)
						{
							/* Channel wird nicht mehr benutzt */
							ChanUse[chn]--;
							
							NoteArray[note] &= (~cmsk);
						}
						
						/* Note losgelassen */
						
						for ( cr = (struct Cracker*) CrackerList.mlh_Head ;
							  ncr = (struct Cracker*) cr->cr_node.ln_Succ ;
							  cr = ncr )
						{
							if ( (cr->cr_chn  == chn ) &&
								 (cr->cr_note == note) &&
								 (cr->cr_stoptime == -1) )
							{
								cr->cr_stoptime = 0;
								break;
							}
						}
						
						// if ((Mask & cmsk) && (!(NoteArray[note] & Mask)))
						// {
						// }
					}
					else
					{
						if (modemsg(&msg))
						{
							UBYTE chn  = msg.mm_Status & MS_ChanBits;
							UBYTE mode = msg.mm_Data1;
							UWORD note;
							UWORD cmsk = 1<<chn;
							
							if (mode == MM_AllOff)
							{
								for (note=0;note<128;note++)
								{
									if (NoteArray[note] & cmsk)
									{
										/* Channel wird nicht mehr benuzt */
										ChanUse[chn]--;
										
										/* Note losgelassen */
										
										for ( cr = (struct Cracker*) CrackerList.mlh_Head ;
											  ncr = (struct Cracker*) cr->cr_node.ln_Succ ;
											  cr = ncr )
										{
											if ( (cr->cr_chn  == chn ) &&
												 (cr->cr_note == note) &&
												 (cr->cr_stoptime == -1) )
											{
												cr->cr_stoptime = 0;
												break;
											}
										}
										
										NoteArray[note] &= (~cmsk);
									}
									
									// if ((Mask & cmsk) && (!(NoteArray[note] & Mask)))
									// {
									// }
								}
							}
						}
					}
				}
			}
			
			if (Err = GetMidiErr(*midi))
			{
				if (Err & CMEF_MsgErr)			Message("MIDI Error: MsgErr!",NULL);
				if (Err & CMEF_BufferFull)		Message("MIDI Error: BufferFull!",NULL);
				if (Err & CMEF_SysExFull)		Message("MIDI Error: SysExFull!",NULL);
				if (Err & CMEF_ParseMem)		Message("MIDI Error: ParseMem!",NULL);
				if (Err & CMEF_RecvErr)			Message("MIDI Error: RecvErr!",NULL);
				if (Err & CMEF_RecvOverflow)	Message("MIDI Error: RecvOverflow!",NULL);
				if (Err & CMEF_SysExTooBig)		Message("MIDI Error: SysExTooBig!",NULL);
			}
		}
		
		if (Active && (gotsignals & timersig))
		{
			treq->tr_node.io_Command = TR_ADDREQUEST;
			treq->tr_time.tv_secs  = 0;
			treq->tr_time.tv_micro = 1000000 / FPS;
			SendIO((struct IORequest*)treq);
			
			delta = GetTimeDelta();
			
			if ((*BGBitMap) && (*PaintBitMap))
			{
				LONG lastpen = -1, newpen;
				
				BltBitMapRastPort(*BGBitMap, 0, 0, PaintRP, 0, 0, *ww, *wh, 0xc0);
				
				for ( cr = (struct Cracker*) CrackerList.mlh_Head ;
					  ncr = (struct Cracker*) cr->cr_node.ln_Succ ;
					  cr = ncr )
				{
					/* move the ray */
					
					cr->cr_starttime += delta;
					if (cr->cr_stoptime != -1) cr->cr_stoptime += delta;
					
					if ((1<<cr->cr_chn) & Mask)
					{
						LONG x1, y1;
						LONG x2, y2;
						WORD sx1, sy1;
						WORD sx2, sy2;
						LONG starttime;
						LONG stoptime;
						
						/* Show the cracker */
						
						/* get the ray's lifetime information */
						
						if (cr->cr_savedtime == -1)
							starttime = cr->cr_starttime;
						else
							starttime = cr->cr_savedtime;
						
						if (cr->cr_stoptime == -1)
							stoptime = 0;
						else
							stoptime = cr->cr_stoptime;
						
						/* calculate ray start and ending points */
						
						x1 = CENTER_X + cr->cr_fx * starttime / SCALE;
						y1 = CENTER_Y - cr->cr_fy * starttime / SCALE;
						
						x2 = CENTER_X + cr->cr_fx *  stoptime / SCALE;
						y2 = CENTER_Y - cr->cr_fy *  stoptime / SCALE;
						
						sx1 = (x1 * (*ww) / MAXCOORD);
						sy1 = (y1 * (*wh) / MAXCOORD);
						
						sx2 = (x2 * (*ww) / MAXCOORD);
						sy2 = (y2 * (*wh) / MAXCOORD);
						
						if (cr->cr_savedtime == -1)
						{
							if ((sx1 > *ww) || (sy1 > *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 take */
								cr->cr_savedtime = starttime;
							}
						}
						
						/* see if the ray has disappeared */
						if ((sx2 > *ww) || (sy2 > *wh) || (sx2 < 0) || (sy2 < 0))
						{
							Remove((struct Node*)cr);
							FreePooled(NotePool,cr,sizeof(struct Cracker));
						}
						else
						{
							/* still visible, so paint it! */
							
							/* change pen if necessary */
							newpen = PenArray[Channelpens+cr->cr_chn];
							if (lastpen != newpen)
							{
								SetAPen(PaintRP, newpen);
								lastpen = newpen;
							}
							
							/* draw the ray */
							Move(PaintRP, sx1, sy1);
							Draw(PaintRP, sx2, sy2);
						}
					}
				}
				
				BltBitMapRastPort(*PaintBitMap, 0, 0, Window->RPort, 0, 0, *ww, *wh, 0xc0);
			}
		}
		
		if (gotsignals & SIGBREAKF_CTRL_C)
		{
			Active=FALSE;
		}
	}
	
	if (TimerActive)
	{
		AbortIO((struct IORequest*)treq);
		WaitIO((struct IORequest*)treq);
	}
	
	for ( cr = (struct Cracker*) CrackerList.mlh_Head ;
		  ncr = (struct Cracker*) cr->cr_node.ln_Succ ;
		  cr = ncr )
	{
		Remove((struct Node*)cr);
		FreePooled(NotePool,cr,sizeof(struct Cracker));
	}
}


/*----------------------------*/
/* Initialize/Update Graphics */
/*----------------------------*/

BOOL InitOrUpdateGraphics(struct Window *Window, WORD ww, WORD wh, LONG *PenArray, struct BitMap **BGBitMap, struct BitMap **PaintBitMap, struct RastPort *PaintRP, struct Layer_Info **LInfo, struct Layer **PaintLayer)
{
	BOOL Success = FALSE;
	
	if (*PaintLayer)
	{
		DeleteLayer(0, *PaintLayer);
		*PaintLayer = NULL;
	}
	
	if (*BGBitMap)
	{
		FreeBitMap(*BGBitMap);
		*BGBitMap = NULL;
	}
	
	if (*PaintBitMap)
	{
		FreeBitMap(*PaintBitMap);
		*PaintBitMap = NULL;
	}
	
	if (Window)
	{
		UWORD i;
		struct ColorMap *cmap = Window->WScreen->ViewPort.ColorMap;
		
		struct BitMap *friend = Window->RPort->BitMap;
		ULONG depth = GetBitMapAttr(friend, BMA_DEPTH);
		ULONG flags = GetBitMapAttr(friend, BMA_FLAGS);
		
		struct RastPort BGRP;
		
		for (i = 0 ; i < NUMPENS ; i++)
		{
			if (PenArray[i] == -1L)
			{
				PenArray[i] = ObtainBestPen(
					cmap,
					(ULONG)PenColors[i][0]<<24, 
					(ULONG)PenColors[i][1]<<24, 
					(ULONG)PenColors[i][2]<<24,
					OBP_Precision, PRECISION_IMAGE,
					TAG_DONE );
			}
		}
		
		if (*BGBitMap = AllocBitMap(ww, wh, depth, flags | BMF_CLEAR, friend))
		{
			InitRastPort(&BGRP);
			BGRP.BitMap = *BGBitMap;
			
			SetAPen(&BGRP, PenArray[Backgroundpen]);
			RectFill(&BGRP, 0, 0, ww-1, wh-1);
			
			if ( *PaintBitMap = AllocBitMap(ww, wh, depth, flags | BMF_CLEAR, friend))
			{
				InitRastPort(PaintRP);
				PaintRP->BitMap = *PaintBitMap;
				
				if (!(*LInfo)) *LInfo = NewLayerInfo();
				
				if (*LInfo)
				{
					if (*PaintLayer = CreateUpfrontLayer(*LInfo, *PaintBitMap, 0, 0, ww-1, wh-1, LAYERSMART, NULL))
					{
						PaintRP->Layer = *PaintLayer;
						
						Success = TRUE;
					}
				}
			}
		}
	}
	
	if (!Success)
	{
		if (*PaintLayer)
		{
			DeleteLayer(0, *PaintLayer);
			*PaintLayer = NULL;
		}
		
		if (*BGBitMap)
		{
			FreeBitMap(*BGBitMap);
			*BGBitMap = NULL;
		}
		
		if (*PaintBitMap)
		{
			FreeBitMap(*PaintBitMap);
			*PaintBitMap = NULL;
		}
	}
	
	return(Success);
}



/*-------------------------*/
/* Free allocated Graphics */
/*-------------------------*/

void FreeGraphics(struct Window *Window, LONG *PenArray, struct BitMap **BGBitMap, struct BitMap **PaintBitMap, struct Layer_Info **LInfo, struct Layer **PaintLayer)
{
	UWORD i;
	
	if (*PaintLayer)
	{
		DeleteLayer(0, *PaintLayer);
		*PaintLayer = NULL;
	}
	
	if (*LInfo)
	{
		DisposeLayerInfo(*LInfo);
		*LInfo = NULL;
	}
	
	if (*BGBitMap)
	{
		FreeBitMap(*BGBitMap);
		BGBitMap = NULL;
	}
	
	if (*PaintBitMap)
	{
		FreeBitMap(*PaintBitMap);
		*PaintBitMap = NULL;
	}
	
	if (Window)
	{
		struct ColorMap *cmap = Window->WScreen->ViewPort.ColorMap;
		
		for (i = 0 ; i < NUMPENS ; i++)
		{
			if (PenArray[i] != -1L)
			{
				ReleasePen( cmap, PenArray[i] );
			}
		}
	}
}



/*----------------*/
/* Open Libraries */
/*----------------*/

BOOL OpenLibs(void)
{
	BOOL Success=FALSE;
	
	SysBase = *((struct ExecBase**)(0x4));
	
	if (DOSBase=(struct DosLibrary*)OpenLibrary("dos.library",39L))
	{
		if (GfxBase=(struct GfxBase*)OpenLibrary("graphics.library",39L))
		{
			if (LayersBase=OpenLibrary("layers.library",39L))
			{
				if (IntuitionBase=(struct IntuitionBase*)OpenLibrary("intuition.library",39L))
				{
					if (GadToolsBase=OpenLibrary("gadtools.library", 39L))
					{
						if (UtilityBase=(struct Library*)OpenLibrary("utility.library",39L))
						{
							if (!(CamdBase=OpenLibrary("camd.library",0L)))
							{
								Message("This program requires camd.library!",NULL);
							}
							else
							{
								if ( __fpinit() )
								{
									Message("Unable to initialize floating point!",NULL);
								}
								else
								{
									FPBase=TRUE;
									
									Success=TRUE;
								}
							}
						}
					}
				}
			}
		}
	}
	if (!Success) CloseLibs();
	
	return(Success);
}


/*-----------------*/
/* Close Libraries */
/*-----------------*/

void CloseLibs(void)
{
	if (FPBase)
	{
		__fpterm();
		FPBase=FALSE;
	}
	
	if (CamdBase)
	{
		CloseLibrary(CamdBase);
		CamdBase=NULL;
	}
	
	if (UtilityBase)
	{
		CloseLibrary(UtilityBase);
		UtilityBase=NULL;
	}
	
	if (GadToolsBase)
	{
		CloseLibrary(GadToolsBase);
		GadToolsBase=NULL;
	}
	
	if (IntuitionBase)
	{
		CloseLibrary((struct Library*)IntuitionBase);
		IntuitionBase=NULL;
	}
	
	if (LayersBase)
	{
		CloseLibrary((struct Library*)LayersBase);
		LayersBase=NULL;
	}
	
	if (GfxBase)
	{
		CloseLibrary((struct Library*)GfxBase);
		GfxBase=NULL;
	}
	
	if (DOSBase)
	{
		CloseLibrary((struct Library*)DOSBase);
		DOSBase=NULL;
	}
}


/*-------------------*/
/* Open timer device */
/*-------------------*/

struct timerequest *OpenTimer(void)
{
	BOOL Success = FALSE;
	
	struct MsgPort *reply = NULL;
	struct timerequest *treq = NULL;
	
	if (!(reply = CreateMsgPort()))
	{
		Message("Unable to create timer port!", NULL);
	}
	else
	{
		if (!(treq = CreateIORequest(reply, sizeof(struct timerequest))))
		{
			Message("Unable to create timer request!", NULL);
		}
		else
		{
			if (OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest*)treq,0))
			{
				Message("Timer: Cannot open timer.device (UNIT_VBLANK).", NULL);
			}
			else
			{
				TimerBase = (struct Library*)treq->tr_node.io_Device;
				Success = TRUE;
			}
		}
	}
	
	if (!Success)
	{
		if (TimerBase)
		{
			CloseDevice((struct IORequest*)treq);
			TimerBase = NULL;
		}
		
		if (treq)
		{
			DeleteIORequest(treq);
			treq = NULL;
		}
		
		if (reply)
		{
			DeleteMsgPort(reply);
			reply = NULL;
		}
	}
	
	return(treq);
}



/*--------------------*/
/* Close timer device */
/*--------------------*/

void CloseTimer(struct timerequest *treq)
{
	struct MsgPort *reply = NULL;
	
	if (treq)
	{
		if (treq->tr_node.io_Device)
		{
			CloseDevice((struct IORequest*)treq);
			TimerBase = NULL;
		}
		
		reply = treq->tr_node.io_Message.mn_ReplyPort;
		
		DeleteIORequest(treq);
		treq = NULL;
	}
	
	if (reply)
	{
		DeleteMsgPort(reply);
		reply = NULL;
	}
}


/*----------------*/
/* Get Time Delta */
/*----------------*/

/* This function returns the time difference in milliseconds */
/* to the time it was called last                            */

struct EClockVal eclock;

ULONG GetTimeDelta(void)
{
	// ULONG old_hi = eclock.ev_hi;
	ULONG old_lo = eclock.ev_lo;
	ULONG E_Freq = ReadEClock(&eclock);
	
	ULONG delta = 1000 * (eclock.ev_lo - old_lo) / E_Freq;
	
	return(delta);
}



/*****************************************************************************/


/*----------------------------*/
/* Show a message to the user */
/*----------------------------*/

LONG __stdargs Message(UBYTE *Msg,UBYTE *Options,...)
{
	LONG retval;
	
	BOOL req = FALSE;
	
	va_list Arg;
	va_start(Arg,Options);
	
	// if (Options) if (strchr(Options,'|')) req = TRUE;
	if (Options) req = TRUE;
	
	if (IntuitionBase && (WBMode || req))
	{
		struct EasyStruct Req={sizeof(struct EasyStruct),0,"Fireworks",0, NULL};
		
		if (!Options) Options = "I see";
		
		Req.es_TextFormat=Msg;
		Req.es_GadgetFormat=Options;
		
		retval=EasyRequestArgs(NULL,&Req,0,Arg);
	}
	else
	{
		if (DOSBase)
		{
			VPrintf(Msg,Arg);
			Printf("\n");
			
			retval=0;
		}
	}
	va_end(Arg);
	
	return(retval);
}


/*-----------------------*/
/* Standard Exit routine */
/*-----------------------*/

void __stdargs _XCEXIT(LONG lcode)
{
	Message("Task wants to exit, return code %ld\nHolding task!", NULL, lcode);
	Wait(0);
}



/*-------------------*/
/* CAM Library stubs */
/*-------------------*/

struct MidiNode *CreateMidi(Tag tag, ...)
{
	return CreateMidiA((struct TagItem *)&tag );
}

BOOL SetMidiAttrs(struct MidiNode *mi, Tag tag, ...)
{
	return SetMidiAttrsA(mi, (struct TagItem *)&tag );
}

struct MidiLink *AddMidiLink(struct MidiNode *mi, LONG type, Tag tag, ...)
{
	return AddMidiLinkA(mi, type, (struct TagItem *)&tag );
}

BOOL SetMidiLinkAttrs(struct MidiLink *mi, Tag tag, ...)
{
	return SetMidiLinkAttrsA(mi, (struct TagItem *)&tag );
}
