From cde1ee8fa147dfd15dc5c5b43093cd8c8a402b74 Mon Sep 17 00:00:00 2001 From: Miller Puckette Date: Wed, 16 Jan 2008 21:54:11 +0000 Subject: 0.41-0 test 11 svn path=/trunk/; revision=9147 --- pd/portmidi/pm_win/README_WIN.txt | 134 ++++- pd/portmidi/pm_win/copy-dll.bat | 4 + pd/portmidi/pm_win/pm_dll.dsp | 10 +- pd/portmidi/pm_win/pmwin.c | 1 + pd/portmidi/pm_win/pmwinmm.c | 1100 +++++++++++++++++-------------------- 5 files changed, 641 insertions(+), 608 deletions(-) (limited to 'pd/portmidi/pm_win') diff --git a/pd/portmidi/pm_win/README_WIN.txt b/pd/portmidi/pm_win/README_WIN.txt index df120e96..3fe5ee41 100644 --- a/pd/portmidi/pm_win/README_WIN.txt +++ b/pd/portmidi/pm_win/README_WIN.txt @@ -20,7 +20,8 @@ intended for debugging, especially in a console application. The Debug version enables some extra error checking and outputs some text as well as a prompt to type ENTER so that you don't lose any debugging text when the program exits. You can turn off this extra debugging info by taking -out the compile-time definition for DEBUG. This debugging version also +out the compile-time definition for DEBUG. (But leave _DEBUG, which I +think is important for compiling in Debug mode.) This debugging version also defines PM_CHECK_ERRORS, which forces a check for error return codes from every call to PortMidi. You can disable this checking (especially if you want to handle error codes in your own way) by removing PM_CHECK_ERRORS @@ -45,9 +46,9 @@ TO INSTALL PORTMIDI: TO COMPILE PORTMIDI: ============================================================================= -3) go to this directory +3) cd to or open the portmidi directory -4) click on the portmidi.dsw workspace +4) start or click on the portmidi.dsw workspace 5) the following projects exist within this workspace: - portmidi (the PortMidi library) @@ -60,18 +61,16 @@ TO COMPILE PORTMIDI: - latency (uses porttime to measure system latency) 6) verify that all project settings are for Win32 Debug release: - - hit Alt-F7 + - type Alt-F7 - highlight all three projects in left part of Project Settings window; - "Settings For" should say "Win32 Debug" -7) set pm_dll as the active project (e.g. Project->Select Active Project) +7) use Build->Batch Build ... to build everything in the project -8) use Build->Batch Build ... to build everything in the project - -9) The settings for these projects were distributed in the zip file, so +8) The settings for these projects were distributed in the zip file, so compile should just work. -10) IMPORTANT! PortMidi uses a DLL, pm_dll.dll, but there is no simple way +9) IMPORTANT! PortMidi uses a DLL, pm_dll.dll, but there is no simple way to set up projects to use pm_dll. THEREFORE, you need to copy DLLs as follows (you can do this with <...>\portmidi\pm_win\copy-dll.bat): copy <...>\portmidi\pm_win\Debug\pm_dll.dll to: @@ -94,13 +93,13 @@ TO COMPILE PORTMIDI: application using PortMidi. The release DLL is about 40KB. This will ensure that the application uses the correct DLL. -11) run test project; use the menu that shows up from the command prompt to +10) run test project; use the menu that shows up from the command prompt to test that portMidi works on your system. tests include: - verify midi output works - verify midi input works - verify midi input w/midi thru works -12) run other projects if you wish: sysex, latency, and midithread +11) run other projects if you wish: sysex, latency, midithread, mm, qtest ============================================================================ TO CREATE YOUR OWN PORTMIDI CLIENT APPLICATION: @@ -179,5 +178,114 @@ To open input: - return - return - - +SYSEX HANDLING -- the most complex, least exercised, and therefore most + buggy part of PortMidi (but maybe bugs are finally gone) + +There are three cases: simple output, stream output, input +Each must deal with: + 1. Buffer Initialization (creating buffers) + 2. Buffer Allocation (finding a free buffer) + 3. Buffer Fill (putting bytes in the buffer) + 4. Buffer Preparation (midiOutPrepare, etc.) + 5. Buffer Send (to Midi device) + 6. Buffer Receive (in callback) + 7. Buffer Empty (removing bytes from buffer) + 8. Buffer Free (returning to the buffer pool) + 9. Buffer Finalization (returning to heap) + +Here's how simple output handles sysex: + 1. Buffer Initialization (creating buffers) + allocated when code tries to write first byte to a buffer + the test is "if (!m->sysex_buffers[0]) { ... }" + this field is initialized to NULL when device is opened + the size is SYSEX_BYTES_PER_BUFFER + allocate_sysex_buffers() does the initialization + note that the actual size of the allocation includes + additional space for a MIDIEVENT (3 longs) which are + not used in this case + 2. Buffer Allocation (finding a free buffer) + see get_free_sysex_buffer() + cycle through m->sysex_buffers[] using m->next_sysex_buffer + to determine where to look next + if nothing is found, wait by blocking on m->sysex_buffer_signal + this is signaled by the callback every time a message is + received + 3. Buffer Fill (putting bytes in the buffer) + essentially a state machine approach + hdr->dwBytesRecorded is a position in message pointed to by m->hdr + keep appending bytes until dwBytesRecorded >= SYSEX_BYTES_PER_BUFFER + then send the message, reseting the state to initial values + 4. Buffer Preparation (midiOutPrepare, etc.) + just before sending in winmm_end_sysex() + 5. Buffer Send (to Midi device) + message is padded with zero at end (since extra space was allocated + this is ok) -- the zero works around a bug in (an old version of) + MIDI YOKE drivers + dwBufferLength gets dwBytesRecorded, and dwBytesRecorded gets 0 + uses midiOutLongMsg() + 6. Buffer Receive (in callback) + 7. Buffer Empty (removing bytes from buffer) + not applicable for output + 8. Buffer Free (returning to the buffer pool) + unprepare message to indicate that it is free + SetEvent on m->buffer_signal in case client is waiting + 9. Buffer Finalization (returning to heap) + when device is closed, winmm_out_delete frees all sysex buffers + +Here's how stream output handles sysex: + 1. Buffer Initialization (creating buffers) + same code as simple output (see above) + 2. Buffer Allocation (finding a free buffer) + same code as simple output (see above) + 3. Buffer Fill (putting bytes in the buffer) + essentially a state machine approach + m->dwBytesRecorded is a position in message + keep appending bytes until buffer is full (one byte to spare) + 4. Buffer Preparation (midiOutPrepare, etc.) + done before sending message + dwBytesRecorded and dwBufferLength are set in winmm_end_sysex + 5. Buffer Send (to Midi device) + uses midiStreamOutMsg() + 6. Buffer Receive (in callback) + 7. Buffer Empty (removing bytes from buffer) + not applicable for output + 8. Buffer Free (returning to the buffer pool) + unprepare message to indicate that it is free + SetEvent on m->buffer_signal in case client is waiting + 9. Buffer Finalization (returning to heap) + when device is closed, winmm_out_delete frees all sysex buffers + + +Here's how input handles sysex: + 1. Buffer Initialization (creating buffers) + two buffers are allocated in winmm_in_open + 2. Buffer Allocation (finding a free buffer) + same code as simple output (see above) + 3. Buffer Fill (putting bytes in the buffer) + not applicable for input + 4. Buffer Preparation (midiOutPrepare, etc.) + done before sending message -- in winmm_in_open and in callback + 5. Buffer Send (to Midi device) + uses midiInAddbuffer in allocate_sysex_input_buffer (called from + winmm_in_open) and callback + 6. Buffer Receive (in callback) + 7. Buffer Empty (removing bytes from buffer) + done without pause in loop in callback + 8. Buffer Free (returning to the buffer pool) + done by midiInAddBuffer in callback, no pointer to buffers + is retained except by device + 9. Buffer Finalization (returning to heap) + when device is closed, empty buffers are delivered to callback, + which frees them + +IMPORTANT: In addition to the above, PortMidi now has +"shortcuts" to optimize the transfer of sysex data. To enable +the optimization for sysex output, the system-dependent code +sets fields in the pmInternal structure: fill_base, fill_offset_ptr, +and fill_length. When fill_base is non-null, the system-independent +part of PortMidi is allowed to directly copy sysex bytes to +"fill_base[*fill_offset_ptr++]" until *fill_offset_ptr reaches +fill_length. See the code for details. + + + \ No newline at end of file diff --git a/pd/portmidi/pm_win/copy-dll.bat b/pd/portmidi/pm_win/copy-dll.bat index 34ccbedd..7c55fff3 100644 --- a/pd/portmidi/pm_win/copy-dll.bat +++ b/pd/portmidi/pm_win/copy-dll.bat @@ -3,11 +3,15 @@ copy Debug\pm_dll.dll ..\pm_test\sysexDebug\pm_dll.dll copy Debug\pm_dll.dll ..\pm_test\midithreadDebug\pm_dll.dll copy Debug\pm_dll.dll ..\pm_test\latencyDebug\pm_dll.dll copy Debug\pm_dll.dll ..\pm_test\midithruDebug\pm_dll.dll +copy Debug\pm_dll.dll ..\pm_test\qtestDebug\pm_dll.dll +copy Debug\pm_dll.dll ..\pm_test\mmDebug\pm_dll.dll copy Release\pm_dll.dll ..\pm_test\testRelease\pm_dll.dll copy Release\pm_dll.dll ..\pm_test\sysexRelease\pm_dll.dll copy Release\pm_dll.dll ..\pm_test\midithreadRelease\pm_dll.dll copy Release\pm_dll.dll ..\pm_test\latencyRelease\pm_dll.dll copy Release\pm_dll.dll ..\pm_test\midithruRelease\pm_dll.dll +copy Release\pm_dll.dll ..\pm_test\qtestRelease\pm_dll.dll +copy Release\pm_dll.dll ..\pm_test\mmRelease\pm_dll.dll diff --git a/pd/portmidi/pm_win/pm_dll.dsp b/pd/portmidi/pm_win/pm_dll.dsp index d08e2de7..77218ccb 100644 --- a/pd/portmidi/pm_win/pm_dll.dsp +++ b/pd/portmidi/pm_win/pm_dll.dsp @@ -38,8 +38,8 @@ RSC=rc.exe # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 -# PROP Output_Dir "pm_win\Release" -# PROP Intermediate_Dir "pm_win\Release" +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /c @@ -64,12 +64,12 @@ LINK32=link.exe # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 -# PROP Output_Dir "pm_win\Debug" -# PROP Intermediate_Dir "pm_win\Debug" +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" # PROP Ignore_Export_Lib 1 # PROP Target_Dir "" # ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /GZ /c -# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "pm_common" /D "_WINDOWS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "pm_common" /D "_WINDOWS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c # ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x409 /d "_DEBUG" diff --git a/pd/portmidi/pm_win/pmwin.c b/pd/portmidi/pm_win/pmwin.c index b289194b..716b68fa 100644 --- a/pd/portmidi/pm_win/pmwin.c +++ b/pd/portmidi/pm_win/pmwin.c @@ -13,6 +13,7 @@ #include "stdlib.h" #include "portmidi.h" +#include "pmutil.h" #include "pminternal.h" #include "pmwinmm.h" #ifdef USE_DLL_FOR_CLEANUP diff --git a/pd/portmidi/pm_win/pmwinmm.c b/pd/portmidi/pm_win/pmwinmm.c index 5bfb0cff..395b1134 100644 --- a/pd/portmidi/pm_win/pmwinmm.c +++ b/pd/portmidi/pm_win/pmwinmm.c @@ -1,8 +1,15 @@ /* pmwinmm.c -- system specific definitions */ +/* without this define, InitializeCriticalSectionAndSpinCount is undefined */ +/* this version level means "Windows 2000 and higher" */ +#define _WIN32_WINNT 0x0500 + #include "windows.h" #include "mmsystem.h" #include "portmidi.h" +#ifdef NEWBUFFER +#include "pmutil.h" +#endif #include "pminternal.h" #include "pmwinmm.h" #include "string.h" @@ -11,7 +18,7 @@ /* asserts used to verify portMidi code logic is sound; later may want something more graceful */ #include - +#define DEBUG 1 #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 @@ -27,25 +34,67 @@ /* callback routines */ static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn, - WORD wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); + WORD wMsg, DWORD dwInstance, + DWORD dwParam1, DWORD dwParam2); static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, - DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); + DWORD dwInstance, DWORD dwParam1, + DWORD dwParam2); static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, - DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); + 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 +/* +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)) -#define MIDIHDR_BUFFER_LENGTH(x) (x) -#define MIDIHDR_SIZE(x) (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) +#ifdef USE_SYSEX_BUFFERS +/* Size of a MIDIHDR with a buffer contaning multiple MIDIEVENT structures */ +#define MIDIHDR_SIZE(x) ((x) + sizeof(MIDIHDR)) +#endif /* ============================================================================== @@ -62,9 +111,7 @@ MIDIOUTCAPS midi_out_mapper_caps; UINT midi_num_outputs = 0; /* per device info */ -typedef struct midiwinmm_struct -{ - +typedef struct midiwinmm_struct { union { HMIDISTRM stream; /* windows handle for stream */ HMIDIOUT out; /* windows handle for out calls */ @@ -75,28 +122,30 @@ typedef struct midiwinmm_struct * 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 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 */ - - LPMIDIHDR *sysex_buffers; /* pool of buffers for sysex data */ - int num_sysex_buffers; /* how many sysex buffers */ +#ifdef USE_SYSEX_BUFFERS + /* sysex buffers will be allocated only when + * a sysex message is sent. The size of the buffer is fixed. + */ + LPMIDIHDR sysex_buffers[NUM_SYSEX_BUFFERS]; /* pool of buffers for sysex data */ int next_sysex_buffer; /* index of next sysexbuffer to send */ - HANDLE sysex_buffer_signal; /* used to wait for sysex buffer to become free */ - +#endif 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 */ + 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 */ int error; /* host error from doing port midi call */ - int callback_error; /* host error from midi in or out callback */ -} -midiwinmm_node, *midiwinmm_type; + CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */ +} midiwinmm_node, *midiwinmm_type; /* @@ -112,11 +161,11 @@ static void pm_winmm_general_inputs() 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 ; + /* 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++) { @@ -140,7 +189,8 @@ static void pm_winmm_mapper_input() capabilities) then you still should retrieve some formof setup info. */ wRtn = midiInGetDevCaps((UINT) MIDIMAPPER, - (LPMIDIINCAPS) & midi_in_mapper_caps, sizeof(MIDIINCAPS)); + (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); @@ -156,7 +206,7 @@ static void pm_winmm_general_outputs() midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs ); if (midi_out_caps == NULL) { - // no error is reported -- see pm_winmm_general_inputs + /* no error is reported -- see pm_winmm_general_inputs */ return ; } @@ -194,7 +244,7 @@ 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; + return m->error; } @@ -229,12 +279,6 @@ static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) 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 */ @@ -244,12 +288,6 @@ static void winmm_get_host_error(PmInternal * midi, char * msg, UINT 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; } } } @@ -263,132 +301,181 @@ 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)); + 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_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */ + hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); + hdr->dwBytesRecorded = 0; hdr->dwFlags = 0; - hdr->dwUser = 0; + hdr->dwUser = hdr->dwBufferLength; return hdr; } +#ifdef USE_SYSEX_BUFFERS 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 + /* we're actually allocating more than data_size because the buffer + * will include 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; } +#endif static PmError allocate_buffers(midiwinmm_type m, long data_size, long count) { - PmError rslt = pmNoError; + int i; /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + m->num_buffers = 0; /* in case no memory can be allocated */ m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); if (!m->buffers) return pmInsufficientMemory; - m->num_buffers = count; - while (count > 0) { + m->max_buffers = count; + for (i = 0; i < count; i++) { LPMIDIHDR hdr = allocate_buffer(data_size); - if (!hdr) rslt = pmInsufficientMemory; - count--; - m->buffers[count] = hdr; /* this may be NULL if allocation fails */ + if (!hdr) { /* free everything allocated so far and return */ + for (i = i - 1; i >= 0; i--) pm_free(m->buffers[i]); + pm_free(m->buffers); + m->max_buffers = 0; + return pmInsufficientMemory; + } + m->buffers[i] = hdr; /* this may be NULL if allocation fails */ } - return rslt; + m->num_buffers = count; + return pmNoError; } -static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size, long count) +#ifdef USE_SYSEX_BUFFERS +static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size) { 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) { + /* sysex_buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + int i; + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { LPMIDIHDR hdr = allocate_sysex_buffer(data_size); + if (!hdr) rslt = pmInsufficientMemory; - count--; - m->sysex_buffers[count] = hdr; /* this may be NULL if allocation fails */ + m->sysex_buffers[i] = hdr; /* this may be NULL if allocation fails */ + hdr->dwFlags = 0; /* mark as free */ } return rslt; } +#endif +#ifdef USE_SYSEX_BUFFERS 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)) { + if (!m->sysex_buffers[0]) { + if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER)) { return NULL; } } /* busy wait until we find a free buffer */ while (TRUE) { int i; - for (i = 0; i < m->num_sysex_buffers; i++) { + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { + /* cycle through buffers, modulo NUM_SYSEX_BUFFERS */ m->next_sysex_buffer++; - if (m->next_sysex_buffer >= m->num_sysex_buffers) m->next_sysex_buffer = 0; + if (m->next_sysex_buffer >= 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); + if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { +#ifdef DEBUG + printf("PortMidi warning: get_free_sysex_buffer() wait timed out after 1000ms\n"); +#endif + } } found_sysex_buffer: r->dwBytesRecorded = 0; - m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR)); + r->dwBufferLength = 0; /* changed to correct value later */ return r; } - +#endif 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++) { + /* cycle through buffers, modulo m->num_buffers */ 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); + if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { +#ifdef DEBUG + 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 (!m->buffers_expanded) { + LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc( + (m->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, m->buffers, + m->num_buffers * sizeof(LPMIDIHDR)); + pm_free(m->buffers); + m->buffers = new_buffers; + m->max_buffers = m->num_buffers + NUM_EXPANSION_BUFFERS; + m->buffers_expanded = TRUE; + } + /* next, add one buffer and return it */ + if (m->num_buffers < m->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; + m->buffers[m->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; - m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR)); + /* actual buffer length is saved in dwUser field */ + r->dwBufferLength = (DWORD) r->dwUser; return r; } +#ifdef EXPANDING_SYSEX_BUFFERS +note: this is not working code, but might be useful if you want + to grow sysex buffers. 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 */ + /* buffer must be smaller than 64k, but be also a multiple of 4 */ if (new_size > 65520) { if (old_size >= 65520) return pmBufferMaxSize; @@ -407,35 +494,54 @@ static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_siz /* make sure we're not going to overwrite any memory */ assert(old_size <= new_size); memcpy(big->lpData, m->hdr->lpData, old_size); - + /* keep track of how many sysex bytes are in message so far */ + big->dwBytesRecorded = m->hdr->dwBytesRecorded; + big->dwBufferLength = new_size; /* find which buffer this was, and replace it */ - - for (i = 0;i < m->num_sysex_buffers;i++) { + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { if (m->sysex_buffers[i] == m->hdr) { m->sysex_buffers[i] = big; + m->sysex_buffer_size[i] = new_size; pm_free(m->hdr); m->hdr = big; break; } } - assert(i != m->num_sysex_buffers); + assert(i != NUM_SYSEX_BUFFERS); return pmNoError; - } +#endif + /* ========================================================================================= begin midi input implementation ========================================================================================= */ + +static PmError allocate_input_buffer(HMIDIIN h, long buffer_len) +{ + LPMIDIHDR hdr = allocate_buffer(buffer_len); + if (!hdr) return pmInsufficientMemory; + 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 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; midiwinmm_type m; - LPMIDIHDR hdr; - long buffer_len; + dwDevice = (DWORD) descriptors[i].descriptor; /* create system dependent device data */ @@ -443,22 +549,31 @@ static PmError winmm_in_open(PmInternal *midi, void *driverInfo) 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->buffers = NULL; /* not used for input */ + m->num_buffers = 0; /* not used for input */ + m->max_buffers = FALSE; /* not used for input */ + m->buffers_expanded = 0; /* not used for input */ + m->next_buffer = 0; /* not used for input */ + m->buffer_signal = 0; /* not used for input */ +#ifdef USE_SYSEX_BUFFERS + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) + m->sysex_buffers[i] = NULL; /* not used for input */ + m->next_sysex_buffer = 0; /* not used for input */ +#endif 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->hdr = NULL; /* not used for input */ m->sync_time = 0; m->delta = 0; m->error = MMSYSERR_NOERROR; - m->callback_error = MMSYSERR_NOERROR; - + /* 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(&m->lock, 4000); /* open device */ pm_hosterror = midiInOpen(&(m->handle.in), /* input device handle */ dwDevice, /* device ID */ @@ -467,32 +582,15 @@ static PmError winmm_in_open(PmInternal *midi, void *driverInfo) 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 */ + 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(m->handle.in, INPUT_SYSEX_LEN)) { + /* either pm_hosterror was set, or the proper return code + is pmInsufficientMemory */ + goto close_device; + } } - 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; @@ -521,6 +619,12 @@ no_memory: to free the parameter midi */ } +static PmError winmm_in_poll(PmInternal *midi) { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + return m->error; +} + + /* winmm_in_close -- close an open midi input device */ /* @@ -540,6 +644,7 @@ static PmError winmm_in_close(PmInternal *midi) pm_hosterror = midiInClose(m->handle.in); } midi->descriptor = NULL; + DeleteCriticalSection(&m->lock); pm_free(m); /* delete */ if (pm_hosterror) { int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, @@ -563,85 +668,124 @@ static void FAR PASCAL winmm_in_callback( 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 ; - } + /* 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(&m->lock); 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; - } + /* 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 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); - - while (i < lpMidiHdr->dwBytesRecorded) { - /* collect bytes from *data into a word */ + 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 processed = 0; + int remaining = lpMidiHdr->dwBytesRecorded; + /* 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 */ + while (remaining > 0) { + unsigned int amt = pm_read_bytes(midi, data + processed, + remaining, dwParam2); + remaining -= amt; + processed += amt; + } +#ifdef DELETE_THIS + unsigned int i = 0; + long size = sizeof(MIDIHDR) + lpMidiHdr->dwBufferLength; + + while (i < lpMidiHdr->dwBytesRecorded) { + /* optimization: if message_count == 0, we are on an (output) + * message boundary so we can transfer data directly to the + * queue + */ + PmEvent event; + if (midi->sysex_message_count == 0 && + !midi->flush && + i <= lpMidiHdr->dwBytesRecorded - 4 && + ((event.message = (((long) data[0]) | + (((long) data[1]) << 8) | (((long) data[2]) << 16) | + (((long) data[3]) << 24))) & + 0x80808080) == 0) { /* all data, no status */ + event.timestamp = dwParam2; + if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { + midi->flush = TRUE; + } + i += 4; + data += 4; + /* non-optimized: process one byte at a time. This is used to + * handle any embedded SYSEX or EOX bytes and to finish */ + } else { 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 */ +#endif + /* 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) { + lpMidiHdr->dwBytesRecorded = 0; + lpMidiHdr->dwFlags = 0; + /* note: no error checking -- can this actually fail? */ + assert(midiInPrepareHeader(hMidiIn, lpMidiHdr, + sizeof(MIDIHDR)) == 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. + */ + assert(midiInAddBuffer(hMidiIn, lpMidiHdr, + sizeof(MIDIHDR)) == MMSYSERR_NOERROR); + } else { + 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; } - entry--; + LeaveCriticalSection(&m->lock); } /* @@ -668,28 +812,6 @@ static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, 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) { @@ -702,26 +824,6 @@ static PmTimestamp pm_time_get(midiwinmm_type m) 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 */ @@ -733,6 +835,9 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) midiwinmm_type m; MIDIPROPTEMPO propdata; MIDIPROPTIMEDIV divdata; + int max_sysex_len = midi->buffer_len * 4; + int output_buffer_len; + int num_buffers; dwDevice = (DWORD) descriptors[i].descriptor; /* create system dependent device data */ @@ -742,10 +847,14 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) m->handle.out = NULL; m->buffers = NULL; m->num_buffers = 0; + m->max_buffers = 0; + m->buffers_expanded = FALSE; m->next_buffer = 0; - m->sysex_buffers = NULL; - m->num_sysex_buffers = 0; +#ifdef USE_SYSEX_BUFFERS + m->sysex_buffers[0] = NULL; + m->sysex_buffers[1] = NULL; m->next_sysex_buffer = 0; +#endif m->last_time = 0; m->first_message = TRUE; /* we treat first message as special case */ m->sysex_mode = FALSE; @@ -755,22 +864,20 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) 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, + /* note: same callback fn as for StreamOpen: */ + (DWORD) winmm_streamout_callback, /* callback fn */ (DWORD) midi, /* callback instance data */ CALLBACK_FUNCTION); /* callback type */ } else { @@ -786,12 +893,17 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) goto free_descriptor; } - if (midi->latency != 0) { + 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 { 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 */ + 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 */ @@ -806,12 +918,12 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) (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 */ + } + /* allocate buffers */ + if (allocate_buffers(m, output_buffer_len, num_buffers)) + goto free_buffers; + /* start device */ + if (midi->latency != 0) { pm_hosterror = midiStreamRestart(m->handle.stream); if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers; } @@ -839,6 +951,7 @@ no_memory: /**/ static void winmm_out_delete(PmInternal *midi) { + int i; /* delete system dependent device data */ midiwinmm_type m = (midiwinmm_type) midi->descriptor; if (m) { @@ -846,27 +959,19 @@ static void winmm_out_delete(PmInternal *midi) /* 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 using stream output, free buffers */ + for (i = 0; i < m->num_buffers; i++) { + if (m->buffers[i]) pm_free(m->buffers[i]); } - - 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); + m->num_buffers = 0; + pm_free(m->buffers); + m->max_buffers = 0; +#ifdef USE_SYSEX_BUFFERS + /* free sysex buffers */ + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { + if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); } +#endif } midi->descriptor = NULL; pm_free(m); /* delete */ @@ -910,6 +1015,40 @@ static PmError winmm_out_abort(PmInternal *midi) return m->error ? pmHostError : pmNoError; } + +static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + assert(m); + if (m->hdr) { + m->error = midiOutPrepareHeader(m->handle.out, m->hdr, + sizeof(MIDIHDR)); + if (m->error) { + /* 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). */ + m->hdr->dwBufferLength = m->hdr->dwBytesRecorded; + m->hdr->dwBytesRecorded = 0; + m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); + } else { + m->error = midiStreamOut(m->handle.stream, m->hdr, + sizeof(MIDIHDR)); + } + midi->fill_base = NULL; + m->hdr = NULL; + if (m->error) { + m->hdr->dwFlags = 0; /* release the buffer */ + return pmHostError; + } + } + return pmNoError; +} + + + #ifdef GARBAGE static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte) { @@ -980,6 +1119,7 @@ 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; @@ -1000,73 +1140,65 @@ static PmError winmm_write_short(PmInternal *midi, PmEvent *event) 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; - } + 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) { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; 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; + /* 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 + */ midiwinmm_type m = (midiwinmm_type) midi->descriptor; PmError rslt = pmNoError; - assert(m); - + LPMIDIHDR hdr = m->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) { - /* 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; +#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX + /* DEBUG CODE: */ + { int i; int len = m->hdr->dwBufferLength; printf("OutLongMsg %d ", len); for (i = 0; i < len; i++) { - printf("%2x ", msg_buffer[i]); + printf("%2x ", (unsigned char) (m->hdr->lpData[i])); } } - */ - - m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); - if (m->error) rslt = pmHostError; - } else if (m->hdr) { +#endif + } else { /* 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); + MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData); + evt->dwEvent += 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; + hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; } - m->hdr = NULL; /* make sure we don't send it again */ + rslt = winmm_write_flush(midi, timestamp); return rslt; } @@ -1077,40 +1209,21 @@ static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, /* write a sysex byte */ PmError rslt = pmNoError; midiwinmm_type m = (midiwinmm_type) midi->descriptor; + LPMIDIHDR hdr = m->hdr; + unsigned char *msg_buffer; 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) { + if (!hdr) { + m->hdr = hdr = get_free_output_buffer(midi); + assert(hdr); + midi->fill_base = m->hdr->lpData; + midi->fill_offset_ptr = &(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; @@ -1122,52 +1235,47 @@ static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, 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 = (unsigned long *) hdr->lpData; *ptr++ = delta; *ptr++ = 0; *ptr = MEVT_F_LONG; - m->hdr->dwBytesRecorded = 3 * sizeof(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; +} + +#ifdef EXPANDING_SYSEX_BUFFERS +note: this code is here as an aid in case you want sysex buffers + to expand to hold large messages completely. If so, you + will want to change SYSEX_BYTES_PER_BUFFER above to some + variable that remembers the buffer size. A good place to + put this value would be in the hdr->dwUser field. - /* add the data byte */ - ptr = (unsigned char *) (m->hdr->lpData + m->hdr->dwBytesRecorded); - *ptr = byte; - full = ++m->hdr->dwBytesRecorded >= m->hdr->dwBufferLength; + rslt = resize_sysex_buffer(midi, m->sysex_byte_count, + m->sysex_byte_count * 2); - /* see if we need to resize */ - if (full) { - int bytesRecorded = m->hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ + if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */ +#endif +#ifdef EXPANDING_SYSEX_BUFFERS + int bytesRecorded = hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); - m->hdr->dwBytesRecorded = bytesRecorded; + 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; -} - +#endif -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) { @@ -1195,223 +1303,32 @@ static PmTimestamp winmm_synchronize(PmInternal *midi) 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 - - +#ifdef USE_SYSEX_BUFFERS /* winmm_out_callback -- recycle sysex buffers */ static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, - DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) + 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)); + printf("out_callback: hdr %x, wMsg %x, MOM_DONE %x\n", + hdr, wMsg, MOM_DONE); + if (wMsg == MOM_DONE) + assert(midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)) == MMSYSERR_NOERROR); /* 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; - } - } + err = SetEvent(m->buffer_signal); assert(err); /* false -> error */ - entry--; } - +#endif /* winmm_streamout_callback -- unprepare (free) buffer header */ static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, @@ -1421,24 +1338,19 @@ static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, 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 ; + + /* 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) { + assert(midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)) == MMSYSERR_NOERROR); } - m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr, - sizeof(MIDIHDR)); + /* signal client in case it is blocked waiting for buffer */ err = SetEvent(m->buffer_signal); assert(err); /* false -> error */ - entry--; } @@ -1460,7 +1372,7 @@ pm_fns_node pm_winmm_in_dictionary = { winmm_in_open, winmm_in_abort, winmm_in_close, - success_poll, + winmm_in_poll, winmm_has_host_error, winmm_get_host_error }; @@ -1529,7 +1441,7 @@ void pm_winmm_term( void ) trying to debug client app */ if (winmm_has_host_error(midi)) { winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN); - printf(msg); + printf("%s\n", msg); } #endif /* close all open ports */ @@ -1537,6 +1449,14 @@ void pm_winmm_term( void ) } } } + 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 DEBUG if (doneAny) { printf("warning: devices were left open. They have been closed.\n"); -- cgit v1.2.1