/***************************************************************
 * Squeak/Siren Interface to the Opcode MIDI System (OMS)
 * 		Stephen T. Pope -- stp@create.ucsb.edu
 * 
 * Version History: see sqMIDI.h
 *
 * N.B.: This driver requires OMS version 2.5.3 or greater. 
 * See http://www.opcode.com to get the latest version of OMS for free.
 *
 * Because of the (Mac/DOS) portability of OMS, this code should be 
 * (more or less) portable to the DOS PC as well (please let me know).
 */

/***************************************************************
 * Includes & Macros
 */
//#include "sq.h"					// General Squeak definitions, includes sqMIDI.h

// MIDI Includes -- STP -- these used to be in sq.h
#include "OMS.h"				// OMS definitions and structs
#include <MIDI.h>				// Apple MIDI Libraries
#include "sqMIDI.h"				// Squeak MIDI Structs and Prims

#include "sqVirtualMachine.h"	//  The virtual machine proxy definition

#include "Strings.h"

#define sqSignature		'SQEK'		// Creator ID.
#define InputPortID		'in  '		// I/O port IDs.
#define OutputPortID 	'out '

//#define DEBUG_MODE  			// Enable/Disable printfs (see macros below)
								// Debugging macros
#ifdef DEBUG_MODE
#define dprint1(str)			printf(str)
#define dprint2(str, val)		printf(str, val)
#define dprint3(str, v1, v2)	printf(str, v1, v2)
Boolean debug_mode = true;
#else
#define dprint1(str)
#define dprint2(str, val)
#define dprint3(str, v1, v2)
Boolean debug_mode = false;
#endif DEBUG_MODE

extern struct VirtualMachine *interpreterProxy;

									// A few macros for error checking
#define S_FAIL			{ interpreterProxy->success(false); return(-1); }
#define S_ERR			{ interpreterProxy->success(false); return((int) err); }
#define S_CHECK			if(err != omsNoErr) { return(0 - (int) err); }
#define S_CHECK_FAIL	if(err != omsNoErr) { interpreterProxy->success(false); \
											return(0 - (int) err); }
#define S_CHECK_SIGNIN	if(!gSignedInToMIDI) { return(-1024); }

									// memory access macros from interp.c
#define longAtput(i, val) (*((int *) (i)) = val)
#define instVarAtPutInt(obj, slot, val) \
		longAtput(((char *)obj + (slot << 2)), (((int)val << 1) | 1))

/***************************************************************
 * Global state variabless
 */
Boolean	gSignedInToMIDI;			// are we signed into MIDI driver?
Boolean	gEchoEnabled;				// toggle to echo event input
Boolean	gInputEnabled;				// toggle to enable event input
Boolean	gCacheControllers;			// toggle to cache controller values

/***************************************************************
 * Controller value caches -- This is the minimum complement
 * A "larger" driver would cache 128*16 controllers and 128*16 key pressures
 */
unsigned char sqControllers[128];	// Controller value table
unsigned char sqKeyPressures[128];	// Polyphonic key pressure table
unsigned char sqChanPressures[16];	// Channel pressure value table
int sqPitchBend[16];				// The value of the pitch wheel

/***************************************************************
 * OMS Driver Internals
 */
short	gInPortRef;					// refNum of the OMS input port
short	gOutPortRef;				// refNum of the OMS output port
short	sqCallback;					// semaphore for read callback
short	gCompatMode;				// OMS compatibility mode

#define NumPorts 8					// Max number of I/O ports
#define MaxNameLen 64				// Max length of device names
unsigned short gOutNodeRefs[NumPorts] = {0, 0, 0, 0, 0, 0, 0, 0};
sqMIDIioPort gNodeNames[NumPorts];	// The IO port table

#define MaxInInQ 64					// The size of the input Q
sqMIDIEvent	sqMIDIInQ[MaxInInQ];	// the input event Q (an array)
int		itemsInInQ;					// Index to write in the input Q
int 	itemsInInQ2;				// Pointer to input to read (<= iIIQ)

OMSMIDIPacket thePacket;			// Small (4-byte) MIDI packet
OMSMIDIPacket255 theBigPacket;		// Large packet for SysEx messages

/***************************************************************
 * Function Prototypes for I/O call-back utilities
 */

#define USESROUTINEDESCRIPTORS
 
pascal void sqMIDIAppHook(OMSAppHookMsg *pkt, long myRefCon);
pascal void	sqMIDIReadHook(OMSPacket *pkt, long myRefCon);


/***************************************************************
 ***************************************************************
 * sqOpenMIDI -- Sign on to OMS, register callbacks, create ports
 *				and connections, etc.
 */
int sqOpenMIDI(int callbackSemaphore, int inputMRSocket) {
	OSErr err;
	OMSAppHookUPP appHook;			// pointer to call-back function
	OMSReadHook2UPP readHook;		// pointer to call-back function
	OMSAPI (long) versionID;		// OMS driver version
//	OMSAPI(OMSIDListH) sqNodes;		// list of I/O node IDs
	OMSConnectionParams sqConn;		// connection parameter structure    JH - uncommented this line 
	OMSNodeInfoListH nodeInfoList;
	int i, j;  // JH added tmp var j
	long LMGetCurrentA5(void);
	int interfaces;
	char *nname;
	Boolean found = false;

	interpreterProxy->success(true);
					//// It's not an error if we're already signed in.
	if (gSignedInToMIDI)
		return (0);
	
	dprint3("\n\tMIDI open -- sem = %d sock = %d\n", callbackSemaphore, inputMRSocket);
	
	sqCallback = 0;
					////	Make sure that OMS V2 is installed.
	versionID = OMSVersion();
	if (versionID == 0) {
		interpreterProxy->success(false);
		return (-128);
	}
	if ((versionID & 0xff000000) != 0x02000000) {
		interpreterProxy->success(false);
		return (-129);
	}
					//// Set input event pointers
	itemsInInQ = 0;
	itemsInInQ2 = 0;

					//// Clear controller value tables
	for (i=0; i<128; i++) {
		sqControllers[i] = 64;
		sqKeyPressures[i] = 0;
	}
	for (i=0; i<16; i++) {
		sqChanPressures[i] = 0;
		sqPitchBend[i] = 0;
	}
					//// Set up call-back routine pointers.
	appHook = NewOMSAppHook(sqMIDIAppHook);
	readHook = NewOMSReadHook2(sqMIDIReadHook);
	
					//// Sign in to OMS.
	err = OMSSignIn(sqSignature, (long)LMGetCurrentA5(), 
					(unsigned char *) "Squeak/Siren", 
					appHook, &gCompatMode);
	S_CHECK_FAIL
	gSignedInToMIDI = true;
	 
					//// Add an input port.
	err = OMSAddPort(sqSignature, InputPortID, omsPortTypeInput, 
				readHook, (long)LMGetCurrentA5(), &gInPortRef);
	if (err)	goto errexit;
	
					//// Add an output port.
	err = OMSAddPort(sqSignature, OutputPortID, omsPortTypeOutput, 
				NULL, 0L, &gOutPortRef);	// no read hook for output
	if (err) goto errexit;

					//// Set up OMS to play to all output ports.
	nodeInfoList = OMSGetNodeInfo(omsIncludeOutputs + omsIncludeReal + omsIncludeVirtual);
	if (nodeInfoList != NULL) {
		interfaces = (*nodeInfoList)->numNodes;
		for (i=0;  i<(*nodeInfoList)->numNodes;  i++) {
			nname = (char *)(*nodeInfoList)->info[i].name;
			dprint2("\tOpen interface %s\n", nname);
			strcpy(gNodeNames[i].name, nname);
			gNodeNames[i].direction = 2;
			gOutNodeRefs[i] = (*nodeInfoList)->info[i].ioRefNum;
		}
		OMSDisposeHandle(nodeInfoList);
	} else
		goto errexit;
		
	
		
// JH - uncommented following block of code
					//// Read from all input ports.
	sqConn.appRefCon = 0;
	nodeInfoList = OMSGetNodeInfo(omsIncludeInputs + omsIncludeReal + omsIncludeVirtual); 
	if (nodeInfoList != NULL) {
		for (i=0;  i<(*nodeInfoList)->numNodes;  i++) {
			nname = (char *)(*nodeInfoList)->info[i].name;
			
			// JH - Actually only read from controllers
			if ((*nodeInfoList)->info[i].flags & kAnyIsController) {
			
				sqConn.nodeUniqueID = (*nodeInfoList)->info[i].uniqueID;
				err = OMSOpenConnections(sqSignature, InputPortID, 1, &sqConn, false);
				if (err) goto errexit;
				for (j=0; j < interfaces; j++)
					if (strcmp(nname, gNodeNames[j].name) == 0) {
						gNodeNames[j].direction += 1;
						found = true;
					}
				if (!found) {
					strcpy(gNodeNames[interfaces].name, nname);
					gNodeNames[interfaces].direction = 1;
					interfaces ++;
				}
				dprint2("\tConnect input %s\n", nname);
			}
		}
		OMSDisposeHandle(nodeInfoList);
	}

					//// Success
	gInputEnabled = false;				// off by default
	gCacheControllers = false;			// off by default
	gEchoEnabled = false;				// off by default
	sqCallback = callbackSemaphore;
	interpreterProxy->success(true);
	return(interfaces);					// Answer the number of interfaces found.

errexit:
	OMSSignOut(sqSignature);
	gSignedInToMIDI = false;
	interpreterProxy->success(false);
	return ((int) err);
}


/***************************************************************
 * sqCloseMIDI -- Close up shop and sign out from OMS
 */
int sqCloseMIDI() {

	interpreterProxy->success(true);
	S_CHECK_SIGNIN
	sqCallback = 0;
						// Perhaps I should do allNotesOff here...
	OMSSignOut(sqSignature);
	gSignedInToMIDI = false;
	gInputEnabled = false;	
	gCacheControllers = false;	
	gEchoEnabled = false;
	return(0);
}

/***************************************************************
 * sqMIDIPlaySize -- Send a MIDI message out the output ports
 */
int sqMIDIPlaySizeOn (int message, int length, int interface) {
	int i;
	unsigned char *ptr = (unsigned char *) message;
	
										// It's an error if we're not signed in
	S_CHECK_SIGNIN
										// Play it now (i.e., no output buffer)
	if (length < 4) {					// Handle a "normal" packet of 3 bytes
		thePacket.flags = 0;
		thePacket.len = length;
		for (i=0; i<length; i++)		// copy output data into packet
			thePacket.data[i] = *(ptr + i);
		if (interface == 0)	{			// Interface 0 means play out all outputs
			for (i=0; i<NumPorts; i++)
				if (gOutNodeRefs[i] != 0) 
					OMSWritePacket2(&thePacket, gOutNodeRefs[i], gOutPortRef);
		} // JH - added curly braces - CW seemed to disambiguate the if-if-else other than as intended(?) 
		else
			OMSWritePacket2(&thePacket, gOutNodeRefs[interface - 1], gOutPortRef);
			
	} else {							// Handle a big packet
		theBigPacket.flags = 0;
		theBigPacket.len = length;
		for (i=0; i<length; i++)  		// copy output data into packet
			theBigPacket.data[i] = *(ptr + i);
		if (interface == 0)	{			// Interface 0 means play out all outputs
			for (i=0; i<NumPorts; i++)
				if (gOutNodeRefs[i] != 0)
				 	OMSWritePacket2((struct OMSMIDIPacket *)&theBigPacket, gOutNodeRefs[i], gOutPortRef);
				//	OMSWritePacket255(&theBigPacket, gOutNodeRefs[i], gOutPortRef);
		} // JH - added curly braces - CW seemed to disambiguate the if-if-else other than as intended(?) 
		else
			OMSWritePacket2((struct OMSMIDIPacket *)&theBigPacket, gOutNodeRefs[interface - 1], gOutPortRef);
		//	OMSWritePacket255(&theBigPacket, gOutNodeRefs[interface - 1], gOutPortRef);
	}
	interpreterProxy->success(true);
	return(length);
}

unsigned char lastCommand;
int lastLength;

/***************************************************************
 * sqMIDIReadHook -- Interrupt routine called on in-coming MIDI messages
 */
pascal void sqMIDIReadHook(OMSPacket *pkt, long myRefCon) {
	sqMIDIEvent *inPtr;
	unsigned char *idata, *odata;
	int len, i, cmd, chan, value;
	long olda5 = SetA5(myRefCon);
	
	len = pkt->len;
	if (len == 0)						// return if empty packet
		goto errexit;
	if (itemsInInQ == MaxInInQ)			// return if input Q full
		goto errexit;	
										//	echo input to the output
	if (gEchoEnabled)
		for (i=0; i<NumPorts; i++)
			if (gOutNodeRefs[i] != 0)
				OMSWritePacket(pkt, gOutNodeRefs[i], gOutPortRef);
	cmd = (pkt->data[0]) & 0xF0;		// Get "command" from 1st byte of packet

if (gCacheControllers) {				// Cache controller values in the driver
		if (cmd == ControlCmd) {			// Read a control command
			chan = pkt->data[1];
			value = pkt->data[2];
			sqControllers[chan] = value;
			goto errexit;
		} else if (cmd == PchWheelCmd) {	// Read a pitch wheel change
			chan = (pkt->data[0]) & 0x0F;
			i = pkt->data[1];
			value = pkt->data[2];
			sqPitchBend[chan] = (value << 7) + i;
			goto errexit;
		} else if (cmd == PolyPressCmd) {	// Read polyphonic key pressure
			chan = pkt->data[1];
			value = pkt->data[2];
			sqKeyPressures[chan] = value;
			goto errexit;
		} else if (cmd == ChanPressCmd) {	// Read channel key pressure
			chan = (pkt->data[0]) & 0x0F;
			value = pkt->data[1];
			sqKeyPressures[chan] = value;
			goto errexit;
		}
	}
	if (!gInputEnabled)
		goto errexit;
												// Get the input Q record to use
	inPtr = &sqMIDIInQ[itemsInInQ++];
										// Catch OMS packet length bug
	if ((cmd == PgmChngCmd) || (cmd == ChanPressCmd))
		len = 2;
	else if (cmd != SysExCmd)
		len = 3;
	inPtr->len = len;
										// Copy the packet to the input Q
	inPtr->flags = (int)pkt->flags;
	inPtr->timeStamp = clock();   // = (int)pkt->beatTimeStamp; for OMS 2 input
	idata = &(inPtr->data[0]);
	odata = &(pkt->data[0]);
	if (len <= PKT_LEN)					// Copy the data bytes
		for (i = 0; i < len; i++)	
			*idata++ = *odata++;

	if (sqCallback != 0)	{
				// Signal the read semaphore
		i = interpreterProxy->signalSemaphoreWithIndex(sqCallback);}
	
errexit:	
	SetA5(olda5);
}

/***************************************************************
 * sqReadMIDIPacket -- Sent in response to the input semaphore
 * This is to up-load MIDI messages into the arguments (a MIDIPacket,
 * its max length, and its data ByteArray).
 */
int sqReadMIDIPacket(int ipacket, int maxSize, int idata) {
	sqMIDIEvent *outPtr;
	unsigned char *cdata;
	int len, i;
	unsigned char *pdata = (unsigned char *)idata;
								// The packet object is defined as:
								// Object subclass: #MIDIPacket
								//	 instanceVariableNames: 'length time flags data '

	interpreterProxy->success(true);
	if (itemsInInQ == 0)
		return (0);
	if (sqCallback == 0)
		return (-1);
										// Set up pointers
	outPtr = &sqMIDIInQ[itemsInInQ2++];
	
	dprint2("Reading %d bytes -- ", outPtr->len);
	if(debug_mode) printf("%x %x %x\n", outPtr->data[0], outPtr->data[1], outPtr->data[2]);
	
	len = outPtr->len;					// copy the response fields
	if (len > maxSize)					// packet longer than normal--answer length
		return (len);
										// copy the driver data into the packet object
	instVarAtPutInt(ipacket, 1, len);	// length, time, flags
	instVarAtPutInt(ipacket, 2, (int)(outPtr->timeStamp));
	instVarAtPutInt(ipacket, 3, (int)(outPtr->flags));

	cdata = &(outPtr->data[0]);			// copy MIDI message bytes into the packet
	for (i=0; i<len; i++) 	
		*pdata++ = *cdata++;
		
	if (itemsInInQ2 == itemsInInQ)		// clear counters if Q is empty
		itemsInInQ2 = itemsInInQ = 0;

	return (len);						// Answer len
}


/***************************************************************
 * sqReadMIDIControl -- Answer one or more controller values from the cache
 */
int sqReadMIDIControl(int fromC, int toC, int intoArray) {
	int i;
	unsigned char *pdata = (unsigned char *)intoArray;
	
										// check controller range
	if ((fromC < 0) || (toC > 287) || (fromC > toC)) {
		interpreterProxy->success(false);
		return (-1);
	}
	pdata += 4;							// Skip object header
	for (i = fromC; i <= toC; i++) {	// Loop to read controllers
		if (i < 128)							// 0-127 = controllers
			*pdata++ = ((int)sqControllers[i]);
		else if (i < 256)						// 128-255 = key pressures
			*pdata++ = ((int)sqKeyPressures[i - 128]);
		else if (i < 272)						// 256-271 = channel pressures
			*pdata++ = ((int)sqChanPressures[i - 256]);
		else 									// 272-287 = pitch bend (16-bit!)
												// Write single int into "Array"
			longAtput(intoArray, (sqPitchBend[i - 272]));
	}
	interpreterProxy->success(true);
	return(toC - fromC + 1);
}


/***************************************************************
 * getDeviceName -- write the name of the selected device into the 2nd arg
 * Answer the ports directionality.
 */
int sqGetMIDIDeviceName(int index, int name) {

	if ((index < 0) || (index > NumPorts))
		return(-1);
	strcpy((char *)name, gNodeNames[index].name);
	return(gNodeNames[index].direction);
}


/***************************************************************
 * sqMIDIioctl -- General driver queries and status
 */
int sqMIDIioctl(int which, int onOff) {
	int answer = 0;
	
	interpreterProxy->success(true);
	switch (which) {
	case (sqMIDIInstalled):			// Is a MIDI driver installed?
		answer = (int)OMSVersion();	// Can I communicate with the "real" driver?
		if (answer == 0)
			answer = false;
		answer = true;
		break;
	case (sqMIDIVersion):			// What driver version is this?
		answer = (int)OMSVersion();
		break;
	case (sqMIDIHasBuffer):			// Is there a time-stamped output buffer?
		answer = false;				// Answer false in the "default" driver
		break;
	case (sqMIDIHasDurs):			// Is there a 1-call note command?
		answer = false;				// Answer false in the "default" driver
		break;
	case (sqMIDIHasClock):			// Does the driver have its own clock?
		answer = false;				// Answer false in the "default" driver
		break;
	case (sqMIDIUseSemaphore):		// Should the driver signal a semaphore on input?
		switch (onOff) {
		case sqMIDIQuery:			// Answer value of flag
			answer = gInputEnabled;
			break;
		case sqMIDITurnOn:
			gInputEnabled = true;	// Set flag
			break;
		case sqMIDITurnOff:
			gInputEnabled = false;	// Clear flag
			break;
		}
		break;
	case (sqMIDIEcho):				// Should we echo in-coming events in the driver?
		switch (onOff) {
		case sqMIDIQuery:
			answer = gEchoEnabled;	// Answer value of flag
			break;
		case sqMIDITurnOn:
			gEchoEnabled = true;	// Set flag
			break;
		case sqMIDITurnOff:
			gEchoEnabled = false;	// Clear flag
			break;
		}
		break;
	case (sqMIDIControllerCache):		// Should we cache controller values for polling?
		switch (onOff) {
		case sqMIDIQuery:
			answer = gCacheControllers;	// Answer value of flag
			break;
		case sqMIDITurnOn:
			gCacheControllers = true;	// Set flag
			break;
		case sqMIDITurnOff:
			gCacheControllers = false;	// Clear flag
			break;
		}	
		break;
	case (sqMIDIEventsAvailable):		// How many events are in the input Q?
		answer = itemsInInQ - itemsInInQ2;
		break;
	case (sqMIDIFlushDriver):			// Special flag to flush the driver's I/O buffers,
		itemsInInQ = itemsInInQ2 = 0;
		OMSAllNotesOff();				// and send all-notes-off
		break;		
	default:							// Default = FAIL
		interpreterProxy->success(false);
	}
	return(answer);
}


/***************************************************************
 * sqMIDIAppHook -- Sent on OMS configuration changes; NO-OPs at present
 */
pascal void sqMIDIAppHook(OMSAppHookMsg *pkt, long myRefCon) {

	long olda5 = SetA5(myRefCon);
	switch (pkt->msgType) {
					// What to do when compatibility mode changes?
		case omsMsgModeChanged:
			// What to do?
			break;
			
					// What to do if a receiver is deleted?
		case omsMsgDestDeleted:
			// What to do?
			break;
			
					// What to do if the studio layout changes?
		case omsMsgNodesChanged:
			// What to do?
			break;
	}
	SetA5(olda5);
}


/***************************************************************
 *    E  N  D
 */
