aboutsummaryrefslogtreecommitdiff
path: root/pd/portmidi/pm_win
diff options
context:
space:
mode:
Diffstat (limited to 'pd/portmidi/pm_win')
-rw-r--r--pd/portmidi/pm_win/README_WIN.txt183
-rw-r--r--pd/portmidi/pm_win/copy-dll.bat13
-rw-r--r--pd/portmidi/pm_win/debugging_dlls.txt145
-rw-r--r--pd/portmidi/pm_win/pm_dll.dsp107
-rw-r--r--pd/portmidi/pm_win/pmdll.c49
-rw-r--r--pd/portmidi/pm_win/pmdll.h5
-rw-r--r--pd/portmidi/pm_win/pmwin.c113
-rw-r--r--pd/portmidi/pm_win/pmwinmm.c1547
-rw-r--r--pd/portmidi/pm_win/pmwinmm.h5
9 files changed, 2167 insertions, 0 deletions
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\<yourProjectName>
+ - 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
+ <...>\<yourProjectName>\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 <assert.h>
+
+#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 );
+