/*
 * portmidi_lite.c -- minimal interface to the portmidi library for Siren
 *
 * Made for use with Visualworks Smalltalk DLLCC
 * Stephen Pope -- stp@create.ucsb.edu -- 2003.07.16
 *
 * The design is based on Siren's earlier MIDI APIs for use with Smalltalk
 * external primitive calls. The basic functions are initialize/terminate, 
 * open/close, and read/poll/write, with special calls for MIDI device and stream management.
 * The driver maintains a list of open streams onto portMIDI interfaces.
 * In-coming controller values can be cached in the driver and bulk-up-loaded to Smalltalk.
 */

#include "userprim.h"
#include "oeAPI.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "assert.h"
#include "porttime.h"
#include "portmidi.h"
#include "portmidi_lite.h"

#define INPUT_BUFFER_SIZE 100
#define OUTPUT_BUFFER_SIZE 0
#define DRIVER_INFO NULL
#define TIME_PROC Pt_Time
#define TIME_INFO NULL
#define TIME_START Pt_Start(1, 0, 0) // timer w/millisecond accuracy

#define PM_OUTPUT 0
#define PM_INPUT 1

// Globals

#define PM_MAX_STREAMS 8			// Allowed # of open streams
#define PM_BUF_LEN 32				// number of events to buffer
									// the PortMIDI streams we play out or get data from
PmStream * streams[PM_MAX_STREAMS] = { 0, 0, 0, 0, 0, 0, 0, 0 };
PmEvent events[PM_BUF_LEN];			// the event buffer

int pm_is_inited = 0,				// global driver state flags
	pm_is_cacheing = 0;

// Controller value caches -- This is the minimum complement.
// A "larger" driver would cache 128*16 controllers and 128*16 key pressures.

unsigned char pm_controllers[PM_MAX_STREAMS][128];		// Controller value table
unsigned char pm_key_pressures[PM_MAX_STREAMS][128];	// Polyphonic key pressure table
unsigned char pm_chan_pressures[PM_MAX_STREAMS][16];	// Channel pressure value table
int pm_pitch_bend[PM_MAX_STREAMS][16];					// The value of the pitch wheel

// Functions

// Find the first NULL pointer in the array of MIDI stream info structures

int pm_free_index() {
	int i;
	for (i = 0; i < PM_MAX_STREAMS; i++) {
		if (streams[i] == 0) {
			streams[i] = (PmStream *) 1;	// for thread-safety
			return i;
		}
	}
	return -1;		// too many open streams
}

// PM error handler

void pm_handle_error(PmError err) {
	Pm_Terminate();
//	printf("An error occured while using PortMIDI\n");
	printf("\tPortMIDI Error number %d: %s\n", err, Pm_GetErrorText(err));	
}


// initialize/terminate calls

int pm_initialize(void) {
	PmError err;
	int i;
	if (pm_is_inited)
		return 0;
//	printf("PM_init\n");
	err = Pm_Initialize();		// call portMIDI
	if (err != pmNoError) {
		printf("PM_init--");
		pm_handle_error(err);
		oeFail(err);
	}
	for (i = 0; i < PM_MAX_STREAMS; i++)
		streams[i] = NULL;
	pm_is_inited = 1;
	return 0;
}

int pm_terminate(void) {
//	printf("PM_terminate\n");
	PmError err;
	err = Pm_Terminate();		// call portMIDI
	if (err != pmNoError) {
		pm_handle_error(err);
		oeFail(err);
	}
	pm_is_inited = 0;
	return 0;
}

// pm_open: open the I/O stream; answer the index

int pm_open(int device, unsigned direction) {
	PmError err;
//	printf("PM_open --");
	if ( ! pm_is_inited)
		pm_initialize();
	int index = pm_free_index();	// find the first free index
	if (index < 0) {
		printf("PM_open -- can't find free index!\n");
		return -1;		// no free streams
	}
	if (direction == PM_INPUT) {
	//	printf("\tPM_open input\n");
		err = Pm_OpenInput(&streams[index], 
			device, 
			DRIVER_INFO,
			OUTPUT_BUFFER_SIZE, 
			TIME_PROC,
			TIME_INFO, 
			NULL);		// no MIDI thru
		if (err != pmNoError) {
			printf("PM_openIn--");
			pm_handle_error(err);
			oeFail(err);
		}
		Pm_SetFilter(streams[index], PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
						// flush the input queue
		while (Pm_Poll(streams[index]))
			Pm_Read(streams[index], events, 1);
	}
	else if (direction == PM_OUTPUT) {
	//	printf("\tPM_open output %d\n", index);
		err = Pm_OpenOutput(&streams[index], 
			device, 
			DRIVER_INFO,
			0, 			// no output buffering
			TIME_PROC,
			TIME_INFO, 
			0);			// no latency
		if (err != pmNoError) {
			printf("PM_openOut--(%d)--", index);
			pm_handle_error(err);
			oeFail(err);
		}
	}
//	printf("PM_open returns %d\n", index);
	return index;
}

// pm_close -- close

int pm_close(unsigned which) {
//	printf("PM_close\n");
	PmError err;
	if (streams[which] != NULL) {
		err = Pm_Close(streams[which]);
		if (err != pmNoError) {
			pm_handle_error(err);
			oeFail(err);
		}
	}
	streams[which] = NULL;
	return 0;
}

// Handling devices

int pm_count_devices(void) {
	int val = Pm_CountDevices();
//	printf("\tpm_count_devices answers %d\n", val);
	return val;
}

int pm_default_input_device(void) {
	return Pm_GetDefaultInputDeviceID();
}

int pm_default_output_device(void) {
	return Pm_GetDefaultOutputDeviceID();
}

int pm_get_name(unsigned which, OEoop resultString) {
	OEint strLen;
//	char string[128];
	const PmDeviceInfo * info = Pm_GetDeviceInfo(which);
//	sprintf(string, "%s: %s %s ", info->name, 
//		(info->input ? "has input " : ""),
//		(info->output ? "has output " : ""));
//	oeCopyCtoOEstring(resultString, string, strlen(string), 1, &strLen);
	oeCopyCtoOEstring(resultString, ((char *) info->name), strlen(info->name), 1, &strLen);
	printf("\tpm_get_name answers %s (%d = %d)\n", info->name, strlen(info->name), strLen);
	return strLen;
}

int pm_has_error(unsigned which) {
	if (streams[which] == NULL) {
		printf:("PM: error getting host error on non-existent dfevice %d", which);
		return(-100);
	}
	return (Pm_HasHostError(streams[which]));
}

//
// I/O
//

// pm_poll -- answer 1 if there's input

int pm_poll(unsigned which) {
	if (streams[which] == NULL)
		return FALSE;
	return Pm_Poll(streams[which]);			// portMIDI polling call
}

// Non-blocking threaded read (might return nil)

long pm_get(unsigned which) {
//	printf("PM_read\n");
	PmError len;
	if (streams[which] == NULL)
		oeFail(-1);				// this should be a serious error
								//check for input
	if ( ! Pm_Poll(streams[which])) 	
	//	UPReturnNil();
		return 0;
								// read input
	len = Pm_Read(streams[which], events, PM_BUF_LEN);
	if (len < 0) {				// on error
		pm_handle_error(len);
		oeFail(len);
	}
	return(events[0].message);
}

// Blocking threaded read

long pm_read(unsigned which) {
//	printf("PM_read\n");
	PmError len;
	if (streams[which] == NULL)
		oeFail(-1);				// this should be a serious error
								// loop to wait for input
	while ( ! Pm_Poll(streams[which])) 	
		usleep(500);			// sleep for 500 usec
								// read input
	len = Pm_Read(streams[which], events, PM_BUF_LEN);
	if (len < 0) {				// on error
		pm_handle_error(len);
		oeFail(len);
	}
/*
	if (pm_is_cacheing)  {					// 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;
		}
	}
*/
	return(events[0].message);
}

// Several formats for the write function

// write a pre-formatted message (a 32-bit long with 3 lower bytes as midi)

int pm_write_short(unsigned which, long msg) {
//	printf("PM_write: %x\n", msg);
	PmError err;
	if (streams[which] == NULL)
		oeFail(-1);
	err = Pm_WriteShort(streams[which], TIME_PROC(TIME_INFO), msg);
	if (err != pmNoError) {
		pm_handle_error(err);
		oeFail(err);
	}
}

// write the 3 given bytes

int pm_write_data3(unsigned which, unsigned char d1, unsigned char d2, unsigned char d3){
	PmError err;
//	printf("PM_write3: %d: %x-%x-%x\n", which, d1, d2, d3);
	if (streams[which] == NULL)
		oeFail(-1);
	err = Pm_WriteShort(streams[which], TIME_PROC(TIME_INFO), Pm_Message(d1, d2, d3));
	if (err != pmNoError) {
		printf("PM_write3--");
		pm_handle_error(err);
		oeFail(err);
	}
}

// write a 2-byte command

int pm_write_data2(unsigned which, unsigned char d1, unsigned char d2){
	return pm_write_data3(which, d1, d2, 0);
}

// write an array of longs

int pm_write_long(unsigned which, long * msg, unsigned length) {
//	printf("PM_write: %x\n", msg);
	PmError err;
	int i;
	int todo = (length > PM_BUF_LEN) ? PM_BUF_LEN : length;
	if (streams[which] == NULL)
		oeFail(-1);
	for (i = 0; i < todo; i++) {
		events[i].timestamp = TIME_PROC(TIME_INFO);
		events[i].message = msg[i];
	}
	err = Pm_Write(streams[which], events, todo);
	if (err != pmNoError) {
		pm_handle_error(err);
		oeFail(err);
	}
}

//
// Special calls for cached controllers
//

int pm_start_controller_cacheing(void) {
	pm_is_cacheing = 1;
}

int pm_stop_controller_cacheing(void) {
	pm_is_cacheing = 0;
}

int pm_read_controllers(unsigned which, unsigned fromController, unsigned toController, short * data) {
	if ( ! pm_is_cacheing) 
		return -1;
/*
	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);
*/
}

// pm_test -- play a note

void pm_test(void) {
	PmError err;
	int i_in = 0, i_op = 0;		// whether I initialized or opened MIDI
	int which = 0;				// which stream I test on
//	printf("PM_test\n");
	if ( ! pm_is_inited) {		// lazy initialization
		err = pm_initialize();
		if (err != pmNoError) {
			pm_handle_error(err);
			oeFail(err);
		}
		i_in = 1;
	}
	if (streams[0] == NULL) { 	// lazy port open
		err = pm_open(Pm_GetDefaultOutputDeviceID(), PM_OUTPUT);
		if (err != pmNoError) {
			pm_handle_error(err);
			oeFail(err);
		}
		i_op = 1;
		which = err;
	}					// play note-on
//	printf("\tplaying...\n");
	pm_write_data3(which, 0x90, 36, 100);
	usleep(700000);				// sleep 700 msec, then play note-off
	pm_write_data3(which, 0x80, 36, 100);
	
	if (i_op) {				// if I open, close
		err = pm_close(which);
		if (err != pmNoError) {
			pm_handle_error(err);
			oeFail(err);
		}
	}
	if (i_in) {				// if I init, terminate
		err = pm_terminate();
		if (err != pmNoError) {
			pm_handle_error(err);
			oeFail(err);
		}
	}
}
