From 90d5b8b4a064420d74678654e94ea4755b377f21 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 15 Dec 2005 00:57:02 +0000 Subject: checking in missing files on behalf of Miller (cleared it with him first). The files are from portmidi17nov04.zip svn path=/trunk/; revision=4216 --- pd/portmidi/pm_win/README_WIN.txt | 183 ++++ pd/portmidi/pm_win/copy-dll.bat | 13 + pd/portmidi/pm_win/debugging_dlls.txt | 145 +++ pd/portmidi/pm_win/pm_dll.dsp | 107 +++ pd/portmidi/pm_win/pmdll.c | 49 ++ pd/portmidi/pm_win/pmdll.h | 5 + pd/portmidi/pm_win/pmwin.c | 113 +++ pd/portmidi/pm_win/pmwinmm.c | 1547 +++++++++++++++++++++++++++++++++ pd/portmidi/pm_win/pmwinmm.h | 5 + 9 files changed, 2167 insertions(+) create mode 100644 pd/portmidi/pm_win/README_WIN.txt create mode 100644 pd/portmidi/pm_win/copy-dll.bat create mode 100644 pd/portmidi/pm_win/debugging_dlls.txt create mode 100644 pd/portmidi/pm_win/pm_dll.dsp create mode 100644 pd/portmidi/pm_win/pmdll.c create mode 100644 pd/portmidi/pm_win/pmdll.h create mode 100644 pd/portmidi/pm_win/pmwin.c create mode 100644 pd/portmidi/pm_win/pmwinmm.c create mode 100644 pd/portmidi/pm_win/pmwinmm.h (limited to 'pd/portmidi/pm_win') diff --git a/pd/portmidi/pm_win/README_WIN.txt b/pd/portmidi/pm_win/README_WIN.txt new file mode 100644 index 00000000..df120e96 --- /dev/null +++ b/pd/portmidi/pm_win/README_WIN.txt @@ -0,0 +1,183 @@ +File: PortMidi Win32 Readme +Author: Belinda Thom, June 16 2002 +Revised by: Roger Dannenberg, June 2002, May 2004 + +============================================================================= +USING PORTMIDI: +============================================================================= + +PortMidi has been created using a DLL because the Win32 MMedia API doesn't +handle midiInput properly in the debugger. Specifically, it doesn't clean up +after itself if the user (i.e. you, a PortMidi application) hasn't explicitly +closed all open midi input devices. This lack of cleanup can lead to much +pain and agony, including the blue-screen-of-death. This situation becomes +increasingly unacceptable when you are debugging your code, so a portMidi DLL +seemed to be the most elegant solution. + +Using Microsoft Visual C++ project files (provided with PortMidi), there +are two configurations of the PortMidi library. The Debug version is +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 +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 +from the predefined symbols list in the Settings dialog box. + +PortMidi is designed to run without a console and should work perfectly +well within a graphical user interface application. The Release version +is both optimized and lacking the debugging printout code of the Debug +version. + +Read the portmidi.h file for PortMidi API details on using the PortMidi API. +See <...>\pm_dll_test\test.c or <...>\multithread\test.c for usage examples. + +============================================================================= +TO INSTALL PORTMIDI: +============================================================================= +1) download portmidi.zip + +2) unzip portmidi.zip into directory: <...>\portmidi + +============================================================================= +TO COMPILE PORTMIDI: +============================================================================= + +3) go to this directory + +4) click on the portmidi.dsw workspace + +5) the following projects exist within this workspace: + - portmidi (the PortMidi library) + - pm_dll (the dll library used to close midi ports on program exit) + - porttime (a small portable library implementing timer facilities) + - test (simple midi I/O testing) + - multithread (an example illustrating low-latency MIDI processing + using a dedicated low-latency thread) + - sysex (simple sysex message I/O testing) + - latency (uses porttime to measure system latency) + +6) verify that all project settings are for Win32 Debug release: + - hit 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) + +8) use Build->Batch Build ... to build everything in the project + +9) 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 + 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: + <...>\portmidi\pm_test\latencyDebug\pm_dll.dll + <...>\portmidi\pm_test\midithreadDebug\pm_dll.dll + <...>\portmidi\pm_test\sysexDebug\pm_dll.dll + <...>\portmidi\pm_test\testDebug\pm_dll.dll + <...>\portmidi\pm_test\midithruDebug\pm_dll.dll + and copy <...>\portmidi\pm_win\Release\pm_dll.dll to: + <...>\portmidi\pm_test\latencyRelease\pm_dll.dll + <...>\portmidi\pm_test\midithreadRelease\pm_dll.dll + <...>\portmidi\pm_test\sysexRelease\pm_dll.dll + <...>\portmidi\pm_test\testRelease\pm_dll.dll + <...>\portmidi\pm_test\midithruRelease\pm_dll.dll + each time you rebuild the pm_dll project, these copies must be redone! + + Since Windows will look in the executable directory for DLLs, we + recommend that you always install a copy of pm_dll.dll (either the + debug version or the release version) in the same directory as the + 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 + 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 + +============================================================================ +TO CREATE YOUR OWN PORTMIDI CLIENT APPLICATION: +============================================================================ + +NOTE: this section needs to be reviewed and tested. My suggestion would +be to copy the test project file (test.dsp) and modify it. -RBD + +The easiest way is to start a new project w/in the portMidi workspace: + +1) To open new project: + - File->New->Projects + - Location: <...>\portmidi\ + - check Add to current workspace + - select Win32 Console Application (recommended for now) + - do *NOT* select the "make dependency" box (you will explicitly do this + in the next step) + - Click OK + - Select "An Empty Project" and click Finish + +2) Now this project will be the active project. Make it explicitly depend + on PortMidi dll: + - Project->Dependencies + - Click pm_dll + +3) Important! in order to be able to use portMidi DLL from your new project + and set breakpoints, copy following files from <...>\pm_dll\Debug into + <...>\\Debug directory: + pm_dll.lib + pm_dll.dll + each time you rebuild pm_dll, these copies must be redone! + +4) add whatever files you wish to add to your new project, using portMidi + calls as desired (see USING PORTMIDI at top of this readme) + +5) when you include portMidi files, do so like this: + - #include "..\pm_dll\portmidi.h" + - etc. + +6) build and run your project + +============================================================================ +DESIGN NOTES +============================================================================ + +The DLL is used so that PortMidi can (usually) close open devices when the +program terminates. Failure to close input devices under WinNT, Win2K, and +probably later systems causes the OS to crash. + +This is accomplished with a .LIB/.DLL pair, linking to the .LIB +in order to access functions in the .DLL. + +PortMidi for Win32 exists as a simple library, +with Win32-specific code in pmwin.c and MM-specific code in pmwinmm.c. +pmwin.c uses a DLL in pmdll.c to call Pm_Terminate() when the program +exits to make sure that all MIDI ports are closed. + +Orderly cleanup after errors are encountered is based on a fixed order of +steps and state changes to reflect each step. Here's the order: + +To open input: + initialize return value to NULL + - allocate the PmInternal strucure (representation of PortMidiStream) + return value is (non-null) PmInternal structure + - allocate midi buffer + set buffer field of PmInternal structure + - call system-dependent open code + - allocate midiwinmm_type for winmm dependent data + set descriptor field of PmInternal structure + - open device + set handle field of midiwinmm_type structure + - allocate buffer 1 for sysex + buffer is added to input port + - allocate buffer 2 for sysex + buffer is added to input port + - return + - return + + + diff --git a/pd/portmidi/pm_win/copy-dll.bat b/pd/portmidi/pm_win/copy-dll.bat new file mode 100644 index 00000000..34ccbedd --- /dev/null +++ b/pd/portmidi/pm_win/copy-dll.bat @@ -0,0 +1,13 @@ +copy Debug\pm_dll.dll ..\pm_test\testDebug\pm_dll.dll +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 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 + + diff --git a/pd/portmidi/pm_win/debugging_dlls.txt b/pd/portmidi/pm_win/debugging_dlls.txt new file mode 100644 index 00000000..82b81a5b --- /dev/null +++ b/pd/portmidi/pm_win/debugging_dlls.txt @@ -0,0 +1,145 @@ +======================================================================================================================== +Methods for Debugging DLLs +======================================================================================================================== +If you have the source for both the DLL and the calling program, open the project for the calling executable file and +debug the DLL from there. If you load a DLL dynamically, you must specify it in the Additional DLLs category of the +Debug tab in the Project Settings dialog box. + +If you have the source for the DLL only, open the project that builds the DLL. Use the Debug tab in the Project +Settings dialog box to specify the executable file that calls the DLL. + +You can also debug a DLL without a project. For example, maybe you just picked up a DLL and source code but you +don’t have an associated project or workspace. You can use the Open command on the File menu to select the .DLL +file you want to debug. The debug information should be in either the .DLL or the related .PDB file. After +Visual C++ opens the file, on the Build menu click Start Debug and Go to begin debugging. + +To debug a DLL using the project for the executable file + +From the Project menu, click Settings. +The Project Settings dialog box appears. + +Choose the Debug tab. + + +In the Category drop-down list box, select General. + + +In the Program Arguments text box, type any command-line arguments required by the executable file. + + +In the Category drop-down list box, select Additional DLLs. + + +In the Local Name column, type the names of DLLs to debug. +If you are debugging remotely, the Remote Name column appears. In this column, type the complete path for the +remote module to map to the local module name. + +In the Preload column, select the check box if you want to load the module before debugging begins. + + +Click OK to store the information in your project. + + +From the Build menu, click Start Debug and Go to start the debugger. +You can set breakpoints in the DLL or the calling program. You can open a source file for the DLL and set breakpoints +in that file, even though it is not a part of the executable file’s project. + +To debug a DLL using the project for the DLL + +From the Project menu, click Settings. +The Project Settings dialog box appears. + +Choose the Debug tab. + + +In the Category drop-down list box, select General. + + +In the Executable For Debug Session text box, type the name of the executable file that calls the DLL. + + +In the Category list box, select Additional DLLs. + + +In the Local Module Name column, type the name of the DLLs you want to debug. + + +Click OK to store the information in your project. + + +Set breakpoints as required in your DLL source files or on function symbols in the DLL. + + +From the Build menu, click Start Debug and Go to start the debugger. +To debug a DLL created with an external project + +From the Project menu, click Settings. +The Project Settings dialog box appears. + +Choose the Debug tab. + + +In the Category drop-down list box, select General. + + +In the Executable For Debug Session text box, type the name of the DLL that your external makefile builds. + + +Click OK to store the information in your project. + + +Build a debug version of the DLL with symbolic debugging information, if you don’t already have one. + + +Follow one of the two procedures immediately preceding this one to debug the DLL. + +======================================================================================================================== +Why Don’t My DLL Breakpoints Work? +======================================================================================================================== +Some reasons why your breakpoints don’t work as expected are listed here, along with solutions or work-arounds for each. +If you follow the instructions in one topic and are still having breakpoint problems, look at some of the other topics. +Often breakpoint problems result from a combination of conditions. + +You can't set a breakpoint in a source file when the corresponding symbolic information isn't loaded into memory by +the debugger. +You cannot set a breakpoint in any source file when the corresponding symbolic information will not be loaded into memory +by the debugger. +Symptoms include messages such as "the breakpoint cannot be set" or a simple, noninformational beep. + +When setting breakpoints before the code to be debugged has been started, the debugger uses a breakpoint list to keep +track of how and where to set breakpoints. When you actually begin the debugging session, the debugger loads the symbolic +information for all the code to be debugged and then walks through its breakpoint list, attempting to set the +breakpoints. + +However, if one or more of the code modules have not been designated to the debugger, there will be no symbolic +information for the debugger to use when walking through its breakpoint list. Situations where this is likely to +occur include: + +Attempts to set breakpoints in a DLL before the call to LoadLibrary. + +Setting a breakpoint in an ActiveX server before the container has started the server. + +Other similar cases. + +To prevent this behavior in Visual C++, specify all additional DLLs and COM servers in the Additional DLLs field +in the Debug/Options dialog box to notify the debugger that you want it to load symbolic debug information for +additional .DLL files. When this has been done, breakpoints set in code that has not yet been loaded into memory +will be "virtual" breakpoints. When the code is actually loaded into memory by the loader, these become physical +breakpoints. Make sure that these additional debugging processes are not already running when you start your +debugging session. The debugging process and these additional processes must be sychronized at the same beginning +point to work correctly, hitting all breakpoints. + +Breakpoints are missed when more than one copy of a DLL is on your hard disk. +Having more than one copy of a DLL on your hard drive, especially if it is in your Windows directory, can cause +debugger confusion. The debugger will load the symbolic information for the DLL specified to it at run time (with the +Additional DLLs field in the Debug/Options dialog box), while Windows has actually loaded a different copy of the +DLL itself into memory. Because there is no way to force the debugger to load a specific DLL, it is a good idea to +keep only one version of a DLL at a time in your path, current directory, and Windows directory. + +You can’t set "Break When Expression Has Changed" breakpoints on a variable local to a DLL. +Setting a "Break When Expression Has Changed" breakpoint on a variable local to a DLL function before the call +to LoadLibrary causes the breakpoint to be virtual (there are no physical addresses for the DLL in memory yet). +Virtual breakpoints involving expressions pose a special problem. The DLL must be specified to the debugger at +startup (causing its symbolic information to be loaded). In addition, the DLL's executable code must also be loaded +into memory before this kind of breakpoint can be set. This means that the calling application's code must be +executed to the point after its call to LoadLibrary before the debugger will allow this type of breakpoint to be set. diff --git a/pd/portmidi/pm_win/pm_dll.dsp b/pd/portmidi/pm_win/pm_dll.dsp new file mode 100644 index 00000000..d08e2de7 --- /dev/null +++ b/pd/portmidi/pm_win/pm_dll.dsp @@ -0,0 +1,107 @@ +# Microsoft Developer Studio Project File - Name="pm_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=pm_dll - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "pm_dll.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "pm_dll.mak" CFG="pm_dll - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "pm_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "pm_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "pm_dll - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# 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 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 +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 + +!ELSEIF "$(CFG)" == "pm_dll - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# 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 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 BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "pm_dll - Win32 Release" +# Name "pm_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\pmdll.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\pmdll.h +# End Source File +# End Group +# End Target +# End Project diff --git a/pd/portmidi/pm_win/pmdll.c b/pd/portmidi/pm_win/pmdll.c new file mode 100644 index 00000000..c3acba33 --- /dev/null +++ b/pd/portmidi/pm_win/pmdll.c @@ -0,0 +1,49 @@ +/* +==================================================================== +DLL to perform action when program shuts down +==================================================================== +*/ + +#include "windows.h" +#include "pmdll.h" + +static close_fn_ptr_type close_function = NULL; + + +DLL_EXPORT pm_set_close_function(close_fn_ptr_type close_fn_ptr) +{ + close_function = close_fn_ptr; +} + + +static void Initialize( void ) { + return; +} + +static void Terminate( void ) { + if (close_function) { + (*close_function)(); + } +} + + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, //DLL module handle + DWORD fdwReason, //for calling function + LPVOID lbpvReserved)//reserved +{ + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + /* when DLL starts, run this */ + Initialize(); + break; + case DLL_PROCESS_DETACH: + /* when DLL ends, this run (note: verified this run */ + Terminate(); + break; + default: + break; + } + return TRUE; +} + + diff --git a/pd/portmidi/pm_win/pmdll.h b/pd/portmidi/pm_win/pmdll.h new file mode 100644 index 00000000..e5ad1c5b --- /dev/null +++ b/pd/portmidi/pm_win/pmdll.h @@ -0,0 +1,5 @@ +#define DLL_EXPORT __declspec( dllexport ) + +typedef void (*close_fn_ptr_type)(); + +DLL_EXPORT pm_set_close_function(close_fn_ptr_type close_fn_ptr); diff --git a/pd/portmidi/pm_win/pmwin.c b/pd/portmidi/pm_win/pmwin.c new file mode 100644 index 00000000..b289194b --- /dev/null +++ b/pd/portmidi/pm_win/pmwin.c @@ -0,0 +1,113 @@ +/* pmwin.c -- PortMidi os-dependent code */ + +/* This file only needs to implement: + pm_init(), which calls various routines to register the + available midi devices, + Pm_GetDefaultInputDeviceID(), and + Pm_GetDefaultOutputDeviceID(). + This file must + be separate from the main portmidi.c file because it is system + dependent, and it is separate from, say, pmwinmm.c, because it + might need to register devices for winmm, directx, and others. + */ + +#include "stdlib.h" +#include "portmidi.h" +#include "pminternal.h" +#include "pmwinmm.h" +#ifdef USE_DLL_FOR_CLEANUP +#include "pmdll.h" /* used to close ports on exit */ +#endif +#ifdef DEBUG +#include "stdio.h" +#endif + +/* pm_exit is called when the program exits. + It calls pm_term to make sure PortMidi is properly closed. + If DEBUG is on, we prompt for input to avoid losing error messages. + */ +static void pm_exit(void) { + pm_term(); +#ifdef DEBUG +#define STRING_MAX 80 + { + char line[STRING_MAX]; + printf("Type ENTER...\n"); + /* note, w/o this prompting, client console application can not see one + of its errors before closing. */ + fgets(line, STRING_MAX, stdin); + } +#endif +} + + +/* pm_init is the windows-dependent initialization.*/ +void pm_init(void) +{ +#ifdef USE_DLL_FOR_CLEANUP + /* we were hoping a DLL could offer more robust cleanup after errors, + but the DLL does not seem to run after crashes. Thus, the atexit() + mechanism is just as powerful, and simpler to implement. + */ + pm_set_close_function(pm_exit); +#ifdef DEBUG + printf("registered pm_term with cleanup DLL\n"); +#endif +#else + atexit(pm_exit); +#ifdef DEBUG + printf("registered pm_exit with atexit()\n"); +#endif +#endif + pm_winmm_init(); + /* initialize other APIs (DirectX?) here */ +} + + +void pm_term(void) { + pm_winmm_term(); +} + + +PmDeviceID Pm_GetDefaultInputDeviceID() { + /* This routine should check the environment and the registry + as specified in portmidi.h, but for now, it just returns + the first device of the proper input/output flavor. + */ + int i; + Pm_Initialize(); /* make sure descriptors exist! */ + for (i = 0; i < pm_descriptor_index; i++) { + if (descriptors[i].pub.input) { + return i; + } + } + return pmNoDevice; +} + +PmDeviceID Pm_GetDefaultOutputDeviceID() { + /* This routine should check the environment and the registry + as specified in portmidi.h, but for now, it just returns + the first device of the proper input/output flavor. + */ + int i; + Pm_Initialize(); /* make sure descriptors exist! */ + for (i = 0; i < pm_descriptor_index; i++) { + if (descriptors[i].pub.output) { + return i; + } + } + return pmNoDevice; + return 0; +} + +#include "stdio.h" + +void *pm_alloc(size_t s) { + return malloc(s); +} + + +void pm_free(void *ptr) { + free(ptr); +} + diff --git a/pd/portmidi/pm_win/pmwinmm.c b/pd/portmidi/pm_win/pmwinmm.c new file mode 100644 index 00000000..5bfb0cff --- /dev/null +++ b/pd/portmidi/pm_win/pmwinmm.c @@ -0,0 +1,1547 @@ +/* pmwinmm.c -- system specific definitions */ + +#include "windows.h" +#include "mmsystem.h" +#include "portmidi.h" +#include "pminternal.h" +#include "pmwinmm.h" +#include "string.h" +#include "porttime.h" + +/* asserts used to verify portMidi code logic is sound; later may want + something more graceful */ +#include + +#ifdef DEBUG +/* this printf stuff really important for debugging client app w/host errors. + probably want to do something else besides read/write from/to console + for portability, however */ +#define STRING_MAX 80 +#include "stdio.h" +#endif + +#define streql(x, y) (strcmp(x, y) == 0) + +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 + +/* callback routines */ +static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn, + WORD wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); +static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); + +extern pm_fns_node pm_winmm_in_dictionary; +extern pm_fns_node pm_winmm_out_dictionary; + +static void winmm_out_delete(PmInternal *midi); /* forward reference */ + +#define SYSEX_BYTES_PER_BUFFER 1024 +/* 3 midi messages per buffer */ +#define OUTPUT_BYTES_PER_BUFFER 36 + +#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3) +#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) +#define MIDIHDR_BUFFER_LENGTH(x) (x) +#define MIDIHDR_SIZE(x) (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) + +/* +============================================================================== +win32 mmedia system specific structure passed to midi callbacks +============================================================================== +*/ + +/* global winmm device info */ +MIDIINCAPS *midi_in_caps = NULL; +MIDIINCAPS midi_in_mapper_caps; +UINT midi_num_inputs = 0; +MIDIOUTCAPS *midi_out_caps = NULL; +MIDIOUTCAPS midi_out_mapper_caps; +UINT midi_num_outputs = 0; + +/* per device info */ +typedef struct midiwinmm_struct +{ + + union { + HMIDISTRM stream; /* windows handle for stream */ + HMIDIOUT out; /* windows handle for out calls */ + HMIDIIN in; /* windows handle for in calls */ + } handle; + + /* midi output messages are sent in these buffers, which are allocated + * in a round-robin fashion, using next_buffer as an index + */ + LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */ + int num_buffers; /* how many buffers */ + int next_buffer; /* index of next buffer to send */ + HANDLE buffer_signal; /* used to wait for buffer to become free */ + + LPMIDIHDR *sysex_buffers; /* pool of buffers for sysex data */ + int num_sysex_buffers; /* how many sysex buffers */ + int next_sysex_buffer; /* index of next sysexbuffer to send */ + HANDLE sysex_buffer_signal; /* used to wait for sysex buffer to become free */ + + unsigned long last_time; /* last output time */ + int first_message; /* flag: treat first message differently */ + int sysex_mode; /* middle of sending sysex */ + unsigned long sysex_word; /* accumulate data when receiving sysex */ + unsigned int sysex_byte_count; /* count how many received or to send */ + LPMIDIHDR hdr; /* the message accumulating sysex to send */ + unsigned long sync_time; /* when did we last determine delta? */ + long delta; /* difference between stream time and + real time */ + int error; /* host error from doing port midi call */ + int callback_error; /* host error from midi in or out callback */ +} +midiwinmm_node, *midiwinmm_type; + + +/* +============================================================================= +general MIDI device queries +============================================================================= +*/ +static void pm_winmm_general_inputs() +{ + UINT i; + WORD wRtn; + midi_num_inputs = midiInGetNumDevs(); + midi_in_caps = pm_alloc(sizeof(MIDIINCAPS) * midi_num_inputs); + + if (midi_in_caps == NULL) { + // if you can't open a particular system-level midi interface + // (such as winmm), we just consider that system or API to be + // unavailable and move on without reporting an error. This + // may be the wrong thing to do, especially in this case. + return ; + } + + for (i = 0; i < midi_num_inputs; i++) { + wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i], + sizeof(MIDIINCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + /* ignore errors here -- if pm_descriptor_max is exceeded, some + devices will not be accessible. */ + pm_add_device("MMSystem", midi_in_caps[i].szPname, TRUE, + (void *) i, &pm_winmm_in_dictionary); + } + } +} + + +static void pm_winmm_mapper_input() +{ + WORD wRtn; + /* Note: if MIDIMAPPER opened as input (documentation implies you + can, but current system fails to retrieve input mapper + capabilities) then you still should retrieve some formof + setup info. */ + wRtn = midiInGetDevCaps((UINT) MIDIMAPPER, + (LPMIDIINCAPS) & midi_in_mapper_caps, sizeof(MIDIINCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device("MMSystem", midi_in_mapper_caps.szPname, TRUE, + (void *) MIDIMAPPER, &pm_winmm_in_dictionary); + } +} + + +static void pm_winmm_general_outputs() +{ + UINT i; + DWORD wRtn; + midi_num_outputs = midiOutGetNumDevs(); + midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs ); + + if (midi_out_caps == NULL) { + // no error is reported -- see pm_winmm_general_inputs + return ; + } + + for (i = 0; i < midi_num_outputs; i++) { + wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i], + sizeof(MIDIOUTCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device("MMSystem", midi_out_caps[i].szPname, FALSE, + (void *) i, &pm_winmm_out_dictionary); + } + } +} + + +static void pm_winmm_mapper_output() +{ + WORD wRtn; + /* Note: if MIDIMAPPER opened as output (pseudo MIDI device + maps device independent messages into device dependant ones, + via NT midimapper program) you still should get some setup info */ + wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS) + & midi_out_mapper_caps, sizeof(MIDIOUTCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device("MMSystem", midi_out_mapper_caps.szPname, FALSE, + (void *) MIDIMAPPER, &pm_winmm_out_dictionary); + } +} + + +/* +========================================================================================= +host error handling +========================================================================================= +*/ +static unsigned int winmm_has_host_error(PmInternal * midi) +{ + midiwinmm_type m = (midiwinmm_type)midi->descriptor; + return m->callback_error || m->error; +} + + +/* str_copy_len -- like strcat, but won't overrun the destination string */ +/* + * returns length of resulting string + */ +static int str_copy_len(char *dst, char *src, int len) +{ + strncpy(dst, src, len); + /* just in case suffex is greater then len, terminate with zero */ + dst[len - 1] = 0; + return strlen(dst); +} + + +static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) +{ + /* precondition: midi != NULL */ + midiwinmm_node * m = (midiwinmm_node *) midi->descriptor; + char *hdr1 = "Host error: "; + char *hdr2 = "Host callback error: "; + + msg[0] = 0; /* initialize result string to empty */ + + if (descriptors[midi->device_id].pub.input) { + /* input and output use different winmm API calls */ + if (m) { /* make sure there is an open device to examine */ + if (m->error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr1, len); + /* read and record host error */ + int err = midiInGetErrorText(m->error, msg + n, len - n); + assert(err == MMSYSERR_NOERROR); + m->error = MMSYSERR_NOERROR; + } else if (m->callback_error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr2, len); + int err = midiInGetErrorText(m->callback_error, msg + n, + len - n); + assert(err == MMSYSERR_NOERROR); + m->callback_error = MMSYSERR_NOERROR; + } + } + } else { /* output port */ + if (m) { + if (m->error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr1, len); + int err = midiOutGetErrorText(m->error, msg + n, len - n); + assert(err == MMSYSERR_NOERROR); + m->error = MMSYSERR_NOERROR; + } else if (m->callback_error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr2, len); + int err = midiOutGetErrorText(m->callback_error, msg + n, + len = n); + assert(err == MMSYSERR_NOERROR); + m->callback_error = MMSYSERR_NOERROR; + } + } + } +} + + +/* +============================================================================= +buffer handling +============================================================================= +*/ +static MIDIHDR *allocate_buffer(long data_size) +{ + /* + * with short messages, the MIDIEVENT structure contains the midi message, + * so there is no need for additional data + */ + + LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SIZE(data_size)); + MIDIEVENT *evt; + if (!hdr) return NULL; + evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ + hdr->lpData = (LPSTR) evt; + hdr->dwBufferLength = MIDIHDR_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */ + hdr->dwFlags = 0; + hdr->dwUser = 0; + return hdr; +} + +static MIDIHDR *allocate_sysex_buffer(long data_size) +{ + /* we're actually allocating slightly more than data_size because one more word of + * data is contained in MIDIEVENT. We include the size of MIDIEVENT because we need + * the MIDIEVENT header in addition to the data + */ + LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); + MIDIEVENT *evt; + if (!hdr) return NULL; + evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ + hdr->lpData = (LPSTR) evt; + hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */ + hdr->dwFlags = 0; + hdr->dwUser = 0; + return hdr; +} + +static PmError allocate_buffers(midiwinmm_type m, long data_size, long count) +{ + PmError rslt = pmNoError; + /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!m->buffers) return pmInsufficientMemory; + m->num_buffers = count; + while (count > 0) { + LPMIDIHDR hdr = allocate_buffer(data_size); + if (!hdr) rslt = pmInsufficientMemory; + count--; + m->buffers[count] = hdr; /* this may be NULL if allocation fails */ + } + return rslt; +} + +static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size, long count) +{ + PmError rslt = pmNoError; + /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + m->sysex_buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!m->sysex_buffers) return pmInsufficientMemory; + m->num_sysex_buffers = count; + while (count > 0) { + LPMIDIHDR hdr = allocate_sysex_buffer(data_size); + if (!hdr) rslt = pmInsufficientMemory; + count--; + m->sysex_buffers[count] = hdr; /* this may be NULL if allocation fails */ + } + return rslt; +} + +static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m->sysex_buffers) { + if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) { + return NULL; + } + } + /* busy wait until we find a free buffer */ + while (TRUE) { + int i; + for (i = 0; i < m->num_sysex_buffers; i++) { + m->next_sysex_buffer++; + if (m->next_sysex_buffer >= m->num_sysex_buffers) m->next_sysex_buffer = 0; + r = m->sysex_buffers[m->next_sysex_buffer]; + if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer; + } + /* after scanning every buffer and not finding anything, block */ + WaitForSingleObject(m->sysex_buffer_signal, INFINITE); + } +found_sysex_buffer: + r->dwBytesRecorded = 0; + m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR)); + return r; +} + + +static LPMIDIHDR get_free_output_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m->buffers) { + if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, 2)) { + return NULL; + } + } + /* busy wait until we find a free buffer */ + while (TRUE) { + int i; + for (i = 0; i < m->num_buffers; i++) { + m->next_buffer++; + if (m->next_buffer >= m->num_buffers) m->next_buffer = 0; + r = m->buffers[m->next_buffer]; + if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer; + } + /* after scanning every buffer and not finding anything, block */ + WaitForSingleObject(m->buffer_signal, INFINITE); + } +found_buffer: + r->dwBytesRecorded = 0; + m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR)); + return r; +} + +static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size) +{ + LPMIDIHDR big; + int i; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + /* buffer must be smaller than 64k, but be also be a multiple of 4 */ + if (new_size > 65520) { + if (old_size >= 65520) + return pmBufferMaxSize; + else + new_size = 65520; + } + /* allocate a bigger message */ + big = allocate_sysex_buffer(new_size); + /* printf("expand to %d bytes\n", new_size);*/ + if (!big) return pmInsufficientMemory; + m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR)); + if (m->error) { + pm_free(big); + return pmHostError; + } + /* make sure we're not going to overwrite any memory */ + assert(old_size <= new_size); + memcpy(big->lpData, m->hdr->lpData, old_size); + + /* find which buffer this was, and replace it */ + + for (i = 0;i < m->num_sysex_buffers;i++) { + if (m->sysex_buffers[i] == m->hdr) { + m->sysex_buffers[i] = big; + pm_free(m->hdr); + m->hdr = big; + break; + } + } + assert(i != m->num_sysex_buffers); + + return pmNoError; + +} +/* +========================================================================================= +begin midi input implementation +========================================================================================= +*/ + +static PmError winmm_in_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + midiwinmm_type m; + LPMIDIHDR hdr; + long buffer_len; + dwDevice = (DWORD) descriptors[i].descriptor; + + /* create system dependent device data */ + m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ + midi->descriptor = m; + if (!m) goto no_memory; + m->handle.in = NULL; + m->buffers = NULL; + m->num_buffers = 0; + m->next_buffer = 0; + m->sysex_buffers = NULL; + m->num_sysex_buffers = 0; + m->next_sysex_buffer = 0; + m->last_time = 0; + m->first_message = TRUE; /* not used for input */ + m->sysex_mode = FALSE; + m->sysex_word = 0; + m->sysex_byte_count = 0; + m->sync_time = 0; + m->delta = 0; + m->error = MMSYSERR_NOERROR; + m->callback_error = MMSYSERR_NOERROR; + + /* open device */ + pm_hosterror = midiInOpen(&(m->handle.in), /* input device handle */ + dwDevice, /* device ID */ + (DWORD) winmm_in_callback, /* callback address */ + (DWORD) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback is a procedure */ + if (pm_hosterror) goto free_descriptor; + + /* allocate first buffer for sysex data */ + buffer_len = midi->buffer_len - 1; + if (midi->buffer_len < 32) + buffer_len = PM_DEFAULT_SYSEX_BUFFER_SIZE; + + hdr = allocate_sysex_buffer(buffer_len); + if (!hdr) goto close_device; + pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) { + pm_free(hdr); + goto close_device; + } + pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) goto close_device; + + /* allocate second buffer */ + hdr = allocate_sysex_buffer(buffer_len); + if (!hdr) goto close_device; + pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) { + pm_free(hdr); + goto reset_device; /* because first buffer was added */ + } + pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) goto reset_device; + + /* start device */ + pm_hosterror = midiInStart(m->handle.in); + if (pm_hosterror) goto reset_device; + return pmNoError; + + /* undo steps leading up to the detected error */ +reset_device: + /* ignore return code (we already have an error to report) */ + midiInReset(m->handle.in); +close_device: + midiInClose(m->handle.in); /* ignore return code */ +free_descriptor: + midi->descriptor = NULL; + pm_free(m); +no_memory: + if (pm_hosterror) { + int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + /* if !pm_hosterror, then the error must be pmInsufficientMemory */ + return pmInsufficientMemory; + /* note: if we return an error code, the device will be + closed and memory will be freed. It's up to the caller + to free the parameter midi */ +} + + +/* winmm_in_close -- close an open midi input device */ +/* + * assume midi is non-null (checked by caller) + */ +static PmError winmm_in_close(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m) return pmBadPtr; + /* device to close */ + if (pm_hosterror = midiInStop(m->handle.in)) { + midiInReset(m->handle.in); /* try to reset and close port */ + midiInClose(m->handle.in); + } else if (pm_hosterror = midiInReset(m->handle.in)) { + midiInClose(m->handle.in); /* best effort to close midi port */ + } else { + pm_hosterror = midiInClose(m->handle.in); + } + midi->descriptor = NULL; + pm_free(m); /* delete */ + if (pm_hosterror) { + int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + return pmNoError; +} + + +/* Callback function executed via midiInput SW interrupt (via midiInOpen). */ +static void FAR PASCAL winmm_in_callback( + HMIDIIN hMidiIn, /* midiInput device Handle */ + WORD wMsg, /* midi msg */ + DWORD dwInstance, /* application data */ + DWORD dwParam1, /* MIDI data */ + DWORD dwParam2) /* device timestamp (wrt most recent midiInStart) */ +{ + static int entry = 0; + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + + if (++entry > 1) { + assert(FALSE); + } + + /* for simplicity, this logic perhaps overly conservative */ + /* note also that this might leak memory if buffers are being + returned as a result of midiInReset */ + if (m->callback_error) { + entry--; + return ; + } + + switch (wMsg) { + case MIM_DATA: { + /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of + message LOB; + dwParam2 is time message received by input device driver, specified + in [ms] from when midiInStart called. + each message is expanded to include the status byte */ + + long new_driver_time = dwParam2; + + if ((dwParam1 & 0x80) == 0) { + /* not a status byte -- ignore it. This happens running the + sysex.c test under Win2K with MidiMan USB 1x1 interface. + Is it a driver bug or a Win32 bug? If not, there's a bug + here somewhere. -RBD + */ + } else { /* data to process */ + PmEvent event; + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + event.timestamp = dwParam2; + event.message = dwParam1; + pm_read_short(midi, &event); + } + break; + } + case MIM_LONGDATA: { + MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1; + unsigned char *data = lpMidiHdr->lpData; + unsigned int i = 0; + long size = sizeof(MIDIHDR) + lpMidiHdr->dwBufferLength; + /* ignore sysex data, but free returned buffers */ + if (lpMidiHdr->dwBytesRecorded > 0 && + midi->filters & PM_FILT_SYSEX) { + m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr, + sizeof(MIDIHDR)); + break; + } + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + + while (i < lpMidiHdr->dwBytesRecorded) { + /* collect bytes from *data into a word */ + pm_read_byte(midi, *data, dwParam2); + data++; + i++; + } + /* when a device is closed, the pending MIM_LONGDATA buffers are + returned to this callback with dwBytesRecorded == 0. In this + case, we do not want to send them back to the interface (if + we do, the interface will not close, and Windows OS may hang). */ + if (lpMidiHdr->dwBytesRecorded > 0) { + m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr, + sizeof(MIDIHDR)); + } else { + pm_free(lpMidiHdr); + } + break; + } + case MIM_OPEN: /* fall thru */ + case MIM_CLOSE: + case MIM_ERROR: + case MIM_LONGERROR: + default: + break; + } + entry--; +} + +/* +========================================================================================= +begin midi output implementation +========================================================================================= +*/ + +/* begin helper routines used by midiOutStream interface */ + +/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */ +static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, + unsigned long delta, unsigned long msg) +{ + unsigned long *ptr = (unsigned long *) + (hdr->lpData + hdr->dwBytesRecorded); + *ptr++ = delta; /* dwDeltaTime */ + *ptr++ = 0; /* dwStream */ + *ptr++ = msg; /* dwEvent */ + hdr->dwBytesRecorded += 3 * sizeof(long); + /* if the addition of three more words (a message) would extend beyond + the buffer length, then return TRUE (full) + */ + return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength; +} + +#ifdef GARBAGE +static void start_sysex_buffer(LPMIDIHDR hdr, unsigned long delta) +{ + unsigned long *ptr = (unsigned long *) hdr->lpData; + *ptr++ = delta; + *ptr++ = 0; + *ptr = MEVT_F_LONG; + hdr->dwBytesRecorded = 3 * sizeof(long); +} + +static int add_byte_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, + unsigned char midi_byte) +{ + allocate message if hdr is null + send message if it is full + add byte to non - full message + unsigned char *ptr = (unsigned char *) (hdr->lpData + hdr->dwBytesRecorded); + *ptr = midi_byte; + return ++hdr->dwBytesRecorded >= hdr->dwBufferLength; +} +#endif + + +static PmTimestamp pm_time_get(midiwinmm_type m) +{ + MMTIME mmtime; + MMRESULT wRtn; + mmtime.wType = TIME_TICKS; + mmtime.u.ticks = 0; + wRtn = midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime)); + assert(wRtn == MMSYSERR_NOERROR); + return mmtime.u.ticks; +} + +#ifdef GARBAGE +static unsigned long synchronize(PmInternal *midi, midiwinmm_type m) +{ + unsigned long pm_stream_time_2 = pm_time_get(m); + unsigned long real_time; + unsigned long pm_stream_time; + /* figure out the time */ + do { + /* read real_time between two reads of stream time */ + pm_stream_time = pm_stream_time_2; + real_time = (*midi->time_proc)(midi->time_info); + pm_stream_time_2 = pm_time_get(m); + /* repeat if more than 1ms elapsed */ + } while (pm_stream_time_2 > pm_stream_time + 1); + m->delta = pm_stream_time - real_time; + m->sync_time = real_time; + return real_time; +} +#endif + + +/* end helper routines used by midiOutStream interface */ + + +static PmError winmm_out_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + midiwinmm_type m; + MIDIPROPTEMPO propdata; + MIDIPROPTIMEDIV divdata; + dwDevice = (DWORD) descriptors[i].descriptor; + + /* create system dependent device data */ + m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ + midi->descriptor = m; + if (!m) goto no_memory; + m->handle.out = NULL; + m->buffers = NULL; + m->num_buffers = 0; + m->next_buffer = 0; + m->sysex_buffers = NULL; + m->num_sysex_buffers = 0; + m->next_sysex_buffer = 0; + m->last_time = 0; + m->first_message = TRUE; /* we treat first message as special case */ + m->sysex_mode = FALSE; + m->sysex_word = 0; + m->sysex_byte_count = 0; + m->hdr = NULL; + m->sync_time = 0; + m->delta = 0; + m->error = MMSYSERR_NOERROR; + m->callback_error = MMSYSERR_NOERROR; + + /* create a signal */ + m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); + m->sysex_buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* this should only fail when there are very serious problems */ + assert(m->buffer_signal); + assert(m->sysex_buffer_signal); + + /* open device */ + if (midi->latency == 0) { + /* use simple midi out calls */ + pm_hosterror = midiOutOpen((LPHMIDIOUT) & m->handle.out, /* device Handle */ + dwDevice, /* device ID */ + (DWORD) winmm_out_callback, + (DWORD) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback type */ + } else { + /* use stream-based midi output (schedulable in future) */ + pm_hosterror = midiStreamOpen(&m->handle.stream, /* device Handle */ + (LPUINT) & dwDevice, /* device ID pointer */ + 1, /* reserved, must be 1 */ + (DWORD) winmm_streamout_callback, + (DWORD) midi, /* callback instance data */ + CALLBACK_FUNCTION); + } + if (pm_hosterror != MMSYSERR_NOERROR) { + goto free_descriptor; + } + + if (midi->latency != 0) { + long dur = 0; + /* with stream output, specified number of buffers allocated here */ + int count = midi->buffer_len; + if (count == 0) + count = midi->latency / 2; /* how many buffers to get */ + + propdata.cbStruct = sizeof(MIDIPROPTEMPO); + propdata.dwTempo = 480000; /* microseconds per quarter */ + pm_hosterror = midiStreamProperty(m->handle.stream, + (LPBYTE) & propdata, + MIDIPROP_SET | MIDIPROP_TEMPO); + if (pm_hosterror) goto close_device; + + divdata.cbStruct = sizeof(MIDIPROPTEMPO); + divdata.dwTimeDiv = 480; /* divisions per quarter */ + pm_hosterror = midiStreamProperty(m->handle.stream, + (LPBYTE) & divdata, + MIDIPROP_SET | MIDIPROP_TIMEDIV); + if (pm_hosterror) goto close_device; + + /* allocate at least 3 buffers */ + if (count < 3) count = 3; + if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, count)) goto free_buffers; + if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) goto free_buffers; + /* start device */ + pm_hosterror = midiStreamRestart(m->handle.stream); + if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers; + } + return pmNoError; + +free_buffers: + /* buffers are freed below by winmm_out_delete */ +close_device: + midiOutClose(m->handle.out); +free_descriptor: + midi->descriptor = NULL; + winmm_out_delete(midi); /* frees buffers and m */ +no_memory: + if (pm_hosterror) { + int err = midiOutGetErrorText(pm_hosterror, (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + return pmInsufficientMemory; +} + + +/* winmm_out_delete -- carefully free data associated with midi */ +/**/ +static void winmm_out_delete(PmInternal *midi) +{ + /* delete system dependent device data */ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m) { + if (m->buffer_signal) { + /* don't report errors -- better not to stop cleanup */ + CloseHandle(m->buffer_signal); + } + if (m->sysex_buffer_signal) { + /* don't report errors -- better not to stop cleanup */ + CloseHandle(m->sysex_buffer_signal); + } + if (m->buffers) { + /* if using stream output, free buffers */ + int i; + for (i = 0; i < m->num_buffers; i++) { + if (m->buffers[i]) pm_free(m->buffers[i]); + } + pm_free(m->buffers); + } + + if (m->sysex_buffers) { + /* free sysex buffers */ + int i; + for (i = 0; i < m->num_sysex_buffers; i++) { + if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); + } + pm_free(m->sysex_buffers); + } + } + midi->descriptor = NULL; + pm_free(m); /* delete */ +} + + +/* see comments for winmm_in_close */ +static PmError winmm_out_close(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m->handle.out) { + /* device to close */ + if (midi->latency == 0) { + pm_hosterror = midiOutClose(m->handle.out); + } else { + pm_hosterror = midiStreamClose(m->handle.stream); + } + /* regardless of outcome, free memory */ + winmm_out_delete(midi); + } + if (pm_hosterror) { + int err = midiOutGetErrorText(pm_hosterror, + (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + return pmNoError; +} + + +static PmError winmm_out_abort(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + m->error = MMSYSERR_NOERROR; + + /* only stop output streams */ + if (midi->latency > 0) { + m->error = midiStreamStop(m->handle.stream); + } + return m->error ? pmHostError : pmNoError; +} + +#ifdef GARBAGE +static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + unsigned char *msg_buffer; + + /* at the beginning of sysex, m->hdr is NULL */ + if (!m->hdr) { /* allocate a buffer if none allocated yet */ + m->hdr = get_free_output_buffer(midi); + if (!m->hdr) return pmInsufficientMemory; + m->sysex_byte_count = 0; + } + /* figure out where to write byte */ + msg_buffer = (unsigned char *) (m->hdr->lpData); + assert(m->hdr->lpData == (char *) (m->hdr + 1)); + + /* check for overflow */ + if (m->sysex_byte_count >= m->hdr->dwBufferLength) { + /* allocate a bigger message -- double it every time */ + LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2); + /* printf("expand to %d bytes\n", m->sysex_byte_count * 2); */ + if (!big) return pmInsufficientMemory; + m->error = midiOutPrepareHeader(m->handle.out, big, + sizeof(MIDIHDR)); + if (m->error) { + m->hdr = NULL; + return pmHostError; + } + memcpy(big->lpData, msg_buffer, m->sysex_byte_count); + msg_buffer = (unsigned char *) (big->lpData); + if (m->buffers[0] == m->hdr) { + m->buffers[0] = big; + pm_free(m->hdr); + /* printf("freed m->hdr\n"); */ + } else if (m->buffers[1] == m->hdr) { + m->buffers[1] = big; + pm_free(m->hdr); + /* printf("freed m->hdr\n"); */ + } + m->hdr = big; + } + + /* append byte to message */ + msg_buffer[m->sysex_byte_count++] = byte; + + /* see if we have a complete message */ + if (byte == MIDI_EOX) { + m->hdr->dwBytesRecorded = m->sysex_byte_count; + /* + { int i; int len = m->hdr->dwBytesRecorded; + printf("OutLongMsg %d ", len); + for (i = 0; i < len; i++) { + printf("%2x ", msg_buffer[i]); + } + } + */ + m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); + m->hdr = NULL; /* stop using this message buffer */ + if (m->error) return pmHostError; + } + return pmNoError; +} +#endif + + +static PmError winmm_write_short(PmInternal *midi, PmEvent *event) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + PmError rslt = pmNoError; + assert(m); + if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ + m->error = midiOutShortMsg(m->handle.out, event->message); + if (m->error) rslt = pmHostError; + } else { /* use midiStream interface -- pass data through buffers */ + unsigned long when = event->timestamp; + unsigned long delta; + int full; + if (when == 0) when = midi->now; + /* when is in real_time; translate to intended stream time */ + when = when + m->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < m->last_time) when = m->last_time; + delta = when - m->last_time; + m->last_time = when; + /* before we insert any data, we must have a buffer */ + if (m->hdr == NULL) { + /* stream interface: buffers allocated when stream is opened */ + m->hdr = get_free_output_buffer(midi); + } + full = add_to_buffer(m, m->hdr, delta, event->message); + if (full) { + m->error = midiStreamOut(m->handle.stream, m->hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + m->hdr = NULL; + } + } + return rslt; +} + + +static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + PmError rslt = pmNoError; + if (midi->latency == 0) { + /* do nothing -- it's handled in winmm_write_byte */ + } else { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + /* sysex expects an empty buffer */ + if (m->hdr) { + m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + m->hdr = NULL; + } + return rslt; +} + + +static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + PmError rslt = pmNoError; + assert(m); + + if (midi->latency == 0) { + /* Not using the stream interface. The entire sysex message is + in m->hdr, and we send it using midiOutLongMsg. + */ + m->hdr->dwBytesRecorded = m->sysex_byte_count; + /* + { int i; int len = m->hdr->dwBytesRecorded; + printf("OutLongMsg %d ", len); + for (i = 0; i < len; i++) { + printf("%2x ", msg_buffer[i]); + } + } + */ + + m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } else if (m->hdr) { + /* Using stream interface. There are accumulated bytes in m->hdr + to send using midiStreamOut + */ + /* add bytes recorded to MIDIEVENT length, but don't + count the MIDIEVENT data (3 longs) */ + MIDIEVENT *evt = (MIDIEVENT *) m->hdr->lpData; + evt->dwEvent += m->hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + m->hdr->dwBytesRecorded = (m->hdr->dwBytesRecorded + 3) & ~3; + + m->error = midiStreamOut(m->handle.stream, m->hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + m->hdr = NULL; /* make sure we don't send it again */ + return rslt; +} + + +static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp) +{ + /* write a sysex byte */ + PmError rslt = pmNoError; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + assert(m); + if (midi->latency == 0) { + /* Not using stream interface. Accumulate the entire message into + m->hdr */ + unsigned char *msg_buffer; + /* at the beginning of sysex, m->hdr is NULL */ + if (!m->hdr) { /* allocate a buffer if none allocated yet */ + m->hdr = get_free_sysex_buffer(midi); + if (!m->hdr) return pmInsufficientMemory; + m->sysex_byte_count = 0; + } + /* figure out where to write byte */ + msg_buffer = (unsigned char *) (m->hdr->lpData); + assert(m->hdr->lpData == (char *) (m->hdr + 1)); + + /* append byte to message */ + msg_buffer[m->sysex_byte_count++] = byte; + + /* check for overflow */ + if (m->sysex_byte_count >= m->hdr->dwBufferLength) { + rslt = resize_sysex_buffer(midi, m->sysex_byte_count, m->sysex_byte_count * 2); + + if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */ + rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */ + + } + + } else { /* latency is not zero, use stream interface: accumulate + sysex data in m->hdr and send whenever the buffer fills */ + int full; + unsigned char *ptr; + + /* if m->hdr does not exist, allocate it */ + if (m->hdr == NULL) { + unsigned long when = (unsigned long) timestamp; + unsigned long delta; + unsigned long *ptr; + if (when == 0) when = midi->now; + /* when is in real_time; translate to intended stream time */ + when = when + m->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < m->last_time) when = m->last_time; + delta = when - m->last_time; + m->last_time = when; + + m->hdr = get_free_sysex_buffer(midi); + assert(m->hdr); + ptr = (unsigned long *) m->hdr->lpData; + *ptr++ = delta; + *ptr++ = 0; + *ptr = MEVT_F_LONG; + m->hdr->dwBytesRecorded = 3 * sizeof(long); + } + + /* add the data byte */ + ptr = (unsigned char *) (m->hdr->lpData + m->hdr->dwBytesRecorded); + *ptr = byte; + full = ++m->hdr->dwBytesRecorded >= m->hdr->dwBufferLength; + + /* see if we need to resize */ + if (full) { + int bytesRecorded = m->hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ + rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); + m->hdr->dwBytesRecorded = bytesRecorded; + + if (rslt == pmBufferMaxSize) /* if buffer can't be resized */ + rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */ + } + } + return rslt; +} + + + +static PmError winmm_write_flush(PmInternal *midi) +{ + PmError rslt = pmNoError; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + assert(m); + if (midi->latency == 0) { + /* all messages are sent immediately */ + } else if ((m->hdr) && (!midi->sysex_in_progress)) { + /* sysex messages are sent upon completion, but ordinary messages + may be sitting in a buffer + */ + m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR)); + m->hdr = NULL; + if (m->error) rslt = pmHostError; + } + return rslt; +} + +static PmTimestamp winmm_synchronize(PmInternal *midi) +{ + midiwinmm_type m; + unsigned long pm_stream_time_2; + unsigned long real_time; + unsigned long pm_stream_time; + + /* only synchronize if we are using stream interface */ + if (midi->latency == 0) return 0; + + /* figure out the time */ + m = (midiwinmm_type) midi->descriptor; + pm_stream_time_2 = pm_time_get(m); + + do { + /* read real_time between two reads of stream time */ + pm_stream_time = pm_stream_time_2; + real_time = (*midi->time_proc)(midi->time_info); + pm_stream_time_2 = pm_time_get(m); + /* repeat if more than 1ms elapsed */ + } while (pm_stream_time_2 > pm_stream_time + 1); + m->delta = pm_stream_time - real_time; + m->sync_time = real_time; + return real_time; +} + + +#ifdef GARBAGE +static PmError winmm_write(PmInternal *midi, + PmEvent *buffer, + long length) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + unsigned long now; + int i; + long msg; + PmError rslt = pmNoError; + + m->error = MMSYSERR_NOERROR; + if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ + for (i = 0; (i < length) && (rslt == pmNoError); i++) { + int b = 0; /* count sysex bytes as they are handled */ + msg = buffer[i].message; + if ((msg & 0xFF) == MIDI_SYSEX) { + /* start a sysex message */ + m->sysex_mode = TRUE; + unsigned char midi_byte = (unsigned char) msg; + rslt = winmm_write_sysex_byte(midi, midi_byte); + b = 8; + } else if ((msg & 0x80) && ((msg & 0xFF) != MIDI_EOX)) { + /* a non-sysex message */ + m->error = midiOutShortMsg(m->handle.out, msg); + if (m->error) rslt = pmHostError; + /* any non-real-time message will terminate sysex message */ + if (!is_real_time(msg)) m->sysex_mode = FALSE; + } + /* transmit sysex bytes until we find EOX */ + if (m->sysex_mode) { + while (b < 32 /*bits*/ && (rslt == pmNoError)) { + unsigned char midi_byte = (unsigned char) (msg >> b); + rslt = winmm_write_sysex_byte(midi, midi_byte); + if (midi_byte == MIDI_EOX) { + b = 24; /* end of message */ + m->sysex_mode = FALSE; + } + b += 8; + } + } + } + } else { /* use midiStream interface -- pass data through buffers */ + LPMIDIHDR hdr = NULL; + now = (*midi->time_proc)(midi->time_info); + if (m->first_message || m->sync_time + 100 /*ms*/ < now) { + /* time to resync */ + now = synchronize(midi, m); + m->first_message = FALSE; + } + for (i = 0; i < length && rslt == pmNoError; i++) { + unsigned long when = buffer[i].timestamp; + unsigned long delta; + if (when == 0) when = now; + /* when is in real_time; translate to intended stream time */ + when = when + m->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < m->last_time) when = m->last_time; + delta = when - m->last_time; + m->last_time = when; + /* before we insert any data, we must have a buffer */ + if (hdr == NULL) { + /* stream interface: buffers allocated when stream is opened */ + hdr = get_free_output_buffer(midi); + assert(hdr); + if (m->sysex_mode) { + /* we are in the middle of a sysex message */ + start_sysex_buffer(hdr, delta); + } + } + msg = buffer[i].message; + if ((msg & 0xFF) == MIDI_SYSEX) { + /* sysex expects an empty buffer */ + if (hdr->dwBytesRecorded != 0) { + m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + hdr = get_free_output_buffer(midi); + assert(hdr); + } + /* when we see a MIDI_SYSEX, we always enter sysex mode and call + start_sysex_buffer() */ + start_sysex_buffer(hdr, delta); + m->sysex_mode = TRUE; + } + /* allow a non-real-time status byte to terminate sysex message */ + if (m->sysex_mode && (msg & 0x80) && (msg & 0xFF) != MIDI_SYSEX && + !is_real_time(msg)) { + /* I'm not sure what WinMM does if you send an incomplete sysex + message, but the best way out of this mess seems to be to + recreate the code used when you encounter an EOX, so ... + */ + MIDIEVENT *evt = (MIDIEVENT) hdr->lpData; + evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) { + rslt = pmHostError; + } + hdr = NULL; /* make sure we don't send it again */ + m->sysex_mode = FALSE; /* skip to normal message send code */ + } + if (m->sysex_mode) { + int b = 0; /* count bytes as they are handled */ + while (b < 32 /* bits per word */ && (rslt == pmNoError)) { + int full; + unsigned char midi_byte = (unsigned char) (msg >> b); + if (!hdr) { + hdr = get_free_output_buffer(midi); + assert(hdr); + /* get ready to put sysex bytes in buffer */ + start_sysex_buffer(hdr, delta); + } + full = add_byte_to_buffer(m, hdr, midi_byte); + if (midi_byte == MIDI_EOX) { + b = 24; /* pretend this is last byte to exit loop */ + m->sysex_mode = FALSE; + } + /* see if it's time to send buffer, note that by always + sending complete sysex message right away, we can use + this code to set up the MIDIEVENT properly + */ + if (full || midi_byte == MIDI_EOX) { + /* add bytes recorded to MIDIEVENT length, but don't + count the MIDIEVENT data (3 longs) */ + MIDIEVENT *evt = (MIDIEVENT *) hdr->lpData; + evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) { + rslt = pmHostError; + } + hdr = NULL; /* make sure we don't send it again */ + } + b += 8; /* shift to next byte */ + } + /* test rslt here in case it was set when we terminated a sysex early + (see above) */ + } else if (rslt == pmNoError) { + int full = add_to_buffer(m, hdr, delta, msg); + if (full) { + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + hdr = NULL; + } + } + } + if (hdr && rslt == pmNoError) { + if (m->sysex_mode) { + MIDIEVENT *evt = (MIDIEVENT *) hdr->lpData; + evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; + } + m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + } + return rslt; +} +#endif + + +/* winmm_out_callback -- recycle sysex buffers */ +static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + int i; + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; + int err = 0; /* set to 0 so that no buffer match will also be an error */ + static int entry = 0; + if (++entry > 1) { + assert(FALSE); + } + if (m->callback_error || wMsg != MOM_DONE) { + entry--; + return ; + } + /* Future optimization: eliminate UnprepareHeader calls -- they aren't + necessary; however, this code uses the prepared-flag to indicate which + buffers are free, so we need to do something to flag empty buffers if + we leave them prepared + */ + m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + /* notify waiting sender that a buffer is available */ + /* any errors could be reported via callback_error, but this is always + treated as a Midi error, so we'd have to write a lot more code to + detect that a non-Midi error occurred and do the right thing to find + the corresponding error message text. Therefore, just use assert() + */ + + /* determine if this is an output buffer or a sysex buffer */ + + for (i = 0 ;i < m->num_buffers;i++) { + if (hdr == m->buffers[i]) { + err = SetEvent(m->buffer_signal); + break; + } + } + for (i = 0 ;i < m->num_sysex_buffers;i++) { + if (hdr == m->sysex_buffers[i]) { + err = SetEvent(m->sysex_buffer_signal); + break; + } + } + assert(err); /* false -> error */ + entry--; +} + + +/* winmm_streamout_callback -- unprepare (free) buffer header */ +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; + int err; + static int entry = 0; + if (++entry > 1) { + /* We've reentered this routine. I assume this never happens, but + check to make sure. Apparently, it is possible that this callback + can be called reentrantly because it happened once while debugging. + It looks like this routine is actually reentrant so we can remove + the assertion if necessary. */ + assert(FALSE); + } + if (m->callback_error || wMsg != MOM_DONE) { + entry--; + return ; + } + m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + err = SetEvent(m->buffer_signal); + assert(err); /* false -> error */ + entry--; +} + + +/* +========================================================================================= +begin exported functions +========================================================================================= +*/ + +#define winmm_in_abort pm_fail_fn +pm_fns_node pm_winmm_in_dictionary = { + none_write_short, + none_sysex, + none_sysex, + none_write_byte, + none_write_short, + none_write_flush, + winmm_synchronize, + winmm_in_open, + winmm_in_abort, + winmm_in_close, + success_poll, + winmm_has_host_error, + winmm_get_host_error + }; + +pm_fns_node pm_winmm_out_dictionary = { + winmm_write_short, + winmm_begin_sysex, + winmm_end_sysex, + winmm_write_byte, + winmm_write_short, /* short realtime message */ + winmm_write_flush, + winmm_synchronize, + winmm_out_open, + winmm_out_abort, + winmm_out_close, + none_poll, + winmm_has_host_error, + winmm_get_host_error + }; + + +/* initialize winmm interface. Note that if there is something wrong + with winmm (e.g. it is not supported or installed), it is not an + error. We should simply return without having added any devices to + the table. Hence, no error code is returned. Furthermore, this init + code is called along with every other supported interface, so the + user would have a very hard time figuring out what hardware and API + generated the error. Finally, it would add complexity to pmwin.c to + remember where the error code came from in order to convert to text. + */ +void pm_winmm_init( void ) +{ + pm_winmm_mapper_input(); + pm_winmm_mapper_output(); + pm_winmm_general_inputs(); + pm_winmm_general_outputs(); +} + + +/* no error codes are returned, even if errors are encountered, because + there is probably nothing the user could do (e.g. it would be an error + to retry. + */ +void pm_winmm_term( void ) +{ + int i; +#ifdef DEBUG + char msg[PM_HOST_ERROR_MSG_LEN]; +#endif + int doneAny = 0; +#ifdef DEBUG + printf("pm_winmm_term called\n"); +#endif + for (i = 0; i < pm_descriptor_index; i++) { + PmInternal * midi = descriptors[i].internalDescriptor; + if (midi) { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m->handle.out) { + /* close next open device*/ +#ifdef DEBUG + if (doneAny == 0) { + printf("begin closing open devices...\n"); + doneAny = 1; + } + /* report any host errors; this EXTEREMELY useful when + trying to debug client app */ + if (winmm_has_host_error(midi)) { + winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN); + printf(msg); + } +#endif + /* close all open ports */ + (*midi->dictionary->close)(midi); + } + } + } +#ifdef DEBUG + if (doneAny) { + printf("warning: devices were left open. They have been closed.\n"); + } + printf("pm_winmm_term exiting\n"); +#endif + pm_descriptor_index = 0; +} diff --git a/pd/portmidi/pm_win/pmwinmm.h b/pd/portmidi/pm_win/pmwinmm.h new file mode 100644 index 00000000..53c5fe28 --- /dev/null +++ b/pd/portmidi/pm_win/pmwinmm.h @@ -0,0 +1,5 @@ +/* midiwin32.h -- system-specific definitions */ + +void pm_winmm_init( void ); +void pm_winmm_term( void ); + -- cgit v1.2.1