
/*-----------------------------------------------------------------------*/
/* PianoMeter.c - graphical MIDI note visualisation using BOOPSI classes */
/*                      ® 1998 by Christian Buchner                      */
/*-----------------------------------------------------------------------*/

#include "PianoMeter.h"
#include "PianoMeter_protos.h"

#define PREF_VERSION 1

struct Task *MyTask;		/* this task */
BOOL WBMode = FALSE;		/* Workbench or not? */


/* Function Prototypes */

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

LONG PianoMeter(struct Prefs *pref);
void MainLoop(struct Globals *glob, struct Prefs *pref);

BOOL WindowLayout(struct Globals *glob, struct Prefs *pref, WORD *minw, WORD *minh, WORD *maxw, WORD *maxh);
BOOL OpenGUI(struct Globals *glob, struct Prefs *pref);
void CloseGUI(struct Globals *glob, struct Prefs *pref);
BOOL CreateOrLayoutGadgets(struct Globals *glob, struct Prefs *pref);

void InitPrefs(struct Prefs *pref);
BOOL LoadPrefs(struct Prefs *pref);
BOOL SavePrefs(struct Globals *glob, struct Prefs *pref, UBYTE *EnvName);

void InitGlobals(struct Globals *glob);

void AsyncSelectMIDILink(struct Globals *glob, struct Prefs *pref);
void AsyncSelectAndPlay(struct Globals *glob, struct Prefs *pref);

struct Screen *GetScreen(struct Globals *glob);

BOOL SelectMIDI(struct Globals *glob, struct Prefs *pref, struct FileRequester **fr, UBYTE *filebuffer, ULONG MaxSize);
void PlayMIDI(struct Globals *glob, struct Prefs *pref);
void StopMIDI(struct Globals *glob, struct Prefs *pref);



/* 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 Menu structures */

enum
{
	Menu_Ignore1,
	Menu_Link,
	Menu_Hooks,
	Menu_Save,
	Menu_Ignore2,
	Menu_About,
	Menu_Ignore3,
	Menu_Quit,
	Menu_Ignore4,
	Menu_Release,
	Menu_Ignore5,
	Menu_Play,
	Menu_Stop,
	Menu_Ignore6,
	Menu_Init1,
	Menu_Init2,
	Menu_Init3,
	Menu_Init4,
	Menu_Init5,
	Menu_Ignore7,
};


#define I1 (1<<5)
#define I2 (1<<6)
#define I3 (1<<7)
#define I4 (1<<8)
#define I5 (1<<9)

struct NewMenu PMNewMenu[]=
{
	/* nm_Type	nm_Label			nm_CommKey	nm_Flags			nm_MutualExclude	nm_UserData */
	NM_TITLE,	"Project",			NULL,		0,					0,					(APTR)Menu_Ignore1,
	NM_ITEM,	"MIDI Link...",		"L",		0,					0,					(APTR)Menu_Link,
	NM_ITEM,	"ASL Hooks",		"A",		CHECKIT|MENUTOGGLE,	0,					(APTR)Menu_Hooks,
	NM_ITEM,	"Save Config",		"C",		0,					0,					(APTR)Menu_Save,
	NM_ITEM,	NM_BARLABEL,		NULL,		0,					0,					(APTR)Menu_Ignore2,
	NM_ITEM,	"About",			"?",		0,					0,					(APTR)Menu_About,
	NM_ITEM,	NM_BARLABEL,		NULL,		0,					0,					(APTR)Menu_Ignore3,
	NM_ITEM,	"Quit",				"Q",		0,					0,					(APTR)Menu_Quit,
	NM_TITLE,	"MIDI",				NULL,		0,					0,					(APTR)Menu_Ignore4,
	NM_ITEM,	"Release Notes",	"N",		0,					0,					(APTR)Menu_Release,
	NM_ITEM,	NM_BARLABEL,		NULL,		0,					0,					(APTR)Menu_Ignore5,
	NM_ITEM,	"Play MIDI file...","P",		0,					0,					(APTR)Menu_Play,
	NM_ITEM,	"Stop playing",		"S",		0,					0,					(APTR)Menu_Stop,
	NM_ITEM,	NM_BARLABEL,		NULL,		0,					0,					(APTR)Menu_Ignore6,
	NM_ITEM,	"Normal Init",		NULL,		CHECKIT,			(   I2|I3|I4|I5),	(APTR)Menu_Init1,
	NM_ITEM,	"GM Reset",			NULL,		CHECKIT,			(I1|   I3|I4|I5),	(APTR)Menu_Init2,
	NM_ITEM,	"GS Reset",			NULL,		CHECKIT,			(I1|I2|   I4|I5),	(APTR)Menu_Init3,
	NM_ITEM,	"XG Reset",			NULL,		CHECKIT,			(I1|I2|I3   |I5),	(APTR)Menu_Init4,
	NM_ITEM,	"MT32 Emu",			NULL,		CHECKIT,			(I1|I2|I3|I4   ),	(APTR)Menu_Init5,
	NM_END,		NULL,				NULL,		0,					0,					(APTR)Menu_Ignore7,
};



/*--------------*/
/* 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())
		{
			BPTR OldDir;
			struct Prefs *pref;
			
			OldDir = CurrentDir(wbmsg->sm_ArgList->wa_Lock);
			
			if (!(pref = AllocVec(sizeof(struct Prefs),MEMF_ANY|MEMF_CLEAR)))
			{
				Message("No memory for prefs!",NULL);
			}
			else
			{
				InitPrefs(pref);
				LoadPrefs(pref);
				
				ReturnCode = PianoMeter(pref);
				
				FreeVec(pref);
			}
			
			CurrentDir(OldDir);
			
			CloseLibs();
		}
		Forbid();
		ReplyMsg((struct Message*)wbmsg);
	}
	return(ReturnCode);
}



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

LONG  ShellInterface(void)
{
	LONG ReturnCode = RETURN_ERROR;
	BPTR lock;
	BPTR OldDir;
	
	if (OpenLibs())
	{
		
		if (!(lock = Lock("PROGDIR:", SHARED_LOCK)))
		{
			Message("Unable to locate program directory.",NULL);
		}
		else
		{
			/* CLI argument parsing */
			
			struct	ArgArray
			{
				UBYTE *aa_Link;
				ULONG *aa_Octaves;
				ULONG *aa_WinX;
				ULONG *aa_WinY;
				ULONG  aa_GM;
				ULONG  aa_GS;
				ULONG  aa_XG;
				ULONG  aa_MT32;
				ULONG  aa_NoHooks;
			} AA;
			
			static UBYTE	*Template = "LINK/K,OCT=OCTAVES/K/N,WINX/K/N,WINY/K/N,GM/S,GS/S,XG/S,MT32/S,NH=NOHOOKS/S";
			struct RDArgs *RDArgs;
			
			struct Prefs *pref;
			
			ReturnCode = RETURN_FAIL;
			
			OldDir = CurrentDir(lock);
			
			if (!(pref = AllocVec(sizeof(struct Prefs),MEMF_ANY|MEMF_CLEAR)))
			{
				Message("No memory for prefs!",NULL);
			}
			else
			{
				InitPrefs(pref);
				LoadPrefs(pref);
				
				memset(&AA, 0, sizeof(struct ArgArray));
				if (RDArgs=ReadArgs(Template, (LONG *)&AA, 0))
				{
					BOOL fault = FALSE;
					UWORD opts;
					
					if (AA.aa_Link)
					{
						strncpy(pref->Link, AA.aa_Link, sizeof(pref->Link));
						pref->Link[sizeof(pref->Link)-1] = 0;
					}
					if (AA.aa_Octaves) pref->Octaves = *AA.aa_Octaves;
					if (pref->Octaves < 1) pref->Octaves = 1;
					if (pref->Octaves > 11) pref->Octaves = 11;
					if (AA.aa_WinX) pref->WinX= *AA.aa_WinX;
					if (AA.aa_WinY) pref->WinY= *AA.aa_WinY;
					if (AA.aa_NoHooks) pref->Flags &= ~(PREFF_ASLHOOKS);
					
					opts = 0;
					if (AA.aa_GM  ) {opts++; pref->Flags |= PREFF_GM  ;};
					if (AA.aa_GS  ) {opts++; pref->Flags |= PREFF_GS  ;};
					if (AA.aa_XG  ) {opts++; pref->Flags |= PREFF_XG  ;};
					if (AA.aa_MT32) {opts++; pref->Flags |= PREFF_MT32;};
					if (opts>1)
					{
						Message("Please specify only one of GM, GS, XG and MT32.", NULL);
						fault = TRUE;
					}
					
					if (!fault)
					{
						ReturnCode = PianoMeter(pref);
					}
					
					FreeArgs(RDArgs);
				}
				else
				{
					PrintFault(IoErr(),"PianoMeter");
				}
				
				FreeVec(pref);
			}
			
			CurrentDir(OldDir);
			
			UnLock(lock);
		}
		CloseLibs();
	}
	return(ReturnCode);
}



/*-----------------------------*/
/* GUI and MIDI initialisation */
/*-----------------------------*/

LONG PianoMeter(struct Prefs *pref)
{
	LONG ReturnCode = RETURN_FAIL;
	
	struct Globals *glob;
	BYTE OldPri;
	
	MyTask = FindTask(NULL);
	
	if (!(glob = AllocVec(sizeof(struct Globals), MEMF_ANY|MEMF_CLEAR)))
	{
		Message("No memory for globals!",NULL);
	}
	else
	{
		InitGlobals(glob);
		
		if (OpenGUI(glob,pref))
		{
			if (!(glob->midi = CreateMidi(
				MIDI_Name, "Piano Meter",
				MIDI_RecvSignal, SIGBREAKB_CTRL_E,
				MIDI_MsgQueue,   500,
				MIDI_ErrFilter, CMEF_All,
				TAG_DONE)))
			{
				Message("Cannot create MIDI port!",NULL);
			}
			else
			{
				if (!(glob->link = AddMidiLink(glob->midi, MLTYPE_Receiver,
					MLINK_Name, "Piano Meter Link",
					MLINK_Location, pref->Link,
					MLINK_EventMask, CMF_Note|CMF_Mode,
					MLINK_Comment,  "Piano Meter [Input]",
					TAG_DONE)))
				{
					Message("Cannot create link to MIDI interface '%s'",NULL,pref->Link);
				}
				else
				{
					SPrintf(glob->WTitle, "Piano Meter [%s]", pref->Link);
					SetWindowTitles(glob->Window, glob->WTitle, (UBYTE*) ~0);
					
					OldPri = MyTask->tc_Node.ln_Pri;
					MyTask->tc_Node.ln_Pri = NORMPRI;
					
					MainLoop(glob, pref);
					ReturnCode = RETURN_OK;
					
					MyTask->tc_Node.ln_Pri = OldPri;
					
					RemoveMidiLink(glob->link);
					glob->link = NULL;
				}
				DeleteMidi(glob->midi);
				glob->midi = NULL;
			}
			CloseGUI(glob, pref);
		}
	}
	return(ReturnCode);
}


BOOL OpenGUI(struct Globals *glob, struct Prefs *pref)
{
	BOOL Success = FALSE;
	WORD minw, minh;
	WORD maxw, maxh;
	
	struct Screen *wbscr;
	
	UWORD init;
	
	if(!(glob->Classes = _initclasses()))
	{
		Message("Unable to initialize the classes.",NULL);
	}
	else
	{
		glob->Screen = NULL;
		
		if (!(glob->AppPort = CreateMsgPort()))
		{
			Message("Failed to create AppMsg port.", NULL);
		}
		else
		{
			if (glob->LockedScreen = GetScreen(glob))
			{
				glob->Screen = glob->LockedScreen;
				
				if (!(glob->VisualInfo = GetVisualInfo(glob->Screen, TAG_DONE)))
				{
					Message("No visual info!",NULL);
				}
				else
				{
					if (!(WindowLayout(glob, pref, &minw, &minh, &maxw, &maxh)))
					{
						Message("Couldn't layout window!",NULL);
					}
					else
					{
						if (!(glob->Window = OpenWindowTags( NULL,
								WA_PubScreen, glob->Screen,
								WA_Title, "Piano Meter",
								WA_Left, (ULONG)pref->WinX,
								WA_Top, (ULONG)pref->WinY,
								WA_InnerWidth, (ULONG)glob->ww,
								WA_InnerHeight, (ULONG)glob->wh,
								WA_GimmeZeroZero, TRUE,
								WA_DepthGadget, TRUE,
								WA_SizeGadget, TRUE,
								WA_SizeBBottom, TRUE,
								WA_CloseGadget, TRUE,
								WA_DragBar, TRUE,
								WA_IDCMP, IDCMP_CLOSEWINDOW|IDCMP_GADGETDOWN|IDCMP_GADGETUP|IDCMP_NEWSIZE|IDCMP_MENUPICK,
								WA_SimpleRefresh, TRUE,
								WA_NoCareRefresh, TRUE,
								WA_NewLookMenus, TRUE,
								WA_Activate, TRUE,
								TAG_DONE ) ))
						{
							Message("Couldn't open window!",NULL);
						}
						else
						{
							if ( wbscr = LockPubScreen("Workbench") )
							{
								if (glob->Screen == wbscr)
								{
									glob->AppWindow = AddAppWindow( 1, NULL, glob->Window, glob->AppPort, TAG_DONE);
								}
								
								UnlockPubScreen( NULL, wbscr );
							}
							
							glob->ww = glob->Window->Width  - glob->Window->BorderLeft-glob->Window->BorderRight ;
							glob->wh = glob->Window->Height - glob->Window->BorderTop -glob->Window->BorderBottom;
							
							glob->Window->MinWidth  = minw + glob->Window->BorderLeft+glob->Window->BorderRight;
							glob->Window->MinHeight = minh + glob->Window->BorderTop +glob->Window->BorderBottom;
							glob->Window->MaxWidth  = maxw + glob->Window->BorderLeft+glob->Window->BorderRight;
							glob->Window->MaxHeight = maxh + glob->Window->BorderTop +glob->Window->BorderBottom;
							
							if (pref->Flags & PREFF_ASLHOOKS)
								PMNewMenu[Menu_Hooks].nm_Flags |= CHECKED;
							else
								PMNewMenu[Menu_Hooks].nm_Flags &= (~CHECKED);
							
							
							for (init = 0; init <= 4 ; init++)
							{
								BOOL set = FALSE;
								
								switch(init)
								{
									case 0:
										if (!(pref->Flags&(PREFF_GM|PREFF_GS|PREFF_XG|PREFF_MT32)))
											set = TRUE;
										break;
									case 1:
										if (pref->Flags & PREFF_GM)
											set = TRUE;
										break;
									case 2:
										if (pref->Flags & PREFF_GS)
											set = TRUE;
										break;
									case 3:
										if (pref->Flags & PREFF_XG)
											set = TRUE;
										break;
									case 4:
										if (pref->Flags & PREFF_MT32)
											set = TRUE;
										break;
								}
								
								if (set)
									PMNewMenu[Menu_Init1+init].nm_Flags |= CHECKED;
								else
									PMNewMenu[Menu_Init1+init].nm_Flags &= ~(CHECKED);
							}
							
							if (!(glob->Menu=(struct Menu *)CreateMenus(PMNewMenu, TAG_DONE)))
							{
								Message("Failed to create intuition menu.",NULL);
							}
							else
							{
								LayoutMenus(glob->Menu, glob->VisualInfo, GTMN_NewLookMenus, TRUE, TAG_DONE);
								SetMenuStrip(glob->Window, glob->Menu);
								
								if (!(CreateOrLayoutGadgets(glob, pref)))
								{
									Message("Couldn't create gadgets!",NULL);
								}
								else
								{
									AddGList(glob->Window,glob->FirstGad,-1,-1,NULL);
									RefreshGList(glob->FirstGad,glob->Window,NULL,-1);
									
									Success=TRUE;
								}
							}
						}
					}
				}
			}
		}
	}
	
	if (!Success)
	{
		CloseGUI(glob, pref);
	}
	
	return(Success);
}


void CloseGUI(struct Globals *glob, struct Prefs *pref)
{
	UWORD i;
	
	if (glob->Classes)
	{
		if (glob->AppPort)
		{
			struct Message *msg;
			
			if (glob->Screen)
			{
				if (glob->VisualInfo)
				{
					if (glob->Window)
					{
						if (glob->Menu)
						{
							if (glob->FirstGad)
							{
								RemoveGList(glob->Window,glob->FirstGad,-1);
								
								if (glob->PianoGad)
								{
									DisposeObject(glob->PianoGad);
									glob->PianoGad=NULL;
								}
								
								for (i=0; i<16; i++)
								{
									if (glob->ChanLedGad[i])
									{
										DisposeObject(glob->ChanLedGad[i]);
										glob->ChanLedGad[i]=NULL;
									}
								}
							}
							ClearMenuStrip(glob->Window);
							
							FreeMenus(glob->Menu);
							glob->Menu = NULL;
						}
						
						if (glob->AppWindow)
						{
							RemoveAppWindow(glob->AppWindow);
							glob->AppWindow = NULL;
						}
						
						CloseWindow(glob->Window);
						glob->Window = NULL;
					}
					FreeVisualInfo(glob->VisualInfo);
					glob->VisualInfo = NULL;
				}
				
				if (glob->LockedScreen)
				{
					UnlockPubScreen(NULL, glob->LockedScreen);
					glob->LockedScreen = FALSE;
				}
				
				glob->Screen = FALSE;
			}
			while(msg=GetMsg(glob->AppPort)) ReplyMsg(msg);
			
			DeleteMsgPort(glob->AppPort);
			glob->AppPort = NULL;
		}
		_freeclasses();
		glob->Classes = FALSE;
	}
}



/*-----------------------------*/
/* Calculate window dimensions */
/*-----------------------------*/

#define XSPACING 4
#define INTERSPACING 2
#define YSPACING 4

BOOL WindowLayout(struct Globals *glob, struct Prefs *pref, WORD *minw, WORD *minh, WORD *maxw, WORD *maxh)
{
	BOOL Success = FALSE;
	
	struct gpDomain gpd;
	struct TagItem ti[2];
	
	WORD pw=0, ph=0, pmaxw=0;
	WORD clw=0, clh=0;
	
	/* Get the domain of a ChanLed object */
	gpd.MethodID  = GM_DOMAIN;
	gpd.gpd_GInfo = NULL;
	gpd.gpd_RPort = &glob->Screen->RastPort;
	gpd.gpd_Which = GDOMAIN_NOMINAL;
	ti[0].ti_Tag  = NULL;
	gpd.gpd_Attrs = ti;
	if (DoCustomClassMethod (cl_ChanLed, (Msg)&gpd) == 1)
	{
		clw = gpd.gpd_Domain.Width;
		clh = gpd.gpd_Domain.Height;
	}
	
	/* Get the domain of the Piano object */
	gpd.MethodID  = GM_DOMAIN;
	gpd.gpd_GInfo = NULL;
	gpd.gpd_RPort = &glob->Screen->RastPort;
	gpd.gpd_Which = GDOMAIN_NOMINAL;
	ti[0].ti_Tag  = BOPA_Piano_Octaves;
	ti[0].ti_Data = pref->Octaves;
	ti[1].ti_Tag  = TAG_DONE;
	gpd.gpd_Attrs = ti;
	if (DoCustomClassMethod (cl_Piano, (Msg)&gpd) == 1)
	{
		pw = gpd.gpd_Domain.Width;
		ph = gpd.gpd_Domain.Height;
	}
	
	/* Get the maximum width of a Piano object (11 octaves) */
	ti[0].ti_Data = 11;
	if (DoCustomClassMethod (cl_Piano, (Msg)&gpd) == 1)
	{
		pmaxw = gpd.gpd_Domain.Width;
	}
	
	/* Calculate window width and height*/
	if (16*clw > pw) glob->ww = XSPACING+16*clw+XSPACING;
		else glob->ww = XSPACING+pw+XSPACING;
	glob->wh = YSPACING+clh+INTERSPACING+ph+YSPACING;
	
	/* Calculate minimum width and height */
	*minw = XSPACING+16*clw+XSPACING;
	*minh = glob->wh;
	
	/* Calculate maximum width and height */
	*maxw = XSPACING+pmaxw+XSPACING;
	*maxh = glob->wh;
	
	Success=TRUE;
	
	return(Success);
}



/*----------------------------*/
/* Gadget creation and layout */
/*----------------------------*/

BOOL CreateOrLayoutGadgets(struct Globals *glob, struct Prefs *pref)
{
	BOOL Success = FALSE;
	
	struct gpDomain gpd;
	struct TagItem ti[2];
	
	WORD pw=0, ph=0;
	WORD clw=0, clh=0;
	
	ULONG gx, gy;
	
	UWORD i;
	struct Gadget *prev;
	
	ULONG OldOctaves = 0;
	
	if (glob->PianoGad)
	{
		GetAttr(BOPA_Piano_Octaves, glob->PianoGad, &OldOctaves);
	}
	
	/* Get the domain of a ChanLed object */
	gpd.MethodID  = GM_DOMAIN;
	gpd.gpd_GInfo = NULL;
	gpd.gpd_RPort = &glob->Screen->RastPort;
	gpd.gpd_Which = GDOMAIN_NOMINAL;
	ti[0].ti_Tag  = NULL;
	gpd.gpd_Attrs = ti;
	if (DoCustomClassMethod (cl_ChanLed, (Msg)&gpd) == 1)
	{
		clw = gpd.gpd_Domain.Width;
		clh = gpd.gpd_Domain.Height;
	}
	
	/* Try to fit a piano as large as possible into the window */
	
	pref->Octaves = 11;
	do
	{
		/* Get the domain of the Piano object */
		gpd.MethodID  = GM_DOMAIN;
		gpd.gpd_GInfo = NULL;
		gpd.gpd_RPort = &glob->Screen->RastPort;
		gpd.gpd_Which = GDOMAIN_NOMINAL;
		ti[0].ti_Tag  = BOPA_Piano_Octaves;
		ti[0].ti_Data = pref->Octaves;
		ti[1].ti_Tag  = TAG_DONE;
		gpd.gpd_Attrs = ti;
		if (DoCustomClassMethod (cl_Piano, (Msg)&gpd) == 1)
		{
			pw = gpd.gpd_Domain.Width;
			ph = gpd.gpd_Domain.Height;
		}
		
		if (XSPACING+pw+XSPACING > glob->ww) (pref->Octaves)--;
		
	} while (XSPACING+pw+XSPACING > glob->ww);
	
	/* Dispose piano only when no. of octaves changed */
	
	if (glob->PianoGad)
	{
		if (pref->Octaves != OldOctaves)
		{
			DisposeObject(glob->PianoGad);
			glob->PianoGad=NULL;
		}
	}
	
	/* Create and/or layout the gadgets */
	
	gx = (ULONG)((glob->ww-pw)/2);
	gy = (ULONG)((glob->wh-clh-INTERSPACING-ph)/2)+clh+INTERSPACING;
	
	if (!(glob->PianoGad))
	{
		if (glob->FirstGad = glob->PianoGad = prev = (struct Gadget*)NewObject( cl_Piano, NULL,
				GA_Left, gx,
				GA_Top, gy,
				GA_Width, (ULONG)pw,
				GA_Height, (ULONG)ph,
				GA_Immediate,TRUE,
				GA_RelVerify,TRUE,
				GA_ID,100,
				BOPA_Piano_Octaves, (ULONG)pref->Octaves,
				TAG_END) )
		{
			Success=TRUE;
		}
	}
	else
	{
		glob->FirstGad = prev = glob->PianoGad;
		
		SetAttrs(glob->PianoGad, 
			GA_Left, gx,
			GA_Top, gy,
			GA_Width, (ULONG)pw,
			GA_Height, (ULONG)ph,
			TAG_END );
		
		Success = TRUE;
	}
	
	if (Success)
	{
		for (i=0; i<16; i++)
		{
			gx = (ULONG)((glob->ww-16*clw)/2)+i*clw;
			gy = (ULONG)((glob->wh-clh-YSPACING-ph)/2);
			
			if (!(glob->ChanLedGad[i]))
			{
				UWORD cmsk = (1 << i);
				
				if (!(glob->ChanLedGad[i] = prev = (struct Gadget*)NewObject( cl_ChanLed, NULL,
						GA_Left, gx, 
						GA_Top, gy,
						GA_Width, (ULONG)clw,
						GA_Height, (ULONG)clh,
						GA_Immediate,TRUE,
						GA_RelVerify,TRUE,
						GA_ID,i,
						BOPA_ChanLed_Channel, (ULONG)i+1,
						BOPA_ChanLed_Enabled, pref->Mask & cmsk ? 1L : 0L,
						BOPA_ChanLed_Lighted, 0L,
						GA_Previous, prev,
						TAG_END)))
				{
					Success=FALSE;
					break;
				}
			}
			else
			{
				SetAttrs(glob->ChanLedGad[i], 
					GA_Left, gx,
					GA_Top, gy,
					GA_Width, (ULONG)clw,
					GA_Height, (ULONG)clh,
					GA_Previous, prev,
					TAG_END );
				
				prev = glob->ChanLedGad[i];
			}
		}
	}
	
	if (!Success)
	{
		if (glob->PianoGad)
		{
			DisposeObject(glob->PianoGad);
			glob->PianoGad=NULL;
		}
		
		for (i=0; i<16; i++)
		{
			if (glob->ChanLedGad[i])
			{
				DisposeObject(glob->ChanLedGad[i]);
				glob->ChanLedGad[i]=NULL;
			}
		}
		
		glob->FirstGad = NULL;
	}
	
	return(Success);
}



/*--------------------*/
/* Set Prefs defaults */
/*--------------------*/

void InitPrefs(struct Prefs *pref)
{
	memset(pref, 0, sizeof(struct Prefs));
	strcpy(pref->Header, "Piano Meter");
	pref->Version = PREF_VERSION;
	strcpy(pref->Link,"out.0");
	pref->Flags = PREFF_ASLHOOKS;
	pref->Octaves = 5;
	pref->WinX = 100;
	pref->WinY = 100;
	pref->Mask = 0xffff;
}



/*------------*/
/* Load Prefs */
/*------------*/

BOOL LoadPrefs(struct Prefs *pref)
{
	BOOL Success = FALSE;
	UBYTE *name = "ENV:MIDI/PianoMeter.prefs";
	BPTR file;
	
	if (file = Open(name, MODE_OLDFILE))
	{
		if (Read(file, pref, sizeof(struct Prefs)) != -1)
		{
			if (stricmp(pref->Header, "Piano Meter"))
			{
				Message("'%s' is not a Piano Meter preferences file. Using defaults.", NULL, name);
			}
			else
			{
				if (pref->Version != PREF_VERSION)
				{
					Message("'%s' preferences version is not supported. Using defaults.", NULL, name);
				}
				else
				{
					Success = TRUE;
				}
			}
		}
		if (!Success) InitPrefs(pref);
		
		Close(file);
	}
	return(Success);
}



/*------------*/
/* Save Prefs */
/*------------*/

BOOL SavePrefs(struct Globals *glob, struct Prefs *pref, UBYTE *EnvName)
{
	BOOL Success = FALSE;
	BPTR OldDir=NULL;
	BPTR EnvLock=NULL;
	BPTR MIDILock=NULL;
	BPTR FH;
	UBYTE *FileName = "PianoMeter.prefs";
	UBYTE *MIDIName = "MIDI";
	
	pref->WinX = glob->Window->LeftEdge;
	pref->WinY = glob->Window->TopEdge;
	pref->WinW = glob->ww;
	pref->WinH = glob->wh;
	
	if (EnvLock=Lock(EnvName,ACCESS_READ))
	{
		OldDir=CurrentDir(EnvLock);
		
		if (!(MIDILock=Lock(MIDIName,ACCESS_READ)))
		{
			MIDILock=CreateDir(MIDIName);
		}
		if (MIDILock)
		{
			CurrentDir(MIDILock);
			
			Success=TRUE;
		}
	}
	
	if (Success)
	{
		Success=FALSE;
		
		if (FH=Open(FileName,MODE_NEWFILE))
		{
			if (Write(FH,pref,sizeof(struct Prefs))==sizeof(struct Prefs))
			{
				Success=TRUE;
			}
			Close(FH);
		}
	}
	
	if (OldDir)  CurrentDir(OldDir);
	if (MIDILock) UnLock(MIDILock);
	if (EnvLock) UnLock(EnvLock);
	
	return(Success);
}



/*--------------------*/
/* Initialize globals */
/*--------------------*/

void InitGlobals(struct Globals *glob)
{
}



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

UWORD NoteArray[128];
UBYTE ChanUse[16];

#define TAGSIZE 32
struct TagItem TagBuffer[TAGSIZE];

void MainLoop(struct Globals *glob, struct Prefs *pref)
{
	UWORD MidKey = (pref->Octaves/2)*12;
	
	BOOL Active;
	ULONG signals;
	ULONG gotsignals;
	struct IntuiMessage *imsg;
	ULONG Cl;
	UWORD Co;
	APTR IA;
	
	ULONG winsig   = (1L << glob->Window->UserPort->mp_SigBit);
	ULONG appsig   = (1L << glob->AppPort->mp_SigBit);
	
	UBYTE Err;
	
	struct TagItem *tagptr;
	ULONG numtags;
	
	Active=TRUE;
	glob->LinkRefresh=FALSE;
	
	memset(NoteArray,0,sizeof(NoteArray));
	memset(ChanUse  ,0,sizeof(ChanUse  ));
	
	while(Active)
	{
		signals = winsig | appsig | SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E;
		
		if (glob->LinkRefresh)
		{
			struct MidiLink *newlink;
			
			if (!(newlink = AddMidiLink(glob->midi, MLTYPE_Receiver,
				MLINK_Name, "Piano Meter Link",
				MLINK_Location, pref->Link,
				MLINK_EventMask, CMF_Note|CMF_Mode,
				MLINK_Comment,  "Piano Meter [Input]",
				TAG_DONE)))
			{
				Message("Cannot create link to MIDI interface '%s'",NULL,pref->Link);
			}
			else
			{
				UWORD note;
				UBYTE chn;
				
				tagptr = TagBuffer;
				numtags = 0;
				
				RemoveMidiLink( glob->link );
				FlushMidi( glob->midi );
				
				glob->link = newlink;
				
				SPrintf(glob->WTitle, "Piano Meter [%s]", pref->Link);
				SetWindowTitles(glob->Window, glob->WTitle, (UBYTE*) ~0);
				
				for (note=0;note<128;note++)
				{
					for (chn=0;chn<16;chn++)
					{
						UWORD key = note-60+MidKey;
						UWORD cmsk = 1<<chn;
						
						if (NoteArray[note] & cmsk)
						{
							ChanUse[chn]--;
							if (!ChanUse[chn]) SetGadgetAttrs(glob->ChanLedGad[chn], glob->Window, NULL, BOPA_ChanLed_Lighted, FALSE, TAG_DONE);
							
							NoteArray[note] &= (~cmsk);
							
							if ((key < 12* (pref->Octaves)) && (pref->Mask & cmsk) && (!(NoteArray[note] & pref->Mask)))
							{
								tagptr->ti_Tag  = BOPA_Piano_KeyState;
								tagptr->ti_Data = (ULONG)(CLEARKEY|key);
								tagptr++;numtags++;
								
								if (numtags == TAGSIZE-1)
								{
									tagptr->ti_Tag = TAG_END;
									SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
									tagptr = TagBuffer; numtags = 0;
								}
							}
						}
					}
				}
				if (numtags)
				{
					tagptr->ti_Tag = TAG_END;
					SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
					tagptr = TagBuffer; numtags = 0;
				}
			}
			
			glob->LinkRefresh = FALSE;
		}
		
		gotsignals = Wait(signals);
		
		if (gotsignals & (1L<<glob->Window->UserPort->mp_SigBit))
		{
			while(Active && (imsg=(struct IntuiMessage*)GetMsg(glob->Window->UserPort)))
			{
				Cl=imsg->Class;
				Co=imsg->Code;
				IA=imsg->IAddress;
				ReplyMsg((struct Message*)imsg);
				
				if (Cl==IDCMP_CLOSEWINDOW)
				{
					if (AskAsync(glob))
					{
						Active = FALSE;
						break;
					}
					else
						AsyncMessage(glob,CloseTask, "Cannot quit yet. Please close all open requesters.","I will");
				}
				
				if (Cl==IDCMP_GADGETDOWN)
				{
					UWORD ID = ((struct Gadget*)IA)->GadgetID;
					
					if (ID < 16)
					{
						ULONG flag;
						UWORD note;
						
						GetAttr(BOPA_ChanLed_Enabled, IA, &flag);
						
						if (flag)
							pref->Mask |= (1<<ID);
						else
							pref->Mask &= (~(1<<ID));
						
						tagptr = TagBuffer;
						numtags = 0;
						
						for (note=0;note<128;note++)
						{
							UWORD key = note-60+MidKey;
							
							if (key < 12* (pref->Octaves))
							{
								if ((NoteArray[note] & pref->Mask))
								{
									tagptr->ti_Tag  = BOPA_Piano_KeyState;
									tagptr->ti_Data = (ULONG)(SETKEY|key);
									tagptr++;numtags++;
								}
								else
								{
									tagptr->ti_Tag  = BOPA_Piano_KeyState;
									tagptr->ti_Data = (ULONG)(CLEARKEY|key);
									tagptr++;numtags++;
								}
								
								if (numtags == TAGSIZE-1)
								{
									tagptr->ti_Tag = TAG_END;
									SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
									tagptr = TagBuffer; numtags = 0;
								}
							}
						}
						
						if (numtags)
						{
							tagptr->ti_Tag = TAG_END;
							SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
							tagptr = TagBuffer; numtags = 0;
						}
					}
				}
				
				if (Cl==IDCMP_GADGETUP)
				{
				}
				
				if (Cl==IDCMP_SIZEVERIFY)
				{
				}
				
				if (Cl==IDCMP_NEWSIZE)
				{
					glob->ww = glob->Window->Width  - glob->Window->BorderLeft-glob->Window->BorderRight ;
					glob->wh = glob->Window->Height - glob->Window->BorderTop -glob->Window->BorderBottom;
					
					RemoveGList(glob->Window,glob->FirstGad,-1);
					
					EraseRect(glob->Window->RPort, 0, 0, glob->ww-1, glob->wh-1);
					
					if (!(CreateOrLayoutGadgets(glob, pref)))
					{
						Message("Error during gadget layout!",NULL);
						Active=FALSE;
						break;
					}
					else
					{
						/* Gadgets have been re-created */
						/* Now set them to the desired state */
						/* even before redrawing */
						
						UBYTE chn;
						UWORD note;
						
						MidKey = (pref->Octaves/2)*12;
						
						for (chn=0;chn<16;chn++)
						{
							SetAttrs(glob->ChanLedGad[chn], BOPA_ChanLed_Enabled, (ULONG)( (pref->Mask & (1<<chn)) ? TRUE:FALSE ), TAG_DONE);
							SetAttrs(glob->ChanLedGad[chn], BOPA_ChanLed_Lighted, (ULONG)( ( ChanUse[chn]  ) ? TRUE:FALSE ), TAG_DONE);
						}
						
						tagptr = TagBuffer;
						numtags = 0;
						
						for (note=0;note<128;note++)
						{
							UWORD key = note-60+MidKey;
							
							if (key < 12* (pref->Octaves))
							{
								if ((NoteArray[note] & pref->Mask))
								{
									tagptr->ti_Tag  = BOPA_Piano_KeyState;
									tagptr->ti_Data = (ULONG)(SETKEY|key);
									tagptr++;numtags++;
								}
								else
								{
									tagptr->ti_Tag  = BOPA_Piano_KeyState;
									tagptr->ti_Data = (ULONG)(CLEARKEY|key);
									tagptr++;numtags++;
								}
								
								if (numtags == TAGSIZE-1)
								{
									tagptr->ti_Tag = TAG_END;
									SetAttrsA(glob->PianoGad,TagBuffer);
									tagptr = TagBuffer; numtags = 0;
								}
							}
						}
						
						if (numtags)
						{
							tagptr->ti_Tag = TAG_END;
							SetAttrsA(glob->PianoGad,TagBuffer);
							tagptr = TagBuffer; numtags = 0;
						}
						
						AddGList(glob->Window,glob->FirstGad,-1,-1,NULL);
						RefreshGList(glob->FirstGad,glob->Window,NULL,-1);
					}
				}
				
				if (Cl==IDCMP_MENUPICK)
				{
					struct MenuItem *n;
					ULONG pick;
					
					while( (Co != MENUNULL) && Active)
					{
						n = ItemAddress( glob->Window->MenuStrip, (ULONG)Co );
						pick = (ULONG) GTMENUITEM_USERDATA( n );
						
						switch(pick)
						{
							case Menu_Link:
							{
								AsyncSelectMIDILink(glob, pref);
							}
							break;
							
							case Menu_Hooks:
							{
								if (n->Flags & CHECKED)
									pref->Flags |= PREFF_ASLHOOKS;
								else
								{
									pref->Flags &= (~PREFF_ASLHOOKS);
									
									/* Damn, there is no (legal) way */
									/* to clear the filter function */
									/* of an existing ASL requester */
									
									if (glob->MIDIFR)
									{
										FreeAslRequest(glob->MIDIFR);
										glob->MIDIFR = NULL;
									}
								}
							}
							break;
							
							case Menu_Save:
							{
								if ((!SavePrefs(glob, pref,"ENVARC:")) ||
									(!SavePrefs(glob, pref,"ENV:")))
									AsyncMessage(glob,SaveTask, "Failed to save configuration!", NULL);
							}
							break;
							
							case Menu_About:
							{
								AsyncMessage(glob,AboutTask,"Piano Meter\n© 1998 by Christian Buchner\nflowerp@eikon.e-technik.tu-muenchen.de","Cool");
							}
							break;
							
							case Menu_Quit:
							{
								if (AskAsync(glob))
									Active = FALSE;
								else
									AsyncMessage(glob,CloseTask, "Cannot quit yet. Please close all open requesters.","I will");
							}
							break;
							
							case Menu_Release:
							{
								UWORD note;
								UBYTE chn;
								
								tagptr = TagBuffer;
								numtags = 0;
								
								for (note=0;note<128;note++)
								{
									for (chn=0;chn<16;chn++)
									{
										UWORD key = note-60+MidKey;
										UWORD cmsk = 1<<chn;
										
										if (NoteArray[note] & cmsk)
										{
											ChanUse[chn]--;
											if (!ChanUse[chn]) SetGadgetAttrs(glob->ChanLedGad[chn], glob->Window, NULL, BOPA_ChanLed_Lighted, FALSE, TAG_DONE);
											
											NoteArray[note] &= (~cmsk);
											
											if ((key < 12* (pref->Octaves)) && (pref->Mask & cmsk) && (!(NoteArray[note] & pref->Mask)))
											{
												tagptr->ti_Tag  = BOPA_Piano_KeyState;
												tagptr->ti_Data = (ULONG)(CLEARKEY|key);
												tagptr++;numtags++;
												
												if (numtags == TAGSIZE-1)
												{
													tagptr->ti_Tag = TAG_END;
													SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
													tagptr = TagBuffer; numtags = 0;
												}
											}
										}
									}
								}
								
								if (numtags)
								{
									tagptr->ti_Tag = TAG_END;
									SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
									tagptr = TagBuffer; numtags = 0;
								}
							}
							break;
							
							case Menu_Play:
							{
								AsyncSelectAndPlay(glob, pref);
							};
							break;
							
							case Menu_Stop:
							{
								StopMIDI(glob,pref);
							}
							break;
							
							case Menu_Init1:
							case Menu_Init2:
							case Menu_Init3:
							case Menu_Init4:
							case Menu_Init5:
							{
								if (n->Flags & CHECKED)
								{
									UWORD init = pick - Menu_Init1;
									pref->Flags &= (~(PREFF_GM|PREFF_GS|PREFF_XG|PREFF_MT32));
									
									switch(init)
									{
										case 1: pref->Flags |= PREFF_GM;
											break;
										case 2: pref->Flags |= PREFF_GS;
											break;
										case 3: pref->Flags |= PREFF_XG;
											break;
										case 4: pref->Flags |= PREFF_MT32;
											break;
									}
								}
							}
							break;
							
						}
						
						Co = n->NextSelect;
					}
				}
			}
		}
		
		if (Active && (gotsignals & SIGBREAKF_CTRL_E))
		{
			MidiMsg msg;
			
			tagptr = TagBuffer;
			numtags = 0;
			
			while (GetMidi(glob->midi,&msg))
			{
				if (noteon(&msg))
				{
					UBYTE chn  = msg.mm_Status & MS_ChanBits;
					UWORD note = msg.mm_Data1;
					UWORD key  = note-60+MidKey;
					UWORD cmsk = 1<<chn;
					
					if (!(NoteArray[note] & cmsk))
					{
						if (!ChanUse[chn]) SetGadgetAttrs(glob->ChanLedGad[chn], glob->Window, NULL, BOPA_ChanLed_Lighted, TRUE, TAG_DONE);
						ChanUse[chn]++;
						
						if ((key < 12* (pref->Octaves)) && (pref->Mask & cmsk) && (!(NoteArray[note] & pref->Mask)))
						{
							tagptr->ti_Tag  = BOPA_Piano_KeyState;
							tagptr->ti_Data = (ULONG)(SETKEY|key);
							tagptr++;numtags++;
						}
						
						NoteArray[note] |= cmsk;
					}
				}
				else
				{
					if (noteoff(&msg))
					{
						UBYTE chn  = msg.mm_Status & MS_ChanBits;
						UWORD note = msg.mm_Data1;
						UWORD key  = note-60+MidKey;
						UWORD cmsk = 1<<chn;
						
						if (NoteArray[note] & cmsk)
						{
							ChanUse[chn]--;
							if (!ChanUse[chn]) SetGadgetAttrs(glob->ChanLedGad[chn], glob->Window, NULL, BOPA_ChanLed_Lighted, FALSE, TAG_DONE);
							
							NoteArray[note] &= (~cmsk);
							
							if ((key < 12* (pref->Octaves)) && (pref->Mask & cmsk) && (!(NoteArray[note] & pref->Mask)))
							{
								tagptr->ti_Tag  = BOPA_Piano_KeyState;
								tagptr->ti_Data = (ULONG)(CLEARKEY|key);
								tagptr++;numtags++;
							}
						}
						
					}
					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++)
								{
									UWORD key = note-60+MidKey;
									
									if (NoteArray[note] & cmsk)
									{
										ChanUse[chn]--;
										if (!ChanUse[chn]) SetGadgetAttrs(glob->ChanLedGad[chn], glob->Window, NULL, BOPA_ChanLed_Lighted, FALSE, TAG_DONE);
										
										NoteArray[note] &= (~cmsk);
										
										if ((key < 12* (pref->Octaves)) && (pref->Mask & cmsk) && (!(NoteArray[note] & pref->Mask)))
										{
											tagptr->ti_Tag  = BOPA_Piano_KeyState;
											tagptr->ti_Data = (ULONG)(CLEARKEY|key);
											tagptr++;numtags++;
											
											if (numtags == TAGSIZE-1)
											{
												tagptr->ti_Tag = TAG_END;
												SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
												tagptr = TagBuffer; numtags = 0;
											}
										}
									}
								}
							}
						}
					}
				}
				
				if (numtags == TAGSIZE-1)
				{
					tagptr->ti_Tag = TAG_END;
					SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
					tagptr = TagBuffer; numtags = 0;
				}
			}
			
			if (numtags)
			{
				tagptr->ti_Tag = TAG_END;
				SetGadgetAttrsA(glob->PianoGad,glob->Window,NULL,TagBuffer);
				tagptr = TagBuffer; numtags = 0;
			}
			
			if (Err = GetMidiErr(glob->midi))
			{
				if (Err & CMEF_MsgErr)			AsyncMessage(glob,ErrTask,"MIDI Error: MsgErr!",NULL);
				if (Err & CMEF_BufferFull)		AsyncMessage(glob,ErrTask,"MIDI Error: BufferFull!",NULL);
				if (Err & CMEF_SysExFull)		AsyncMessage(glob,ErrTask,"MIDI Error: SysExFull!",NULL);
				if (Err & CMEF_ParseMem)		AsyncMessage(glob,ErrTask,"MIDI Error: ParseMem!",NULL);
				if (Err & CMEF_RecvErr)			AsyncMessage(glob,ErrTask,"MIDI Error: RecvErr!",NULL);
				if (Err & CMEF_RecvOverflow)	AsyncMessage(glob,ErrTask,"MIDI Error: RecvOverflow!",NULL);
				if (Err & CMEF_SysExTooBig)		AsyncMessage(glob,ErrTask,"MIDI Error: SysExTooBig!",NULL);
			}
		}
		
		if (Active && (gotsignals & appsig))
		{
			struct AppMessage *appm;
			
			while(appm = (struct AppMessage*)GetMsg(glob->AppPort))
			{
				if (appm->am_Type == AMTYPE_APPWINDOW)
				{
					struct WBArg *arg = appm->am_ArgList;
					ULONG i;
					
					for (i=0 ; i < appm->am_NumArgs ; i++, arg++)
					{
						BOOL IsMIDI = FALSE;
						
						BPTR OldDir;
						BPTR lock;
						
						OldDir = CurrentDir(arg->wa_Lock);
						
						if (lock = Lock(arg->wa_Name, SHARED_LOCK))
						{
							BPTR dupedlock;
							BPTR file;
							
							if (dupedlock = DupLock(lock))
							{
								if (file = OpenFromLock(dupedlock))
								{
									UBYTE header[20];
									
									if (Read(file, header, sizeof(header)) == sizeof(header))
									{
										if ( ((header[0] == 'M') &&
											  (header[1] == 'T') &&
											  (header[2] == 'h') &&
											  (header[3] == 'd'))
											
											||
											
											 ((header[0] == 'X') &&
											  (header[1] == 'P') &&
											  (header[2] == 'K') &&
											  (header[3] == 'F') &&
											  (header[16]== 'M') &&
											  (header[17]== 'T') &&
											  (header[18]== 'h') &&
											  (header[19]== 'd')) )
										{
											if (NameFromLock(lock, pref->MIDIFile, sizeof(pref->MIDIFile)))
											{
												IsMIDI = TRUE;
											}
										}
									}
									Close(file);
								} else UnLock(dupedlock);
							}
							UnLock(lock);
						}
						CurrentDir(OldDir);
						
						if (IsMIDI) PlayMIDI(glob, pref);
						else
							AsyncMessage(glob,DropTask, "Please drop only MIDI files\ninto the Piano Meter window.","Sorry");
					}
				}
				ReplyMsg((struct Message*)appm);
			}
		}
		
		if (gotsignals & SIGBREAKF_CTRL_C)
		{
			if (AskAsync(glob))
			{
				Active = FALSE;
			}
			else
				AsyncMessage(glob,CloseTask, "Cannot quit yet. Please close all open requesters.","I will");
		}
	}
}



/*-----------------------------------*/
/* Asynchronously select a MIDI link */
/*-----------------------------------*/

void SyncSelectMIDILink(struct Globals *glob, struct Prefs *pref, APTR UserData)
{
	APTR listreq;
	
	if (!(listreq = AllocListRequest(
		LISTREQ_Screen, GetScreen(glob),
		LISTREQ_TitleText, "Select input link",
		TAG_DONE )))
	{
		Message("Couldn't allocate a list requester.",NULL);
	}
	else
	{
		if (SelectCluster( listreq, pref->Link, sizeof(pref->Link), TAG_DONE ))
		{
			if (strcmp((glob->link)->ml_Location->mcl_Node.ln_Name,pref->Link))
			{
				glob->LinkRefresh = TRUE;
				Signal(MyTask, SIGBREAKF_CTRL_E);	/* wake up main task */
			}
		}
		FreeListRequest(listreq);
	}
}

void AsyncSelectMIDILink(struct Globals *glob, struct Prefs *pref)
{
	if (!StartAsyncTask(glob, pref, "Piano Meter MIDI link selection task", LinkTask, &SyncSelectMIDILink, NULL, 0))
	{
		SyncSelectMIDILink(glob, pref, NULL);
	}
}



/*------------------------------------------*/
/* Asynchronously select & play a MIDI file */
/*------------------------------------------*/

void SelectAndPlay(struct Globals *glob, struct Prefs *pref, APTR UserData)
{
	if (SelectMIDI(glob, pref, &glob->MIDIFR, pref->MIDIFile,sizeof(pref->MIDIFile)))
	{
		PlayMIDI(glob,pref);
	}
}

void AsyncSelectAndPlay(struct Globals *glob, struct Prefs *pref)
{
	if (!StartAsyncTask(glob, pref, "Piano Meter MIDI file selection task", PlayTask, &SelectAndPlay, NULL, 0))
	{
		SelectAndPlay(glob, pref, NULL);
	}
}



/*--------------------*/
/* Get current screen */
/*--------------------*/

struct Screen *GetScreen(struct Globals *glob)
{
	struct Window *win;
	struct Screen *scr = NULL;
	
	if (MyTask)
	{
		win = ((struct Window*)((struct Process*)MyTask)->pr_WindowPtr);
		if (win) scr = win->WScreen;
	}
	
	if (!scr)
	{
		if (glob->LockedScreen)
			scr = glob->LockedScreen;
		else
			scr = glob->LockedScreen = LockPubScreen(NULL);
	}
	
	return(scr);
}



/*--------------------------------------*/
/* MIDI hook function for ASL requester */
/*--------------------------------------*/

ULONG __asm __saveds MIDIFilter (register __a0 struct Hook *h, register __a2 struct FileRequester *fr, register __a1 struct AnchorPath *ap)
{
	ULONG use = FALSE;
	
	UBYTE buffer[300];
	UBYTE header[20];
	BPTR file;
	
	if (ap->ap_Info.fib_DirEntryType >0)
		use = TRUE;
	else
	{
		strncpy (buffer, fr->fr_Drawer, sizeof (buffer));
		AddPart (buffer, ap->ap_Info.fib_FileName, sizeof (buffer));
		
		if (file = Open (buffer, MODE_OLDFILE))
		{
			if (Read(file,header,sizeof(header)) == sizeof(header))
			{
				if ( ((header[0] == 'M') &&
					  (header[1] == 'T') &&
					  (header[2] == 'h') &&
					  (header[3] == 'd'))
					
					||
					
					 ((header[0] == 'X') &&
					  (header[1] == 'P') &&
					  (header[2] == 'K') &&
					  (header[3] == 'F') &&
					  (header[16]== 'M') &&
					  (header[17]== 'T') &&
					  (header[18]== 'h') &&
					  (header[19]== 'd')) )
				{
					use = TRUE;
				}
			}
			Close (file);
		}
	}
	return(use);
}



/*----------------------------*/
/* Select a MIDI file to load */
/*----------------------------*/

BOOL SelectMIDI(struct Globals *glob, struct Prefs *pref, struct FileRequester **fr, UBYTE *filebuffer, ULONG MaxSize)
{
	BOOL Selected=FALSE;
	
	UBYTE initialdrawer[200];
	UBYTE initialfile  [40];
	
	struct Hook filter = {NULL, NULL, NULL, NULL, NULL};
	UBYTE save, *ptr;
	
	filter.h_Entry = (HOOKFUNC)MIDIFilter;
	
	ptr = PathPart(filebuffer);
	save = *ptr; *ptr = 0;
	strncpy (initialdrawer,filebuffer,sizeof(initialdrawer));
	initialdrawer[sizeof(initialdrawer)-1]=0;
	*ptr = save;
	strncpy (initialfile, FilePart(filebuffer), sizeof(initialfile));
	initialfile[sizeof(initialfile)-1]=0;
	
	if (!(*fr))
	{
		if (!((*fr) = AllocAslRequestTags (ASL_FileRequest,
					ASLFR_TitleText,	"Select MIDI file to play",
					ASLFR_PositiveText,	"Play",
					ASLFR_RejectIcons,	TRUE,
					ASLFR_DoPatterns, TRUE,
					TAG_DONE)))
		{
			Message("Couldn't allocate asl requester",NULL);
		}
	}
	
	if (*fr)
	{
		Selected = AslRequestTags(*fr,
					ASLFR_Screen, GetScreen(glob),
					ASLFR_InitialDrawer, initialdrawer,
					ASLFR_InitialFile, initialfile,
					(pref->Flags & PREFF_ASLHOOKS) ? ASLFR_FilterFunc : TAG_IGNORE,	&filter,
					TAG_DONE);
		
		if (Selected)
		{
			strncpy (filebuffer, (*fr)->fr_Drawer, MaxSize);
			AddPart (filebuffer, (*fr)->fr_File,   MaxSize);
		}
	}
	
	return(Selected);
}



/*-----------------------------------*/
/* Invoke PlayMF to play a MIDI file */
/*-----------------------------------*/

void PlayMIDI(struct Globals *glob, struct Prefs *pref)
{
	UBYTE CommandString[300];
	UBYTE *init = "";
	BPTR lock;
	BPTR out;
	BOOL CloseOut = FALSE;
	
	if (pref->Flags & PREFF_GM)   init = "GM";
	if (pref->Flags & PREFF_GS)   init = "GS";
	if (pref->Flags & PREFF_XG)   init = "XG";
	if (pref->Flags & PREFF_MT32) init = "MT32";
	
	if (lock = Lock("/PlayMF/PlayMF", SHARED_LOCK ))
	{
		UnLock(lock);
		
		SPrintf(CommandString,"Run >NIL: /PlayMF/PlayMF \42%s\42 LINK=\42%s\42 %s REPLACE", pref->MIDIFile, pref->Link, init);
	}
	else
	{
		SPrintf(CommandString,"Run >NIL: PlayMF \42%s\42 LINK=\42%s\42 %s REPLACE", pref->MIDIFile, pref->Link, init);
	}
	
	if ((out = Output()) == NULL)
	{
		if (out = Open("NIL:", MODE_OLDFILE)) CloseOut = TRUE;
	}
	
	if (!Execute( CommandString, NULL, out))
	{
		AsyncMessage(glob,LaunchTask, "Unable to launch PlayMF.\nPlease make sure it is located somewhere in your search path.","Damn");
	}
	
	if (CloseOut) Close(out);
}



/*--------------------------*/
/* Stop PlayMF (if running) */
/*--------------------------*/

void StopMIDI(struct Globals *glob, struct Prefs *pref)
{
	APTR lock;
	struct MidiCluster *clust;
	struct MidiLink *ml;
	
	if (lock = LockCAMD(CD_Linkages))
	{
		if (clust = FindCluster(pref->Link))
		{
			for (ml = (struct MidiLink*)clust->mcl_Senders.lh_Head ;
				 ml->ml_Node.ln_Succ ;
				 ml = (struct MidiLink*)ml->ml_Node.ln_Succ )
			{
				if (!stricmp("PlayMF Player", ml->ml_MidiNode->mi_Node.ln_Name))
				{
					Signal(ml->ml_MidiNode->mi_SigTask, SIGBREAKF_CTRL_C);
					break;
				}
			}
		}
		UnlockCAMD(lock);
	}
}
