diff options
Diffstat (limited to '3rdparty/portmidi/pm_common')
-rw-r--r-- | 3rdparty/portmidi/pm_common/CMakeLists.txt | 230 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_common/pminternal.h | 59 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_common/pmjni.vcproj | 161 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_common/pmutil.c | 2 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_common/pmutil.h | 198 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_common/portmidi-dynamic.vcproj | 179 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_common/portmidi-static.vcproj | 141 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_common/portmidi.c | 720 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_common/portmidi.h | 908 |
9 files changed, 1385 insertions, 1213 deletions
diff --git a/3rdparty/portmidi/pm_common/CMakeLists.txt b/3rdparty/portmidi/pm_common/CMakeLists.txt index e1710474ae3..062887bc111 100644 --- a/3rdparty/portmidi/pm_common/CMakeLists.txt +++ b/3rdparty/portmidi/pm_common/CMakeLists.txt @@ -1,32 +1,69 @@ -# pm_common +# pm_common/CMakeLists.txt -- how to build portmidi library + +# creates the portmidi library +# exports PM_NEEDED_LIBS to parent. It seems that PM_NEEDED_LIBS for +# Linux should include Thread::Thread and ALSA::ALSA, but these +# are not visible in other CMake files, even though the portmidi +# target is. Therefore, Thread::Thread is replaced by +# CMAKE_THREAD_LIBS_INIT and ALSA::ALSA is replaced by ALSA_LIBRARIES. +# Is there a better way to do this? Maybe this whole file should be +# at the parent level. + +# Support alternative name for static libraries to avoid confusion. +# (In particular, Xcode has automatically converted portmidi.a to +# portmidi.dylib without warning, so using portmidi-static.a eliminates +# this possibility, but default for all libs is "portmidi"): +set(PM_STATIC_LIB_NAME "portmidi" CACHE STRING + "For static builds, the PortMidi library name, e.g. portmidi-static. + Default is portmidi") +set(PM_ACTUAL_LIB_NAME "portmidi") +if(NOT BUILD_SHARED_LIBS) + set(PM_ACTUAL_LIB_NAME ${PM_STATIC_LIB_NAME}) +endif() # set the build directory for libportmidi.a to be in portmidi, not in -# portmidi/pm_common +# portmidi/pm_common. Must be done here BEFORE add_library below. if(APPLE OR WIN32) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) # set the build directory for .dylib libraries set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) - - # the first time CMake configures, save off CMake's built-in flags - if(NOT DEFAULT_DEBUG_FLAGS) - set(DEFAULT_DEBUG_FLAGS ${CMAKE_C_FLAGS_DEBUG} CACHE - STRING "CMake's default debug flags" FORCE) - set(DEFAULT_RELEASE_FLAGS ${CMAKE_C_FLAGS_RELEASE} CACHE - STRING "CMake's default release flags" FORCE) - else(NOT DEFAULT_DEBUG_FLAGS) - message(STATUS "DEFAULT_DEBUG_FLAGS not nil: " ${DEFAULT_DEBUG_FLAGS}) - endif(NOT DEFAULT_DEBUG_FLAGS) -else(APPLE OR WIN32) - set(LINUX_FLAGS "-DPMALSA") endif(APPLE OR WIN32) -if(APPLE) - set(CMAKE_OSX_SYSROOT /Developer/SDKs/MacOSX10.5.sdk CACHE - PATH "-isysroot parameter for compiler" FORCE) - set(CMAKE_C_FLAGS "-mmacosx-version-min=10.5" CACHE - STRING "needed in conjunction with CMAKE_OSX_SYSROOT" FORCE) -endif(APPLE) +# we need full paths to sources because they are shared with other targets +# (in particular pmjni). Set PMDIR to the top-level portmidi directory: +get_filename_component(PMDIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +set(PM_LIB_PUBLIC_SRC ${PMDIR}/pm_common/portmidi.c + ${PMDIR}/pm_common/pmutil.c + ${PMDIR}/porttime/porttime.c) +add_library(portmidi ${PM_LIB_PUBLIC_SRC}) + +# MSVCRT_DLL is "DLL" for shared runtime library, and "" for static: +set_target_properties(portmidi PROPERTIES + VERSION ${LIBRARY_VERSION} + SOVERSION ${LIBRARY_SOVERSION} + OUTPUT_NAME "${PM_ACTUAL_LIB_NAME}" + MSVC_RUNTIME_LIBRARY + "MultiThreaded$<$<CONFIG:Debug>:Debug>${MSVCRT_DLL}" + WINDOWS_EXPORT_ALL_SYMBOLS TRUE) +target_include_directories(portmidi PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>) + + +option(PM_CHECK_ERRORS +"Insert a check for error return values at the end of each PortMidi function. +If an error is encountered, a text message is printed using printf(), the user +is asked to type ENTER, and then exit(-1) is called to clean up and terminate +the program. + +You should not use PM_CHECK_ERRORS if printf() does not work (e.g. this is not +a console application under Windows, or there is no visible console on some +other OS), and you should not use PM_CHECK_ERRORS if you intend to recover +from errors rather than abruptly terminate the program." OFF) +if(PM_CHECK_ERRORS) + target_compile_definitions(portmidi PRIVATE PM_CHECK_ERRORS) +endif(PM_CHECK_ERRORS) macro(prepend_path RESULT PATH) set(${RESULT}) @@ -35,94 +72,79 @@ macro(prepend_path RESULT PATH) endforeach(FILE) endmacro(prepend_path) -set(CMAKE_C_FLAGS_DEBUG - "${DEFAULT_DEBUG_FLAGS} -DPM_CHECK_ERRORS=1 -DDEBUG ${LINUX_FLAGS}" - CACHE STRING "enable extra checks for debugging" FORCE) - -set(CMAKE_C_FLAGS_RELEASE "${DEFAULT_RELEASE_FLAGS} ${LINUX_FLAGS}" - CACHE STRING "flags for release version" FORCE) +# UNIX needs pthread library +if(NOT WIN32) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) +endif() # first include the appropriate system-dependent file: -if(UNIX) - # add the -g switch for Linux and Mac OS X (not used in Win32) - set (CMAKE_C_FLAGS_DEBUG "-g ${CMAKE_C_FLAGS_DEBUG}" - CACHE STRING "enable extra checks for debugging" FORCE) - if(APPLE) - set(MACSRC pmmacosxcm pmmac readbinaryplist finddefault) - prepend_path(LIBSRC ../pm_mac/ ${MACSRC}) - list(APPEND LIBSRC ../porttime/ptmacosx_mach) - - include_directories(${CMAKE_OSX_SYSROOT}/Developer/Headers/FlatCarbon) - set(FRAMEWORK_PATH ${CMAKE_OSX_SYSROOT}/System/Library/Frameworks) - set(COREAUDIO_LIB "${FRAMEWORK_PATH}/CoreAudio.framework") - set(COREFOUNDATION_LIB "${FRAMEWORK_PATH}/CoreFoundation.framework") - set(COREMIDI_LIB "${FRAMEWORK_PATH}/CoreMIDI.framework") - set(CORESERVICES_LIB "${FRAMEWORK_PATH}/CoreServices.framework") - set(PM_NEEDED_LIBS ${COREAUDIO_LIB} ${COREFOUNDATION_LIB} - ${COREMIDI_LIB} ${CORESERVICES_LIB} - CACHE INTERNAL "") - - set(JAVAVM_LIB "${FRAMEWORK_PATH}/JavaVM.framework") - set(JAVA_INCLUDE_PATHS ${JAVAVM_LIB}/Headers) - message(STATUS "SYSROOT: " ${CMAKE_OSX_SYSROOT}) - else(APPLE) - # LINUX settings... - include(FindJNI) - message(STATUS "JAVA_JVM_LIB_PATH is " ${JAVA_JVM_LIB_PATH}) - message(STATUS "JAVA_INCLUDE_PATH is " ${JAVA_INCLUDE_PATH}) - message(STATUS "JAVA_INCLUDE_PATH2 is " ${JAVA_INCLUDE_PATH2}) - message(STATUS "JAVA_JVM_LIBRARY is " ${JAVA_JVM_LIBRARY}) - set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2}) - # libjvm.so is found relative to JAVA_INCLUDE_PATH: - set(JAVAVM_LIB ${JAVA_JVM_LIBRARY}/libjvm.so) - - set(LINUXSRC pmlinuxalsa pmlinux finddefault) - prepend_path(LIBSRC ../pm_linux/ ${LINUXSRC}) - list(APPEND LIBSRC ../porttime/ptlinux) - - set(PM_NEEDED_LIBS pthread asound) - endif(APPLE) -else(UNIX) - if(WIN32) +if(UNIX AND APPLE) + set(Threads::Threads "" PARENT_SCOPE) + find_library(COREAUDIO_LIBRARY CoreAudio REQUIRED) + find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED) + find_library(COREMIDI_LIBRARY CoreMIDI REQUIRED) + find_library(CORESERVICES_LIBRARY CoreServices REQUIRED) + set(PM_LIB_PRIVATE_SRC + ${PMDIR}/porttime/ptmacosx_mach.c + ${PMDIR}/pm_mac/pmmac.c + ${PMDIR}/pm_mac/pmmacosxcm.c + ${PMDIR}/pm_mac/finddefault.c + ${PMDIR}/pm_mac/readbinaryplist.c) + set(PM_NEEDED_LIBS ${CMAKE_THREAD_LIBS_INIT} ${COREAUDIO_LIBRARY} + ${COREFOUNDATION_LIBRARY} ${COREMIDI_LIBRARY} ${CORESERVICES_LIBRARY} + PARENT_SCOPE) + target_link_libraries(portmidi PRIVATE Threads::Threads ${COREAUDIO_LIBRARY} + ${COREFOUNDATION_LIBRARY} ${COREMIDI_LIBRARY} ${CORESERVICES_LIBRARY}) + # set to CMake default; is this right?: + set_target_properties(portmidi PROPERTIES MACOSX_RPATH ON) +elseif(HAIKU) + set(PM_LIB_PRIVATE_SRC + ${PMDIR}/porttime/pthaiku.cpp + ${PMDIR}/pm_haiku/pmhaiku.cpp + ${PMDIR}/pm_linux/finddefault.c) + set(PM_NEEDED_LIBS be midi midi2 PARENT_SCOPE) + target_link_libraries(portmidi PRIVATE be midi midi2) +elseif(UNIX) + target_compile_definitions(portmidi PRIVATE ${LINUX_FLAGS}) + set(PM_LIB_PRIVATE_SRC + ${PMDIR}/porttime/ptlinux.c + ${PMDIR}/pm_linux/pmlinux.c + ${PMDIR}/pm_linux/pmlinuxnull.c + ${PMDIR}/pm_linux/finddefault.c) + if(${LINUX_DEFINES} MATCHES ".*PMALSA.*") + # Note that ALSA is not required if PMNULL is defined -- PortMidi will then + # compile without ALSA and report no MIDI devices. Later, PMSNDIO or PMJACK + # might be additional options. + find_package(ALSA REQUIRED) + list(APPEND PM_LIB_PRIVATE_SRC ${PMDIR}/pm_linux/pmlinuxalsa.c) + set(PM_NEEDED_LIBS ${CMAKE_THREAD_LIBS_INIT} ${ALSA_LIBRARIES} PARENT_SCOPE) + target_link_libraries(portmidi PRIVATE Threads::Threads ALSA::ALSA) + set(PKGCONFIG_REQUIRES_PRIVATE "alsa" PARENT_SCOPE) + else() + message(WARNING "No PMALSA, so PortMidi will not use ALSA, " + "and will not find or open MIDI devices.") + set(PM_NEEDED_LIBS ${CMAKE_THREAD_LIBS_INIT} PARENT_SCOPE) + target_link_libraries(portmidi PRIVATE Threads::Threads) + endif() +elseif(WIN32) + set(PM_LIB_PRIVATE_SRC + ${PMDIR}/porttime/ptwinmm.c + ${PMDIR}/pm_win/pmwin.c + ${PMDIR}/pm_win/pmwinmm.c) + set(PM_NEEDED_LIBS winmm PARENT_SCOPE) + target_link_libraries(portmidi PRIVATE winmm) +# if(NOT BUILD_SHARED_LIBS AND PM_USE_STATIC_RUNTIME) # /MDd is multithread debug DLL, /MTd is multithread debug # /MD is multithread DLL, /MT is multithread. Change to static: - include(../pm_win/static.cmake) - - include(FindJNI) - - set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2}) - # message(STATUS "JAVA_INCLUDE_PATHS: " ${JAVA_INCLUDE_PATHS}) - - set(WINSRC pmwin pmwinmm) - prepend_path(LIBSRC ../pm_win/ ${WINSRC}) - list(APPEND LIBSRC ../porttime/ptwinmm) - set(PM_NEEDED_LIBS winmm.lib) - endif(WIN32) -endif(UNIX) -set(JNI_EXTRA_LIBS ${PM_NEEDED_LIBS} ${JAVA_JVM_LIBRARY}) - -# this completes the list of library sources by adding shared code -list(APPEND LIBSRC pmutil portmidi) - -# now add the shared files to make the complete list of library sources -add_library(portmidi-static ${LIBSRC}) -set_target_properties(portmidi-static PROPERTIES OUTPUT_NAME "portmidi_s") -target_link_libraries(portmidi-static ${PM_NEEDED_LIBS}) +# include(../pm_win/static.cmake) +# endif() +else() + message(FATAL_ERROR "Operating system not supported.") +endif() -# define the jni library -include_directories(${JAVA_INCLUDE_PATHS}) +set(PM_LIB_PUBLIC_SRC ${PM_LIB_PUBLIC_SRC} PARENT_SCOPE) # export to parent +set(PM_LIB_PRIVATE_SRC ${PM_LIB_PRIVATE_SRC} PARENT_SCOPE) # export to parent -set(JNISRC ${LIBSRC} ../pm_java/pmjni/pmjni.c) -add_library(pmjni SHARED ${JNISRC}) -target_link_libraries(pmjni ${JNI_EXTRA_LIBS}) -set_target_properties(pmjni PROPERTIES EXECUTABLE_EXTENSION "jnilib") +target_sources(portmidi PRIVATE ${PM_LIB_PRIVATE_SRC}) -# install the libraries (Linux and Mac OS X command line) -if(UNIX) - INSTALL(TARGETS portmidi-static pmjni - LIBRARY DESTINATION /usr/local/lib - ARCHIVE DESTINATION /usr/local/lib) -# .h files installed by pm_dylib/CMakeLists.txt, so don't need them here -# INSTALL(FILES portmidi.h ../porttime/porttime.h -# DESTINATION /usr/local/include) -endif(UNIX) diff --git a/3rdparty/portmidi/pm_common/pminternal.h b/3rdparty/portmidi/pm_common/pminternal.h index 6b6242026dd..80efbb07f64 100644 --- a/3rdparty/portmidi/pm_common/pminternal.h +++ b/3rdparty/portmidi/pm_common/pminternal.h @@ -1,4 +1,4 @@ -/* pminternal.h -- header for interface implementations */ +/** @file pminternal.h header for PortMidi implementations */ /* this file is included by files that implement library internals */ /* Here is a guide to implementers: @@ -17,6 +17,8 @@ assumptions about pm_fns_type functions are given below. */ +/** @cond INTERNAL - add INTERNAL to Doxygen ENABLED_SECTIONS to include */ + #ifdef __cplusplus extern "C" { #endif @@ -27,8 +29,10 @@ extern int pm_initialized; /* see note in portmidi.c */ void *pm_alloc(size_t s); void pm_free(void *ptr); -/* if an error occurs while opening or closing a midi stream, set these: */ -extern int pm_hosterror; +/* if a host error (an error reported by the host MIDI API that is not + * mapped to a PortMidi error code) occurs in a synchronous operation + * (i.e., not in a callback from another thread) set these: */ +extern int pm_hosterror; /* boolean */ extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; struct pm_internal_struct; @@ -51,31 +55,30 @@ typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi); of the open fails */ typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi, void *driverInfo); +typedef PmError (*pm_create_fn)(int is_input, const char *name, + void *driverInfo); +typedef PmError (*pm_delete_fn)(PmDeviceID id); typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi); /* pm_close_fn should clean up all memory and close the device if any part of the close fails. */ typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi); typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi); -typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg, - unsigned int len); -typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi); +typedef unsigned int (*pm_check_host_error_fn)(struct pm_internal_struct *midi); typedef struct { pm_write_short_fn write_short; /* output short MIDI msg */ pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */ pm_end_sysex_fn end_sysex; /* marks end of sysex message */ pm_write_byte_fn write_byte; /* accumulate one more sysex byte */ - pm_write_realtime_fn write_realtime; /* send real-time message within sysex */ + pm_write_realtime_fn write_realtime; /* send real-time msg within sysex */ pm_write_flush_fn write_flush; /* send any accumulated but unsent data */ - pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */ + pm_synchronize_fn synchronize; /* synchronize PM time to stream time */ pm_open_fn open; /* open MIDI device */ pm_abort_fn abort; /* abort */ pm_close_fn close; /* close device */ pm_poll_fn poll; /* read pending midi events into portmidi buffer */ - pm_has_host_error_fn has_host_error; /* true when device has had host - error message */ - pm_host_error_fn host_error; /* provide text readable host error message - for device (clears and resets) */ + pm_check_host_error_fn check_host_error; /* true when device has had host */ + /* error; sets pm_hosterror and writes message to pm_hosterror_text */ } pm_fns_node, *pm_fns_type; @@ -83,23 +86,25 @@ typedef struct { extern pm_fns_node pm_none_dictionary; typedef struct { - PmDeviceInfo pub; /* some portmidi state also saved in here (for autmatic - device closing (see PmDeviceInfo struct) */ - void *descriptor; /* ID number passed to win32 multimedia API open */ - void *internalDescriptor; /* points to PmInternal device, allows automatic - device closing */ + PmDeviceInfo pub; /* some portmidi state also saved in here (for automatic + device closing -- see PmDeviceInfo struct) */ + int deleted; /* is this is a deleted virtual device? */ + void *descriptor; /* ID number passed to win32 multimedia API open, + * coreMIDI endpoint, etc., representing the device */ + struct pm_internal_struct *pm_internal; /* points to PmInternal device */ + /* when the device is open, allows automatic device closing */ pm_fns_type dictionary; } descriptor_node, *descriptor_type; extern int pm_descriptor_max; -extern descriptor_type descriptors; -extern int pm_descriptor_index; +extern descriptor_type pm_descriptors; +extern int pm_descriptor_len; typedef uint32_t (*time_get_proc_type)(void *time_info); typedef struct pm_internal_struct { - int device_id; /* which device is open (index to descriptors) */ - short write_flag; /* MIDI_IN, or MIDI_OUT */ + int device_id; /* which device is open (index to pm_descriptors) */ + short is_input; /* MIDI IN (true) or MIDI OUT (false) */ PmTimeProcPtr time_proc; /* where to get the time */ void *time_info; /* pass this to get_time() */ @@ -129,7 +134,7 @@ typedef struct pm_internal_struct { PmTimestamp now; /* set by PmWrite to current time */ int first_message; /* initially true, used to run first synchronization */ pm_fns_type dictionary; /* implementation functions */ - void *descriptor; /* system-dependent state */ + void *api_info; /* system-dependent state */ /* the following are used to expedite sysex data */ /* on windows, in debug mode, based on some profiling, these optimizations * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte, @@ -138,7 +143,7 @@ typedef struct pm_internal_struct { */ unsigned char *fill_base; /* addr of ptr to sysex data */ uint32_t *fill_offset_ptr; /* offset of next sysex byte */ - int32_t fill_length; /* how many sysex bytes to write */ + uint32_t fill_length; /* how many sysex bytes to write */ } PmInternal; @@ -155,8 +160,11 @@ PmTimestamp none_synchronize(PmInternal *midi); PmError pm_fail_fn(PmInternal *midi); PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); PmError pm_success_fn(PmInternal *midi); -PmError pm_add_device(char *interf, char *name, int input, void *descriptor, - pm_fns_type dictionary); +PmError pm_add_interf(const char *interf, pm_create_fn create_fn, + pm_delete_fn delete_fn); +PmError pm_add_device(const char *interf, const char *name, int is_input, + int is_virtual, void *descriptor, pm_fns_type dictionary); +void pm_undo_add_device(int id); uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, PmTimestamp timestamp); void pm_read_short(PmInternal *midi, PmEvent *event); @@ -176,3 +184,4 @@ int pm_find_default_device(char *pattern, int is_input); } #endif +/** @endcond */ diff --git a/3rdparty/portmidi/pm_common/pmjni.vcproj b/3rdparty/portmidi/pm_common/pmjni.vcproj deleted file mode 100644 index b783f3e0c54..00000000000 --- a/3rdparty/portmidi/pm_common/pmjni.vcproj +++ /dev/null @@ -1,161 +0,0 @@ -<?xml version="1.0" encoding = "Windows-1252"?> -<VisualStudioProject - ProjectType="Visual C++" - Version="9.00" - Name="pmjni" - ProjectGUID="{87B548BD-F5CE-4D1F-9181-390966AC5855}" - Keyword="Win32Proj"> - <Platforms> - <Platform - Name="Win32"/> - </Platforms> - <Configurations> - <Configuration - Name="Debug|Win32" - OutputDirectory="Debug" - IntermediateDirectory="pmjni.dir\Debug" - ConfigurationType="2" - UseOfMFC="0" - ATLMinimizesCRunTimeLibraryUsage="FALSE" - CharacterSet="2"> - <Tool - Name="VCCLCompilerTool" - AdditionalOptions=" /Zm1000" - AdditionalIncludeDirectories="..\pm_common;..\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - BasicRuntimeChecks="3" - CompileAs="1" - DebugInformationFormat="3" - ExceptionHandling="0" - InlineFunctionExpansion="0" - Optimization="0" - RuntimeLibrary="1" - WarningLevel="3" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\",pmjni_EXPORTS" - AssemblerListingLocation="Debug" - ObjectFile="$(IntDir)\" - ProgramDataBaseFileName="../Debug/pmjni.pdb" -/> - <Tool - Name="VCCustomBuildTool"/> - <Tool - Name="VCResourceCompilerTool" - AdditionalIncludeDirectories="..\pm_common;..\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\",pmjni_EXPORTS"/> - <Tool - Name="VCMIDLTool" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\",pmjni_EXPORTS" - MkTypLibCompatible="FALSE" - TargetEnvironment="1" - GenerateStublessProxies="TRUE" - TypeLibraryName="$(InputName).tlb" - OutputDirectory="$(IntDir)" - HeaderFileName="$(InputName).h" - DLLDataFileName="" - InterfaceIdentifierFileName="$(InputName)_i.c" - ProxyFileName="$(InputName)_p.c"/> - <Tool - Name="VCPreBuildEventTool"/> - <Tool - Name="VCPreLinkEventTool"/> - <Tool - Name="VCPostBuildEventTool"/> - <Tool - Name="VCLinkerTool" - AdditionalOptions=" /STACK:10000000 /machine:I386 /debug" - AdditionalDependencies="$(NOINHERIT) kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib winmm.lib "C:\Program Files\Java\jdk1.6.0_16\include\..\lib\jvm.lib" " - OutputFile="..\Debug\pmjni.dll" - Version="0.0" - GenerateManifest="TRUE" - LinkIncremental="2" - AdditionalLibraryDirectories="" - ProgramDataBaseFile="..\Debug\pmjni.pdb" - GenerateDebugInformation="TRUE" - ImportLibrary="..\Debug\pmjni.lib"/> - </Configuration> - <Configuration - Name="Release|Win32" - OutputDirectory="Release" - IntermediateDirectory="pmjni.dir\Release" - ConfigurationType="2" - UseOfMFC="0" - ATLMinimizesCRunTimeLibraryUsage="FALSE" - CharacterSet="2"> - <Tool - Name="VCCLCompilerTool" - AdditionalOptions=" /Zm1000" - AdditionalIncludeDirectories="..\pm_common;..\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - CompileAs="1" - ExceptionHandling="0" - InlineFunctionExpansion="2" - Optimization="2" - RuntimeLibrary="0" - WarningLevel="3" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\",pmjni_EXPORTS" - AssemblerListingLocation="Release" - ObjectFile="$(IntDir)\" - ProgramDataBaseFileName="../Release/pmjni.pdb" -/> - <Tool - Name="VCCustomBuildTool"/> - <Tool - Name="VCResourceCompilerTool" - AdditionalIncludeDirectories="..\pm_common;..\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\",pmjni_EXPORTS"/> - <Tool - Name="VCMIDLTool" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\",pmjni_EXPORTS" - MkTypLibCompatible="FALSE" - TargetEnvironment="1" - GenerateStublessProxies="TRUE" - TypeLibraryName="$(InputName).tlb" - OutputDirectory="$(IntDir)" - HeaderFileName="$(InputName).h" - DLLDataFileName="" - InterfaceIdentifierFileName="$(InputName)_i.c" - ProxyFileName="$(InputName)_p.c"/> - <Tool - Name="VCPreBuildEventTool"/> - <Tool - Name="VCPreLinkEventTool"/> - <Tool - Name="VCPostBuildEventTool"/> - <Tool - Name="VCLinkerTool" - AdditionalOptions=" /STACK:10000000 /machine:I386" - AdditionalDependencies="$(NOINHERIT) kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib winmm.lib "C:\Program Files\Java\jdk1.6.0_16\include\..\lib\jvm.lib" " - OutputFile="..\Release\pmjni.dll" - Version="0.0" - GenerateManifest="TRUE" - LinkIncremental="1" - AdditionalLibraryDirectories="" - ProgramDataBaseFile="..\Release\pmjni.pdb" - ImportLibrary="..\Release\pmjni.lib"/> - </Configuration> - </Configurations> - <Files> - <Filter - Name="Source Files" - Filter=""> - <File - RelativePath="..\pm_win\pmwin.c"> - </File> - <File - RelativePath="..\pm_win\pmwinmm.c"> - </File> - <File - RelativePath="..\porttime\ptwinmm.c"> - </File> - <File - RelativePath="..\pm_common\pmutil.c"> - </File> - <File - RelativePath="..\pm_common\portmidi.c"> - </File> - <File - RelativePath="..\pm_java\pmjni\pmjni.c"> - </File> - </Filter> - </Files> - <Globals> - </Globals> -</VisualStudioProject> diff --git a/3rdparty/portmidi/pm_common/pmutil.c b/3rdparty/portmidi/pm_common/pmutil.c index 7d0abe35337..a70fe2fa1f8 100644 --- a/3rdparty/portmidi/pm_common/pmutil.c +++ b/3rdparty/portmidi/pm_common/pmutil.c @@ -8,7 +8,7 @@ #include "pmutil.h" #include "pminternal.h" -#if defined(WIN32) || defined(_MSC_VER) +#ifdef WIN32 #define bzero(addr, siz) memset(addr, 0, siz) #endif diff --git a/3rdparty/portmidi/pm_common/pmutil.h b/3rdparty/portmidi/pm_common/pmutil.h index ef5ee4bf84a..17f6984a0d7 100644 --- a/3rdparty/portmidi/pm_common/pmutil.h +++ b/3rdparty/portmidi/pm_common/pmutil.h @@ -1,31 +1,48 @@ -/* pmutil.h -- some helpful utilities for building midi - applications that use PortMidi +/** @file pmutil.h lock-free queue for building MIDI + applications with PortMidi. + + PortMidi is not reentrant, and locks can suffer from priority + inversion. To support coordination between system callbacks, a + high-priority thread created with PortTime, and the main + application thread, PortMidi uses a lock-free, non-blocking + queue. The queue implementation is not particular to MIDI and is + available for other uses. */ #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ +/** @defgroup grp_pmutil Lock-free Queue + @{ +*/ + +/** The queue representation is opaque. Declare a queue as PmQueue * */ typedef void PmQueue; -/* - A single-reader, single-writer queue is created by - Pm_QueueCreate(), which takes the number of messages and - the message size as parameters. The queue only accepts - fixed sized messages. Returns NULL if memory cannot be allocated. +/** create a single-reader, single-writer queue. + + @param num_msgs the number of messages the queue can hold + + @param the fixed message size + + @return the allocated and initialized queue, or NULL if memory + cannot be allocated. Allocation uses #pm_malloc(). + + The queue only accepts fixed sized messages. This queue implementation uses the "light pipe" algorithm which operates correctly even with multi-processors and out-of-order memory writes. (see Alexander Dokumentov, "Lock-free Interprocess - Communication," Dr. Dobbs Portal, http://www.ddj.com/, - articleID=189401457, June 15, 2006. This algorithm requires - that messages be translated to a form where no words contain - zeros. Each word becomes its own "data valid" tag. Because of - this translation, we cannot return a pointer to data still in - the queue when the "peek" method is called. Instead, a buffer - is preallocated so that data can be copied there. Pm_QueuePeek() - dequeues a message into this buffer and returns a pointer to - it. A subsequent Pm_Dequeue() will copy from this buffer. + Communication," Dr. Dobbs Portal, http://www.ddj.com/, + articleID=189401457, June 15, 2006. This algorithm requires that + messages be translated to a form where no words contain + zeros. Each word becomes its own "data valid" tag. Because of this + translation, we cannot return a pointer to data still in the queue + when the "peek" method is called. Instead, a buffer is + preallocated so that data can be copied there. Pm_QueuePeek() + dequeues a message into this buffer and returns a pointer to it. A + subsequent Pm_Dequeue() will copy from this buffer. This implementation does not try to keep reader/writer data in separate cache lines or prevent thrashing on cache lines. @@ -36,92 +53,127 @@ typedef void PmQueue; of the cache line, especially if messages are smaller than cache lines. See the Dokumentov article for explanation. - The algorithm is extended to handle "overflow" reporting. To report - an overflow, the sender writes the current tail position to a field. - The receiver must acknowlege receipt by zeroing the field. The sender - will not send more until the field is zeroed. - - Pm_QueueDestroy() destroys the queue and frees its storage. + The algorithm is extended to handle "overflow" reporting. To + report an overflow, the sender writes the current tail position to + a field. The receiver must acknowlege receipt by zeroing the + field. The sender will not send more until the field is zeroed. */ - PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); + +/** destroy a queue and free its storage. + + @param queue a queue created by #Pm_QueueCreate(). + + @return pmNoError or an error code. + + Uses #pm_free(). + + */ PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); -/* - Pm_Dequeue() removes one item from the queue, copying it into msg. - Returns 1 if successful, and 0 if the queue is empty. - Returns pmBufferOverflow if what would have been the next thing - in the queue was dropped due to overflow. (So when overflow occurs, - the receiver can receive a queue full of messages before getting the - overflow report. This protocol ensures that the reader will be +/** remove one message from the queue, copying it into \p msg. + + @param queue a queue created by #Pm_QueueCreate(). + + @param msg address to which the message, if any, is copied. + + @return 1 if successful, and 0 if the queue is empty. Returns + #pmBufferOverflow if what would have been the next thing in the + queue was dropped due to overflow. (So when overflow occurs, the + receiver can receive a queue full of messages before getting the + overflow report. This protocol ensures that the reader will be notified when data is lost due to overflow. */ PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg); +/** insert one message into the queue, copying it from \p msg. -/* - Pm_Enqueue() inserts one item into the queue, copying it from msg. - Returns pmNoError if successful and pmBufferOverflow if the queue was - already full. If pmBufferOverflow is returned, the overflow flag is set. + @param queue a queue created by #Pm_QueueCreate(). + + @param msg address of the message to be enqueued. + + @return #pmNoError if successful and #pmBufferOverflow if the + queue was already full. If #pmBufferOverflow is returned, the + overflow flag is set. */ PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); +/** test if the queue is full. -/* - Pm_QueueFull() returns non-zero if the queue is full - Pm_QueueEmpty() returns non-zero if the queue is empty + @param queue a queue created by #Pm_QueueCreate(). - Either condition may change immediately because a parallel - enqueue or dequeue operation could be in progress. Furthermore, - Pm_QueueEmpty() is optimistic: it may say false, when due to - out-of-order writes, the full message has not arrived. Therefore, - Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns - false. On the other hand, Pm_QueueFull() is pessimistic: if it - returns false, then Pm_Enqueue() is guaranteed to succeed. + @return non-zero iff the queue is empty, and @pmBadPtr if \p queue + is NULL. - Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. - Pm_QueueEmpty() returns FALSE if queue is NULL. + The full condition may change immediately because a parallel + dequeue operation could be in progress. The result is + pessimistic: if it returns false (zero) to the single writer, then + #Pm_Enqueue() is guaranteed to succeed. */ PMEXPORT int Pm_QueueFull(PmQueue *queue); + +/** test if the queue is empty. + + @param queue a queue created by #Pm_QueueCreate(). + + @return zero iff the queue is either empty or NULL. + + The empty condition may change immediately because a parallel + enqueue operation could be in progress. Furthermore, the + result is optimistic: it may say false, when due to + out-of-order writes, the full message has not arrived. Therefore, + #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns + false. +*/ PMEXPORT int Pm_QueueEmpty(PmQueue *queue); +/** get a pointer to the item at the head of the queue. -/* - Pm_QueuePeek() returns a pointer to the item at the head of the queue, - or NULL if the queue is empty. The item is not removed from the queue. - Pm_QueuePeek() will not indicate when an overflow occurs. If you want - to get and check pmBufferOverflow messages, use the return value of - Pm_QueuePeek() *only* as an indication that you should call - Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would - return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally - clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume - enqueuing messages. A subsequent call to Pm_QueuePeek() - will return a pointer to the first message *after* the overflow. - Using this as an indication to call Pm_Dequeue(), the first call - to Pm_Dequeue() will return pmBufferOverflow. The second call will - return success, copying the same message pointed to by the previous - Pm_QueuePeek(). - - When to use Pm_QueuePeek(): (1) when you need to look at the message + @param queue a queue created by #Pm_QueueCreate(). + + @result a pointer to the head message or NULL if the queue is empty. + + The message is not removed from the queue. #Pm_QueuePeek() will + not indicate when an overflow occurs. If you want to get and check + #pmBufferOverflow messages, use the return value of + #Pm_QueuePeek() *only* as an indication that you should call + #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue() + would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL, + but internally clear the #pmBufferOverflow flag, enabling + #Pm_Enqueue() to resume enqueuing messages. A subsequent call to + #Pm_QueuePeek() will return a pointer to the first message *after* + the overflow. Using this as an indication to call #Pm_Dequeue(), + the first call to #Pm_Dequeue() will return #pmBufferOverflow. The + second call will return success, copying the same message pointed + to by the previous #Pm_QueuePeek(). + + When to use #Pm_QueuePeek(): (1) when you need to look at the message data to decide who should be called to receive it. (2) when you need to know a message is ready but cannot accept the message. - Note that Pm_QueuePeek() is not a fast check, so if possible, you - might as well just call Pm_Dequeue() and accept the data if it is there. + Note that #Pm_QueuePeek() is not a fast check, so if possible, you + might as well just call #Pm_Dequeue() and accept the data if it is there. */ PMEXPORT void *Pm_QueuePeek(PmQueue *queue); -/* - Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow - condition to the reader (dequeuer). E.g. when transfering data from - the OS to an application, if the OS indicates a buffer overrun, - Pm_SetOverflow() can be used to insure that the reader receives a - pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue - is NULL, returns pmBufferOverflow if buffer is already in an overflow - state, returns pmNoError if successfully set overflow state. +/** allows the writer (enqueuer) to signal an overflow + condition to the reader (dequeuer). + + @param queue a queue created by #Pm_QueueCreate(). + + @return #pmNoError if overflow is set, or #pmBadPtr if queue is + NULL, or #pmBufferOverflow if buffer is already in an overflow + state. + + E.g., when transfering data from the OS to an application, if the + OS indicates a buffer overrun, #Pm_SetOverflow() can be used to + insure that the reader receives a #pmBufferOverflow result from + #Pm_Dequeue(). */ PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); +/** @} */ + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/3rdparty/portmidi/pm_common/portmidi-dynamic.vcproj b/3rdparty/portmidi/pm_common/portmidi-dynamic.vcproj deleted file mode 100644 index 42b4a271a20..00000000000 --- a/3rdparty/portmidi/pm_common/portmidi-dynamic.vcproj +++ /dev/null @@ -1,179 +0,0 @@ -<?xml version="1.0" encoding = "Windows-1252"?> -<VisualStudioProject - ProjectType="Visual C++" - Version="9.00" - Name="portmidi-dynamic" - ProjectGUID="{7283FAD1-7415-4061-A19A-FF5C7BCE9306}" - Keyword="Win32Proj"> - <Platforms> - <Platform - Name="Win32"/> - </Platforms> - <Configurations> - <Configuration - Name="Debug|Win32" - OutputDirectory="Debug" - IntermediateDirectory="portmidi-dynamic.dir\Debug" - ConfigurationType="2" - UseOfMFC="0" - ATLMinimizesCRunTimeLibraryUsage="FALSE" - CharacterSet="2"> - <Tool - Name="VCCLCompilerTool" - AdditionalOptions=" /Zm1000" - AdditionalIncludeDirectories="C:\Users\rbd\portmedia\portmidi\pm_common;C:\Users\rbd\portmedia\portmidi\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - BasicRuntimeChecks="3" - CompileAs="1" - DebugInformationFormat="3" - ExceptionHandling="0" - InlineFunctionExpansion="0" - Optimization="0" - RuntimeLibrary="3" - WarningLevel="3" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\",portmidi_dynamic_EXPORTS" - AssemblerListingLocation="Debug" - ObjectFile="$(IntDir)\" - ProgramDataBaseFileName="C:/Users/rbd/portmedia/portmidi/Debug/portmidi.pdb" -/> - <Tool - Name="VCCustomBuildTool"/> - <Tool - Name="VCResourceCompilerTool" - AdditionalIncludeDirectories="C:\Users\rbd\portmedia\portmidi\pm_common;C:\Users\rbd\portmedia\portmidi\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\",portmidi_dynamic_EXPORTS"/> - <Tool - Name="VCMIDLTool" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\",portmidi_dynamic_EXPORTS" - MkTypLibCompatible="FALSE" - TargetEnvironment="1" - GenerateStublessProxies="TRUE" - TypeLibraryName="$(InputName).tlb" - OutputDirectory="$(IntDir)" - HeaderFileName="$(InputName).h" - DLLDataFileName="" - InterfaceIdentifierFileName="$(InputName)_i.c" - ProxyFileName="$(InputName)_p.c"/> - <Tool - Name="VCPreBuildEventTool"/> - <Tool - Name="VCPreLinkEventTool"/> - <Tool - Name="VCPostBuildEventTool"/> - <Tool - Name="VCLinkerTool" - AdditionalOptions=" /STACK:10000000 /machine:I386 /debug" - AdditionalDependencies="$(NOINHERIT) kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib winmm.lib " - OutputFile="C:\Users\rbd\portmedia\portmidi\Debug\portmidi.dll" - Version="0.0" - GenerateManifest="TRUE" - LinkIncremental="2" - AdditionalLibraryDirectories="" - ProgramDataBaseFile="C:\Users\rbd\portmedia\portmidi\Debug\portmidi.pdb" - GenerateDebugInformation="TRUE" - ImportLibrary="C:\Users\rbd\portmedia\portmidi\Debug\portmidi.lib"/> - </Configuration> - <Configuration - Name="Release|Win32" - OutputDirectory="Release" - IntermediateDirectory="portmidi-dynamic.dir\Release" - ConfigurationType="2" - UseOfMFC="0" - ATLMinimizesCRunTimeLibraryUsage="FALSE" - CharacterSet="2"> - <Tool - Name="VCCLCompilerTool" - AdditionalOptions=" /Zm1000" - AdditionalIncludeDirectories="C:\Users\rbd\portmedia\portmidi\pm_common;C:\Users\rbd\portmedia\portmidi\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - CompileAs="1" - ExceptionHandling="0" - InlineFunctionExpansion="2" - Optimization="2" - RuntimeLibrary="2" - WarningLevel="3" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\",portmidi_dynamic_EXPORTS" - AssemblerListingLocation="Release" - ObjectFile="$(IntDir)\" - ProgramDataBaseFileName="C:/Users/rbd/portmedia/portmidi/Release/portmidi.pdb" -/> - <Tool - Name="VCCustomBuildTool"/> - <Tool - Name="VCResourceCompilerTool" - AdditionalIncludeDirectories="C:\Users\rbd\portmedia\portmidi\pm_common;C:\Users\rbd\portmedia\portmidi\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\",portmidi_dynamic_EXPORTS"/> - <Tool - Name="VCMIDLTool" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\",portmidi_dynamic_EXPORTS" - MkTypLibCompatible="FALSE" - TargetEnvironment="1" - GenerateStublessProxies="TRUE" - TypeLibraryName="$(InputName).tlb" - OutputDirectory="$(IntDir)" - HeaderFileName="$(InputName).h" - DLLDataFileName="" - InterfaceIdentifierFileName="$(InputName)_i.c" - ProxyFileName="$(InputName)_p.c"/> - <Tool - Name="VCPreBuildEventTool"/> - <Tool - Name="VCPreLinkEventTool"/> - <Tool - Name="VCPostBuildEventTool"/> - <Tool - Name="VCLinkerTool" - AdditionalOptions=" /STACK:10000000 /machine:I386" - AdditionalDependencies="$(NOINHERIT) kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib winmm.lib " - OutputFile="C:\Users\rbd\portmedia\portmidi\Release\portmidi.dll" - Version="0.0" - GenerateManifest="TRUE" - LinkIncremental="1" - AdditionalLibraryDirectories="" - ProgramDataBaseFile="C:\Users\rbd\portmedia\portmidi\Release\portmidi.pdb" - ImportLibrary="C:\Users\rbd\portmedia\portmidi\Release\portmidi.lib"/> - </Configuration> - </Configurations> - <Files> - <File - RelativePath="C:\Users\rbd\portmedia\portmidi\pm_common\CMakeLists.txt"> - <FileConfiguration - Name="Debug|Win32"> - <Tool - Name="VCCustomBuildTool" - Description="Building Custom Rule C:/Users/rbd/portmedia/portmidi/pm_common/CMakeLists.txt" - CommandLine=""C:\Program Files\CMake 2.6\bin\cmake.exe" -HC:/Users/rbd/portmedia/portmidi -BC:/Users/rbd/portmedia/portmidi --check-stamp-file CMakeFiles/generate.stamp" - AdditionalDependencies="C:\Users\rbd\portmedia\portmidi\pm_common\CMakeLists.txt;"C:\Program Files\CMake 2.6\share\cmake-2.6\Modules\FindJNI.cmake";C:\Users\rbd\portmedia\portmidi\pm_common\CMakeLists.txt;" - Outputs="CMakeFiles\generate.stamp"/> - </FileConfiguration> - <FileConfiguration - Name="Release|Win32"> - <Tool - Name="VCCustomBuildTool" - Description="Building Custom Rule C:/Users/rbd/portmedia/portmidi/pm_common/CMakeLists.txt" - CommandLine=""C:\Program Files\CMake 2.6\bin\cmake.exe" -HC:/Users/rbd/portmedia/portmidi -BC:/Users/rbd/portmedia/portmidi --check-stamp-file CMakeFiles/generate.stamp" - AdditionalDependencies="C:\Users\rbd\portmedia\portmidi\pm_common\CMakeLists.txt;"C:\Program Files\CMake 2.6\share\cmake-2.6\Modules\FindJNI.cmake";C:\Users\rbd\portmedia\portmidi\pm_common\CMakeLists.txt;" - Outputs="CMakeFiles\generate.stamp"/> - </FileConfiguration> - </File> - <Filter - Name="Source Files" - Filter=""> - <File - RelativePath="C:\Users\rbd\portmedia\portmidi\pm_win\pmwin.c"> - </File> - <File - RelativePath="C:\Users\rbd\portmedia\portmidi\pm_win\pmwinmm.c"> - </File> - <File - RelativePath="C:\Users\rbd\portmedia\portmidi\porttime\ptwinmm.c"> - </File> - <File - RelativePath="C:\Users\rbd\portmedia\portmidi\pm_common\pmutil.c"> - </File> - <File - RelativePath="C:\Users\rbd\portmedia\portmidi\pm_common\portmidi.c"> - </File> - </Filter> - </Files> - <Globals> - </Globals> -</VisualStudioProject> diff --git a/3rdparty/portmidi/pm_common/portmidi-static.vcproj b/3rdparty/portmidi/pm_common/portmidi-static.vcproj deleted file mode 100644 index 4702b2d1e31..00000000000 --- a/3rdparty/portmidi/pm_common/portmidi-static.vcproj +++ /dev/null @@ -1,141 +0,0 @@ -<?xml version="1.0" encoding = "Windows-1252"?> -<VisualStudioProject - ProjectType="Visual C++" - Version="9.00" - Name="portmidi-static" - ProjectGUID="{2985D5DA-D91E-44E0-924B-E612B6AA33F6}" - Keyword="Win32Proj"> - <Platforms> - <Platform - Name="Win32"/> - </Platforms> - <Configurations> - <Configuration - Name="Debug|Win32" - OutputDirectory="Debug" - IntermediateDirectory="portmidi-static.dir\Debug" - ConfigurationType="4" - UseOfMFC="0" - ATLMinimizesCRunTimeLibraryUsage="FALSE" - CharacterSet="2"> - <Tool - Name="VCCLCompilerTool" - AdditionalOptions=" /Zm1000" - AdditionalIncludeDirectories="..\pm_common;..\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - BasicRuntimeChecks="3" - CompileAs="1" - DebugInformationFormat="3" - ExceptionHandling="0" - InlineFunctionExpansion="0" - Optimization="0" - RuntimeLibrary="1" - WarningLevel="3" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\"" - AssemblerListingLocation="Debug" - ObjectFile="$(IntDir)\" - ProgramDataBaseFileName="../Debug/portmidi_s.pdb" -/> - <Tool - Name="VCCustomBuildTool"/> - <Tool - Name="VCResourceCompilerTool" - AdditionalIncludeDirectories="..\pm_common;..\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\""/> - <Tool - Name="VCMIDLTool" - PreprocessorDefinitions="WIN32,_WINDOWS,_DEBUG,PM_CHECK_ERRORS=1,DEBUG,CMAKE_INTDIR=\"Debug\"" - MkTypLibCompatible="FALSE" - TargetEnvironment="1" - GenerateStublessProxies="TRUE" - TypeLibraryName="$(InputName).tlb" - OutputDirectory="$(IntDir)" - HeaderFileName="$(InputName).h" - DLLDataFileName="" - InterfaceIdentifierFileName="$(InputName)_i.c" - ProxyFileName="$(InputName)_p.c"/> - <Tool - Name="VCPreBuildEventTool"/> - <Tool - Name="VCPreLinkEventTool"/> - <Tool - Name="VCPostBuildEventTool"/> - <Tool - Name="VCLibrarianTool" - OutputFile="..\Debug\portmidi_s.lib"/> - </Configuration> - <Configuration - Name="Release|Win32" - OutputDirectory="Release" - IntermediateDirectory="portmidi-static.dir\Release" - ConfigurationType="4" - UseOfMFC="0" - ATLMinimizesCRunTimeLibraryUsage="FALSE" - CharacterSet="2"> - <Tool - Name="VCCLCompilerTool" - AdditionalOptions=" /Zm1000" - AdditionalIncludeDirectories="..\pm_common;..\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - CompileAs="1" - ExceptionHandling="0" - InlineFunctionExpansion="2" - Optimization="2" - RuntimeLibrary="0" - WarningLevel="3" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\"" - AssemblerListingLocation="Release" - ObjectFile="$(IntDir)\" - ProgramDataBaseFileName="../Release/portmidi_s.pdb" -/> - <Tool - Name="VCCustomBuildTool"/> - <Tool - Name="VCResourceCompilerTool" - AdditionalIncludeDirectories="..\pm_common;..\porttime;"C:\Program Files\Java\jdk1.6.0_16\include";"C:\Program Files\Java\jdk1.6.0_16\include\win32";" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\""/> - <Tool - Name="VCMIDLTool" - PreprocessorDefinitions="WIN32,_WINDOWS,NDEBUG,CMAKE_INTDIR=\"Release\"" - MkTypLibCompatible="FALSE" - TargetEnvironment="1" - GenerateStublessProxies="TRUE" - TypeLibraryName="$(InputName).tlb" - OutputDirectory="$(IntDir)" - HeaderFileName="$(InputName).h" - DLLDataFileName="" - InterfaceIdentifierFileName="$(InputName)_i.c" - ProxyFileName="$(InputName)_p.c"/> - <Tool - Name="VCPreBuildEventTool"/> - <Tool - Name="VCPreLinkEventTool"/> - <Tool - Name="VCPostBuildEventTool"/> - <Tool - Name="VCLibrarianTool" - OutputFile="..\Release\portmidi_s.lib"/> - </Configuration> - </Configurations> - <Files> - <Filter - Name="Source Files" - Filter=""> - <File - RelativePath="..\pm_win\pmwin.c"> - </File> - <File - RelativePath="..\pm_win\pmwinmm.c"> - </File> - <File - RelativePath="..\porttime\ptwinmm.c"> - </File> - <File - RelativePath="..\pm_common\pmutil.c"> - </File> - <File - RelativePath="..\pm_common\portmidi.c"> - </File> - </Filter> - </Files> - <Globals> - </Globals> -</VisualStudioProject> diff --git a/3rdparty/portmidi/pm_common/portmidi.c b/3rdparty/portmidi/pm_common/portmidi.c index 2ed9171e781..7c906dcae42 100644 --- a/3rdparty/portmidi/pm_common/portmidi.c +++ b/3rdparty/portmidi/pm_common/portmidi.c @@ -1,7 +1,5 @@ -#ifdef _MSC_VER - #pragma warning(disable: 4244) // stop warnings about downsize typecasts - #pragma warning(disable: 4018) // stop warnings about signed/unsigned -#endif +/* portmidi.c -- cross-platform MIDI I/O library */ +/* see license.txt for license */ #include "stdlib.h" #include "string.h" @@ -36,12 +34,21 @@ #define is_empty(midi) ((midi)->tail == (midi)->head) -/* this is not static so that pm_init can set it directly if +/* this is not static so that pm_init can set it directly * (see pmmac.c:pm_init()) */ int pm_initialized = FALSE; -int pm_hosterror; +int pm_hosterror; /* boolean */ + +/* if PM_CHECK_ERRORS is enabled, but the caller wants to + * handle an error condition, declare this as extern and + * set to FALSE (this override is provided specifically + * for the test program virttest.c, where pmNameConflict + * is expected in a call to Pm_CreateVirtualInput()): + */ +int pm_check_errors = TRUE; + char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; #ifdef PM_CHECK_ERRORS @@ -54,15 +61,16 @@ static void prompt_and_exit(void) { char line[STRING_MAX]; printf("type ENTER..."); - fgets(line, STRING_MAX, stdin); + char *rslt = fgets(line, STRING_MAX, stdin); /* this will clean up open ports: */ exit(-1); } - static PmError pm_errmsg(PmError err) { - if (err == pmHostError) { + if (!pm_check_errors) { /* see pm_check_errors declaration above */ + ; + } else if (err == pmHostError) { /* it seems pointless to allocate memory and copy the string, * so I will do the work of Pm_GetHostErrorText directly */ @@ -87,52 +95,193 @@ system implementation of portmidi interface */ int pm_descriptor_max = 0; -int pm_descriptor_index = 0; -descriptor_type descriptors = NULL; +int pm_descriptor_len = 0; +descriptor_type pm_descriptors = NULL; + +/* interface pm_descriptors are simple: an array of string/fnptr pairs: */ +#define MAX_INTERF 4 +static struct { + const char *interf; + pm_create_fn create_fn; + pm_delete_fn delete_fn; +} pm_interf_list[MAX_INTERF]; + +static int pm_interf_list_len = 0; + + +/* pm_add_interf -- describe an interface to library + * + * This is called at initialization time, once for each + * supported interface (e.g., CoreMIDI). The strings + * are retained but NOT COPIED, so do not destroy them! + * + * The purpose is to register functions that create/delete + * a virtual input or output device. + * + * returns pmInsufficientMemor if interface memory is + * exceeded, otherwise returns pmNoError. + */ +PmError pm_add_interf(const char *interf, pm_create_fn create_fn, + pm_delete_fn delete_fn) +{ + if (pm_interf_list_len >= MAX_INTERF) { + return pmInsufficientMemory; + } + pm_interf_list[pm_interf_list_len].interf = interf; + pm_interf_list[pm_interf_list_len].create_fn = create_fn; + pm_interf_list[pm_interf_list_len].delete_fn = delete_fn; + pm_interf_list_len++; + return pmNoError; +} + + +PmError pm_create_virtual(PmInternal *midi, int is_input, const char *interf, + const char *name, void *device_info) +{ + int i; + if (pm_interf_list_len == 0) { + return pmNotImplemented; + } + if (!interf) { + /* default interface is the first one */ + interf = pm_interf_list[0].interf; + } + for (i = 0; i < pm_interf_list_len; i++) { + if (strcmp(pm_interf_list[i].interf, + interf) == 0) { + int id = (*pm_interf_list[i].create_fn)(is_input, name, + device_info); + pm_descriptors[id].pub.is_virtual = TRUE; + return id; + } + } + return pmInterfaceNotSupported; +} + /* pm_add_device -- describe interface/device pair to library * * This is called at intialization time, once for each - * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1) - * The strings are retained but NOT COPIED, so do not destroy them! + * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1). + * This is also called when user creates a virtual device. + * + * Normally, increasing integer indices are returned. If the device + * is virtual, a linear search is performed to ensure that the name + * is unique. If the name is already taken, the call will fail and + * no device is added. + * + * interf is assumed to be static memory, so it is NOT COPIED and + * NOT FREED. + * name is owned by caller, COPIED if needed, and FREED by PortMidi. + * Caller is resposible for freeing name when pm_add_device returns. * - * returns pmInvalidDeviceId if device memory is exceeded - * otherwise returns pmNoError + * returns pmInvalidDeviceId if device memory is exceeded or a virtual + * device would take the name of an existing device. + * otherwise returns index (portmidi device_id) of the added device */ -PmError pm_add_device(char *interf, char *name, int input, - void *descriptor, pm_fns_type dictionary) { - if (pm_descriptor_index >= pm_descriptor_max) { - // expand descriptors +PmError pm_add_device(const char *interf, const char *name, int is_input, + int is_virtual, void *descriptor, pm_fns_type dictionary) { + /* printf("pm_add_device: %s %s %d %p %p\n", + interf, name, is_input, descriptor, dictionary); */ + int device_id; + PmDeviceInfo *d; + /* if virtual, search for duplicate name or unused ID; otherwise, + * just add a new device at the next integer available: + */ + for (device_id = (is_virtual ? 0 : pm_descriptor_len); + device_id < pm_descriptor_len; device_id++) { + d = &pm_descriptors[device_id].pub; + d->structVersion = 200; + if (strcmp(d->interf, interf) == 0 && strcmp(d->name, name) == 0) { + /* only reuse a name if it is a deleted virtual device with + * a matching direction (input or output) */ + if (pm_descriptors[device_id].deleted && is_input == d->input) { + /* here, we know d->is_virtual because only virtual devices + * can be deleted, and we know is_virtual because we are + * in this loop. + */ + pm_free((void *) d->name); /* reuse this device entry */ + d->name = NULL; + break; + /* name conflict exists if the new device appears to others as + * the same direction (input or output) as the existing device. + * Note that virtual inputs appear to others as outputs and + * vice versa. + * The direction of the new virtual device to others is "output" + * if is_input, i.e., virtual inputs appear to others as outputs. + * The existing device appears to others as "output" if + * (d->is_virtual == d->input) by the same logic. + * The compare will detect if device directions are the same: + */ + } else if (is_input == (d->is_virtual == d->input)) { + return pmNameConflict; + } + } + } + if (device_id >= pm_descriptor_max) { + // expand pm_descriptors descriptor_type new_descriptors = (descriptor_type) pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32)); if (!new_descriptors) return pmInsufficientMemory; - if (descriptors) { - memcpy(new_descriptors, descriptors, + if (pm_descriptors) { + memcpy(new_descriptors, pm_descriptors, sizeof(descriptor_node) * pm_descriptor_max); - free(descriptors); + pm_free(pm_descriptors); } pm_descriptor_max += 32; - descriptors = new_descriptors; + pm_descriptors = new_descriptors; + } + if (device_id == pm_descriptor_len) { + pm_descriptor_len++; /* extending array of pm_descriptors */ } - descriptors[pm_descriptor_index].pub.interf = interf; - descriptors[pm_descriptor_index].pub.name = name; - descriptors[pm_descriptor_index].pub.input = input; - descriptors[pm_descriptor_index].pub.output = !input; + d = &pm_descriptors[device_id].pub; + d->interf = interf; + d->name = pm_alloc(strlen(name) + 1); + if (!d->name) { + return pmInsufficientMemory; + } +#pragma warning(suppress: 4996) // don't use suggested strncpy_s + strcpy(d->name, name); + d->input = is_input; + d->output = !is_input; + d->is_virtual = FALSE; /* caller should set to TRUE if this is virtual */ /* default state: nothing to close (for automatic device closing) */ - descriptors[pm_descriptor_index].pub.opened = FALSE; + d->opened = FALSE; + + pm_descriptors[device_id].deleted = FALSE; /* ID number passed to win32 multimedia API open */ - descriptors[pm_descriptor_index].descriptor = descriptor; + pm_descriptors[device_id].descriptor = descriptor; /* points to PmInternal, allows automatic device closing */ - descriptors[pm_descriptor_index].internalDescriptor = NULL; + pm_descriptors[device_id].pm_internal = NULL; - descriptors[pm_descriptor_index].dictionary = dictionary; - - pm_descriptor_index++; + pm_descriptors[device_id].dictionary = dictionary; - return pmNoError; + return device_id; +} + + +/* Undo a successful call to pm_add_device(). If a new device was + * allocated, it must be the last device in pm_descriptors, so it is + * easy to delete by decrementing the length of pm_descriptors, but + * first free the name (which was copied to the heap). Otherwise, + * the device must be a virtual device that was created previously + * and is in the interior of the array of pm_descriptors. Leave it, + * but mark it as deleted. + */ +void pm_undo_add_device(int id) +{ + /* Clear some fields (not all are strictly necessary) */ + pm_descriptors[id].deleted = TRUE; + pm_descriptors[id].descriptor = NULL; + pm_descriptors[id].pm_internal = NULL; + + if (id == pm_descriptor_len - 1) { + pm_free(pm_descriptors[id].pub.name); + pm_descriptor_len--; + } } @@ -144,7 +293,7 @@ int pm_find_default_device(char *pattern, int is_input) int id = pmNoDevice; int i; /* first parse pattern into name, interf parts */ - char *interf_pref = (char *)""; /* initially assume it is not there */ + char const *interf_pref = ""; /* initially assume it is not there */ char *name_pref = strstr(pattern, ", "); if (name_pref) { /* found separator, adjust the pointer */ @@ -154,7 +303,7 @@ int pm_find_default_device(char *pattern, int is_input) } else { name_pref = pattern; /* whole string is the name pattern */ } - for (i = 0; i < pm_descriptor_index; i++) { + for (i = 0; i < pm_descriptor_len; i++) { const PmDeviceInfo *info = Pm_GetDeviceInfo(i); if (info->input == is_input && strstr(info->name, name_pref) && @@ -173,56 +322,71 @@ portmidi implementation ==================================================================== */ -PMEXPORT int Pm_CountDevices( void ) { +PMEXPORT int Pm_CountDevices(void) +{ Pm_Initialize(); /* no error checking -- Pm_Initialize() does not fail */ - return pm_descriptor_index; + return pm_descriptor_len; } -PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) { +PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo(PmDeviceID id) +{ Pm_Initialize(); /* no error check needed */ - if (id >= 0 && id < pm_descriptor_index) { - return &descriptors[id].pub; + if (id >= 0 && id < pm_descriptor_len && !pm_descriptors[id].deleted) { + return &pm_descriptors[id].pub; } return NULL; } /* pm_success_fn -- "noop" function pointer */ -PmError pm_success_fn(PmInternal *midi) { +PmError pm_success_fn(PmInternal *midi) +{ return pmNoError; } /* none_write -- returns an error if called */ -PmError none_write_short(PmInternal *midi, PmEvent *buffer) { +PmError none_write_short(PmInternal *midi, PmEvent *buffer) +{ return pmBadPtr; } /* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */ -PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp) { +PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp) +{ return pmBadPtr; } PmError none_write_byte(PmInternal *midi, unsigned char byte, - PmTimestamp timestamp) { + PmTimestamp timestamp) +{ return pmBadPtr; } /* pm_fail_fn -- generic function, returns error if called */ -PmError pm_fail_fn(PmInternal *midi) { +PmError pm_fail_fn(PmInternal *midi) +{ return pmBadPtr; } -static PmError none_open(PmInternal *midi, void *driverInfo) { +static PmError none_open(PmInternal *midi, void *driverInfo) +{ return pmBadPtr; } -static void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) { + +static void none_get_host_error(PmInternal * midi, char * msg, + unsigned int len) +{ *msg = 0; // empty string } -static unsigned int none_has_host_error(PmInternal * midi) { + +static unsigned int none_check_host_error(PmInternal * midi) +{ return FALSE; } -PmTimestamp none_synchronize(PmInternal *midi) { + +PmTimestamp none_synchronize(PmInternal *midi) +{ return 0; } @@ -238,15 +402,15 @@ pm_fns_node pm_none_dictionary = { none_write_flush, none_synchronize, none_open, - none_abort, + none_abort, none_close, none_poll, - none_has_host_error, - none_get_host_error + none_check_host_error, }; -PMEXPORT const char *Pm_GetErrorText( PmError errnum ) { +PMEXPORT const char *Pm_GetErrorText(PmError errnum) +{ const char *msg; switch(errnum) @@ -255,47 +419,59 @@ PMEXPORT const char *Pm_GetErrorText( PmError errnum ) { msg = ""; break; case pmHostError: - msg = "PortMidi: `Host error'"; + msg = "PortMidi: Host error"; break; case pmInvalidDeviceId: - msg = "PortMidi: `Invalid device ID'"; + msg = "PortMidi: Invalid device ID"; break; case pmInsufficientMemory: - msg = "PortMidi: `Insufficient memory'"; + msg = "PortMidi: Insufficient memory"; break; case pmBufferTooSmall: - msg = "PortMidi: `Buffer too small'"; + msg = "PortMidi: Buffer too small"; break; case pmBadPtr: - msg = "PortMidi: `Bad pointer'"; + msg = "PortMidi: Bad pointer"; break; case pmInternalError: - msg = "PortMidi: `Internal PortMidi Error'"; + msg = "PortMidi: Internal PortMidi Error"; break; case pmBufferOverflow: - msg = "PortMidi: `Buffer overflow'"; + msg = "PortMidi: Buffer overflow"; break; case pmBadData: - msg = "PortMidi: `Invalid MIDI message Data'"; + msg = "PortMidi: Invalid MIDI message Data"; break; case pmBufferMaxSize: - msg = "PortMidi: `Buffer cannot be made larger'"; + msg = "PortMidi: Buffer cannot be made larger"; + break; + case pmNotImplemented: + msg = "PortMidi: Function is not implemented"; + break; + case pmInterfaceNotSupported: + msg = "PortMidi: Interface not supported"; break; - default: - msg = "PortMidi: `Illegal error number'"; + case pmNameConflict: + msg = "PortMidi: Cannot create virtual device: name is taken"; + break; + default: + msg = "PortMidi: Illegal error number"; break; } return msg; } -/* This can be called whenever you get a pmHostError return value. +/* This can be called whenever you get a pmHostError return value + * or TRUE from Pm_HasHostError(). * The error will always be in the global pm_hosterror_text. */ -PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len) { +PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len) +{ assert(msg); assert(len > 0); if (pm_hosterror) { +#pragma warning(suppress: 4996) // don't use suggested strncpy_s strncpy(msg, (char *) pm_hosterror_text, len); pm_hosterror = FALSE; pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it @@ -307,24 +483,22 @@ PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len) { } -PMEXPORT int Pm_HasHostError(PortMidiStream * stream) { +PMEXPORT int Pm_HasHostError(PortMidiStream * stream) +{ if (pm_hosterror) return TRUE; if (stream) { PmInternal * midi = (PmInternal *) stream; - pm_hosterror = (*midi->dictionary->has_host_error)(midi); - if (pm_hosterror) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - /* now error message is global */ - return TRUE; - } + return (*midi->dictionary->check_host_error)(midi); } return FALSE; } -PMEXPORT PmError Pm_Initialize( void ) { +PMEXPORT PmError Pm_Initialize(void) +{ if (!pm_initialized) { + pm_descriptor_len = 0; + pm_interf_list_len = 0; pm_hosterror = FALSE; pm_hosterror_text[0] = 0; /* the null string */ pm_init(); @@ -334,16 +508,24 @@ PMEXPORT PmError Pm_Initialize( void ) { } -PMEXPORT PmError Pm_Terminate( void ) { +PMEXPORT PmError Pm_Terminate(void) +{ if (pm_initialized) { pm_term(); - // if there are no devices, descriptors might still be NULL - if (descriptors != NULL) { - free(descriptors); - descriptors = NULL; + /* if there are no devices, pm_descriptors might still be NULL */ + if (pm_descriptors != NULL) { + int i; /* free names copied into pm_descriptors */ + for (i = 0; i < pm_descriptor_len; i++) { + if (pm_descriptors[i].pub.name) { + pm_free(pm_descriptors[i].pub.name); + } + } + pm_free(pm_descriptors); + pm_descriptors = NULL; } - pm_descriptor_index = 0; + pm_descriptor_len = 0; pm_descriptor_max = 0; + pm_interf_list_len = 0; pm_initialized = FALSE; } return pmNoError; @@ -354,7 +536,8 @@ PMEXPORT PmError Pm_Terminate( void ) { /* * returns number of messages actually read, or error code */ -PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) { +PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) +{ PmInternal *midi = (PmInternal *) stream; int n = 0; PmError err = pmNoError; @@ -362,9 +545,9 @@ PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) { /* arg checking */ if(midi == NULL) err = pmBadPtr; - else if(!descriptors[midi->device_id].pub.opened) + else if(!pm_descriptors[midi->device_id].pub.opened) err = pmBadPtr; - else if(!descriptors[midi->device_id].pub.input) + else if(!pm_descriptors[midi->device_id].pub.input) err = pmBadPtr; /* First poll for data in the buffer... * This either simply checks for data, or attempts first to fill the buffer @@ -375,9 +558,7 @@ PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) { if (err != pmNoError) { if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; + midi->dictionary->check_host_error(midi); } return pm_errmsg(err); } @@ -395,7 +576,7 @@ PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) { return n; } -PMEXPORT PmError Pm_Poll( PortMidiStream *stream ) +PMEXPORT PmError Pm_Poll(PortMidiStream *stream) { PmInternal *midi = (PmInternal *) stream; PmError err; @@ -404,23 +585,18 @@ PMEXPORT PmError Pm_Poll( PortMidiStream *stream ) /* arg checking */ if(midi == NULL) err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.opened) + else if (!pm_descriptors[midi->device_id].pub.opened) err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.input) + else if (!pm_descriptors[midi->device_id].pub.input) err = pmBadPtr; else err = (*(midi->dictionary->poll))(midi); if (err != pmNoError) { - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } return pm_errmsg(err); } - return !Pm_QueueEmpty(midi->queue); + return (PmError) !Pm_QueueEmpty(midi->queue); } @@ -432,11 +608,6 @@ static PmError pm_end_sysex(PmInternal *midi) { PmError err = (*midi->dictionary->end_sysex)(midi, 0); midi->sysex_in_progress = FALSE; - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } return err; } @@ -445,7 +616,8 @@ static PmError pm_end_sysex(PmInternal *midi) Pm_WriteSysEx all operate a state machine that "outputs" calls to write_short, begin_sysex, write_byte, end_sysex, and write_realtime */ -PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length) +PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer, + int32_t length) { PmInternal *midi = (PmInternal *) stream; PmError err = pmNoError; @@ -456,9 +628,9 @@ PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t leng /* arg checking */ if(midi == NULL) err = pmBadPtr; - else if(!descriptors[midi->device_id].pub.opened) + else if(!pm_descriptors[midi->device_id].pub.opened) err = pmBadPtr; - else if(!descriptors[midi->device_id].pub.output) + else if(!pm_descriptors[midi->device_id].pub.output) err = pmBadPtr; else err = pmNoError; @@ -569,16 +741,15 @@ PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t leng err = (*midi->dictionary->write_flush)(midi, 0); pm_write_error: if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; + midi->dictionary->check_host_error(midi); } error_exit: return pm_errmsg(err); } -PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when, PmMessage msg) +PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when, + PmMessage msg) { PmEvent event; @@ -666,79 +837,110 @@ end_of_sysex: -PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream, - PmDeviceID inputDevice, - void *inputDriverInfo, - int32_t bufferSize, - PmTimeProcPtr time_proc, - void *time_info) +PmError pm_create_internal(PmInternal **stream, PmDeviceID device_id, + int is_input, int latency, PmTimeProcPtr time_proc, + void *time_info, int buffer_size) { PmInternal *midi; - PmError err = pmNoError; - pm_hosterror = FALSE; - *stream = NULL; - - /* arg checking */ - if (inputDevice < 0 || inputDevice >= pm_descriptor_index) - err = pmInvalidDeviceId; - else if (!descriptors[inputDevice].pub.input) - err = pmInvalidDeviceId; - else if(descriptors[inputDevice].pub.opened) - err = pmInvalidDeviceId; - - if (err != pmNoError) - goto error_return; - + if (device_id < 0 || device_id >= pm_descriptor_len) { + return pmInvalidDeviceId; + } + if (latency < 0) { /* force a legal value */ + latency = 0; + } /* create portMidi internal data */ midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); *stream = midi; if (!midi) { - err = pmInsufficientMemory; - goto error_return; + return pmInsufficientMemory; } - midi->device_id = inputDevice; - midi->write_flag = FALSE; + midi->device_id = device_id; + midi->is_input = is_input; midi->time_proc = time_proc; + /* if latency != 0, we need a time reference for output. + we always need a time reference for input. + If none is provided, use PortTime library */ + if (time_proc == NULL && (latency != 0 || is_input)) { + if (!Pt_Started()) + Pt_Start(1, 0, 0); + /* time_get does not take a parameter, so coerce */ + midi->time_proc = (PmTimeProcPtr) Pt_Time; + } midi->time_info = time_info; - /* windows adds timestamps in the driver and these are more accurate than - using a time_proc, so do not automatically provide a time proc. Non-win - implementations may want to provide a default time_proc in their - system-specific midi_out_open() method. - */ - if (bufferSize <= 0) bufferSize = 256; /* default buffer size */ - midi->queue = Pm_QueueCreate(bufferSize, (int32_t) sizeof(PmEvent)); - if (!midi->queue) { - /* free portMidi data */ - *stream = NULL; - pm_free(midi); - err = pmInsufficientMemory; - goto error_return; + if (is_input) { + midi->latency = 0; /* unused by input */ + if (buffer_size <= 0) buffer_size = 256; /* default buffer size */ + midi->queue = Pm_QueueCreate(buffer_size, (int32_t) sizeof(PmEvent)); + if (!midi->queue) { + /* free portMidi data */ + *stream = NULL; + pm_free(midi); + return pmInsufficientMemory; + } + } else { + /* if latency zero, output immediate (timestamps ignored) */ + /* if latency < 0, use 0 but don't return an error */ + if (latency < 0) latency = 0; + midi->latency = latency; + midi->queue = NULL; /* unused by output; input needs to allocate: */ } - midi->buffer_len = bufferSize; /* portMidi input storage */ - midi->latency = 0; /* not used */ + midi->buffer_len = buffer_size; /* portMidi input storage */ midi->sysex_in_progress = FALSE; midi->sysex_message = 0; midi->sysex_message_count = 0; - midi->filters = PM_FILT_ACTIVE; + midi->filters = (is_input ? PM_FILT_ACTIVE : 0); midi->channel_mask = 0xFFFF; midi->sync_time = 0; midi->first_message = TRUE; - midi->dictionary = descriptors[inputDevice].dictionary; + midi->api_info = NULL; midi->fill_base = NULL; midi->fill_offset_ptr = NULL; midi->fill_length = 0; - descriptors[inputDevice].internalDescriptor = midi; + midi->dictionary = pm_descriptors[device_id].dictionary; + pm_descriptors[device_id].pm_internal = midi; + return pmNoError; +} + + +PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream, + PmDeviceID inputDevice, + void *inputDriverInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info) +{ + PmInternal *midi = NULL; + PmError err = pmNoError; + pm_hosterror = FALSE; + *stream = NULL; /* invariant: *stream == midi */ + + /* arg checking */ + if (!pm_descriptors[inputDevice].pub.input) + err = pmInvalidDeviceId; + else if (pm_descriptors[inputDevice].pub.opened) + err = pmInvalidDeviceId; + if (err != pmNoError) + goto error_return; + + /* common initialization of PmInternal structure (midi): */ + err = pm_create_internal(&midi, inputDevice, TRUE, 0, time_proc, + time_info, bufferSize); + *stream = midi; + if (err) { + goto error_return; + } + /* open system dependent input device */ err = (*midi->dictionary->open)(midi, inputDriverInfo); if (err) { *stream = NULL; - descriptors[inputDevice].internalDescriptor = NULL; + pm_descriptors[inputDevice].pm_internal = NULL; /* free portMidi data */ Pm_QueueDestroy(midi->queue); pm_free(midi); } else { /* portMidi input open successful */ - descriptors[inputDevice].pub.opened = TRUE; + pm_descriptors[inputDevice].pub.opened = TRUE; } error_return: /* note: if there is a pmHostError, it is the responsibility @@ -750,12 +952,12 @@ error_return: PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream, - PmDeviceID outputDevice, - void *outputDriverInfo, - int32_t bufferSize, - PmTimeProcPtr time_proc, - void *time_info, - int32_t latency ) + PmDeviceID outputDevice, + void *outputDriverInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info, + int32_t latency) { PmInternal *midi; PmError err = pmNoError; @@ -763,62 +965,33 @@ PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream, *stream = NULL; /* arg checking */ - if (outputDevice < 0 || outputDevice >= pm_descriptor_index) + if (outputDevice < 0 || outputDevice >= pm_descriptor_len) err = pmInvalidDeviceId; - else if (!descriptors[outputDevice].pub.output) + else if (!pm_descriptors[outputDevice].pub.output) err = pmInvalidDeviceId; - else if (descriptors[outputDevice].pub.opened) + else if (pm_descriptors[outputDevice].pub.opened) err = pmInvalidDeviceId; if (err != pmNoError) goto error_return; - /* create portMidi internal data */ - midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); - *stream = midi; - if (!midi) { - err = pmInsufficientMemory; + /* common initialization of PmInternal structure (midi): */ + err = pm_create_internal(&midi, outputDevice, FALSE, latency, time_proc, + time_info, bufferSize); + *stream = midi; + if (err) { goto error_return; } - midi->device_id = outputDevice; - midi->write_flag = TRUE; - midi->time_proc = time_proc; - /* if latency > 0, we need a time reference. If none is provided, - use PortTime library */ - if (time_proc == NULL && latency != 0) { - if (!Pt_Started()) - Pt_Start(1, 0, 0); - /* time_get does not take a parameter, so coerce */ - midi->time_proc = (PmTimeProcPtr) Pt_Time; - } - midi->time_info = time_info; - midi->buffer_len = bufferSize; - midi->queue = NULL; /* unused by output */ - /* if latency zero, output immediate (timestamps ignored) */ - /* if latency < 0, use 0 but don't return an error */ - if (latency < 0) latency = 0; - midi->latency = latency; - midi->sysex_in_progress = FALSE; - midi->sysex_message = 0; /* unused by output */ - midi->sysex_message_count = 0; /* unused by output */ - midi->filters = 0; /* not used for output */ - midi->channel_mask = 0xFFFF; - midi->sync_time = 0; - midi->first_message = TRUE; - midi->dictionary = descriptors[outputDevice].dictionary; - midi->fill_base = NULL; - midi->fill_offset_ptr = NULL; - midi->fill_length = 0; - descriptors[outputDevice].internalDescriptor = midi; + /* open system dependent output device */ err = (*midi->dictionary->open)(midi, outputDriverInfo); if (err) { *stream = NULL; - descriptors[outputDevice].internalDescriptor = NULL; + pm_descriptors[outputDevice].pm_internal = NULL; /* free portMidi data */ pm_free(midi); } else { /* portMidi input open successful */ - descriptors[outputDevice].pub.opened = TRUE; + pm_descriptors[outputDevice].pub.opened = TRUE; } error_return: /* note: system-dependent code must set pm_hosterror and @@ -828,6 +1001,91 @@ error_return: } +static PmError create_virtual_device(const char *name, const char *interf, + void *device_info, int is_input) +{ + PmError err = pmNoError; + int i; + pm_hosterror = FALSE; + + /* arg checking */ + if (!name) { + err = pmInvalidDeviceId; + goto error_return; + } + + Pm_Initialize(); /* just in case */ + + /* create the virtual device */ + if (pm_interf_list_len == 0) { + return pmNotImplemented; + } + if (!interf) { + /* default interface is the first one */ + interf = pm_interf_list[0].interf; + } + /* look up and call the create_fn for interf(ace), e.g. "CoreMIDI" */ + for (i = 0; i < pm_interf_list_len; i++) { + if (strcmp(pm_interf_list[i].interf, interf) == 0) { + int id = (*pm_interf_list[i].create_fn)(is_input, + name, device_info); + /* id could be pmNameConflict or an actual descriptor index */ + if (id >= 0) { + pm_descriptors[id].pub.is_virtual = TRUE; + } + err = id; + goto error_return; + } + } + err = pmInterfaceNotSupported; + +error_return: + /* note: if there is a pmHostError, it is the responsibility + * of the system-dependent code (*midi->dictionary->open)() + * to set pm_hosterror and pm_hosterror_text + */ + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_CreateVirtualInput(const char *name, + const char *interf, + void *deviceInfo) +{ + return create_virtual_device(name, interf, deviceInfo, TRUE); +} + +PMEXPORT PmError Pm_CreateVirtualOutput(const char *name, const char *interf, + void *deviceInfo) +{ + return create_virtual_device(name, interf, deviceInfo, FALSE); +} + +PmError Pm_DeleteVirtualDevice(PmDeviceID id) +{ + int i; + const char *interf = pm_descriptors[id].pub.interf; + PmError err = pmBadData; /* returned if we cannot find the interface- + * specific delete function */ + /* arg checking */ + if (id < 0 || id >= pm_descriptor_len || + pm_descriptors[id].pub.opened || pm_descriptors[id].deleted) { + return pmInvalidDeviceId; + } + /* delete function pointer is in interfaces list */ + for (i = 0; i < pm_interf_list_len; i++) { + if (strcmp(pm_interf_list[i].interf, interf) == 0) { + err = (*pm_interf_list[i].delete_fn)(id); + break; + } + } + pm_descriptors[id].deleted = TRUE; + /* (pm_internal should already be NULL because !pub.opened) */ + pm_descriptors[id].pm_internal = NULL; + pm_descriptors[id].descriptor = NULL; + return err; +} + PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) { PmInternal *midi = (PmInternal *) stream; @@ -842,14 +1100,15 @@ PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) } -PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters) { +PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters) +{ PmInternal *midi = (PmInternal *) stream; PmError err = pmNoError; /* arg checking */ if (midi == NULL) err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.opened) + else if (!pm_descriptors[midi->device_id].pub.opened) err = pmBadPtr; else midi->filters = filters; @@ -857,7 +1116,8 @@ PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters) { } -PMEXPORT PmError Pm_Close( PortMidiStream *stream ) { +PMEXPORT PmError Pm_Close(PortMidiStream *stream) +{ PmInternal *midi = (PmInternal *) stream; PmError err = pmNoError; @@ -866,10 +1126,10 @@ PMEXPORT PmError Pm_Close( PortMidiStream *stream ) { if (midi == NULL) /* midi must point to something */ err = pmBadPtr; /* if it is an open device, the device_id will be valid */ - else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_index) + else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_len) err = pmBadPtr; /* and the device should be in the opened state */ - else if (!descriptors[midi->device_id].pub.opened) + else if (!pm_descriptors[midi->device_id].pub.opened) err = pmBadPtr; if (err != pmNoError) @@ -878,8 +1138,8 @@ PMEXPORT PmError Pm_Close( PortMidiStream *stream ) { /* close the device */ err = (*midi->dictionary->close)(midi); /* even if an error occurred, continue with cleanup */ - descriptors[midi->device_id].internalDescriptor = NULL; - descriptors[midi->device_id].pub.opened = FALSE; + pm_descriptors[midi->device_id].pm_internal = NULL; + pm_descriptors[midi->device_id].pub.opened = FALSE; if (midi->queue) Pm_QueueDestroy(midi->queue); pm_free(midi); error_return: @@ -889,56 +1149,57 @@ error_return: return pm_errmsg(err); } -PmError Pm_Synchronize( PortMidiStream* stream ) { +PmError Pm_Synchronize(PortMidiStream* stream) +{ PmInternal *midi = (PmInternal *) stream; PmError err = pmNoError; if (midi == NULL) err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.output) + else if (!pm_descriptors[midi->device_id].pub.output) err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.opened) + else if (!pm_descriptors[midi->device_id].pub.opened) err = pmBadPtr; else midi->first_message = TRUE; return err; } -PMEXPORT PmError Pm_Abort( PortMidiStream* stream ) { +PMEXPORT PmError Pm_Abort(PortMidiStream* stream) +{ PmInternal *midi = (PmInternal *) stream; PmError err; /* arg checking */ if (midi == NULL) err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.output) + else if (!pm_descriptors[midi->device_id].pub.output) err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.opened) + else if (!pm_descriptors[midi->device_id].pub.opened) err = pmBadPtr; else err = (*midi->dictionary->abort)(midi); if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; + midi->dictionary->check_host_error(midi); } return pm_errmsg(err); } -/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */ +/* pm_channel_filtered returns non-zero if the channel mask is + blocking the current channel */ #define pm_channel_filtered(status, mask) \ ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask)))) -/* The following two functions will checks to see if a MIDI message matches - the filtering criteria. Since the sysex routines only want to filter realtime messages, - we need to have separate routines. +/* The following two functions will checks to see if a MIDI message + matches the filtering criteria. Since the sysex routines only want + to filter realtime messages, we need to have separate routines. */ -/* pm_realtime_filtered returns non-zero if the filter will kill the current message. - Note that only realtime messages are checked here. +/* pm_realtime_filtered returns non-zero if the filter will kill the + current message. Note that only realtime messages are checked here. */ #define pm_realtime_filtered(status, filters) \ ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters))) @@ -959,18 +1220,21 @@ PMEXPORT PmError Pm_Abort( PortMidiStream* stream ) { }*/ -/* pm_status_filtered returns non-zero if a filter will kill the current message, based on status. - Note that sysex and real time are not checked. It is up to the subsystem (winmm, core midi, alsa) - to filter sysex, as it is handled more easily and efficiently at that level. - Realtime message are filtered in pm_realtime_filtered. +/* pm_status_filtered returns non-zero if a filter will kill the + current message, based on status. Note that sysex and real time are + not checked. It is up to the subsystem (winmm, core midi, alsa) to + filter sysex, as it is handled more easily and efficiently at that + level. Realtime message are filtered in pm_realtime_filtered. */ -#define pm_status_filtered(status, filters) ((1 << (16 + ((status) >> 4))) & (filters)) +#define pm_status_filtered(status, filters) \ + ((1 << (16 + ((status) >> 4))) & (filters)) /* return ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE)) || ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE)) - || ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH)) + || ((status == MIDI_CHANNEL_AT) && + (filters & PM_FILT_CHANNEL_AFTERTOUCH)) || ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH)) || ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM)) || ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL)) diff --git a/3rdparty/portmidi/pm_common/portmidi.h b/3rdparty/portmidi/pm_common/portmidi.h index c57432fae12..1a4c7e7d0ab 100644 --- a/3rdparty/portmidi/pm_common/portmidi.h +++ b/3rdparty/portmidi/pm_common/portmidi.h @@ -48,69 +48,43 @@ extern "C" { * * NOTES ON HOST ERROR REPORTING: * - * PortMidi errors (of type PmError) are generic, system-independent errors. - * When an error does not map to one of the more specific PmErrors, the - * catch-all code pmHostError is returned. This means that PortMidi has - * retained a more specific system-dependent error code. The caller can - * get more information by calling Pm_HasHostError() to test if there is - * a pending host error, and Pm_GetHostErrorText() to get a text string - * describing the error. Host errors are reported on a per-device basis - * because only after you open a device does PortMidi have a place to - * record the host error code. I.e. only - * those routines that receive a (PortMidiStream *) argument check and - * report errors. One exception to this is that Pm_OpenInput() and - * Pm_OpenOutput() can report errors even though when an error occurs, - * there is no PortMidiStream* to hold the error. Fortunately, both - * of these functions return any error immediately, so we do not really - * need per-device error memory. Instead, any host error code is stored - * in a global, pmHostError is returned, and the user can call - * Pm_GetHostErrorText() to get the error message (and the invalid stream - * parameter will be ignored.) The functions - * pm_init and pm_term do not fail or raise - * errors. The job of pm_init is to locate all available devices so that - * the caller can get information via PmDeviceInfo(). If an error occurs, - * the device is simply not listed as available. - * - * Host errors come in two flavors: - * a) host error - * b) host error during callback - * These can occur w/midi input or output devices. (b) can only happen - * asynchronously (during callback routines), whereas (a) only occurs while - * synchronously running PortMidi and any resulting system dependent calls. - * Both (a) and (b) are reported by the next read or write call. You can - * also query for asynchronous errors (b) at any time by calling - * Pm_HasHostError(). + * PortMidi errors (of type PmError) are generic, + * system-independent errors. When an error does not map to one of + * the more specific PmErrors, the catch-all code pmHostError is + * returned. This means that PortMidi has retained a more specific + * system-dependent error code. The caller can get more information + * by calling Pm_GetHostErrorText() to get a text string describing + * the error. Host errors can arise asynchronously from callbacks, + * * so there is no specific return code. Asynchronous errors are + * checked and reported by Pm_Poll. You can also check by calling + * Pm_HasHostError(). If this returns TRUE, Pm_GetHostErrorText() + * will return a text description of the error. * * NOTES ON COMPILE-TIME SWITCHES * - * DEBUG assumes stdio and a console. Use this if you want automatic, simple - * error reporting, e.g. for prototyping. If you are using MFC or some - * other graphical interface with no console, DEBUG probably should be - * undefined. - * PM_CHECK_ERRORS more-or-less takes over error checking for return values, - * stopping your program and printing error messages when an error - * occurs. This also uses stdio for console text I/O. + * DEBUG assumes stdio and a console. Use this if you want + * automatic, simple error reporting, e.g. for prototyping. If + * you are using MFC or some other graphical interface with no + * console, DEBUG probably should be undefined. + * PM_CHECK_ERRORS more-or-less takes over error checking for + * return values, stopping your program and printing error + * messages when an error occurs. This also uses stdio for + * console text I/O. You can selectively disable this error + * checking by declaring extern int pm_check_errors; and + * setting pm_check_errors = FALSE; You can also reenable. */ +/** + \defgroup grp_basics Basic Definitions + @{ +*/ -#ifndef WIN32 -// Linux and OS X have stdint.h #include <stdint.h> -#else -#ifndef INT32_DEFINED -// rather than having users install a special .h file for windows, -// just put the required definitions inline here. porttime.h uses -// these too, so the definitions are (unfortunately) duplicated there -typedef int int32_t; -typedef unsigned int uint32_t; -#define INT32_DEFINED -#endif -#endif -//#ifdef _WINDLL -//#define PMEXPORT __declspec(dllexport) -//#else +#ifdef _WINDLL +#define PMEXPORT __declspec(dllexport) +#else #define PMEXPORT -//#endif +#endif #ifndef FALSE #define FALSE 0 @@ -122,113 +96,206 @@ typedef unsigned int uint32_t; /* default size of buffers for sysex transmission: */ #define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024 -/** List of portmidi errors.*/ + typedef enum { - pmNoError = 0, - pmNoData = 0, /**< A "no error" return that also indicates no data avail. */ - pmGotData = 1, /**< A "no error" return that also indicates data available */ + pmNoError = 0, /**< Normal return value indicating no error. */ + pmNoData = 0, /**< @brief No error, also indicates no data available. + * Use this constant where a value greater than zero would + * indicate data is available. + */ + pmGotData = 1, /**< A "no error" return also indicating data available. */ pmHostError = -10000, - pmInvalidDeviceId, /** out of range or + pmInvalidDeviceId, /**< Out of range or * output device when input is requested or * input device when output is requested or - * device is already opened + * device is already opened. */ pmInsufficientMemory, pmBufferTooSmall, pmBufferOverflow, - pmBadPtr, /* PortMidiStream parameter is NULL or + pmBadPtr, /**< #PortMidiStream parameter is NULL or * stream is not opened or * stream is output when input is required or - * stream is input when output is required */ - pmBadData, /** illegal midi data, e.g. missing EOX */ + * stream is input when output is required. */ + pmBadData, /**< Illegal midi data, e.g., missing EOX. */ pmInternalError, - pmBufferMaxSize /** buffer is already as large as it can be */ - /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */ -} PmError; + pmBufferMaxSize, /**< Buffer is already as large as it can be. */ + pmNotImplemented, /**< The function is not implemented, nothing was done. */ + pmInterfaceNotSupported, /**< The requested interface is not supported. */ + pmNameConflict /**< Cannot create virtual device because name is taken. */ + /* NOTE: If you add a new error type, you must update Pm_GetErrorText(). */ +} PmError; /**< @brief @enum PmError PortMidi error code; a common return type. + * No error is indicated by zero; errors are indicated by < 0. + */ /** - Pm_Initialize() is the library initialisation function - call this before + Pm_Initialize() is the library initialization function - call this before using the library. + + *NOTE:* PortMidi scans for available devices when #Pm_Initialize + is called. To observe subsequent changes in the available + devices, you must shut down PortMidi by calling #Pm_Terminate and + then restart by calling #Pm_Initialize again. *IMPORTANT*: On + MacOS, #Pm_Initialize *must* always be called on the same + thread. Otherwise, changes in the available MIDI devices will + *not* be seen by PortMidi. As an example, if you start PortMidi in + a thread for processing MIDI, do not try to rescan devices by + calling #Pm_Initialize in a GUI thread. Instead, start PortMidi + the first time and every time in the GUI thread. Alternatively, + let the GUI request a restart in the MIDI thread. (These + restrictions only apply to macOS.) Speaking of threads, on all + platforms, you are allowed to call #Pm_Initialize in one thread, + yet send MIDI or poll for incoming MIDI in another + thread. However, PortMidi is not "thread safe," which means you + cannot allow threads to call PortMidi functions concurrently. + + @return pmNoError. + + PortMidi is designed to support multiple interfaces (such as ALSA, + CoreMIDI and WinMM). It is possible to return pmNoError because there + are no supported interfaces. In that case, zero devices will be + available. */ -PMEXPORT PmError Pm_Initialize( void ); +PMEXPORT PmError Pm_Initialize(void); /** Pm_Terminate() is the library termination function - call this after using the library. */ -PMEXPORT PmError Pm_Terminate( void ); +PMEXPORT PmError Pm_Terminate(void); -/** A single PortMidiStream is a descriptor for an open MIDI device. -*/ +/** Represents an open MIDI device. */ typedef void PortMidiStream; + +/** A shorter form of #PortMidiStream. */ #define PmStream PortMidiStream -/** - Test whether stream has a pending host error. Normally, the client finds - out about errors through returned error codes, but some errors can occur - asynchronously where the client does not - explicitly call a function, and therefore cannot receive an error code. - The client can test for a pending error using Pm_HasHostError(). If true, - the error can be accessed and cleared by calling Pm_GetErrorText(). - Errors are also cleared by calling other functions that can return - errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The - client does not need to call Pm_HasHostError(). Any pending error will be - reported the next time the client performs an explicit function call on - the stream, e.g. an input or output operation. Until the error is cleared, - no new error codes will be obtained, even for a different stream. +/** Test whether stream has a pending host error. Normally, the client + finds out about errors through returned error codes, but some + errors can occur asynchronously where the client does not + explicitly call a function, and therefore cannot receive an error + code. The client can test for a pending error using + Pm_HasHostError(). If true, the error can be accessed by calling + Pm_GetHostErrorText(). Pm_Poll() is similar to Pm_HasHostError(), + but if there is no error, it will return TRUE (1) if there is a + pending input message. */ -PMEXPORT int Pm_HasHostError( PortMidiStream * stream ); +PMEXPORT int Pm_HasHostError(PortMidiStream * stream); -/** Translate portmidi error number into human readable message. +/** Translate portmidi error number into human readable message. These strings are constants (set at compile time) so client has - no need to allocate storage + no need to allocate storage. */ -PMEXPORT const char *Pm_GetErrorText( PmError errnum ); +PMEXPORT const char *Pm_GetErrorText(PmError errnum); -/** Translate portmidi host error into human readable message. +/** Translate portmidi host error into human readable message. These strings are computed at run time, so client has to allocate storage. After this routine executes, the host error is cleared. */ PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len); -#define HDRLENGTH 50 -#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less - than this number of characters */ - -/** - Device enumeration mechanism. - - Device ids range from 0 to Pm_CountDevices()-1. +/** Any host error msg has at most this many characters, including EOS. */ +#define PM_HOST_ERROR_MSG_LEN 256u +/** Devices are represented as small integers. Device ids range from 0 + to Pm_CountDevices()-1. Pm_GetDeviceInfo() is used to get information + about the device, and Pm_OpenInput() and PmOpenOutput() are used to + open the device. */ typedef int PmDeviceID; + +/** This PmDeviceID (constant) value represents no device and may be + returned by Pm_GetDefaultInputDeviceID() or + Pm_GetDefaultOutputDeviceID() if no default exists. +*/ #define pmNoDevice -1 -typedef struct { - int structVersion; /**< this internal structure version */ - const char *interf; /**< underlying MIDI API, e.g. MMSystem or DirectX */ - const char *name; /**< device name, e.g. USB MidiSport 1x1 */ - int input; /**< true iff input is available */ - int output; /**< true iff output is available */ - int opened; /**< used by generic PortMidi code to do error checking on arguments */ +/** MIDI device information is returned in this structure, which is + owned by PortMidi and read-only to applications. See Pm_GetDeviceInfo(). +*/ +#define PM_DEVICEINFO_VERS 200 +typedef struct { + int structVersion; /**< @brief this internal structure version */ + const char *interf; /**< @brief underlying MIDI API, e.g. + "MMSystem" or "DirectX" */ + char *name; /**< @brief device name, e.g. "USB MidiSport 1x1" */ + int input; /**< @brief true iff input is available */ + int output; /**< @brief true iff output is available */ + int opened; /**< @brief used by generic PortMidi for error checking */ + int is_virtual; /**< @brief true iff this is/was a virtual device */ } PmDeviceInfo; -/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */ -PMEXPORT int Pm_CountDevices( void ); -/** - Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID() +/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */ +PMEXPORT int Pm_CountDevices(void); +/** Return the default device ID or pmNoDevice if there are no devices. The result (but not pmNoDevice) can be passed to Pm_OpenMidi(). - The default device can be specified using a small application - named pmdefaults that is part of the PortMidi distribution. This - program in turn uses the Java Preferences object created by + The use of these functions is not recommended. There is no natural + "default device" on any system, so defaults must be set by users. + (Currently, PortMidi just returns the first device it finds as + "default".) The (unsolved) problem is how to implement simple + preferences for a cross-platform library. (More notes follow, but + you can stop reading here.) + + To implement preferences, you need (1) a standard place to put + them, (2) a representation for the preferences, (3) a graphical + interface to test and set preferences, (4) a "natural" way to + invoke the preference-setting program. To solve (3), PortMidi + originally chose to use Java and Swing to implement a + cross-platform GUI program called "pmdefaults." Java's Preferences + class already provide a location (problem 1) and representation + (problem 2). However, this solution was complex, requiring + PortMidi to parse binary Java preference files and requiring users + to install and invoke Java programs. It did not seem possible to + integrate pmdefaults into the system preference subsystems on + macOS, Windows, and Linux, so the user had to install and run + pmdefaults as an application. Moreover, Java is falling out of + favor. + + A simpler solution is pass the burden to applications. It is easy + to scan devices with PortMidi and build a device menu, and to save + menu selections in application preferences for next time. This is + my recommendation for any GUI program. For simple command-line + applications and utilities, see pm_test where all the test + programs now accept device numbers on the command line and/or + prompt for their entry. + + Some advice for preferences: MIDI devices used to be built-in or + plug-in cards, so the numbers rarely changed. Now MIDI devices are + often plug-in USB devices, so device numbers change, and you + probably need to design to reinitialize PortMidi to rescan + devices. MIDI is pretty stateless, so this isn't a big problem, + although it means you cannot find a new device while playing or + recording MIDI. + + Since device numbering can change whenever a USB device is plugged + in, preferences should record *names* of devices rather than + device numbers. It is simple enough to use string matching to find + a prefered device, so PortMidi does not provide any built-in + lookup function. See below for details of the Java preferences API. + + In the future, I would like to remove the legacy code that parses + Java preference data (macOS plist, linux prefs.xml, Windows + registry entries) and replace it with something more useful. Maybe + something really simple: $HOME/.portmidi? Or maybe a new + pmdefaults written with PyGame? Or use QT? If applications write + their own preferences, maybe a minimal command line preference + setter is all that's needed? Or maybe command line application + users are happy without a preference system? Comments and + proposals are welcome. + + For completeness, here is a description of the original use of + Java for preference setting: The default device can be specified + using a small application named pmdefaults that is part of the + PortMidi distribution. This program in turn uses the Java + Preferences object created by java.util.prefs.Preferences.userRoot().node("/PortMidi"); the - preference is set by calling - prefs.put("PM_RECOMMENDED_OUTPUT_DEVICE", prefName); - or prefs.put("PM_RECOMMENDED_INPUT_DEVICE", prefName); + preference is set by calling + prefs.put("PM_RECOMMENDED_OUTPUT_DEVICE", prefName); or + prefs.put("PM_RECOMMENDED_INPUT_DEVICE", prefName); In the statements above, prefName is a string describing the MIDI device in the form "interf, name" where interf identifies @@ -248,143 +315,274 @@ PMEXPORT int Pm_CountDevices( void ); the entire preference string is interpreted as a name, and the interface part is the empty string, which matches anything. - On the MAC, preferences are stored in - /Users/$NAME/Library/Preferences/com.apple.java.util.prefs.plist + On the MAC, preferences are stored in + /Users/$NAME/Library/Preferences/com.apple.java.util.prefs.plist which is a binary file. In addition to the pmdefaults program, there are utilities that can read and edit this preference file. - - On the PC, - - On Linux, - + On Windows, the Registry is used. On Linux, preferences are in an + XML file. */ -PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID( void ); -/** see PmDeviceID Pm_GetDefaultInputDeviceID() */ -PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID( void ); +PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void); -/** - PmTimestamp is used to represent a millisecond clock with arbitrary - start time. The type is used for all MIDI timestampes and clocks. +/** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */ +PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID(void); + +/** Represents a millisecond clock with arbitrary start time. + This type is used for all MIDI timestamps and clocks. */ typedef int32_t PmTimestamp; typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); /** TRUE if t1 before t2 */ #define PmBefore(t1,t2) ((t1-t2) < 0) +/** @} */ /** \defgroup grp_device Input/Output Devices Handling @{ */ -/** - Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure - referring to the device specified by id. - If id is out of range the function returns NULL. +/** Get a PmDeviceInfo structure describing a MIDI device. + + @param id the device to be queried. - The returned structure is owned by the PortMidi implementation and must - not be manipulated or freed. The pointer is guaranteed to be valid - between calls to Pm_Initialize() and Pm_Terminate(). -*/ -PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); + If \p id is out of range or if the device designates a deleted + virtual device, the function returns NULL. -/** - Pm_OpenInput() and Pm_OpenOutput() open devices. + The returned structure is owned by the PortMidi implementation and + must not be manipulated or freed. The pointer is guaranteed to be + valid between calls to Pm_Initialize() and Pm_Terminate(). +*/ +PMEXPORT const PmDeviceInfo *Pm_GetDeviceInfo(PmDeviceID id); - stream is the address of a PortMidiStream pointer which will receive - a pointer to the newly opened stream. +/** Open a MIDI device for input. - inputDevice is the id of the device used for input (see PmDeviceID above). + @param stream the address of a #PortMidiStream pointer which will + receive a pointer to the newly opened stream. - inputDriverInfo is a pointer to an optional driver specific data structure - containing additional information for device setup or handle processing. - inputDriverInfo is never required for correct operation. If not used - inputDriverInfo should be NULL. + @param inputDevice the ID of the device to be opened (see #PmDeviceID). - outputDevice is the id of the device used for output (see PmDeviceID above.) + @param inputDriverInfo a pointer to an optional driver-specific + data structure containing additional information for device setup + or handle processing. This parameter is never required for correct + operation. If not used, specify NULL. - outputDriverInfo is a pointer to an optional driver specific data structure - containing additional information for device setup or handle processing. - outputDriverInfo is never required for correct operation. If not used - outputDriverInfo should be NULL. + @param bufferSize the number of input events to be buffered + waiting to be read using Pm_Read(). Messages will be lost if the + number of unread messages exceeds this value. - For input, the buffersize specifies the number of input events to be - buffered waiting to be read using Pm_Read(). For output, buffersize - specifies the number of output events to be buffered waiting for output. - (In some cases -- see below -- PortMidi does not buffer output at all - and merely passes data to a lower-level API, in which case buffersize - is ignored.) - - latency is the delay in milliseconds applied to timestamps to determine - when the output should actually occur. (If latency is < 0, 0 is assumed.) - If latency is zero, timestamps are ignored and all output is delivered - immediately. If latency is greater than zero, output is delayed until the - message timestamp plus the latency. (NOTE: the time is measured relative - to the time source indicated by time_proc. Timestamps are absolute, - not relative delays or offsets.) In some cases, PortMidi can obtain - better timing than your application by passing timestamps along to the - device driver or hardware. Latency may also help you to synchronize midi - data to audio data by matching midi latency to the audio buffer latency. - - time_proc is a pointer to a procedure that returns time in milliseconds. It - may be NULL, in which case a default millisecond timebase (PortTime) is - used. If the application wants to use PortTime, it should start the timer - (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the - application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput, - it may get a ptAlreadyStarted error from Pt_Start, and the application's + @param time_proc (address of) a procedure that returns time in + milliseconds. It may be NULL, in which case a default millisecond + timebase (PortTime) is used. If the application wants to use + PortTime, it should start the timer (call Pt_Start) before calling + Pm_OpenInput or Pm_OpenOutput. If the application tries to start + the timer *after* Pm_OpenInput or Pm_OpenOutput, it may get a + ptAlreadyStarted error from Pt_Start, and the application's preferred time resolution and callback function will be ignored. - time_proc result values are appended to incoming MIDI data, and time_proc - times are used to schedule outgoing MIDI data (when latency is non-zero). + \p time_proc result values are appended to incoming MIDI data, + normally by mapping system-provided timestamps to the \p time_proc + timestamps to maintain the precision of system-provided + timestamps. - time_info is a pointer passed to time_proc. + @param time_info is a pointer passed to time_proc. - Example: If I provide a timestamp of 5000, latency is 1, and time_proc - returns 4990, then the desired output time will be when time_proc returns - timestamp+latency = 5001. This will be 5001-4990 = 11ms from now. - - return value: - Upon success Pm_Open() returns PmNoError and places a pointer to a - valid PortMidiStream in the stream argument. - If a call to Pm_Open() fails a nonzero error code is returned (see - PMError above) and the value of port is invalid. + @return #pmNoError and places a pointer to a valid + #PortMidiStream in the stream argument. If the open operation + fails, a nonzero error code is returned (see #PMError) and + the value of stream is invalid. Any stream that is successfully opened should eventually be closed by calling Pm_Close(). - */ -PMEXPORT PmError Pm_OpenInput( PortMidiStream** stream, +PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream, PmDeviceID inputDevice, void *inputDriverInfo, int32_t bufferSize, PmTimeProcPtr time_proc, - void *time_info ); + void *time_info); + +/** Open a MIDI device for output. + + @param stream the address of a #PortMidiStream pointer which will + receive a pointer to the newly opened stream. + + @param outputDevice the ID of the device to be opened (see #PmDeviceID). + + @param outputDriverInfo a pointer to an optional driver-specific + data structure containing additional information for device setup + or handle processing. This parameter is never required for correct + operation. If not used, specify NULL. + + @param bufferSize the number of output events to be buffered + waiting for output. In some cases -- see below -- PortMidi does + not buffer output at all and merely passes data to a lower-level + API, in which case \p bufferSize is ignored. Since MIDI speeds now + vary from 1 to 50 or more messages per ms (over USB), put some + thought into this number. E.g. if latency is 20ms and you want to + burst 100 messages in that time (5000 messages per second), you + should set \p bufferSize to at least 100. The default on Windows + assumes an average rate of 500 messages per second and in this + example, output would be slowed waiting for free buffers. + + @param latency the delay in milliseconds applied to timestamps + to determine when the output should actually occur. (If latency is + < 0, 0 is assumed.) If latency is zero, timestamps are ignored + and all output is delivered immediately. If latency is greater + than zero, output is delayed until the message timestamp plus the + latency. (NOTE: the time is measured relative to the time source + indicated by time_proc. Timestamps are absolute, not relative + delays or offsets.) In some cases, PortMidi can obtain better + timing than your application by passing timestamps along to the + device driver or hardware, so the best strategy to minimize jitter + is: wait until the real time to send the message, compute the + message, attach the *ideal* output time (not the current real + time, because some time may have elapsed), and send the + message. The \p latency will be added to the timestamp, and + provided the elapsed computation time has not exceeded \p latency, + the message will be delivered according to the timestamp. If the + real time is already past the timestamp, the message will be + delivered as soon as possible. Latency may also help you to + synchronize MIDI data to audio data by matching \p latency to the + audio buffer latency. + + @param time_proc (address of) a pointer to a procedure that + returns time in milliseconds. It may be NULL, in which case a + default millisecond timebase (PortTime) is used. If the + application wants to use PortTime, it should start the timer (call + Pt_Start) before calling #Pm_OpenInput or #Pm_OpenOutput. If the + application tries to start the timer *after* #Pm_OpenInput or + #Pm_OpenOutput, it may get a #ptAlreadyStarted error from #Pt_Start, + and the application's preferred time resolution and callback + function will be ignored. \p time_proc times are used to schedule + outgoing MIDI data (when latency is non-zero), usually by mapping + from time_proc timestamps to internal system timestamps to + maintain the precision of system-supported timing. + + @param time_info a pointer passed to time_proc. + + @return #pmNoError and places a pointer to a valid #PortMidiStream + in the stream argument. If the operation fails, a nonzero error + code is returned (see PMError) and the value of \p stream is + invalid. + + Note: ALSA appears to have a fixed-size priority queue for timed + output messages. Testing indicates the queue can hold a little + over 400 3-byte MIDI messages. Thus, you can send 10,000 + messages/second if the latency is 30ms (30ms * 10000 msgs/sec * + 0.001 sec/ms = 300 msgs), but not if the latency is 50ms + (resulting in about 500 pending messages, which is greater than + the 400 message limit). Since timestamps in ALSA are relative, + they are of less value than absolute timestamps in macOS and + Windows. This is a limitation of ALSA and apparently a design + flaw. + + Example 1: If I provide a timestamp of 5000, latency is 1, and + time_proc returns 4990, then the desired output time will be when + time_proc returns timestamp+latency = 5001. This will be 5001-4990 + = 11ms from now. + + Example 2: If I want to send at exactly 5010, and latency is 10, I + should wait until 5000, compute the messages and provide a + timestamp of 5000. As long as computation takes less than 10ms, + the message will be delivered at time 5010. + + Example 3 (recommended): It is often convenient to ignore latency. + E.g. if a sequence says to output at time 5010, just wait until + 5010, compute the message and use 5010 for the timestamp. Delivery + will then be at 5010+latency, but unless you are synchronizing to + something else, the absolute delay by latency will not matter. -PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream, + Any stream that is successfully opened should eventually be closed + by calling Pm_Close(). +*/ +PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream, PmDeviceID outputDevice, void *outputDriverInfo, int32_t bufferSize, PmTimeProcPtr time_proc, void *time_info, - int32_t latency ); + int32_t latency); + +/** Create a virtual input device. + + @param name gives the virtual device name, which is visible to + other applications. + + @param interf is the interface (System API) used to create the + device Default interfaces are "MMSystem", "CoreMIDI" and + "ALSA". Currently, these are the only ones implemented, but future + implementations could support DirectMusic, Jack, sndio, or others. + + @param deviceInfo contains interface-dependent additional + information, e.g., hints or options. There are none at present, and + NULL is the recommended value. + + @return a device ID or #pmNameConflict (\p name is invalid or + already exists) or #pmInterfaceNotSupported (\p interf is does not + match a supported interface). + + The created virtual device appears to other applications as if it + is an output device. The device must be opened to obtain a stream + and read from it. + + Virtual devices are not supported by Windows (Multimedia API). Calls + on Windows do nothing except return #pmNotImplemented. +*/ +PMEXPORT PmError Pm_CreateVirtualInput(const char *name, + const char *interf, + void *deviceInfo); + +/** Create a virtual output device. + + @param name gives the virtual device name, which is visible to + other applications. + + @param interf is the interface (System API) used to create the + device Default interfaces are "MMSystem", "CoreMIDI" and + "ALSA". Currently, these are the only ones implemented, but future + implementations could support DirectMusic, Jack, sndio, or others. + + @param deviceInfo contains interface-dependent additional + information, e.g., hints or options. There are none at present, and + NULL is the recommended value. + + @return a device ID or #pmInvalidDeviceId (\p name is invalid or + already exists) or #pmInterfaceNotSupported (\p interf is does not + match a supported interface). + + The created virtual device appears to other applications as if it + is an input device. The device must be opened to obtain a stream + and write to it. + + Virtual devices are not supported by Windows (Multimedia API). Calls + on Windows do nothing except return #pmNotImplemented. +*/ +PMEXPORT PmError Pm_CreateVirtualOutput(const char *name, + const char *interf, + void *deviceInfo); + +/** Remove a virtual device. + + @param device a device ID (small integer) designating the device. + + The device is removed; other applications can no longer see or open + this virtual device, which may be either for input or output. The + device must not be open. The device ID may be reused, but existing + devices are not renumbered. This means that the device ID could be + in the range from 0 to #Pm_CountDevices(), yet the device ID does + not designate a device. In that case, passing the ID to + #Pm_GetDeviceInfo() will return NULL. + + @return #pmNoError if the device was deleted or #pmInvalidDeviceId + if the device is open, already deleted, or \p device is out of + range. +*/ +PMEXPORT PmError Pm_DeleteVirtualDevice(PmDeviceID device); /** @} */ /** - \defgroup grp_events_filters Events and Filters Handling + @defgroup grp_events_filters Events and Filters Handling @{ */ -/* \function PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ) - Pm_SetFilter() sets filters on an open input stream to drop selected - input types. By default, only active sensing messages are filtered. - To prohibit, say, active sensing and sysex messages, call - Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX); - - Filtering is useful when midi routing or midi thru functionality is being - provided by the user application. - For example, you may want to exclude timing messages (clock, MTC, start/stop/continue), - while allowing note-related messages to pass. - Or you may be using a sequencer or drum-machine for MIDI clock information but want to - exclude any notes it may play. - */ - /* Filter bit-mask definitions */ /** filter active sensing messages (0xFE): */ #define PM_FILT_ACTIVE (1 << 0x0E) @@ -412,7 +610,8 @@ PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream, /** per-note aftertouch (0xA0-0xAF) */ #define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A) /** filter both channel and poly aftertouch */ -#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH) +#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | \ + PM_FILT_POLY_AFTERTOUCH) /** Program changes (0xC0-0xCF) */ #define PM_FILT_PROGRAM (1 << 0x1C) /** Control Changes (CC's) (0xB0-0xBF)*/ @@ -425,20 +624,48 @@ PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream, #define PM_FILT_SONG_POSITION (1 << 0x02) /** Song Select (0xF3)*/ #define PM_FILT_SONG_SELECT (1 << 0x03) -/** Tuning request (0xF6)*/ +/** Tuning request (0xF6) */ #define PM_FILT_TUNE (1 << 0x06) /** All System Common messages (mtc, song position, song select, tune request) */ -#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE) +#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | \ + PM_FILT_SONG_SELECT | PM_FILT_TUNE) + + +/* Set filters on an open input stream to drop selected input types. + + @param stream an open MIDI input stream. + @param filters indicate message types to filter (block). -PMEXPORT PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ); + @return #pmNoError or an error code. + By default, only active sensing messages are filtered. + To prohibit, say, active sensing and sysex messages, call + Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX); + + Filtering is useful when midi routing or midi thru functionality + is being provided by the user application. + For example, you may want to exclude timing messages (clock, MTC, + start/stop/continue), while allowing note-related messages to pass. + Or you may be using a sequencer or drum-machine for MIDI clock + information but want to exclude any notes it may play. + */ +PMEXPORT PmError Pm_SetFilter(PortMidiStream* stream, int32_t filters); + +/** Create a mask that filters one channel. */ #define Pm_Channel(channel) (1<<(channel)) -/** - Pm_SetChannelMask() filters incoming messages based on channel. - The mask is a 16-bit bitfield corresponding to appropriate channels. - The Pm_Channel macro can assist in calling this function. - i.e. to set receive only input on channel 1, call with + +/** Filter incoming messages based on channel. + + @param stream an open MIDI input stream. + + @param mask indicates channels to be received. + + @return #pmNoError or an error code. + + The \p mask is a 16-bit bitfield corresponding to appropriate channels. + The #Pm_Channel macro can assist in calling this function. + I.e. to receive only input on channel 1, call with Pm_SetChannelMask(Pm_Channel(1)); Multiple channels should be OR'd together, like Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11)) @@ -451,71 +678,94 @@ PMEXPORT PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ); */ PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); -/** - Pm_Abort() terminates outgoing messages immediately - The caller should immediately close the output port; - this call may result in transmission of a partial midi message. - There is no abort for Midi input because the user can simply - ignore messages in the buffer and close an input device at - any time. +/** Terminate outgoing messages immediately. + + @param stream an open MIDI output stream. + + @result #pmNoError or an error code. + + The caller should immediately close the output port; this call may + result in transmission of a partial MIDI message. There is no + abort for Midi input because the user can simply ignore messages + in the buffer and close an input device at any time. If the + specified behavior cannot be achieved through the system-level + interface (ALSA, CoreMIDI, etc.), the behavior may be that of + Pm_Close(). */ -PMEXPORT PmError Pm_Abort( PortMidiStream* stream ); +PMEXPORT PmError Pm_Abort(PortMidiStream* stream); -/** - Pm_Close() closes a midi stream, flushing any pending buffers. - (PortMidi attempts to close open streams when the application - exits -- this is particularly difficult under Windows.) -*/ -PMEXPORT PmError Pm_Close( PortMidiStream* stream ); +/** Close a midi stream, flush any pending buffers if possible. -/** - Pm_Synchronize() instructs PortMidi to (re)synchronize to the - time_proc passed when the stream was opened. Typically, this - is used when the stream must be opened before the time_proc - reference is actually advancing. In this case, message timing - may be erratic, but since timestamps of zero mean - "send immediately," initialization messages with zero timestamps - can be written without a functioning time reference and without - problems. Before the first MIDI message with a non-zero - timestamp is written to the stream, the time reference must - begin to advance (for example, if the time_proc computes time - based on audio samples, time might begin to advance when an - audio stream becomes active). After time_proc return values - become valid, and BEFORE writing the first non-zero timestamped - MIDI message, call Pm_Synchronize() so that PortMidi can observe - the difference between the current time_proc value and its - MIDI stream time. + @param stream an open MIDI input or output stream. + + @result #pmNoError or an error code. + + If the system-level interface (ALSA, CoreMIDI, etc.) does not + support flushing remaining messages, the behavior may be one of + the following (most preferred first): block until all pending + timestamped messages are delivered; deliver messages to a server + or kernel process for later delivery but return immediately; drop + messages (as in Pm_Abort()). Therefore, to be safe, applications + should wait until the output queue is empty before calling + Pm_Close(). E.g. calling Pt_Sleep(100 + latency); will give a + 100ms "cushion" beyond latency (if any) before closing. +*/ +PMEXPORT PmError Pm_Close(PortMidiStream* stream); + +/** (re)synchronize to the time_proc passed when the stream was opened. + + @param stream an open MIDI input or output stream. + + @result #pmNoError or an error code. + + Typically, this is used when the stream must be opened before the + time_proc reference is actually advancing. In this case, message + timing may be erratic, but since timestamps of zero mean "send + immediately," initialization messages with zero timestamps can be + written without a functioning time reference and without + problems. Before the first MIDI message with a non-zero timestamp + is written to the stream, the time reference must begin to advance + (for example, if the time_proc computes time based on audio + samples, time might begin to advance when an audio stream becomes + active). After time_proc return values become valid, and BEFORE + writing the first non-zero timestamped MIDI message, call + Pm_Synchronize() so that PortMidi can observe the difference + between the current time_proc value and its MIDI stream time. - In the more normal case where time_proc - values advance continuously, there is no need to call - Pm_Synchronize. PortMidi will always synchronize at the - first output message and periodically thereafter. + In the more normal case where time_proc values advance + continuously, there is no need to call #Pm_Synchronize. PortMidi + will always synchronize at the first output message and + periodically thereafter. */ -PmError Pm_Synchronize( PortMidiStream* stream ); +PMEXPORT PmError Pm_Synchronize(PortMidiStream* stream); -/** - Pm_Message() encodes a short Midi message into a 32-bit word. If data1 +/** Encode a short Midi message into a 32-bit word. If data1 and/or data2 are not present, use zero. - - Pm_MessageStatus(), Pm_MessageData1(), and - Pm_MessageData2() extract fields from a 32-bit midi message. */ #define Pm_Message(status, data1, data2) \ ((((data2) << 16) & 0xFF0000) | \ (((data1) << 8) & 0xFF00) | \ ((status) & 0xFF)) +/** Extract the status field from a 32-bit midi message. */ #define Pm_MessageStatus(msg) ((msg) & 0xFF) +/** Extract the 1st data field (e.g., pitch) from a 32-bit midi message. */ #define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF) +/** Extract the 2nd data field (e.g., velocity) from a 32-bit midi message. */ #define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF) -typedef int32_t PmMessage; /**< see PmEvent */ +typedef uint32_t PmMessage; /**< @brief see #PmEvent */ /** - All midi data comes in the form of PmEvent structures. A sysex + All MIDI data comes in the form of PmEvent structures. A sysex message is encoded as a sequence of PmEvent structures, with each structure carrying 4 bytes of the message, i.e. only the first PmEvent carries the status byte. + All other MIDI messages take 1 to 3 bytes and are encoded in a whole + PmMessage with status in the low-order byte and remaining bytes + unused, i.e., a 3-byte note-on message will occupy 3 low-order bytes + of PmMessage, leaving the high-order byte unused. + Note that MIDI allows nested messages: the so-called "real-time" MIDI messages can be inserted into the MIDI byte stream at any location, including within a sysex message. MIDI real-time messages are one-byte @@ -555,8 +805,8 @@ typedef int32_t PmMessage; /**< see PmEvent */ messages should be sent in the correct order, and timestamps MUST be non-decreasing. See also "Example" for Pm_OpenOutput() above. - A sysex message will generally fill many PmEvent structures. On - output to a PortMidiStream with non-zero latency, the first timestamp + A sysex message will generally fill many #PmEvent structures. On + output to a #PortMidiStream with non-zero latency, the first timestamp on sysex message data will determine the time to begin sending the message. PortMidi implementations may ignore timestamps for the remainder of the sysex message. @@ -580,71 +830,127 @@ typedef struct { PmTimestamp timestamp; } PmEvent; -/** - @} -*/ +/** @} */ + /** \defgroup grp_io Reading and Writing Midi Messages @{ */ -/** - Pm_Read() retrieves midi data into a buffer, and returns the number - of events read. Result is a non-negative number unless an error occurs, - in which case a PmError value will be returned. +/** Retrieve midi data into a buffer. - Buffer Overflow + @param stream the open input stream. - The problem: if an input overflow occurs, data will be lost, ultimately - because there is no flow control all the way back to the data source. - When data is lost, the receiver should be notified and some sort of - graceful recovery should take place, e.g. you shouldn't resume receiving - in the middle of a long sysex message. + @return the number of events read, or, if the result is negative, + a #PmError value will be returned. - With a lock-free fifo, which is pretty much what we're stuck with to - enable portability to the Mac, it's tricky for the producer and consumer - to synchronously reset the buffer and resume normal operation. + The Buffer Overflow Problem - Solution: the buffer managed by PortMidi will be flushed when an overflow - occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow) - and ordinary processing resumes as soon as a new message arrives. The - remainder of a partial sysex message is not considered to be a "new - message" and will be flushed as well. + The problem: if an input overflow occurs, data will be lost, + ultimately because there is no flow control all the way back to + the data source. When data is lost, the receiver should be + notified and some sort of graceful recovery should take place, + e.g. you shouldn't resume receiving in the middle of a long sysex + message. + With a lock-free fifo, which is pretty much what we're stuck with + to enable portability to the Mac, it's tricky for the producer and + consumer to synchronously reset the buffer and resume normal + operation. + + Solution: the entire buffer managed by PortMidi will be flushed + when an overflow occurs. The consumer (Pm_Read()) gets an error + message (#pmBufferOverflow) and ordinary processing resumes as + soon as a new message arrives. The remainder of a partial sysex + message is not considered to be a "new message" and will be + flushed as well. */ -PMEXPORT int Pm_Read( PortMidiStream *stream, PmEvent *buffer, int32_t length ); +PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length); -/** - Pm_Poll() tests whether input is available, - returning TRUE, FALSE, or an error value. +/** Test whether input is available. + + @param stream an open input stream. + + @return TRUE, FALSE, or an error value. + + If there was an asynchronous error, pmHostError is returned and you must + call again to determine if input is (also) available. + + You should probably *not* use this function. Call Pm_Read() + instead. If it returns 0, then there is no data available. It is + possible for Pm_Poll() to return TRUE before the complete message + is available, so Pm_Read() could return 0 even after Pm_Poll() + returns TRUE. Only call Pm_Poll() if you want to know that data is + probably available even though you are not ready to receive data. */ -PMEXPORT PmError Pm_Poll( PortMidiStream *stream); +PMEXPORT PmError Pm_Poll(PortMidiStream *stream); -/** - Pm_Write() writes midi data from a buffer. This may contain: +/** Write MIDI data from a buffer. + + @param stream an open output stream. + + @param buffer (address of) an array of MIDI event data. + + @param length the length of the \p buffer. + + @return TRUE, FALSE, or an error value. + + \b buffer may contain: - short messages - or - sysex messages that are converted into a sequence of PmEvent structures, e.g. sending data from a file or forwarding them - from midi input. + from midi input, with 4 SysEx bytes per PmEvent message, + low-order byte first, until the last message, which may + contain from 1 to 4 bytes ending in MIDI EOX (0xF7). + - PortMidi allows 1-byte real-time messages to be embedded + within SysEx messages, but only on 4-byte boundaries so + that SysEx data always uses a full 4 bytes (except possibly + at the end). Each real-time message always occupies a full + PmEvent (3 of the 4 bytes in the PmEvent's message are + ignored) even when embedded in a SysEx message. Use Pm_WriteSysEx() to write a sysex message stored as a contiguous array of bytes. Sysex data may contain embedded real-time messages. + + \p buffer is managed by the caller. The buffer may be destroyed + as soon as this call returns. */ -PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length ); +PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer, + int32_t length); -/** - Pm_WriteShort() writes a timestamped non-system-exclusive midi message. - Messages are delivered in order as received, and timestamps must be - non-decreasing. (But timestamps are ignored if the stream was opened - with latency = 0.) +/** Write a timestamped non-system-exclusive midi message. + + @param stream an open output stream. + + @param when timestamp for the event. + + @param msg the data for the event. + + @result #pmNoError or an error code. + + Messages are delivered in order, and timestamps must be + non-decreasing. (But timestamps are ignored if the stream was + opened with latency = 0, and otherwise, non-decreasing timestamps + are "corrected" to the lowest valid value.) */ -PMEXPORT PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, int32_t msg); +PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when, + PmMessage msg); -/** - Pm_WriteSysEx() writes a timestamped system-exclusive midi message. +/** Write a timestamped system-exclusive midi message. + + @param stream an open output stream. + + @param when timestamp for the event. + + @param msg the sysex message, terminated with an EOX status byte. + + @result #pmNoError or an error code. + + \p msg is managed by the caller and may be destroyed when this + call returns. */ -PMEXPORT PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg); +PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, + unsigned char *msg); /** @} */ |