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

/* Library bases */

struct ExecBase			*SysBase;
struct DosLibrary		*DOSBase;
struct GfxBase			*GfxBase;
struct IntuitionBase	*IntuitionBase;
struct Library 			*GadToolsBase;
struct Library			*UtilityBase;
struct Library			*CamdBase;

/* 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, ULONG *Octaves);
BOOL CreateOrLayoutGadgets(struct Screen *scr, struct Gadget **FirstGad, struct Gadget **PianoGad, struct Gadget **ChanLedGad, WORD ww, WORD wh, ULONG *Octaves);

LONG PianoMeter(UBYTE *linkname, ULONG Octaves, LONG WinX, LONG WinY);
void MainLoop(struct Window *Window, struct Gadget **FirstGad, struct Gadget **PianoGad, struct Gadget **ChanLedGad, struct MidiNode **midi, struct MidiLink **link, ULONG *Octaves);
BOOL OpenLibs(void);
void CloseLibs(void);
LONG DoCustomClassMethod (struct IClass *cl, Msg msg);
LONG __stdargs Message(UBYTE *Msg,UBYTE *Options,...);
void __stdargs _XCEXIT(LONG lcode);


/* 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 = PianoMeter("out.0", 5, 100, 200);
			
			CloseLibs();
		}
		Forbid();
		ReplyMsg((struct Message*)wbmsg);
	}
	return(ReturnCode);
}



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

LONG  ShellInterface(void)
{
	LONG ReturnCode = RETURN_ERROR;
	
	UBYTE *linkname = "out.0";
	
	ULONG Octaves = 5;
	LONG WinX = 100;
	LONG WinY = 200;
	
	if (OpenLibs())
	{
		/* CLI argument parsing */
		
		struct	ArgArray
		{
			UBYTE *aa_Link;
			ULONG *aa_Octaves;
			ULONG *aa_WinX;
			ULONG *aa_WinY;
		} AA = {NULL, NULL, NULL, NULL};
		
		static UBYTE	*Template = "LINK/K,OCT=OCTAVES/K/N,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_Octaves) Octaves = *AA.aa_Octaves;
			if (Octaves < 1) Octaves = 1;
			if (Octaves > 11) Octaves = 11;
			if (AA.aa_WinX) WinX= *AA.aa_WinX;
			if (AA.aa_WinY) WinY= *AA.aa_WinY;
			
			ReturnCode = PianoMeter(linkname, Octaves, WinX, WinY);
			
			FreeArgs(RDArgs);
		}
		else
		{
			PrintFault(IoErr(),"vu");
		}
		CloseLibs();
	}
	return(ReturnCode);
}



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

LONG PianoMeter(UBYTE *linkname, ULONG Octaves, 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 Gadget *FirstGad = NULL;
	
	struct Gadget *PianoGad = NULL;
	
	UWORD i;
	struct Gadget *ChanLedGad[16]={	NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
									NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL };
	
	WORD ww, wh;
	WORD minw, minh;
	WORD maxw, maxh;
	
	struct Task *MyTask;
	BYTE OldPri;
	
	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,  "Piano Meter [Input]",
			TAG_DONE)))
		{
			Message("Cannot create link to MIDI interface '%s'",NULL,linkname);
		}
		else
		{
			if( !_initclasses() )
			{
				Message("Unable to initialize the classes.",NULL);
			}
			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, &Octaves)))
						{
							Message("Couldn't layout window!",NULL);
						}
						else
						{
							if (!(Window = OpenWindowTags( NULL,
									WA_PubScreen, scr,
									WA_Title, "Piano Meter",
									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_GADGETDOWN|IDCMP_GADGETUP|IDCMP_SIZEVERIFY|IDCMP_NEWSIZE|IDCMP_MENUPICK,
									WA_SimpleRefresh, TRUE,
									WA_NoCareRefresh, 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 (!(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 (!(CreateOrLayoutGadgets(scr, &FirstGad, &PianoGad, ChanLedGad, ww, wh, &Octaves)))
									{
										Message("Couldn't create gadgets!",NULL);
									}
									else
									{
										AddGList(Window,FirstGad,-1,-1,NULL);
										RefreshGList(FirstGad,Window,NULL,-1);
										
										MyTask = FindTask(NULL);
										OldPri = MyTask->tc_Node.ln_Pri;
										MyTask->tc_Node.ln_Pri = 10;
										
										MainLoop(Window, &FirstGad, &PianoGad, ChanLedGad, &midi, &link, &Octaves);
										ReturnCode = RETURN_OK;
										
										MyTask->tc_Node.ln_Pri = OldPri;
										
										RemoveGList(Window,FirstGad,-1);
										
										DisposeObject(PianoGad);
										PianoGad=NULL;
										
										for (i=0; i<16; i++)
										{
											DisposeObject(ChanLedGad[i]);
											ChanLedGad[i]=NULL;
										}
									}
									ClearMenuStrip(Window);
									
									FreeMenus(Menu);
								}
								CloseWindow(Window);
							}
						}
						FreeVisualInfo(VisualInfo);
					}
					UnlockPubScreen(NULL, scr);
				}
				_freeclasses();
			}
			RemoveMidiLink(link);
		}
		DeleteMidi(midi);
	}
	return(ReturnCode);
}


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


BOOL WindowLayout(struct Screen *scr, WORD *ww, WORD *wh, WORD *minw, WORD *minh, WORD *maxw, WORD *maxh, ULONG *Octaves)
{
	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 = &scr->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 = &scr->RastPort;
	gpd.gpd_Which = GDOMAIN_NOMINAL;
	ti[0].ti_Tag  = BOPA_Piano_Octaves;
	ti[0].ti_Data = *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) *ww = XSPACING+16*clw+XSPACING;
		else *ww = XSPACING+pw+XSPACING;
	*wh = YSPACING+clh+INTERSPACING+ph+YSPACING;
	
	/* Calculate minimum width and height */
	*minw = XSPACING+16*clw+XSPACING;
	*minh = *wh;
	
	/* Calculate maximum width and height */
	*maxw = XSPACING+pmaxw+XSPACING;
	*maxh = *wh;
	
	Success=TRUE;
	
	return(Success);
}



BOOL CreateOrLayoutGadgets(struct Screen *scr, struct Gadget **FirstGad, struct Gadget **PianoGad, struct Gadget **ChanLedGad, WORD ww, WORD wh, ULONG *Octaves)
{
	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 (*PianoGad)
	{
		GetAttr(BOPA_Piano_Octaves, *PianoGad, &OldOctaves);
	}
	
	/* Get the domain of a ChanLed object */
	gpd.MethodID  = GM_DOMAIN;
	gpd.gpd_GInfo = NULL;
	gpd.gpd_RPort = &scr->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 */
	
	*Octaves = 11;
	do
	{
		/* Get the domain of the Piano object */
		gpd.MethodID  = GM_DOMAIN;
		gpd.gpd_GInfo = NULL;
		gpd.gpd_RPort = &scr->RastPort;
		gpd.gpd_Which = GDOMAIN_NOMINAL;
		ti[0].ti_Tag  = BOPA_Piano_Octaves;
		ti[0].ti_Data = *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 > ww) (*Octaves)--;
		
	} while (XSPACING+pw+XSPACING > ww);
	
	/* Dispose piano only when no. of octaves changed */
	
	if (*PianoGad)
	{
		if (*Octaves != OldOctaves)
		{
			DisposeObject(*PianoGad);
			*PianoGad=NULL;
		}
	}
	
	/* Create and/or layout the gadgets */
	
	gx = (ULONG)((ww-pw)/2);
	gy = (ULONG)((wh-clh-INTERSPACING-ph)/2)+clh+INTERSPACING;
	
	if (!(*PianoGad))
	{
		if (*FirstGad = *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, *Octaves,
				TAG_END) )
		{
			Success=TRUE;
		}
	}
	else
	{
		*FirstGad = prev = *PianoGad;
		
		SetAttrs(*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)((ww-16*clw)/2)+i*clw;
			gy = (ULONG)((wh-clh-YSPACING-ph)/2);
			
			if (!(ChanLedGad[i]))
			{
				if (!(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, 1L,
						BOPA_ChanLed_Lighted, 0L,
						GA_Previous, prev,
						TAG_END)))
				{
					Success=FALSE;
					break;
				}
			}
			else
			{
				SetAttrs(ChanLedGad[i], 
					GA_Left, gx,
					GA_Top, gy,
					GA_Width, (ULONG)clw,
					GA_Height, (ULONG)clh,
					GA_Previous, prev,
					TAG_END );
				
				prev = ChanLedGad[i];
			}
		}
	}
	
	if (!Success)
	{
		if (*PianoGad)
		{
			DisposeObject(*PianoGad);
			*PianoGad=NULL;
		}
		
		for (i=0; i<16; i++)
		{
			if (ChanLedGad[i])
			{
				DisposeObject(ChanLedGad[i]);
				ChanLedGad[i]=NULL;
			}
		}
		
		*FirstGad = NULL;
	}
	
	return(Success);
}



#define TAGSIZE 32

UWORD NoteArray[128];
struct TagItem TagBuffer[TAGSIZE];
UBYTE ChanUse[16];

void MainLoop(struct Window *Window, struct Gadget **FirstGad, struct Gadget **PianoGad, struct Gadget **ChanLedGad, struct MidiNode **midi, struct MidiLink **link, ULONG *Octaves)
{
	UWORD MidKey = (*Octaves/2)*12;
	UWORD Mask = 0xffff;
	
	BOOL Active;
	ULONG signals;
	ULONG gotsignals;
	struct IntuiMessage *imsg;
	ULONG Cl;
	UWORD Co;
	APTR IA;
	
	UBYTE Err;
	
	struct TagItem *tagptr;
	ULONG numtags;
	
	Active=TRUE;
	
	memset(NoteArray,0,sizeof(NoteArray));
	memset(ChanUse  ,0,sizeof(ChanUse  ));
	
	while(Active)
	{
		signals = (1L<<Window->UserPort->mp_SigBit) | SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E;
		
		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_GADGETDOWN)
				{
					UWORD ID = ((struct Gadget*)IA)->GadgetID;
					
					if (ID < 16)
					{
						ULONG flag;
						UWORD note;
						
						GetAttr(BOPA_ChanLed_Enabled, IA, &flag);
						
						if (flag)
							Mask |= (1<<ID);
						else
							Mask &= (~(1<<ID));
						
						tagptr = TagBuffer;
						numtags = 0;
						
						for (note=0;note<128;note++)
						{
							UWORD key = note-60+MidKey;
							
							if (key < 12* (*Octaves))
							{
								if ((NoteArray[note] & 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(*PianoGad,Window,NULL,TagBuffer);
									tagptr = TagBuffer; numtags = 0;
								}
							}
						}
						
						if (numtags)
						{
							tagptr->ti_Tag = TAG_END;
							SetGadgetAttrsA(*PianoGad,Window,NULL,TagBuffer);
							tagptr = TagBuffer; numtags = 0;
						}
					}
				}
				
				if (Cl==IDCMP_GADGETUP)
				{
				}
				
				if (Cl==IDCMP_SIZEVERIFY)
				{
				}
				
				if (Cl==IDCMP_NEWSIZE)
				{
					WORD ww = Window->Width  - Window->BorderLeft-Window->BorderRight ;
					WORD wh = Window->Height - Window->BorderTop -Window->BorderBottom;
					
					RemoveGList(Window,*FirstGad,-1);
					
					EraseRect(Window->RPort, 0, 0, ww-1, wh-1);
					
					if (!(CreateOrLayoutGadgets(Window->WScreen, FirstGad, PianoGad, ChanLedGad, ww, wh, Octaves)))
					{
						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 = (*Octaves/2)*12;
						
						for (chn=0;chn<16;chn++)
						{
							SetAttrs(ChanLedGad[chn], BOPA_ChanLed_Enabled, (ULONG)( (Mask & (1<<chn)) ? TRUE:FALSE ), TAG_DONE);
							SetAttrs(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* (*Octaves))
							{
								if ((NoteArray[note] & 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(*PianoGad,TagBuffer);
									tagptr = TagBuffer; numtags = 0;
								}
							}
						}
						
						if (numtags)
						{
							tagptr->ti_Tag = TAG_END;
							SetAttrsA(*PianoGad,TagBuffer);
							tagptr = TagBuffer; numtags = 0;
						}
						
						AddGList(Window,*FirstGad,-1,-1,NULL);
						RefreshGList(*FirstGad,Window,NULL,-1);
					}
				}
				
				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,  "Piano Meter [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("Piano Monitor\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;
			
			tagptr = TagBuffer;
			numtags = 0;
			
			while (GetMidi(*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 ((key < 12* (*Octaves)) && (Mask & cmsk) && !((NoteArray[note] & Mask)))
					{
						tagptr->ti_Tag  = BOPA_Piano_KeyState;
						tagptr->ti_Data = (ULONG)(SETKEY|key);
						tagptr++;numtags++;
					}
					
					if (!(NoteArray[note] & cmsk))
					{
						if (!ChanUse[chn]) SetGadgetAttrs(ChanLedGad[chn], Window, NULL, BOPA_ChanLed_Lighted, TRUE, TAG_DONE);
						ChanUse[chn]++;
						
						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(ChanLedGad[chn], Window, NULL, BOPA_ChanLed_Lighted, FALSE, TAG_DONE);
							
							NoteArray[note] &= (~cmsk);
						}
						
						if ((key < 12* (*Octaves)) && (Mask & cmsk) && (!(NoteArray[note] & 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(ChanLedGad[chn], Window, NULL, BOPA_ChanLed_Lighted, FALSE, TAG_DONE);
										
										NoteArray[note] &= (~cmsk);
									}
									
									if ((key < 12* (*Octaves)) && (Mask & cmsk) && (!(NoteArray[note] & 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(*PianoGad,Window,NULL,TagBuffer);
											tagptr = TagBuffer; numtags = 0;
										}
									}
								}
							}
						}
					}
				}
				
				if (numtags == TAGSIZE-1)
				{
					tagptr->ti_Tag = TAG_END;
					SetGadgetAttrsA(*PianoGad,Window,NULL,TagBuffer);
					tagptr = TagBuffer; numtags = 0;
				}
			}
			
			if (numtags)
			{
				tagptr->ti_Tag = TAG_END;
				SetGadgetAttrsA(*PianoGad,Window,NULL,TagBuffer);
				tagptr = TagBuffer; numtags = 0;
			}
			
			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 (gotsignals & SIGBREAKF_CTRL_C)
		{
			Active=FALSE;
		}
	}
}



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 (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
						{
							Success=TRUE;
						}
					}
				}
			}
		}
	}
	if (!Success) CloseLibs();
	
	return(Success);
}


void CloseLibs(void)
{
	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 (GfxBase)
	{
		CloseLibrary((struct Library*)GfxBase);
		GfxBase=NULL;
	}
	
	if (DOSBase)
	{
		CloseLibrary((struct Library*)DOSBase);
		DOSBase=NULL;
	}
}


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

LONG DoCustomClassMethod (struct IClass *cl, Msg msg)
{
    LONG (__asm *disp)(register __a0 Class *, register __a2 Object *, register __a1 Msg msg);
    disp = cl->cl_Dispatcher.h_Entry;
    return (*disp)(cl, (Object *) cl, msg);
}


/* 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,"Piano Meter",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 );
}
