From 988f5d2b5343850e19ad1512cefe6c53953aa02e Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Mon, 7 Oct 2024 06:50:04 +0200 Subject: Added bunch of examples --- portmidi/pm_win/pmwinmm.c | 1196 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1196 insertions(+) create mode 100755 portmidi/pm_win/pmwinmm.c (limited to 'portmidi/pm_win/pmwinmm.c') diff --git a/portmidi/pm_win/pmwinmm.c b/portmidi/pm_win/pmwinmm.c new file mode 100755 index 0000000..6f4b6f3 --- /dev/null +++ b/portmidi/pm_win/pmwinmm.c @@ -0,0 +1,1196 @@ +/* pmwinmm.c -- system specific definitions */ + +#ifndef _WIN32_WINNT + /* without this define, InitializeCriticalSectionAndSpinCount is + * undefined. This version level means "Windows 2000 and higher" + */ + #define _WIN32_WINNT 0x0500 +#endif + +#define UNICODE 1 +#include +#include "windows.h" +#include "mmsystem.h" +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" +#include "pmwinmm.h" +#include +#include "porttime.h" +#ifndef UNICODE +#error Expected UNICODE to be defined +#endif + + +/* asserts used to verify portMidi code logic is sound; later may want + something more graceful */ +#include +#ifdef MMDEBUG +/* 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, + UINT wMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2); +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, + DWORD_PTR 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 */ + +/* +A note about buffers: WinMM seems to hold onto buffers longer than +one would expect, e.g. when I tried using 2 small buffers to send +long sysex messages, at some point WinMM held both buffers. This problem +was fixed by making buffers bigger. Therefore, it seems that there should +be enough buffer space to hold a whole sysex message. + +The bufferSize passed into Pm_OpenInput (passed into here as buffer_len) +will be used to estimate the largest sysex message (= buffer_len * 4 bytes). +Call that the max_sysex_len = buffer_len * 4. + +For simple midi output (latency == 0), allocate 3 buffers, each with half +the size of max_sysex_len, but each at least 256 bytes. + +For stream output, there will already be enough space in very short +buffers, so use them, but make sure there are at least 16. + +For input, use many small buffers rather than 2 large ones so that when +there are short sysex messages arriving frequently (as in control surfaces) +there will be more free buffers to fill. Use max_sysex_len / 64 buffers, +but at least 16, of size 64 bytes each. + +The following constants help to represent these design parameters: +*/ +#define NUM_SIMPLE_SYSEX_BUFFERS 3 +#define MIN_SIMPLE_SYSEX_LEN 256 + +#define MIN_STREAM_BUFFERS 16 +#define STREAM_BUFFER_LEN 24 + +#define INPUT_SYSEX_LEN 64 +#define MIN_INPUT_BUFFERS 16 + +/* if we run out of space for output (assume this is due to a sysex msg, + expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN + */ +#define NUM_EXPANSION_BUFFERS 128 +#define EXPANSION_BUFFER_LEN 1024 + +/* A sysex buffer has 3 DWORDS as a header plus the actual message size */ +#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3) +/* A MIDIHDR with a sysex message is the buffer length plus the header size */ +#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_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 winmm_info_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 max_buffers; /* length of buffers array */ + int buffers_expanded; /* buffers array expanded for extra msgs? */ + int num_buffers; /* how many buffers allocated in buffers array */ + int next_buffer; /* index of next buffer to send */ + HANDLE buffer_signal; /* used to wait for 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 */ + 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 */ + CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */ +} winmm_info_node, *winmm_info_type; + + +/* +============================================================================= +general MIDI device queries +============================================================================= +*/ + +/* add a device after converting device (product) name to UTF-8 */ +static void pm_add_device_w(char *api, WCHAR *device_name, int is_input, + int is_virtual, void *descriptor, pm_fns_type dictionary) +{ + char utf8name[4 * MAXPNAMELEN]; + WideCharToMultiByte(CP_UTF8, 0, device_name, -1, + utf8name, 4 * MAXPNAMELEN - 1, NULL, NULL); + /* ignore errors here -- if pm_descriptor_max is exceeded, + some devices will not be accessible. */ + pm_add_device(api, utf8name, is_input, is_virtual, descriptor, dictionary); +} + + +static void pm_winmm_general_inputs() +{ + UINT i; + WORD wRtn; + midi_num_inputs = midiInGetNumDevs(); + midi_in_caps = (MIDIINCAPS *) 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. + */ + return; + } + + for (i = 0; i < midi_num_inputs; i++) { + wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i], + sizeof(MIDIINCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device_w("MMSystem", midi_in_caps[i].szPname, TRUE, FALSE, + (void *) (intptr_t) 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 form of + setup info. */ + wRtn = midiInGetDevCaps((UINT) MIDIMAPPER, + (LPMIDIINCAPS) & midi_in_mapper_caps, + sizeof(MIDIINCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device_w("MMSystem", midi_in_mapper_caps.szPname, TRUE, FALSE, + (void *) (intptr_t) 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_w("MMSystem", midi_out_caps[i].szPname, FALSE, FALSE, + (void *) (intptr_t) 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_w("MMSystem", midi_out_mapper_caps.szPname, FALSE, FALSE, + (void *) (intptr_t) MIDIMAPPER, + &pm_winmm_out_dictionary); + } +} + + +/* +============================================================================ +host error handling +============================================================================ +*/ + +static unsigned int winmm_check_host_error(PmInternal *midi) +{ + return FALSE; +} + + +/* +============================================================================= +buffer handling +============================================================================= +*/ +static MIDIHDR *allocate_buffer(long data_size) +{ + 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); + hdr->dwBytesRecorded = 0; + hdr->dwFlags = 0; + hdr->dwUser = hdr->dwBufferLength; + return hdr; +} + + +static PmError allocate_buffers(winmm_info_type info, long data_size, + long count) +{ + int i; + /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + info->num_buffers = 0; /* in case no memory can be allocated */ + info->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!info->buffers) return pmInsufficientMemory; + info->max_buffers = count; + for (i = 0; i < count; i++) { + LPMIDIHDR hdr = allocate_buffer(data_size); + if (!hdr) { /* free everything allocated so far and return */ + for (i = i - 1; i >= 0; i--) pm_free(info->buffers[i]); + pm_free(info->buffers); + info->max_buffers = 0; + return pmInsufficientMemory; + } + info->buffers[i] = hdr; /* this may be NULL if allocation fails */ + } + info->num_buffers = count; + return pmNoError; +} + + +static LPMIDIHDR get_free_output_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + winmm_info_type info = (winmm_info_type) midi->api_info; + while (TRUE) { + int i; + for (i = 0; i < info->num_buffers; i++) { + /* cycle through buffers, modulo info->num_buffers */ + info->next_buffer++; + if (info->next_buffer >= info->num_buffers) info->next_buffer = 0; + r = info->buffers[info->next_buffer]; + if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer; + } + /* after scanning every buffer and not finding anything, block */ + if (WaitForSingleObject(info->buffer_signal, 1000) == WAIT_TIMEOUT) { +#ifdef MMDEBUG + printf("PortMidi warning: get_free_output_buffer() " + "wait timed out after 1000ms\n"); +#endif + /* if we're trying to send a sysex message, maybe the + * message is too big and we need more message buffers. + * Expand the buffer pool by 128KB using 1024-byte buffers. + */ + /* first, expand the buffers array if necessary */ + if (!info->buffers_expanded) { + LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc( + (info->num_buffers + NUM_EXPANSION_BUFFERS) * + sizeof(LPMIDIHDR)); + /* if no memory, we could return a no-memory error, but user + * probably will be unprepared to deal with it. Maybe the + * MIDI driver is temporarily hung so we should just wait. + * I don't know the right answer, but waiting is easier. + */ + if (!new_buffers) continue; + /* copy buffers to new_buffers and replace buffers */ + memcpy(new_buffers, info->buffers, + info->num_buffers * sizeof(LPMIDIHDR)); + pm_free(info->buffers); + info->buffers = new_buffers; + info->max_buffers = info->num_buffers + NUM_EXPANSION_BUFFERS; + info->buffers_expanded = TRUE; + } + /* next, add one buffer and return it */ + if (info->num_buffers < info->max_buffers) { + r = allocate_buffer(EXPANSION_BUFFER_LEN); + /* again, if there's no memory, we may not really be + * dead -- maybe the system is temporarily hung and + * we can just wait longer for a message buffer */ + if (!r) continue; + info->buffers[info->num_buffers++] = r; + goto found_buffer; /* break out of 2 loops */ + } + /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers, + * and we have no free buffers to send. We'll just keep + * polling to see if any buffers show up. + */ + } + } +found_buffer: + r->dwBytesRecorded = 0; + /* actual buffer length is saved in dwUser field */ + r->dwBufferLength = (DWORD) r->dwUser; + return r; +} + +/* +============================================================================ +begin midi input implementation +============================================================================ +*/ + + +static unsigned int allocate_input_buffer(HMIDIIN h, long buffer_len) +{ + LPMIDIHDR hdr = allocate_buffer(buffer_len); + if (!hdr) return pmInsufficientMemory; + /* note: pm_hosterror is normally a boolean, but here, we store Win + * error code. The caller must test the value for nonzero, set + * pm_hosterror_text, and then set pm_hosterror to TRUE */ + pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) { + pm_free(hdr); + return pm_hosterror; + } + pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR)); + return pm_hosterror; +} + + +static winmm_info_type winmm_info_create() +{ + winmm_info_type info = (winmm_info_type) pm_alloc(sizeof(winmm_info_node)); + info->handle.in = NULL; + info->handle.out = NULL; + info->buffers = NULL; /* not used for input */ + info->num_buffers = 0; /* not used for input */ + info->max_buffers = 0; /* not used for input */ + info->buffers_expanded = FALSE; /* not used for input */ + info->next_buffer = 0; /* not used for input */ + info->buffer_signal = 0; /* not used for input */ + info->last_time = 0; + info->first_message = TRUE; /* not used for input */ + info->sysex_mode = FALSE; + info->sysex_word = 0; + info->sysex_byte_count = 0; + info->hdr = NULL; /* not used for input */ + info->sync_time = 0; + info->delta = 0; + return info; +} + + +static void report_hosterror(LPWCH error_msg) +{ + WideCharToMultiByte(CP_UTF8, 0, error_msg, -1, pm_hosterror_text, + sizeof(pm_hosterror_text), NULL, NULL); + if (pm_hosterror == MMSYSERR_NOMEM) { + /* add explanation to Window's confusing error message */ + /* if there's room: */ + if (PM_HOST_ERROR_MSG_LEN - strlen(pm_hosterror_text) > 60) { + strcat_s(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, + " Probably this MIDI device is open " + "in another application."); + } + } + pm_hosterror = TRUE; +} + + +static void report_hosterror_in() +{ + WCHAR error_msg[PM_HOST_ERROR_MSG_LEN]; + int err = midiInGetErrorText(pm_hosterror, error_msg, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + report_hosterror(error_msg); +} + + +static void report_hosterror_out() +{ + WCHAR error_msg[PM_HOST_ERROR_MSG_LEN]; + int err = midiOutGetErrorText(pm_hosterror, error_msg, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + report_hosterror(error_msg); +} + + +static PmError winmm_in_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + int max_sysex_len = midi->buffer_len * 4; + int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN; + winmm_info_type info; + + dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor; + + /* create system dependent device data */ + info = winmm_info_create(); + midi->api_info = info; + if (!info) goto no_memory; + /* 4000 is based on Windows documentation -- that's the value used + in the memory manager. It's small enough that it should not + hurt performance even if it's not optimal. + */ + InitializeCriticalSectionAndSpinCount(&info->lock, 4000); + /* open device */ + pm_hosterror = midiInOpen( + &(info->handle.in), /* input device handle */ + dwDevice, /* device ID */ + (DWORD_PTR) winmm_in_callback, /* callback address */ + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback is a procedure */ + if (pm_hosterror) goto free_descriptor; + + if (num_input_buffers < MIN_INPUT_BUFFERS) + num_input_buffers = MIN_INPUT_BUFFERS; + for (i = 0; i < num_input_buffers; i++) { + if (allocate_input_buffer(info->handle.in, INPUT_SYSEX_LEN)) { + /* either pm_hosterror was set, or the proper return code + is pmInsufficientMemory */ + goto close_device; + } + } + /* start device */ + pm_hosterror = midiInStart(info->handle.in); + if (!pm_hosterror) { + return pmNoError; + } + + /* undo steps leading up to the detected error */ + + /* ignore return code (we already have an error to report) */ + midiInReset(info->handle.in); +close_device: + midiInClose(info->handle.in); /* ignore return code */ +free_descriptor: + midi->api_info = NULL; + pm_free(info); +no_memory: + if (pm_hosterror) { + report_hosterror_in(); + 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) +{ + winmm_info_type info = (winmm_info_type) midi->api_info; + if (!info) return pmBadPtr; + /* device to close */ + if ((pm_hosterror = midiInStop(info->handle.in))) { + midiInReset(info->handle.in); /* try to reset and close port */ + midiInClose(info->handle.in); + } else if ((pm_hosterror = midiInReset(info->handle.in))) { + midiInClose(info->handle.in); /* best effort to close midi port */ + } else { + pm_hosterror = midiInClose(info->handle.in); + } + midi->api_info = NULL; + DeleteCriticalSection(&info->lock); + pm_free(info); /* delete */ + if (pm_hosterror) { + report_hosterror_in(); + 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 */ + UINT wMsg, /* midi msg */ + DWORD_PTR dwInstance, /* application data */ + DWORD_PTR dwParam1, /* MIDI data */ + DWORD_PTR dwParam2) /* device timestamp (wrt most recent midiInStart) */ +{ + PmInternal *midi = (PmInternal *) dwInstance; + winmm_info_type info = (winmm_info_type) midi->api_info; + + /* NOTE: we do not just EnterCriticalSection() here because an + * MIM_CLOSE message arrives when the port is closed, but then + * the info->lock has been destroyed. + */ + + switch (wMsg) { + case MIM_DATA: { + /* if this callback is reentered with data, we're in trouble. + * It's hard to imagine that Microsoft would allow callbacks + * to be reentrant -- isn't the model that this is like a + * hardware interrupt? -- but I've seen reentrant behavior + * using a debugger, so it happens. + */ + EnterCriticalSection(&info->lock); + + /* 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 */ + + if ((dwParam1 & 0x80) == 0) { + /* not a status byte -- ignore it. This happened running the + sysex.c test under Win2K with MidiMan USB 1x1 interface, + but I can't reproduce it. -RBD + */ + /* printf("non-status byte found\n"); */ + } else { /* data to process */ + PmEvent event; + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + event.timestamp = (PmTimestamp)dwParam2; + event.message = (PmMessage)dwParam1; + pm_read_short(midi, &event); + } + LeaveCriticalSection(&info->lock); + break; + } + case MIM_LONGDATA: { + MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1; + unsigned char *data = (unsigned char *) lpMidiHdr->lpData; + unsigned int processed = 0; + int remaining = lpMidiHdr->dwBytesRecorded; + + EnterCriticalSection(&info->lock); + /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", + lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */ + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + /* can there be more than one message in one buffer? */ + /* assume yes and iterate through them */ + pm_read_bytes(midi, data + processed, remaining, (PmTimestamp)dwParam2); + + /* 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) { + MMRESULT rslt; + lpMidiHdr->dwBytesRecorded = 0; + lpMidiHdr->dwFlags = 0; + + /* note: no error checking -- can this actually fail? */ + rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); + assert(rslt == MMSYSERR_NOERROR); + /* note: I don't think this can fail except possibly for + * MMSYSERR_NOMEM, but the pain of reporting this + * unlikely but probably catastrophic error does not seem + * worth it. + */ + rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); + assert(rslt == MMSYSERR_NOERROR); + LeaveCriticalSection(&info->lock); + } else { + midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR)); + LeaveCriticalSection(&info->lock); + pm_free(lpMidiHdr); + } + break; + } + case MIM_OPEN: + break; + case MIM_CLOSE: + break; + case MIM_ERROR: + /* printf("MIM_ERROR\n"); */ + break; + case MIM_LONGERROR: + /* printf("MIM_LONGERROR\n"); */ + break; + default: + break; + } +} + +/* +=========================================================================== +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(winmm_info_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; +} + + +static PmTimestamp pm_time_get(winmm_info_type info) +{ + MMTIME mmtime; + MMRESULT wRtn; + mmtime.wType = TIME_TICKS; + mmtime.u.ticks = 0; + wRtn = midiStreamPosition(info->handle.stream, &mmtime, sizeof(mmtime)); + assert(wRtn == MMSYSERR_NOERROR); + return mmtime.u.ticks; +} + + +/* end helper routines used by midiOutStream interface */ + + +static PmError winmm_out_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + winmm_info_type info; + MIDIPROPTEMPO propdata; + MIDIPROPTIMEDIV divdata; + int max_sysex_len = midi->buffer_len * 4; + int output_buffer_len; + int num_buffers; + dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor; + + /* create system dependent device data */ + info = winmm_info_create(); + midi->api_info = info; + if (!info) goto no_memory; + /* create a signal */ + info->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); + /* this should only fail when there are very serious problems */ + assert(info->buffer_signal); + /* open device */ + if (midi->latency == 0) { + /* use simple midi out calls */ + pm_hosterror = midiOutOpen( + (LPHMIDIOUT) & info->handle.out, /* device Handle */ + dwDevice, /* device ID */ + /* note: same callback fn as for StreamOpen: */ + (DWORD_PTR) winmm_streamout_callback, /* callback fn */ + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback type */ + } else { + /* use stream-based midi output (schedulable in future) */ + pm_hosterror = midiStreamOpen( + &info->handle.stream, /* device Handle */ + (LPUINT) & dwDevice, /* device ID pointer */ + 1, /* reserved, must be 1 */ + (DWORD_PTR) winmm_streamout_callback, + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); + } + if (pm_hosterror != MMSYSERR_NOERROR) { + goto free_descriptor; + } + + if (midi->latency == 0) { + num_buffers = NUM_SIMPLE_SYSEX_BUFFERS; + output_buffer_len = max_sysex_len / num_buffers; + if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN) + output_buffer_len = MIN_SIMPLE_SYSEX_LEN; + } else { + num_buffers = max(midi->buffer_len, midi->latency / 2); + if (num_buffers < MIN_STREAM_BUFFERS) + num_buffers = MIN_STREAM_BUFFERS; + output_buffer_len = STREAM_BUFFER_LEN; + + propdata.cbStruct = sizeof(MIDIPROPTEMPO); + propdata.dwTempo = 480000; /* microseconds per quarter */ + pm_hosterror = midiStreamProperty(info->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(info->handle.stream, + (LPBYTE) & divdata, + MIDIPROP_SET | MIDIPROP_TIMEDIV); + if (pm_hosterror) goto close_device; + } + /* allocate buffers */ + if (allocate_buffers(info, output_buffer_len, num_buffers)) + goto free_buffers; + /* start device */ + if (midi->latency != 0) { + pm_hosterror = midiStreamRestart(info->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(info->handle.out); +free_descriptor: + midi->api_info = NULL; + winmm_out_delete(midi); /* frees buffers and m */ +no_memory: + if (pm_hosterror) { + report_hosterror_out(); + return pmHostError; + } + return pmInsufficientMemory; +} + + +/* winmm_out_delete -- carefully free data associated with midi */ +/**/ +static void winmm_out_delete(PmInternal *midi) +{ + int i; + /* delete system dependent device data */ + winmm_info_type info = (winmm_info_type) midi->api_info; + if (info) { + if (info->buffer_signal) { + /* don't report errors -- better not to stop cleanup */ + CloseHandle(info->buffer_signal); + } + /* if using stream output, free buffers */ + for (i = 0; i < info->num_buffers; i++) { + if (info->buffers[i]) pm_free(info->buffers[i]); + } + info->num_buffers = 0; + pm_free(info->buffers); + info->max_buffers = 0; + } + midi->api_info = NULL; + pm_free(info); /* delete */ +} + + +/* see comments for winmm_in_close */ +static PmError winmm_out_close(PmInternal *midi) +{ + winmm_info_type info = (winmm_info_type) midi->api_info; + if (info->handle.out) { + /* device to close */ + if (midi->latency == 0) { + pm_hosterror = midiOutClose(info->handle.out); + } else { + pm_hosterror = midiStreamClose(info->handle.stream); + } + /* regardless of outcome, free memory */ + winmm_out_delete(midi); + } + if (pm_hosterror) { + report_hosterror_out(); + return pmHostError; + } + return pmNoError; +} + + +static PmError winmm_out_abort(PmInternal *midi) +{ + winmm_info_type info = (winmm_info_type) midi->api_info; + + /* only stop output streams */ + if (midi->latency > 0) { + pm_hosterror = midiStreamStop(info->handle.stream); + if (pm_hosterror) { + report_hosterror_out(); + return pmHostError; + } + } + return pmNoError; +} + + +static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp) +{ + winmm_info_type info = (winmm_info_type) midi->api_info; + assert(info); + if (info->hdr) { + pm_hosterror = midiOutPrepareHeader(info->handle.out, info->hdr, + sizeof(MIDIHDR)); + if (pm_hosterror) { + /* do not send message */ + } else if (midi->latency == 0) { + /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded + * should be zero. This is set in get_free_sysex_buffer(). + * The msg length goes in dwBufferLength in spite of what + * Microsoft documentation says (or doesn't say). */ + info->hdr->dwBufferLength = info->hdr->dwBytesRecorded; + info->hdr->dwBytesRecorded = 0; + pm_hosterror = midiOutLongMsg(info->handle.out, info->hdr, + sizeof(MIDIHDR)); + } else { + pm_hosterror = midiStreamOut(info->handle.stream, info->hdr, + sizeof(MIDIHDR)); + } + midi->fill_base = NULL; + info->hdr = NULL; + if (pm_hosterror) { + report_hosterror_out(); + return pmHostError; + } + } + return pmNoError; +} + + +static PmError winmm_write_short(PmInternal *midi, PmEvent *event) +{ + winmm_info_type info = (winmm_info_type) midi->api_info; + PmError rslt = pmNoError; + assert(info); + + if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ + pm_hosterror = midiOutShortMsg(info->handle.out, event->message); + if (pm_hosterror) { + if (info->hdr) { /* device disconnect may delete hdr */ + info->hdr->dwFlags = 0; /* release the buffer */ + } + report_hosterror_out(); + return 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 + info->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < info->last_time) when = info->last_time; + delta = when - info->last_time; + info->last_time = when; + /* before we insert any data, we must have a buffer */ + if (info->hdr == NULL) { + /* stream interface: buffers allocated when stream is opened */ + info->hdr = get_free_output_buffer(midi); + } + full = add_to_buffer(info, info->hdr, delta, event->message); + /* note: winmm_write_flush sets pm_hosterror etc. on host error */ + if (full) rslt = winmm_write_flush(midi, when); + } + return rslt; +} + +#define winmm_begin_sysex winmm_write_flush +#ifndef winmm_begin_sysex +static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + winmm_info_type m = (winmm_info_type) midi->api_info; + PmError rslt = pmNoError; + + if (midi->latency == 0) { + /* do nothing -- it's handled in winmm_write_byte */ + } else { + /* sysex expects an empty sysex buffer, so send whatever is here */ + rslt = winmm_write_flush(midi); + } + return rslt; +} +#endif + +static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + /* could check for callback_error here, but I haven't checked + * what happens if we exit early and don't finish the sysex msg + * and clean up + */ + winmm_info_type info = (winmm_info_type) midi->api_info; + PmError rslt = pmNoError; + LPMIDIHDR hdr = info->hdr; + if (!hdr) return rslt; /* something bad happened earlier, + do not report an error because it would have been + reported (at least) once already */ + /* a(n old) version of MIDI YOKE requires a zero byte after + * the sysex message, but do not increment dwBytesRecorded: */ + hdr->lpData[hdr->dwBytesRecorded] = 0; + if (midi->latency == 0) { +#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX + /* DEBUG CODE: */ + { int i; int len = info->hdr->dwBufferLength; + printf("OutLongMsg %d ", len); + for (i = 0; i < len; i++) { + printf("%2x ", (unsigned char) (info->hdr->lpData[i])); + } + } +#endif + } else { + /* Using stream interface. There are accumulated bytes in info->hdr + to send using midiStreamOut + */ + /* 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; + } + rslt = winmm_write_flush(midi, timestamp); + return rslt; +} + + +static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp) +{ + /* write a sysex byte */ + PmError rslt = pmNoError; + winmm_info_type info = (winmm_info_type) midi->api_info; + LPMIDIHDR hdr = info->hdr; + unsigned char *msg_buffer; + assert(info); + if (!hdr) { + info->hdr = hdr = get_free_output_buffer(midi); + assert(hdr); + midi->fill_base = (unsigned char *) info->hdr->lpData; + midi->fill_offset_ptr = (uint32_t *) &(hdr->dwBytesRecorded); + /* when buffer fills, Pm_WriteSysEx will revert to calling + * pmwin_write_byte, which expect to have space, so leave + * one byte free for pmwin_write_byte. Leave another byte + * of space for zero after message to make early version of + * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */ + midi->fill_length = hdr->dwBufferLength - 2; + if (midi->latency != 0) { + 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 + info->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < info->last_time) when = info->last_time; + delta = when - info->last_time; + info->last_time = when; + + ptr = (unsigned long *) hdr->lpData; + *ptr++ = delta; + *ptr++ = 0; + *ptr = MEVT_F_LONG; + hdr->dwBytesRecorded = 3 * sizeof(long); + /* data will be added at an offset of dwBytesRecorded ... */ + } + } + /* add the data byte */ + msg_buffer = (unsigned char *) (hdr->lpData); + msg_buffer[hdr->dwBytesRecorded++] = byte; + + /* see if buffer is full, leave one byte extra for pad */ + if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) { + /* write what we've got and continue */ + rslt = winmm_end_sysex(midi, timestamp); + } + return rslt; +} + + +static PmTimestamp winmm_synchronize(PmInternal *midi) +{ + winmm_info_type info; + 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 */ + info = (winmm_info_type) midi->api_info; + pm_stream_time_2 = pm_time_get(info); + + 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(info); + /* repeat if more than 1ms elapsed */ + } while (pm_stream_time_2 > pm_stream_time + 1); + info->delta = pm_stream_time - real_time; + info->sync_time = real_time; + return real_time; +} + + +/* winmm_streamout_callback -- unprepare (free) buffer header */ +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + PmInternal *midi = (PmInternal *) dwInstance; + winmm_info_type info = (winmm_info_type) midi->api_info; + LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; + int err; + + /* Even if an error is pending, I think we should unprepare msgs and + signal their arrival + */ + /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", + hdr, wMsg, MOM_DONE); */ + if (wMsg == MOM_DONE) { + MMRESULT ret = midiOutUnprepareHeader(info->handle.out, hdr, + sizeof(MIDIHDR)); + assert(ret == MMSYSERR_NOERROR); + } else if (wMsg == MOM_CLOSE) { + /* The streaming API gets a callback when the device is closed. + * The non-streaming API gets a callback when the device is + * removed or closed. It is misleading to set is_removed when + * the device is closed normally, but in that case, midi itself + * will be freed immediately, so there should be no way to + * observe is_removed == TRUE. On the other hand, if the device + * is removed, setting is_removed will cause PortMidi to return + * the pmDeviceRemoved error on attempts to output to the device. + * In the case of normal closing, due to midiOutClose(), + * the call below is reentrant (!), but for some reason this does + * not cause an error or infinite recursion, so we are not taking + * any precautions to flag midi as "in the process of closing." + */ + midi->is_removed = TRUE; + midiOutClose(info->handle.out); + } + /* signal client in case it is blocked waiting for buffer */ + err = SetEvent(info->buffer_signal); + assert(err); /* false -> error */ +} + + +/* +=========================================================================== +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_check_host_error + }; + +pm_fns_node pm_winmm_out_dictionary = { + winmm_write_short, + winmm_begin_sysex, + winmm_end_sysex, + winmm_write_byte, + /* short realtime message: */ winmm_write_short, + winmm_write_flush, + winmm_synchronize, + winmm_out_open, + winmm_out_abort, + winmm_out_close, + none_poll, + winmm_check_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 MMDEBUG + int doneAny = 0; + printf("pm_winmm_term called\n"); +#endif + for (i = 0; i < pm_descriptor_len; i++) { + PmInternal *midi = pm_descriptors[i].pm_internal; + if (midi) { + winmm_info_type info = (winmm_info_type) midi->api_info; + if (info->handle.out) { + /* close next open device*/ +#ifdef MMDEBUG + if (doneAny == 0) { + printf("begin closing open devices...\n"); + doneAny = 1; + } +#endif + /* close all open ports */ + (*midi->dictionary->close)(midi); + } + } + } + if (midi_in_caps) { + pm_free(midi_in_caps); + midi_in_caps = NULL; + } + if (midi_out_caps) { + pm_free(midi_out_caps); + midi_out_caps = NULL; + } +#ifdef MMDEBUG + if (doneAny) { + printf("warning: devices were left open. They have been closed.\n"); + } + printf("pm_winmm_term exiting\n"); +#endif + pm_descriptor_len = 0; +} -- cgit v1.2.3