From 90d5b8b4a064420d74678654e94ea4755b377f21 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 15 Dec 2005 00:57:02 +0000 Subject: checking in missing files on behalf of Miller (cleared it with him first). The files are from portmidi17nov04.zip svn path=/trunk/; revision=4216 --- pd/portmidi/pm_win/pmwinmm.c | 1547 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1547 insertions(+) create mode 100644 pd/portmidi/pm_win/pmwinmm.c (limited to 'pd/portmidi/pm_win/pmwinmm.c') diff --git a/pd/portmidi/pm_win/pmwinmm.c b/pd/portmidi/pm_win/pmwinmm.c new file mode 100644 index 00000000..5bfb0cff --- /dev/null +++ b/pd/portmidi/pm_win/pmwinmm.c @@ -0,0 +1,1547 @@ +/* pmwinmm.c -- system specific definitions */ + +#include "windows.h" +#include "mmsystem.h" +#include "portmidi.h" +#include "pminternal.h" +#include "pmwinmm.h" +#include "string.h" +#include "porttime.h" + +/* asserts used to verify portMidi code logic is sound; later may want + something more graceful */ +#include + +#ifdef DEBUG +/* this printf stuff really important for debugging client app w/host errors. + probably want to do something else besides read/write from/to console + for portability, however */ +#define STRING_MAX 80 +#include "stdio.h" +#endif + +#define streql(x, y) (strcmp(x, y) == 0) + +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 + +/* callback routines */ +static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn, + WORD wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); +static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); + +extern pm_fns_node pm_winmm_in_dictionary; +extern pm_fns_node pm_winmm_out_dictionary; + +static void winmm_out_delete(PmInternal *midi); /* forward reference */ + +#define SYSEX_BYTES_PER_BUFFER 1024 +/* 3 midi messages per buffer */ +#define OUTPUT_BYTES_PER_BUFFER 36 + +#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3) +#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) +#define MIDIHDR_BUFFER_LENGTH(x) (x) +#define MIDIHDR_SIZE(x) (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) + +/* +============================================================================== +win32 mmedia system specific structure passed to midi callbacks +============================================================================== +*/ + +/* global winmm device info */ +MIDIINCAPS *midi_in_caps = NULL; +MIDIINCAPS midi_in_mapper_caps; +UINT midi_num_inputs = 0; +MIDIOUTCAPS *midi_out_caps = NULL; +MIDIOUTCAPS midi_out_mapper_caps; +UINT midi_num_outputs = 0; + +/* per device info */ +typedef struct midiwinmm_struct +{ + + union { + HMIDISTRM stream; /* windows handle for stream */ + HMIDIOUT out; /* windows handle for out calls */ + HMIDIIN in; /* windows handle for in calls */ + } handle; + + /* midi output messages are sent in these buffers, which are allocated + * in a round-robin fashion, using next_buffer as an index + */ + LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */ + int num_buffers; /* how many buffers */ + int next_buffer; /* index of next buffer to send */ + HANDLE buffer_signal; /* used to wait for buffer to become free */ + + LPMIDIHDR *sysex_buffers; /* pool of buffers for sysex data */ + int num_sysex_buffers; /* how many sysex buffers */ + int next_sysex_buffer; /* index of next sysexbuffer to send */ + HANDLE sysex_buffer_signal; /* used to wait for sysex buffer to become free */ + + unsigned long last_time; /* last output time */ + int first_message; /* flag: treat first message differently */ + int sysex_mode; /* middle of sending sysex */ + unsigned long sysex_word; /* accumulate data when receiving sysex */ + unsigned int sysex_byte_count; /* count how many received or to send */ + LPMIDIHDR hdr; /* the message accumulating sysex to send */ + unsigned long sync_time; /* when did we last determine delta? */ + long delta; /* difference between stream time and + real time */ + int error; /* host error from doing port midi call */ + int callback_error; /* host error from midi in or out callback */ +} +midiwinmm_node, *midiwinmm_type; + + +/* +============================================================================= +general MIDI device queries +============================================================================= +*/ +static void pm_winmm_general_inputs() +{ + UINT i; + WORD wRtn; + midi_num_inputs = midiInGetNumDevs(); + midi_in_caps = pm_alloc(sizeof(MIDIINCAPS) * midi_num_inputs); + + if (midi_in_caps == NULL) { + // if you can't open a particular system-level midi interface + // (such as winmm), we just consider that system or API to be + // unavailable and move on without reporting an error. This + // may be the wrong thing to do, especially in this case. + return ; + } + + for (i = 0; i < midi_num_inputs; i++) { + wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i], + sizeof(MIDIINCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + /* ignore errors here -- if pm_descriptor_max is exceeded, some + devices will not be accessible. */ + pm_add_device("MMSystem", midi_in_caps[i].szPname, TRUE, + (void *) i, &pm_winmm_in_dictionary); + } + } +} + + +static void pm_winmm_mapper_input() +{ + WORD wRtn; + /* Note: if MIDIMAPPER opened as input (documentation implies you + can, but current system fails to retrieve input mapper + capabilities) then you still should retrieve some formof + setup info. */ + wRtn = midiInGetDevCaps((UINT) MIDIMAPPER, + (LPMIDIINCAPS) & midi_in_mapper_caps, sizeof(MIDIINCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device("MMSystem", midi_in_mapper_caps.szPname, TRUE, + (void *) MIDIMAPPER, &pm_winmm_in_dictionary); + } +} + + +static void pm_winmm_general_outputs() +{ + UINT i; + DWORD wRtn; + midi_num_outputs = midiOutGetNumDevs(); + midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs ); + + if (midi_out_caps == NULL) { + // no error is reported -- see pm_winmm_general_inputs + return ; + } + + for (i = 0; i < midi_num_outputs; i++) { + wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i], + sizeof(MIDIOUTCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device("MMSystem", midi_out_caps[i].szPname, FALSE, + (void *) i, &pm_winmm_out_dictionary); + } + } +} + + +static void pm_winmm_mapper_output() +{ + WORD wRtn; + /* Note: if MIDIMAPPER opened as output (pseudo MIDI device + maps device independent messages into device dependant ones, + via NT midimapper program) you still should get some setup info */ + wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS) + & midi_out_mapper_caps, sizeof(MIDIOUTCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device("MMSystem", midi_out_mapper_caps.szPname, FALSE, + (void *) MIDIMAPPER, &pm_winmm_out_dictionary); + } +} + + +/* +========================================================================================= +host error handling +========================================================================================= +*/ +static unsigned int winmm_has_host_error(PmInternal * midi) +{ + midiwinmm_type m = (midiwinmm_type)midi->descriptor; + return m->callback_error || m->error; +} + + +/* str_copy_len -- like strcat, but won't overrun the destination string */ +/* + * returns length of resulting string + */ +static int str_copy_len(char *dst, char *src, int len) +{ + strncpy(dst, src, len); + /* just in case suffex is greater then len, terminate with zero */ + dst[len - 1] = 0; + return strlen(dst); +} + + +static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) +{ + /* precondition: midi != NULL */ + midiwinmm_node * m = (midiwinmm_node *) midi->descriptor; + char *hdr1 = "Host error: "; + char *hdr2 = "Host callback error: "; + + msg[0] = 0; /* initialize result string to empty */ + + if (descriptors[midi->device_id].pub.input) { + /* input and output use different winmm API calls */ + if (m) { /* make sure there is an open device to examine */ + if (m->error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr1, len); + /* read and record host error */ + int err = midiInGetErrorText(m->error, msg + n, len - n); + assert(err == MMSYSERR_NOERROR); + m->error = MMSYSERR_NOERROR; + } else if (m->callback_error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr2, len); + int err = midiInGetErrorText(m->callback_error, msg + n, + len - n); + assert(err == MMSYSERR_NOERROR); + m->callback_error = MMSYSERR_NOERROR; + } + } + } else { /* output port */ + if (m) { + if (m->error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr1, len); + int err = midiOutGetErrorText(m->error, msg + n, len - n); + assert(err == MMSYSERR_NOERROR); + m->error = MMSYSERR_NOERROR; + } else if (m->callback_error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr2, len); + int err = midiOutGetErrorText(m->callback_error, msg + n, + len = n); + assert(err == MMSYSERR_NOERROR); + m->callback_error = MMSYSERR_NOERROR; + } + } + } +} + + +/* +============================================================================= +buffer handling +============================================================================= +*/ +static MIDIHDR *allocate_buffer(long data_size) +{ + /* + * with short messages, the MIDIEVENT structure contains the midi message, + * so there is no need for additional data + */ + + LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SIZE(data_size)); + MIDIEVENT *evt; + if (!hdr) return NULL; + evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ + hdr->lpData = (LPSTR) evt; + hdr->dwBufferLength = MIDIHDR_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */ + hdr->dwFlags = 0; + hdr->dwUser = 0; + return hdr; +} + +static MIDIHDR *allocate_sysex_buffer(long data_size) +{ + /* we're actually allocating slightly more than data_size because one more word of + * data is contained in MIDIEVENT. We include the size of MIDIEVENT because we need + * the MIDIEVENT header in addition to the data + */ + LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); + MIDIEVENT *evt; + if (!hdr) return NULL; + evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ + hdr->lpData = (LPSTR) evt; + hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */ + hdr->dwFlags = 0; + hdr->dwUser = 0; + return hdr; +} + +static PmError allocate_buffers(midiwinmm_type m, long data_size, long count) +{ + PmError rslt = pmNoError; + /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!m->buffers) return pmInsufficientMemory; + m->num_buffers = count; + while (count > 0) { + LPMIDIHDR hdr = allocate_buffer(data_size); + if (!hdr) rslt = pmInsufficientMemory; + count--; + m->buffers[count] = hdr; /* this may be NULL if allocation fails */ + } + return rslt; +} + +static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size, long count) +{ + PmError rslt = pmNoError; + /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + m->sysex_buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!m->sysex_buffers) return pmInsufficientMemory; + m->num_sysex_buffers = count; + while (count > 0) { + LPMIDIHDR hdr = allocate_sysex_buffer(data_size); + if (!hdr) rslt = pmInsufficientMemory; + count--; + m->sysex_buffers[count] = hdr; /* this may be NULL if allocation fails */ + } + return rslt; +} + +static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m->sysex_buffers) { + if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) { + return NULL; + } + } + /* busy wait until we find a free buffer */ + while (TRUE) { + int i; + for (i = 0; i < m->num_sysex_buffers; i++) { + m->next_sysex_buffer++; + if (m->next_sysex_buffer >= m->num_sysex_buffers) m->next_sysex_buffer = 0; + r = m->sysex_buffers[m->next_sysex_buffer]; + if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer; + } + /* after scanning every buffer and not finding anything, block */ + WaitForSingleObject(m->sysex_buffer_signal, INFINITE); + } +found_sysex_buffer: + r->dwBytesRecorded = 0; + m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR)); + return r; +} + + +static LPMIDIHDR get_free_output_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m->buffers) { + if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, 2)) { + return NULL; + } + } + /* busy wait until we find a free buffer */ + while (TRUE) { + int i; + for (i = 0; i < m->num_buffers; i++) { + m->next_buffer++; + if (m->next_buffer >= m->num_buffers) m->next_buffer = 0; + r = m->buffers[m->next_buffer]; + if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer; + } + /* after scanning every buffer and not finding anything, block */ + WaitForSingleObject(m->buffer_signal, INFINITE); + } +found_buffer: + r->dwBytesRecorded = 0; + m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR)); + return r; +} + +static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size) +{ + LPMIDIHDR big; + int i; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + /* buffer must be smaller than 64k, but be also be a multiple of 4 */ + if (new_size > 65520) { + if (old_size >= 65520) + return pmBufferMaxSize; + else + new_size = 65520; + } + /* allocate a bigger message */ + big = allocate_sysex_buffer(new_size); + /* printf("expand to %d bytes\n", new_size);*/ + if (!big) return pmInsufficientMemory; + m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR)); + if (m->error) { + pm_free(big); + return pmHostError; + } + /* make sure we're not going to overwrite any memory */ + assert(old_size <= new_size); + memcpy(big->lpData, m->hdr->lpData, old_size); + + /* find which buffer this was, and replace it */ + + for (i = 0;i < m->num_sysex_buffers;i++) { + if (m->sysex_buffers[i] == m->hdr) { + m->sysex_buffers[i] = big; + pm_free(m->hdr); + m->hdr = big; + break; + } + } + assert(i != m->num_sysex_buffers); + + return pmNoError; + +} +/* +========================================================================================= +begin midi input implementation +========================================================================================= +*/ + +static PmError winmm_in_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + midiwinmm_type m; + LPMIDIHDR hdr; + long buffer_len; + dwDevice = (DWORD) descriptors[i].descriptor; + + /* create system dependent device data */ + m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ + midi->descriptor = m; + if (!m) goto no_memory; + m->handle.in = NULL; + m->buffers = NULL; + m->num_buffers = 0; + m->next_buffer = 0; + m->sysex_buffers = NULL; + m->num_sysex_buffers = 0; + m->next_sysex_buffer = 0; + m->last_time = 0; + m->first_message = TRUE; /* not used for input */ + m->sysex_mode = FALSE; + m->sysex_word = 0; + m->sysex_byte_count = 0; + m->sync_time = 0; + m->delta = 0; + m->error = MMSYSERR_NOERROR; + m->callback_error = MMSYSERR_NOERROR; + + /* open device */ + pm_hosterror = midiInOpen(&(m->handle.in), /* input device handle */ + dwDevice, /* device ID */ + (DWORD) winmm_in_callback, /* callback address */ + (DWORD) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback is a procedure */ + if (pm_hosterror) goto free_descriptor; + + /* allocate first buffer for sysex data */ + buffer_len = midi->buffer_len - 1; + if (midi->buffer_len < 32) + buffer_len = PM_DEFAULT_SYSEX_BUFFER_SIZE; + + hdr = allocate_sysex_buffer(buffer_len); + if (!hdr) goto close_device; + pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) { + pm_free(hdr); + goto close_device; + } + pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) goto close_device; + + /* allocate second buffer */ + hdr = allocate_sysex_buffer(buffer_len); + if (!hdr) goto close_device; + pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) { + pm_free(hdr); + goto reset_device; /* because first buffer was added */ + } + pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) goto reset_device; + + /* start device */ + pm_hosterror = midiInStart(m->handle.in); + if (pm_hosterror) goto reset_device; + return pmNoError; + + /* undo steps leading up to the detected error */ +reset_device: + /* ignore return code (we already have an error to report) */ + midiInReset(m->handle.in); +close_device: + midiInClose(m->handle.in); /* ignore return code */ +free_descriptor: + midi->descriptor = NULL; + pm_free(m); +no_memory: + if (pm_hosterror) { + int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + /* if !pm_hosterror, then the error must be pmInsufficientMemory */ + return pmInsufficientMemory; + /* note: if we return an error code, the device will be + closed and memory will be freed. It's up to the caller + to free the parameter midi */ +} + + +/* winmm_in_close -- close an open midi input device */ +/* + * assume midi is non-null (checked by caller) + */ +static PmError winmm_in_close(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m) return pmBadPtr; + /* device to close */ + if (pm_hosterror = midiInStop(m->handle.in)) { + midiInReset(m->handle.in); /* try to reset and close port */ + midiInClose(m->handle.in); + } else if (pm_hosterror = midiInReset(m->handle.in)) { + midiInClose(m->handle.in); /* best effort to close midi port */ + } else { + pm_hosterror = midiInClose(m->handle.in); + } + midi->descriptor = NULL; + pm_free(m); /* delete */ + if (pm_hosterror) { + int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + return pmNoError; +} + + +/* Callback function executed via midiInput SW interrupt (via midiInOpen). */ +static void FAR PASCAL winmm_in_callback( + HMIDIIN hMidiIn, /* midiInput device Handle */ + WORD wMsg, /* midi msg */ + DWORD dwInstance, /* application data */ + DWORD dwParam1, /* MIDI data */ + DWORD dwParam2) /* device timestamp (wrt most recent midiInStart) */ +{ + static int entry = 0; + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + + if (++entry > 1) { + assert(FALSE); + } + + /* for simplicity, this logic perhaps overly conservative */ + /* note also that this might leak memory if buffers are being + returned as a result of midiInReset */ + if (m->callback_error) { + entry--; + return ; + } + + switch (wMsg) { + case MIM_DATA: { + /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of + message LOB; + dwParam2 is time message received by input device driver, specified + in [ms] from when midiInStart called. + each message is expanded to include the status byte */ + + long new_driver_time = dwParam2; + + if ((dwParam1 & 0x80) == 0) { + /* not a status byte -- ignore it. This happens running the + sysex.c test under Win2K with MidiMan USB 1x1 interface. + Is it a driver bug or a Win32 bug? If not, there's a bug + here somewhere. -RBD + */ + } else { /* data to process */ + PmEvent event; + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + event.timestamp = dwParam2; + event.message = dwParam1; + pm_read_short(midi, &event); + } + break; + } + case MIM_LONGDATA: { + MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1; + unsigned char *data = lpMidiHdr->lpData; + unsigned int i = 0; + long size = sizeof(MIDIHDR) + lpMidiHdr->dwBufferLength; + /* ignore sysex data, but free returned buffers */ + if (lpMidiHdr->dwBytesRecorded > 0 && + midi->filters & PM_FILT_SYSEX) { + m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr, + sizeof(MIDIHDR)); + break; + } + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + + while (i < lpMidiHdr->dwBytesRecorded) { + /* collect bytes from *data into a word */ + pm_read_byte(midi, *data, dwParam2); + data++; + i++; + } + /* when a device is closed, the pending MIM_LONGDATA buffers are + returned to this callback with dwBytesRecorded == 0. In this + case, we do not want to send them back to the interface (if + we do, the interface will not close, and Windows OS may hang). */ + if (lpMidiHdr->dwBytesRecorded > 0) { + m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr, + sizeof(MIDIHDR)); + } else { + pm_free(lpMidiHdr); + } + break; + } + case MIM_OPEN: /* fall thru */ + case MIM_CLOSE: + case MIM_ERROR: + case MIM_LONGERROR: + default: + break; + } + entry--; +} + +/* +========================================================================================= +begin midi output implementation +========================================================================================= +*/ + +/* begin helper routines used by midiOutStream interface */ + +/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */ +static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, + unsigned long delta, unsigned long msg) +{ + unsigned long *ptr = (unsigned long *) + (hdr->lpData + hdr->dwBytesRecorded); + *ptr++ = delta; /* dwDeltaTime */ + *ptr++ = 0; /* dwStream */ + *ptr++ = msg; /* dwEvent */ + hdr->dwBytesRecorded += 3 * sizeof(long); + /* if the addition of three more words (a message) would extend beyond + the buffer length, then return TRUE (full) + */ + return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength; +} + +#ifdef GARBAGE +static void start_sysex_buffer(LPMIDIHDR hdr, unsigned long delta) +{ + unsigned long *ptr = (unsigned long *) hdr->lpData; + *ptr++ = delta; + *ptr++ = 0; + *ptr = MEVT_F_LONG; + hdr->dwBytesRecorded = 3 * sizeof(long); +} + +static int add_byte_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, + unsigned char midi_byte) +{ + allocate message if hdr is null + send message if it is full + add byte to non - full message + unsigned char *ptr = (unsigned char *) (hdr->lpData + hdr->dwBytesRecorded); + *ptr = midi_byte; + return ++hdr->dwBytesRecorded >= hdr->dwBufferLength; +} +#endif + + +static PmTimestamp pm_time_get(midiwinmm_type m) +{ + MMTIME mmtime; + MMRESULT wRtn; + mmtime.wType = TIME_TICKS; + mmtime.u.ticks = 0; + wRtn = midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime)); + assert(wRtn == MMSYSERR_NOERROR); + return mmtime.u.ticks; +} + +#ifdef GARBAGE +static unsigned long synchronize(PmInternal *midi, midiwinmm_type m) +{ + unsigned long pm_stream_time_2 = pm_time_get(m); + unsigned long real_time; + unsigned long pm_stream_time; + /* figure out the time */ + do { + /* read real_time between two reads of stream time */ + pm_stream_time = pm_stream_time_2; + real_time = (*midi->time_proc)(midi->time_info); + pm_stream_time_2 = pm_time_get(m); + /* repeat if more than 1ms elapsed */ + } while (pm_stream_time_2 > pm_stream_time + 1); + m->delta = pm_stream_time - real_time; + m->sync_time = real_time; + return real_time; +} +#endif + + +/* end helper routines used by midiOutStream interface */ + + +static PmError winmm_out_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + midiwinmm_type m; + MIDIPROPTEMPO propdata; + MIDIPROPTIMEDIV divdata; + dwDevice = (DWORD) descriptors[i].descriptor; + + /* create system dependent device data */ + m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ + midi->descriptor = m; + if (!m) goto no_memory; + m->handle.out = NULL; + m->buffers = NULL; + m->num_buffers = 0; + m->next_buffer = 0; + m->sysex_buffers = NULL; + m->num_sysex_buffers = 0; + m->next_sysex_buffer = 0; + m->last_time = 0; + m->first_message = TRUE; /* we treat first message as special case */ + m->sysex_mode = FALSE; + m->sysex_word = 0; + m->sysex_byte_count = 0; + m->hdr = NULL; + m->sync_time = 0; + m->delta = 0; + m->error = MMSYSERR_NOERROR; + m->callback_error = MMSYSERR_NOERROR; + + /* create a signal */ + m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); + m->sysex_buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* this should only fail when there are very serious problems */ + assert(m->buffer_signal); + assert(m->sysex_buffer_signal); + + /* open device */ + if (midi->latency == 0) { + /* use simple midi out calls */ + pm_hosterror = midiOutOpen((LPHMIDIOUT) & m->handle.out, /* device Handle */ + dwDevice, /* device ID */ + (DWORD) winmm_out_callback, + (DWORD) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback type */ + } else { + /* use stream-based midi output (schedulable in future) */ + pm_hosterror = midiStreamOpen(&m->handle.stream, /* device Handle */ + (LPUINT) & dwDevice, /* device ID pointer */ + 1, /* reserved, must be 1 */ + (DWORD) winmm_streamout_callback, + (DWORD) midi, /* callback instance data */ + CALLBACK_FUNCTION); + } + if (pm_hosterror != MMSYSERR_NOERROR) { + goto free_descriptor; + } + + if (midi->latency != 0) { + long dur = 0; + /* with stream output, specified number of buffers allocated here */ + int count = midi->buffer_len; + if (count == 0) + count = midi->latency / 2; /* how many buffers to get */ + + propdata.cbStruct = sizeof(MIDIPROPTEMPO); + propdata.dwTempo = 480000; /* microseconds per quarter */ + pm_hosterror = midiStreamProperty(m->handle.stream, + (LPBYTE) & propdata, + MIDIPROP_SET | MIDIPROP_TEMPO); + if (pm_hosterror) goto close_device; + + divdata.cbStruct = sizeof(MIDIPROPTEMPO); + divdata.dwTimeDiv = 480; /* divisions per quarter */ + pm_hosterror = midiStreamProperty(m->handle.stream, + (LPBYTE) & divdata, + MIDIPROP_SET | MIDIPROP_TIMEDIV); + if (pm_hosterror) goto close_device; + + /* allocate at least 3 buffers */ + if (count < 3) count = 3; + if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, count)) goto free_buffers; + if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) goto free_buffers; + /* start device */ + pm_hosterror = midiStreamRestart(m->handle.stream); + if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers; + } + return pmNoError; + +free_buffers: + /* buffers are freed below by winmm_out_delete */ +close_device: + midiOutClose(m->handle.out); +free_descriptor: + midi->descriptor = NULL; + winmm_out_delete(midi); /* frees buffers and m */ +no_memory: + if (pm_hosterror) { + int err = midiOutGetErrorText(pm_hosterror, (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + return pmInsufficientMemory; +} + + +/* winmm_out_delete -- carefully free data associated with midi */ +/**/ +static void winmm_out_delete(PmInternal *midi) +{ + /* delete system dependent device data */ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m) { + if (m->buffer_signal) { + /* don't report errors -- better not to stop cleanup */ + CloseHandle(m->buffer_signal); + } + if (m->sysex_buffer_signal) { + /* don't report errors -- better not to stop cleanup */ + CloseHandle(m->sysex_buffer_signal); + } + if (m->buffers) { + /* if using stream output, free buffers */ + int i; + for (i = 0; i < m->num_buffers; i++) { + if (m->buffers[i]) pm_free(m->buffers[i]); + } + pm_free(m->buffers); + } + + if (m->sysex_buffers) { + /* free sysex buffers */ + int i; + for (i = 0; i < m->num_sysex_buffers; i++) { + if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); + } + pm_free(m->sysex_buffers); + } + } + midi->descriptor = NULL; + pm_free(m); /* delete */ +} + + +/* see comments for winmm_in_close */ +static PmError winmm_out_close(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m->handle.out) { + /* device to close */ + if (midi->latency == 0) { + pm_hosterror = midiOutClose(m->handle.out); + } else { + pm_hosterror = midiStreamClose(m->handle.stream); + } + /* regardless of outcome, free memory */ + winmm_out_delete(midi); + } + if (pm_hosterror) { + int err = midiOutGetErrorText(pm_hosterror, + (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + return pmNoError; +} + + +static PmError winmm_out_abort(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + m->error = MMSYSERR_NOERROR; + + /* only stop output streams */ + if (midi->latency > 0) { + m->error = midiStreamStop(m->handle.stream); + } + return m->error ? pmHostError : pmNoError; +} + +#ifdef GARBAGE +static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + unsigned char *msg_buffer; + + /* at the beginning of sysex, m->hdr is NULL */ + if (!m->hdr) { /* allocate a buffer if none allocated yet */ + m->hdr = get_free_output_buffer(midi); + if (!m->hdr) return pmInsufficientMemory; + m->sysex_byte_count = 0; + } + /* figure out where to write byte */ + msg_buffer = (unsigned char *) (m->hdr->lpData); + assert(m->hdr->lpData == (char *) (m->hdr + 1)); + + /* check for overflow */ + if (m->sysex_byte_count >= m->hdr->dwBufferLength) { + /* allocate a bigger message -- double it every time */ + LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2); + /* printf("expand to %d bytes\n", m->sysex_byte_count * 2); */ + if (!big) return pmInsufficientMemory; + m->error = midiOutPrepareHeader(m->handle.out, big, + sizeof(MIDIHDR)); + if (m->error) { + m->hdr = NULL; + return pmHostError; + } + memcpy(big->lpData, msg_buffer, m->sysex_byte_count); + msg_buffer = (unsigned char *) (big->lpData); + if (m->buffers[0] == m->hdr) { + m->buffers[0] = big; + pm_free(m->hdr); + /* printf("freed m->hdr\n"); */ + } else if (m->buffers[1] == m->hdr) { + m->buffers[1] = big; + pm_free(m->hdr); + /* printf("freed m->hdr\n"); */ + } + m->hdr = big; + } + + /* append byte to message */ + msg_buffer[m->sysex_byte_count++] = byte; + + /* see if we have a complete message */ + if (byte == MIDI_EOX) { + m->hdr->dwBytesRecorded = m->sysex_byte_count; + /* + { int i; int len = m->hdr->dwBytesRecorded; + printf("OutLongMsg %d ", len); + for (i = 0; i < len; i++) { + printf("%2x ", msg_buffer[i]); + } + } + */ + m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); + m->hdr = NULL; /* stop using this message buffer */ + if (m->error) return pmHostError; + } + return pmNoError; +} +#endif + + +static PmError winmm_write_short(PmInternal *midi, PmEvent *event) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + PmError rslt = pmNoError; + assert(m); + if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ + m->error = midiOutShortMsg(m->handle.out, event->message); + if (m->error) rslt = pmHostError; + } else { /* use midiStream interface -- pass data through buffers */ + unsigned long when = event->timestamp; + unsigned long delta; + int full; + if (when == 0) when = midi->now; + /* when is in real_time; translate to intended stream time */ + when = when + m->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < m->last_time) when = m->last_time; + delta = when - m->last_time; + m->last_time = when; + /* before we insert any data, we must have a buffer */ + if (m->hdr == NULL) { + /* stream interface: buffers allocated when stream is opened */ + m->hdr = get_free_output_buffer(midi); + } + full = add_to_buffer(m, m->hdr, delta, event->message); + if (full) { + m->error = midiStreamOut(m->handle.stream, m->hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + m->hdr = NULL; + } + } + return rslt; +} + + +static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + PmError rslt = pmNoError; + if (midi->latency == 0) { + /* do nothing -- it's handled in winmm_write_byte */ + } else { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + /* sysex expects an empty buffer */ + if (m->hdr) { + m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + m->hdr = NULL; + } + return rslt; +} + + +static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + PmError rslt = pmNoError; + assert(m); + + if (midi->latency == 0) { + /* Not using the stream interface. The entire sysex message is + in m->hdr, and we send it using midiOutLongMsg. + */ + m->hdr->dwBytesRecorded = m->sysex_byte_count; + /* + { int i; int len = m->hdr->dwBytesRecorded; + printf("OutLongMsg %d ", len); + for (i = 0; i < len; i++) { + printf("%2x ", msg_buffer[i]); + } + } + */ + + m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } else if (m->hdr) { + /* Using stream interface. There are accumulated bytes in m->hdr + to send using midiStreamOut + */ + /* add bytes recorded to MIDIEVENT length, but don't + count the MIDIEVENT data (3 longs) */ + MIDIEVENT *evt = (MIDIEVENT *) m->hdr->lpData; + evt->dwEvent += m->hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + m->hdr->dwBytesRecorded = (m->hdr->dwBytesRecorded + 3) & ~3; + + m->error = midiStreamOut(m->handle.stream, m->hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + m->hdr = NULL; /* make sure we don't send it again */ + return rslt; +} + + +static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp) +{ + /* write a sysex byte */ + PmError rslt = pmNoError; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + assert(m); + if (midi->latency == 0) { + /* Not using stream interface. Accumulate the entire message into + m->hdr */ + unsigned char *msg_buffer; + /* at the beginning of sysex, m->hdr is NULL */ + if (!m->hdr) { /* allocate a buffer if none allocated yet */ + m->hdr = get_free_sysex_buffer(midi); + if (!m->hdr) return pmInsufficientMemory; + m->sysex_byte_count = 0; + } + /* figure out where to write byte */ + msg_buffer = (unsigned char *) (m->hdr->lpData); + assert(m->hdr->lpData == (char *) (m->hdr + 1)); + + /* append byte to message */ + msg_buffer[m->sysex_byte_count++] = byte; + + /* check for overflow */ + if (m->sysex_byte_count >= m->hdr->dwBufferLength) { + rslt = resize_sysex_buffer(midi, m->sysex_byte_count, m->sysex_byte_count * 2); + + if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */ + rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */ + + } + + } else { /* latency is not zero, use stream interface: accumulate + sysex data in m->hdr and send whenever the buffer fills */ + int full; + unsigned char *ptr; + + /* if m->hdr does not exist, allocate it */ + if (m->hdr == NULL) { + unsigned long when = (unsigned long) timestamp; + unsigned long delta; + unsigned long *ptr; + if (when == 0) when = midi->now; + /* when is in real_time; translate to intended stream time */ + when = when + m->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < m->last_time) when = m->last_time; + delta = when - m->last_time; + m->last_time = when; + + m->hdr = get_free_sysex_buffer(midi); + assert(m->hdr); + ptr = (unsigned long *) m->hdr->lpData; + *ptr++ = delta; + *ptr++ = 0; + *ptr = MEVT_F_LONG; + m->hdr->dwBytesRecorded = 3 * sizeof(long); + } + + /* add the data byte */ + ptr = (unsigned char *) (m->hdr->lpData + m->hdr->dwBytesRecorded); + *ptr = byte; + full = ++m->hdr->dwBytesRecorded >= m->hdr->dwBufferLength; + + /* see if we need to resize */ + if (full) { + int bytesRecorded = m->hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ + rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); + m->hdr->dwBytesRecorded = bytesRecorded; + + if (rslt == pmBufferMaxSize) /* if buffer can't be resized */ + rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */ + } + } + return rslt; +} + + + +static PmError winmm_write_flush(PmInternal *midi) +{ + PmError rslt = pmNoError; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + assert(m); + if (midi->latency == 0) { + /* all messages are sent immediately */ + } else if ((m->hdr) && (!midi->sysex_in_progress)) { + /* sysex messages are sent upon completion, but ordinary messages + may be sitting in a buffer + */ + m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR)); + m->hdr = NULL; + if (m->error) rslt = pmHostError; + } + return rslt; +} + +static PmTimestamp winmm_synchronize(PmInternal *midi) +{ + midiwinmm_type m; + unsigned long pm_stream_time_2; + unsigned long real_time; + unsigned long pm_stream_time; + + /* only synchronize if we are using stream interface */ + if (midi->latency == 0) return 0; + + /* figure out the time */ + m = (midiwinmm_type) midi->descriptor; + pm_stream_time_2 = pm_time_get(m); + + do { + /* read real_time between two reads of stream time */ + pm_stream_time = pm_stream_time_2; + real_time = (*midi->time_proc)(midi->time_info); + pm_stream_time_2 = pm_time_get(m); + /* repeat if more than 1ms elapsed */ + } while (pm_stream_time_2 > pm_stream_time + 1); + m->delta = pm_stream_time - real_time; + m->sync_time = real_time; + return real_time; +} + + +#ifdef GARBAGE +static PmError winmm_write(PmInternal *midi, + PmEvent *buffer, + long length) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + unsigned long now; + int i; + long msg; + PmError rslt = pmNoError; + + m->error = MMSYSERR_NOERROR; + if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ + for (i = 0; (i < length) && (rslt == pmNoError); i++) { + int b = 0; /* count sysex bytes as they are handled */ + msg = buffer[i].message; + if ((msg & 0xFF) == MIDI_SYSEX) { + /* start a sysex message */ + m->sysex_mode = TRUE; + unsigned char midi_byte = (unsigned char) msg; + rslt = winmm_write_sysex_byte(midi, midi_byte); + b = 8; + } else if ((msg & 0x80) && ((msg & 0xFF) != MIDI_EOX)) { + /* a non-sysex message */ + m->error = midiOutShortMsg(m->handle.out, msg); + if (m->error) rslt = pmHostError; + /* any non-real-time message will terminate sysex message */ + if (!is_real_time(msg)) m->sysex_mode = FALSE; + } + /* transmit sysex bytes until we find EOX */ + if (m->sysex_mode) { + while (b < 32 /*bits*/ && (rslt == pmNoError)) { + unsigned char midi_byte = (unsigned char) (msg >> b); + rslt = winmm_write_sysex_byte(midi, midi_byte); + if (midi_byte == MIDI_EOX) { + b = 24; /* end of message */ + m->sysex_mode = FALSE; + } + b += 8; + } + } + } + } else { /* use midiStream interface -- pass data through buffers */ + LPMIDIHDR hdr = NULL; + now = (*midi->time_proc)(midi->time_info); + if (m->first_message || m->sync_time + 100 /*ms*/ < now) { + /* time to resync */ + now = synchronize(midi, m); + m->first_message = FALSE; + } + for (i = 0; i < length && rslt == pmNoError; i++) { + unsigned long when = buffer[i].timestamp; + unsigned long delta; + if (when == 0) when = now; + /* when is in real_time; translate to intended stream time */ + when = when + m->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < m->last_time) when = m->last_time; + delta = when - m->last_time; + m->last_time = when; + /* before we insert any data, we must have a buffer */ + if (hdr == NULL) { + /* stream interface: buffers allocated when stream is opened */ + hdr = get_free_output_buffer(midi); + assert(hdr); + if (m->sysex_mode) { + /* we are in the middle of a sysex message */ + start_sysex_buffer(hdr, delta); + } + } + msg = buffer[i].message; + if ((msg & 0xFF) == MIDI_SYSEX) { + /* sysex expects an empty buffer */ + if (hdr->dwBytesRecorded != 0) { + m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + hdr = get_free_output_buffer(midi); + assert(hdr); + } + /* when we see a MIDI_SYSEX, we always enter sysex mode and call + start_sysex_buffer() */ + start_sysex_buffer(hdr, delta); + m->sysex_mode = TRUE; + } + /* allow a non-real-time status byte to terminate sysex message */ + if (m->sysex_mode && (msg & 0x80) && (msg & 0xFF) != MIDI_SYSEX && + !is_real_time(msg)) { + /* I'm not sure what WinMM does if you send an incomplete sysex + message, but the best way out of this mess seems to be to + recreate the code used when you encounter an EOX, so ... + */ + MIDIEVENT *evt = (MIDIEVENT) hdr->lpData; + evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) { + rslt = pmHostError; + } + hdr = NULL; /* make sure we don't send it again */ + m->sysex_mode = FALSE; /* skip to normal message send code */ + } + if (m->sysex_mode) { + int b = 0; /* count bytes as they are handled */ + while (b < 32 /* bits per word */ && (rslt == pmNoError)) { + int full; + unsigned char midi_byte = (unsigned char) (msg >> b); + if (!hdr) { + hdr = get_free_output_buffer(midi); + assert(hdr); + /* get ready to put sysex bytes in buffer */ + start_sysex_buffer(hdr, delta); + } + full = add_byte_to_buffer(m, hdr, midi_byte); + if (midi_byte == MIDI_EOX) { + b = 24; /* pretend this is last byte to exit loop */ + m->sysex_mode = FALSE; + } + /* see if it's time to send buffer, note that by always + sending complete sysex message right away, we can use + this code to set up the MIDIEVENT properly + */ + if (full || midi_byte == MIDI_EOX) { + /* add bytes recorded to MIDIEVENT length, but don't + count the MIDIEVENT data (3 longs) */ + MIDIEVENT *evt = (MIDIEVENT *) hdr->lpData; + evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) { + rslt = pmHostError; + } + hdr = NULL; /* make sure we don't send it again */ + } + b += 8; /* shift to next byte */ + } + /* test rslt here in case it was set when we terminated a sysex early + (see above) */ + } else if (rslt == pmNoError) { + int full = add_to_buffer(m, hdr, delta, msg); + if (full) { + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + hdr = NULL; + } + } + } + if (hdr && rslt == pmNoError) { + if (m->sysex_mode) { + MIDIEVENT *evt = (MIDIEVENT *) hdr->lpData; + evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; + } + m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + } + return rslt; +} +#endif + + +/* winmm_out_callback -- recycle sysex buffers */ +static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + int i; + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; + int err = 0; /* set to 0 so that no buffer match will also be an error */ + static int entry = 0; + if (++entry > 1) { + assert(FALSE); + } + if (m->callback_error || wMsg != MOM_DONE) { + entry--; + return ; + } + /* Future optimization: eliminate UnprepareHeader calls -- they aren't + necessary; however, this code uses the prepared-flag to indicate which + buffers are free, so we need to do something to flag empty buffers if + we leave them prepared + */ + m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + /* notify waiting sender that a buffer is available */ + /* any errors could be reported via callback_error, but this is always + treated as a Midi error, so we'd have to write a lot more code to + detect that a non-Midi error occurred and do the right thing to find + the corresponding error message text. Therefore, just use assert() + */ + + /* determine if this is an output buffer or a sysex buffer */ + + for (i = 0 ;i < m->num_buffers;i++) { + if (hdr == m->buffers[i]) { + err = SetEvent(m->buffer_signal); + break; + } + } + for (i = 0 ;i < m->num_sysex_buffers;i++) { + if (hdr == m->sysex_buffers[i]) { + err = SetEvent(m->sysex_buffer_signal); + break; + } + } + assert(err); /* false -> error */ + entry--; +} + + +/* winmm_streamout_callback -- unprepare (free) buffer header */ +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; + int err; + static int entry = 0; + if (++entry > 1) { + /* We've reentered this routine. I assume this never happens, but + check to make sure. Apparently, it is possible that this callback + can be called reentrantly because it happened once while debugging. + It looks like this routine is actually reentrant so we can remove + the assertion if necessary. */ + assert(FALSE); + } + if (m->callback_error || wMsg != MOM_DONE) { + entry--; + return ; + } + m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + err = SetEvent(m->buffer_signal); + assert(err); /* false -> error */ + entry--; +} + + +/* +========================================================================================= +begin exported functions +========================================================================================= +*/ + +#define winmm_in_abort pm_fail_fn +pm_fns_node pm_winmm_in_dictionary = { + none_write_short, + none_sysex, + none_sysex, + none_write_byte, + none_write_short, + none_write_flush, + winmm_synchronize, + winmm_in_open, + winmm_in_abort, + winmm_in_close, + success_poll, + winmm_has_host_error, + winmm_get_host_error + }; + +pm_fns_node pm_winmm_out_dictionary = { + winmm_write_short, + winmm_begin_sysex, + winmm_end_sysex, + winmm_write_byte, + winmm_write_short, /* short realtime message */ + winmm_write_flush, + winmm_synchronize, + winmm_out_open, + winmm_out_abort, + winmm_out_close, + none_poll, + winmm_has_host_error, + winmm_get_host_error + }; + + +/* initialize winmm interface. Note that if there is something wrong + with winmm (e.g. it is not supported or installed), it is not an + error. We should simply return without having added any devices to + the table. Hence, no error code is returned. Furthermore, this init + code is called along with every other supported interface, so the + user would have a very hard time figuring out what hardware and API + generated the error. Finally, it would add complexity to pmwin.c to + remember where the error code came from in order to convert to text. + */ +void pm_winmm_init( void ) +{ + pm_winmm_mapper_input(); + pm_winmm_mapper_output(); + pm_winmm_general_inputs(); + pm_winmm_general_outputs(); +} + + +/* no error codes are returned, even if errors are encountered, because + there is probably nothing the user could do (e.g. it would be an error + to retry. + */ +void pm_winmm_term( void ) +{ + int i; +#ifdef DEBUG + char msg[PM_HOST_ERROR_MSG_LEN]; +#endif + int doneAny = 0; +#ifdef DEBUG + printf("pm_winmm_term called\n"); +#endif + for (i = 0; i < pm_descriptor_index; i++) { + PmInternal * midi = descriptors[i].internalDescriptor; + if (midi) { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m->handle.out) { + /* close next open device*/ +#ifdef DEBUG + if (doneAny == 0) { + printf("begin closing open devices...\n"); + doneAny = 1; + } + /* report any host errors; this EXTEREMELY useful when + trying to debug client app */ + if (winmm_has_host_error(midi)) { + winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN); + printf(msg); + } +#endif + /* close all open ports */ + (*midi->dictionary->close)(midi); + } + } + } +#ifdef DEBUG + if (doneAny) { + printf("warning: devices were left open. They have been closed.\n"); + } + printf("pm_winmm_term exiting\n"); +#endif + pm_descriptor_index = 0; +} -- cgit v1.2.1