summaryrefslogtreecommitdiff
path: root/portmidi
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi')
-rw-r--r--portmidi/.github/workflows/build.yml47
-rw-r--r--portmidi/.github/workflows/docs.yml28
-rw-r--r--portmidi/.gitignore62
-rw-r--r--portmidi/CHANGELOG.txt213
-rw-r--r--portmidi/CMakeLists.txt188
-rw-r--r--portmidi/Doxyfile2682
-rw-r--r--portmidi/README.md128
-rwxr-xr-xportmidi/README.txt88
-rw-r--r--portmidi/license.txt40
-rw-r--r--portmidi/packaging/PortMidiConfig.cmake.in15
-rw-r--r--portmidi/packaging/portmidi.pc.in11
-rw-r--r--portmidi/pm_common/CMakeLists.txt167
-rwxr-xr-xportmidi/pm_common/pminternal.h190
-rwxr-xr-xportmidi/pm_common/pmutil.c284
-rwxr-xr-xportmidi/pm_common/pmutil.h184
-rwxr-xr-xportmidi/pm_common/portmidi.c1472
-rwxr-xr-xportmidi/pm_common/portmidi.h974
-rw-r--r--portmidi/pm_haiku/pmhaiku.cpp473
-rw-r--r--portmidi/pm_java/CMakeLists.txt56
-rw-r--r--portmidi/pm_java/README.txt62
-rw-r--r--portmidi/pm_java/jportmidi/JPortMidi.java541
-rw-r--r--portmidi/pm_java/jportmidi/JPortMidiApi.java117
-rw-r--r--portmidi/pm_java/jportmidi/JPortMidiException.java12
-rw-r--r--portmidi/pm_java/make.bat50
-rw-r--r--portmidi/pm_java/pmjni/jportmidi_JportMidiApi.h293
-rw-r--r--portmidi/pm_java/pmjni/pmjni.c354
-rw-r--r--portmidi/pm_java/pmjni/pmjni.rc63
-rwxr-xr-xportmidi/pm_linux/README_LINUX.txt99
-rwxr-xr-xportmidi/pm_linux/pmlinux.c68
-rwxr-xr-xportmidi/pm_linux/pmlinuxalsa.c893
-rwxr-xr-xportmidi/pm_linux/pmlinuxalsa.h6
-rw-r--r--portmidi/pm_linux/pmlinuxnull.c31
-rw-r--r--portmidi/pm_linux/pmlinuxnull.h6
-rwxr-xr-xportmidi/pm_mac/Makefile.osx125
-rw-r--r--portmidi/pm_mac/README_MAC.txt65
-rwxr-xr-xportmidi/pm_mac/pmmac.c44
-rwxr-xr-xportmidi/pm_mac/pmmacosxcm.c1179
-rwxr-xr-xportmidi/pm_mac/pmmacosxcm.h4
-rw-r--r--portmidi/pm_sndio/pmsndio.c365
-rw-r--r--portmidi/pm_sndio/pmsndio.h5
-rw-r--r--portmidi/pm_test/CMakeLists.txt46
-rw-r--r--portmidi/pm_test/README.txt363
-rw-r--r--portmidi/pm_test/fast.c290
-rw-r--r--portmidi/pm_test/fastrcv.c255
-rwxr-xr-xportmidi/pm_test/latency.c287
-rw-r--r--portmidi/pm_test/midiclock.c282
-rwxr-xr-xportmidi/pm_test/midithread.c343
-rwxr-xr-xportmidi/pm_test/midithru.c455
-rwxr-xr-xportmidi/pm_test/mm.c595
-rw-r--r--portmidi/pm_test/multivirtual.c223
-rw-r--r--portmidi/pm_test/pmlist.c63
-rw-r--r--portmidi/pm_test/qtest.c174
-rw-r--r--portmidi/pm_test/recvvirtual.c175
-rw-r--r--portmidi/pm_test/sendvirtual.c194
-rwxr-xr-xportmidi/pm_test/sysex.c556
-rwxr-xr-xportmidi/pm_test/testio.c594
-rwxr-xr-xportmidi/pm_test/txdata.syx257
-rw-r--r--portmidi/pm_test/virttest.c339
-rwxr-xr-xportmidi/pm_win/README_WIN.txt174
-rwxr-xr-xportmidi/pm_win/debugging_dlls.txt145
-rwxr-xr-xportmidi/pm_win/pmwin.c98
-rwxr-xr-xportmidi/pm_win/pmwinmm.c1196
-rwxr-xr-xportmidi/pm_win/pmwinmm.h5
-rw-r--r--portmidi/pm_win/static.cmake24
-rw-r--r--portmidi/portmusic_logo.pngbin0 -> 753 bytes
-rwxr-xr-xportmidi/porttime/porttime.c3
-rwxr-xr-xportmidi/porttime/porttime.h103
-rw-r--r--portmidi/porttime/pthaiku.cpp88
-rwxr-xr-xportmidi/porttime/ptlinux.c139
-rwxr-xr-xportmidi/porttime/ptmacosx_cf.c140
-rwxr-xr-xportmidi/porttime/ptmacosx_mach.c204
-rwxr-xr-xportmidi/porttime/ptwinmm.c70
72 files changed, 19564 insertions, 0 deletions
diff --git a/portmidi/.github/workflows/build.yml b/portmidi/.github/workflows/build.yml
new file mode 100644
index 0000000..351b5cb
--- /dev/null
+++ b/portmidi/.github/workflows/build.yml
@@ -0,0 +1,47 @@
+name: build
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: Ubuntu
+ os: ubuntu-latest
+ install_dir: ~/portmidi
+ cmake_extras: -DCMAKE_BUILD_TYPE=RelWithDebInfo
+ - name: macOS
+ os: macos-latest
+ install_dir: ~/portmidi
+ cmake_extras: -DCMAKE_BUILD_TYPE=RelWithDebInfo
+ - name: Windows
+ os: windows-latest
+ install_dir: C:\portmidi
+ cmake_config: --config RelWithDebInfo
+
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Check out Git repository
+ uses: actions/checkout@v2
+ - name: "[Ubuntu] Install dependencies"
+ run: sudo apt install -y libasound2-dev
+ if: runner.os == 'Linux'
+ - name: Configure
+ run: cmake -D CMAKE_INSTALL_PREFIX=${{ matrix.install_dir }} ${{ matrix.cmake_extras }} -S . -B build
+ - name: Build
+ run: cmake --build build ${{ matrix.cmake_config }}
+ env:
+ CMAKE_BUILD_PARALLEL_LEVEL: 2
+ - name: Install
+ run: cmake --install . ${{ matrix.cmake_config }}
+ working-directory: build
+ - name: Upload Build Artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{ matrix.name }} portmidi build
+ path: ${{ matrix.install_dir }}
diff --git a/portmidi/.github/workflows/docs.yml b/portmidi/.github/workflows/docs.yml
new file mode 100644
index 0000000..d0e251b
--- /dev/null
+++ b/portmidi/.github/workflows/docs.yml
@@ -0,0 +1,28 @@
+name: Generate Docs
+
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ doxygen:
+ name: Doxygen
+ runs-on: ubuntu-latest
+ steps:
+ - name: "Check out repository"
+ uses: actions/checkout@v2
+
+ - name: Install Doxygen
+ run: sudo apt-get update && sudo apt-get install -y --no-install-recommends doxygen
+
+ - name: Generate Documentation
+ run: doxygen
+ working-directory: .
+
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: docs/html
diff --git a/portmidi/.gitignore b/portmidi/.gitignore
new file mode 100644
index 0000000..19d6650
--- /dev/null
+++ b/portmidi/.gitignore
@@ -0,0 +1,62 @@
+.DS_Store
+build*/
+*~
+CMakeCache.txt
+CMakeFiles/
+CMakeScripts/
+/portmidi.pc
+/x64/
+/Debug/
+/Release/
+/pm_java/pmdefaults/pmdefaults.jar
+/pm_java/pmdefaults.sln
+/pm_java/pmjni.dir/
+/pm_java/x64/
+portmidi.build/
+cmake_install.cmake
+*.xcodeproj/
+/.vs/
+/portmidi.sln
+*.vcxproj
+*.vcxproj.filters
+*.vcxproj.user
+/Makefile
+/libportmidi.so*
+/libportmidi.a
+/libportmidi_static.a
+/libpmjni.so*
+/packaging/PortMidiConfig.cmake
+/packaging/PortMidiConfigVersion.cmake
+/packaging/portmidi.pc
+/pm_common/Makefile
+/pm_common/portmidi.dir/
+/pm_java/Makefile
+/pm_test/Debug/
+/pm_test/Release/
+/pm_test/Makefile
+/pm_test/fastrcv
+/pm_test/fastrcv.dir/
+/pm_test/latency
+/pm_test/latency.dir/
+/pm_test/midiclock
+/pm_test/midiclock.dir/
+/pm_test/midithread
+/pm_test/midithread.dir/
+/pm_test/midithru
+/pm_test/midithru.dir/
+/pm_test/mm
+/pm_test/mm.dir/
+/pm_test/multivirtual
+/pm_test/qtest
+/pm_test/qtest.dir/
+/pm_test/recvvirtual
+/pm_test/sendvirtual
+/pm_test/sysex
+/pm_test/sysex.dir/
+/pm_test/testio
+/pm_test/testio.dir/
+/pm_test/virttest
+/pm_test/fast
+/pm_test/fast.dir/
+/pm_test/pmlist
+/pm_test/pmlist.dir/
diff --git a/portmidi/CHANGELOG.txt b/portmidi/CHANGELOG.txt
new file mode 100644
index 0000000..fee4330
--- /dev/null
+++ b/portmidi/CHANGELOG.txt
@@ -0,0 +1,213 @@
+/* CHANGELOG FOR PORTMIDI
+ *
+ * 21Feb22 v2.0.3 Roger Dannenberg
+ * - this version allows multiple hardware devices to have the same name.
+ *
+ * 03Jan22 v2.0.2 Roger Dannenberg
+ * - many changes for CMake including install support
+ * - bare-bones Java and PmDefaults support. It runs, but no
+ * installation.
+ *
+ * 16Sep21 Roger Dannenberg
+ * - Added CreateVirtualInput and CreateVirtualOutput functions (macOS
+ * & Linux) only.
+ * - Fix for unicode endpoints on macOS CoreMIDI.
+ * - Parsing in macOS of realtime message embedded in short messages
+ * (can this actually happen?)
+ * - renamed pm_test/test.c to pm_test/testio.c
+ * - with this release, pm_java, pm_csharp, pm_cl, pm_python, pm_qt
+ * are marked as "legacy code" and README.txt's refer to other
+ * projects. I had hoped for "one-stop shopping" for language
+ * bindings, but developers decided to move work to independent
+ * repositories. Maybe that's better.
+ *
+ * 19Oct09 Roger Dannenberg
+ * - Changes dynamic library names from portmidi_d to portmidi to
+ * be backward-compatible with programs expecting a library by
+ * the old name.
+ *
+ * 04Oct09 Roger Dannenberg
+ * - Converted to using Cmake.
+ * - Renamed static and dynamic library files to portmidi_s and portmidi_d
+ * - Eliminated VC9 and VC8 files (went back to simply test.vcproj, etc.,
+ * use Cmake to switch from the provided VC9 files to VC8 or other)
+ * - Many small changes to prepare for 64-bit architectures (but only
+ * tested on 32-bit machines)
+ *
+ * 16Jun09 Roger Dannenberg
+ * - Started using Microsoft Visual C++ Version 9 (Express). Converted
+ * all *-VC9.vcproj file to *.vcproj and renamed old project files to
+ * *-VC8.proj. Previously, output from VC9 went to special VC9 files,
+ * that breaks any program or script looking for output in release or
+ * debug files, so now both compiler version output to the same folders.
+ * Now, debug version uses static linking with debug DLL runtime, and
+ * release version uses static linking with statically linked runtime.
+ * Converted to Inno Setup and worked on scripts to make things build
+ * properly, especially pmdefaults.
+ *
+ * 02Jan09 Roger Dannenberg
+ * - Created Java interface and wrote PmDefaults application to set
+ * values for Pm_GetDefaultInputDeviceID() and
+ * Pm_GetDefaultOutputDeviceID(). Other fixes.
+ *
+ * 19Jun08 Roger Dannenberg and Austin Sung
+ * - Removed USE_DLL_FOR_CLEANUP -- Windows 2000 through Vista seem to be
+ * fixed now, and can recover if MIDI ports are left open
+ * - Various other minor patches
+ *
+ * 17Jan07 Roger Dannenberg
+ * - Lots more help for Common Lisp user in pm_cl
+ * - Minor fix to eliminate a compiler warning
+ * - Went back to single library in OS X for both portmidi and porttime
+ *
+ * 16Jan07 Roger Dannenberg
+ * - OOPS! fixed bug where short messages all had zero data
+ * - Makefile.osx static library build now makes universal (i386 + ppc)
+ * binaries
+ *
+ * 15Jan07 Roger Dannenberg
+ * - multiple rewrites of sysex handling code to take care of
+ * error-handling, embedded messages, message filtering,
+ * driver bugs, and host limitations.
+ * - fixed windows to use dwBufferLength rather than
+ * dwBytesRecorded for long buffer output (fix by Nigel Brown)
+ * - Win32 MME code always appends an extra zero to long buffer
+ * output to work around a problem with earlier versions of Midi Yoke
+ * - Added mm, a command line Midi Monitor to pm_test suite
+ * - Revised copyright notice to match PortAudio/MIT license (requests
+ * are moved out of the license proper and into a separate paragraph)
+ *
+ * 18Oct06 Roger Dannenberg
+ * - replace FIFO in pmutil with Light Pipe-based multiprocessor-safe alg.
+ * - replace FIFO in portmidi.c with PmQueue from pmutil
+ *
+ * 07Oct06 cpr & Roger Dannenberg
+ * - overhaul of CoreMIDI input to handle running status and multiple
+ * - messages per packet, with additional error detection
+ * - added Leigh Smith and Rick Taube support for Common Lisp and
+ * - dynamic link libraries in OSX
+ * - initialize static global seq = NULL in pmlinuxalsa.c
+ *
+ * 05Sep06 Sebastien Frippiat
+ * - check if (ALSA) seq exists before closing it in pm_linuxalsa_term()
+ *
+ * 05Sep06 Andreas Micheler and Cecilio
+ * - fixed memory leak by freeing someo objects in pm_winmm_term()
+ * - and another leak by freeing descriptors in Pm_Terminate()
+ *
+ * 23Aug06 RBD
+ * - various minor fixes
+ *
+ * 04Nov05 Olivier Tristan
+ * - changes to OS X to properly retrieve real device name on CoreMidi
+ *
+ * 19Jul05 Roger Dannenberg
+ * - included pmBufferMaxSize in Pm_GetErrorText()
+ *
+ * 23Mar05 Torgier Strand Henriksen
+ * - cleaner termination of porttime thread under Linux
+ *
+ * 15Nov04 Ben Allison
+ * - sysex output now uses one buffer/message and reallocates buffer
+ * - if needed
+ * - filters expanded for many message types and channels
+ * - detailed changes are as follows:
+ * ------------- in pmwinmm.c --------------
+ * - new #define symbol: OUTPUT_BYTES_PER_BUFFER
+ * - change SYSEX_BYTES_PER_BUFFER to 1024
+ * - added MIDIHDR_BUFFER_LENGTH(x) to correctly count midihdr buffer length
+ * - change MIDIHDR_SIZE(x) to (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
+ * - change allocate_buffer to use new MIDIHDR_BUFFER_LENGTH macro
+ * - new macros for MIDIHDR_SYSEX_SIZE and MIDIHDR_SYSEX_BUFFER_LENGTH
+ * - similar to above, but counts appropriately for sysex messages
+ * - added the following members to midiwinmm_struct for sysex data:
+ * - LPMIDIHDR *sysex_buffers; ** pool of buffers for sysex data **
+ * - int num_sysex_buffers; ** how many sysex buffers **
+ * - int next_sysex_buffer; ** index of next sysexbuffer to send **
+ * - HANDLE sysex_buffer_signal; ** to wait for free sysex buffer **
+ * - duplicated allocate_buffer, alocate_buffers and get_free_output_buffer
+ * - into equivalent sysex_buffer form
+ * - changed winmm_in_open to initialize new midiwinmm_struct members and
+ * - to use the new allocate_sysex_buffer() function instead of
+ * - allocate_buffer()
+ * - changed winmm_out_open to initialize new members, create sysex buffer
+ * - signal, and allocate 2 sysex buffers
+ * - changed winmm_out_delete to free sysex buffers and shut down the sysex
+ * - buffer signal
+ * - create new function resize_sysex_buffer which resizes m->hdr to the
+ * - passed size, and corrects the midiwinmm_struct accordingly.
+ * - changed winmm_write_byte to use new resize_sysex_buffer function,
+ * - if resize fails, write current buffer to output and continue
+ * - changed winmm_out_callback to use buffer_signal or sysex_buffer_signal
+ * - depending on which buffer was finished
+ * ------------- in portmidi.h --------------
+ * - added pmBufferMaxSize to PmError to indicate that the buffer would be
+ * - too large for the underlying API
+ * - added additional filters
+ * - added prototype, documentation, and helper macro for Pm_SetChannelMask
+ * ------------- in portmidi.c --------------
+ * - added pm_status_filtered() and pm_realtime_filtered() functions to
+ * separate filtering logic from buffer logic in pm_read_short
+ * - added Pm_SetChannelMask function
+ * - added pm_channel_filtered() function
+ * ------------- in pminternal.h --------------
+ * - added member to PortMidiStream for channel mask
+ *
+ * 25May04 RBD
+ * - removed support for MIDI THRU
+ * - moved filtering from Pm_Read to pm_enqueue to avoid buffer ovfl
+ * - extensive work on Mac OS X port, especially sysex and error handling
+ *
+ * 18May04 RBD
+ * - removed side-effects from assert() calls. Now you can disable assert().
+ * - no longer check pm_hosterror everywhere, fixing a bug where an open
+ * failure could cause a write not to work on a previously opened port
+ * until you call Pm_GetHostErrorText().
+ * 16May04 RBD and Chris Roberts
+ * - Some documentation wordsmithing in portmidi.h
+ * - Dynamically allocate port descriptor structures
+ * - Fixed parameter error in midiInPrepareBuffer and midiInAddBuffer.
+ *
+ * 09Oct03 RBD
+ * - Changed Thru handling. Now the client does all the work and the client
+ * must poll or read to keep thru messages flowing.
+ *
+ * 31May03 RBD
+ * - Fixed various bugs.
+ * - Added linux ALSA support with help from Clemens Ladisch
+ * - Added Mac OS X support, implemented by Jon Parise, updated and
+ * integrated by Andrew Zeldis and Zico Kolter
+ * - Added latency program to build histogram of system latency using PortTime.
+ *
+ * 30Jun02 RBD Extensive rewrite of sysex handling. It works now.
+ * Extensive reworking of error reporting and error text -- no
+ * longer use dictionary call to delete data; instead, Pm_Open
+ * and Pm_Close clean up before returning an error code, and
+ * error text is saved in a system-independent location.
+ * Wrote sysex.c to test sysex message handling.
+ *
+ * 15Jun02 BCT changes:
+ * - Added pmHostError text handling.
+ * - For robustness, check PortMidi stream args not NULL.
+ * - Re-C-ANSI-fied code (changed many C++ comments to C style)
+ * - Reorganized code in pmwinmm according to input/output functionality (made
+ * cleanup handling easier to reason about)
+ * - Fixed Pm_Write calls (portmidi.h says these should not return length but Pm_Error)
+ * - Cleaned up memory handling (now system specific data deleted via dictionary
+ * call in PortMidi, allows client to query host errors).
+ * - Added explicit asserts to verify various aspects of pmwinmm implementation behaves as
+ * logic implies it should. Specifically: verified callback routines not reentrant and
+ * all verified status for all unchecked Win32 MMedia API calls perform successfully
+ * - Moved portmidi initialization and clean-up routines into DLL to fix Win32 MMedia API
+ * bug (i.e. if devices not explicitly closed, must reboot to debug application further).
+ * With this change, clients no longer need explicitly call Pm_Initialize, Pm_Terminate, or
+ * explicitly Pm_Close open devices when using WinMM version of PortMidi.
+ *
+ * 23Jan02 RBD Fixed bug in pmwinmm.c thru handling
+ *
+ * 21Jan02 RBD Added tests in Pm_OpenInput() and Pm_OpenOutput() to prevent
+ * opening an input as output and vice versa.
+ * Added comments and documentation.
+ * Implemented Pm_Terminate().
+ *
+ */
diff --git a/portmidi/CMakeLists.txt b/portmidi/CMakeLists.txt
new file mode 100644
index 0000000..0107e8c
--- /dev/null
+++ b/portmidi/CMakeLists.txt
@@ -0,0 +1,188 @@
+# portmidi
+# Roger B. Dannenberg (and others)
+# Sep 2009 - 2021
+
+cmake_minimum_required(VERSION 3.21)
+# (ALSA::ALSA new in 3.12 and used in pm_common/CMakeLists.txt)
+# Some Java stuff failed on 3.17 but works with 3.20+
+
+cmake_policy(SET CMP0091 NEW) # enables MSVC_RUNTIME_LIBRARY target property
+
+# Previously, PortMidi versions were simply SVN commit version numbers.
+# Versions are now in the form x.y.z
+# Changed 1.0 to 2.0 because API is extended with virtual ports:
+set(SOVERSION "2")
+set(VERSION "2.0.4")
+
+project(portmidi VERSION "${VERSION}"
+ DESCRIPTION "Cross-Platform MIDI IO")
+
+set(LIBRARY_SOVERSION "${SOVERSION}")
+set(LIBRARY_VERSION "${VERSION}")
+
+option(BUILD_SHARED_LIBS "Build shared libraries" ON)
+
+option(PM_USE_STATIC_RUNTIME
+ "Use MSVC static runtime. Only applies when BUILD_SHARED_LIBS is OFF"
+ ON)
+
+option(USE_SNDIO "Use sndio" OFF)
+
+# MSVCRT_DLL is used to construct the MSVC_RUNTIME_LIBRARY property
+# (see pm_common/CMakeLists.txt and pm_test/CMakeLists.txt)
+if(PM_USE_STATIC_RUNTIME AND NOT BUILD_SHARED_LIBS)
+ set(MSVCRT_DLL "")
+else()
+ set(MSVCRT_DLL "DLL")
+endif()
+
+# Always build with position-independent code (-fPIC)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
+set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9 CACHE STRING
+ "make for this OS version or higher")
+
+# PM_ACTUAL_LIB_NAME is in this scope -- see pm_common/CMakeLists.txt
+# PM_NEEDED_LIBS is in this scope -- see pm_common/CMakeLists.txt
+
+include(GNUInstallDirs)
+
+# Build Types
+# credit: http://cliutils.gitlab.io/modern-cmake/chapters/features.html
+set(DEFAULT_BUILD_TYPE "Release")
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ message(STATUS
+ "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.")
+ set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE
+ STRING "Choose the type of build." FORCE)
+ # Set the possible values of build type for cmake-gui
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
+endif()
+
+# where to put libraries? Everything goes here in this directory
+# (or Debug or Release, depending on the OS)
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+
+option(BUILD_JAVA_NATIVE_INTERFACE
+ "build the Java PortMidi interface library" OFF)
+
+# Defines are used in both portmidi (in pm_common/) and pmjni (in pm_java),
+# so define them here to be inherited by both libraries.
+#
+# PortMidi software architecture supports multiple system API's to lower-
+# level MIDI drivers, e.g. PMNULL (no drivers), Jack (but not supported yet),
+# and sndio (BSD, not supported yet). Interfaces are selected by defining,
+# e.g., PMALSA. (In principle, we should require PMCOREMIDI (for macOS)
+# and PMWINMM (for windows), but these are assumed.
+#
+if(APPLE OR WIN32)
+else(APPLE_OR_WIN32)
+ set(LINUX_DEFINES "PMALSA" CACHE STRING "must define either PMALSA or PMNULL")
+ add_compile_definitions(${LINUX_DEFINES})
+endif(APPLE OR WIN32)
+
+if(BUILD_JAVA_NATIVE_INTERFACE)
+ message(WARNING
+ "Java API and PmDefaults program updated 2021, but support has "
+ "been discontinued. If you need/use this, let developers know.")
+ set(PMJNI_IF_EXISTS "pmjni") # used by INSTALL below
+else(BUILD_JAVA_NATIVE_INTERFACE)
+ set(PMJNI_IF_EXISTS "") # used by INSTALL below
+endif(BUILD_JAVA_NATIVE_INTERFACE)
+
+
+# Something like this might help if you need to build for a specific cpu type:
+# set(CMAKE_OSX_ARCHITECTURES x86_64 CACHE STRING
+# "change to support other architectures" FORCE)
+
+include_directories(pm_common porttime)
+add_subdirectory(pm_common)
+
+option(BUILD_PORTMIDI_TESTS
+ "Build test programs, including midi monitor (mm)" OFF)
+if(BUILD_PORTMIDI_TESTS)
+ add_subdirectory(pm_test)
+endif(BUILD_PORTMIDI_TESTS)
+
+# See note above about Java support (probably) discontinued
+if(BUILD_JAVA_NATIVE_INTERFACE)
+ add_subdirectory(pm_java)
+endif(BUILD_JAVA_NATIVE_INTERFACE)
+
+# Install the libraries and headers (Linux and Mac OS X command line)
+INSTALL(TARGETS portmidi ${PMJNI_IF_EXISTS}
+ EXPORT PortMidiTargets
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+ INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
+
+INSTALL(FILES
+ pm_common/portmidi.h
+ pm_common/pmutil.h
+ porttime/porttime.h
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+
+# pkgconfig - generate pc file
+# See https://cmake.org/cmake/help/latest/command/configure_file.html
+if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
+ set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
+else()
+ set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
+endif()
+if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
+ set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
+else()
+ set(PKGCONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
+endif()
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/portmidi.pc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/packaging/portmidi.pc
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+
+# CMake config
+set(PORTMIDI_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/PortMidi")
+install(
+ EXPORT PortMidiTargets
+ FILE PortMidiTargets.cmake
+ NAMESPACE PortMidi::
+ DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}"
+)
+include(CMakePackageConfigHelpers)
+configure_package_config_file(packaging/PortMidiConfig.cmake.in
+ "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake"
+ INSTALL_DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}"
+)
+write_basic_package_version_file(
+ "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake"
+ VERSION "${CMAKE_PROJECT_VERSION}"
+ COMPATIBILITY SameMajorVersion
+)
+install(
+ FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfig.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/packaging/PortMidiConfigVersion.cmake"
+ DESTINATION "${PORTMIDI_INSTALL_CMAKEDIR}"
+)
+
+
+
+
+# Finding out what CMake is doing is really hard, e.g. COMPILE_FLAGS
+# does not include COMPILE_OPTIONS or COMPILE_DEFINTIONS. Thus, the
+# following report is probably not complete...
+MESSAGE(STATUS "PortMidi Library name: " ${PM_ACTUAL_LIB_NAME})
+MESSAGE(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
+MESSAGE(STATUS "Library Type: " ${LIB_TYPE})
+MESSAGE(STATUS "Compiler flags: " ${CMAKE_CXX_COMPILE_FLAGS})
+get_directory_property(prop COMPILE_DEFINITIONS)
+MESSAGE(STATUS "Compile definitions: " ${prop})
+get_directory_property(prop COMPILE_OPTIONS)
+MESSAGE(STATUS "Compile options: " ${prop})
+MESSAGE(STATUS "Compiler cxx debug flags: " ${CMAKE_CXX_FLAGS_DEBUG})
+MESSAGE(STATUS "Compiler cxx release flags: " ${CMAKE_CXX_FLAGS_RELEASE})
+MESSAGE(STATUS "Compiler cxx min size flags: " ${CMAKE_CXX_FLAGS_MINSIZEREL})
+MESSAGE(STATUS "Compiler cxx flags: " ${CMAKE_CXX_FLAGS})
+
diff --git a/portmidi/Doxyfile b/portmidi/Doxyfile
new file mode 100644
index 0000000..95e3708
--- /dev/null
+++ b/portmidi/Doxyfile
@@ -0,0 +1,2682 @@
+# Doxyfile 1.9.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = PortMidi
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "Cross-platform MIDI IO library"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO = portmusic_logo.png
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = ../github-portmidi-portmidi_docs
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:^^"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 5
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = YES
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which effectively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS = 1
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = YES
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+SHOW_HEADERFILE = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+WARN_IF_INCOMPLETE_DOC = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# Possible values are: NO, YES and FAIL_ON_WARNINGS.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT =pm_common porttime/porttime.h pm_common/pmutil.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
+# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
+# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS = *.c \
+ *.cc \
+ *.cxx \
+ *.cpp \
+ *.c++ \
+ *.java \
+ *.ii \
+ *.ixx \
+ *.ipp \
+ *.i++ \
+ *.inl \
+ *.idl \
+ *.ddl \
+ *.odl \
+ *.h \
+ *.hh \
+ *.hxx \
+ *.hpp \
+ *.h++ \
+ *.l \
+ *.cs \
+ *.d \
+ *.php \
+ *.php4 \
+ *.php5 \
+ *.phtml \
+ *.inc \
+ *.m \
+ *.markdown \
+ *.md \
+ *.mm \
+ *.dox \
+ *.py \
+ *.pyw \
+ *.f90 \
+ *.f95 \
+ *.f03 \
+ *.f08 \
+ *.f18 \
+ *.f \
+ *.for \
+ *.vhd \
+ *.vhdl \
+ *.ucf \
+ *.qsf \
+ *.ice
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS = TRUE, FALSE, PMEXPORT
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see:
+# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
+# performance. This can be particularly helpful with template rich C++ code for
+# which doxygen's built-in parser lacks the necessary type information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
+# tag is set to YES then doxygen will add the directory of each input to the
+# include path.
+# The default value is: YES.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_ADD_INC_PATHS = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the directory containing a file called compile_commands.json. This
+# file is the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
+# options used when the source files were built. This is equivalent to
+# specifying the -p option to a clang tool, such as clang-check. These options
+# will then be passed to the parser. Any options specified with CLANG_OPTIONS
+# will be added as well.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = docs
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a color-wheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = YES
+
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
+# area (value NO) or if it should extend to the full height of the window (value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for the
+# project logo, title, and description. If either GENERATOR_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FULL_SIDEBAR = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION = MathJax_2
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH =
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html
+# #tex-and-latex-extensions):
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME =
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
+#
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to Sqlite3 output
+#---------------------------------------------------------------------------
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = NO
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
+# The default value is: YES.
+
+DOT_CLEANUP = YES
diff --git a/portmidi/README.md b/portmidi/README.md
new file mode 100644
index 0000000..3f0463c
--- /dev/null
+++ b/portmidi/README.md
@@ -0,0 +1,128 @@
+# PortMidi - Cross-Platform MIDI IO
+
+This is the canonical release of PortMidi.
+
+See other repositories within [PortMidi](https://github.com/PortMidi)
+for related code and bindings (although currently, not much is here).
+
+## [Full C API documentation is here.](https://portmidi.github.io/portmidi_docs/)
+
+## Compiling and Using PortMidi
+
+Use CMake (or ccmake) to create a Makefile for Linux/BSD or a
+project file for Xcode or MS Visual Studio. Use make or an IDE to compile:
+```
+sudo apt install libasound2-dev # on Linux, you need ALSA
+cd portmidi # start in the top-level portmidi directory
+ccmake . # set any options interactively, type c to configure
+ # type g to generate a Makefile or IDE project
+ # type q to quit
+ # (alternatively, run the CMake GUI and use
+ # Configure and Generate buttons to build IDE project)
+make # compile sources and build PortMidi library
+ # (alternatively, open project file with your IDE)
+sudo make install # if you want to install to your system
+```
+
+## Installation
+
+My advice is to build PortMidi and statically link it to your
+application. This gives you more control over versions. However,
+installing PortMidi to your system is preferred by some, and the
+following should do it:
+```
+cmake .
+make
+sudo make install
+```
+
+## Language Bindings
+
+Here is a guide to some other projects using PortMidi. There is not
+much coordination, so let us know if there are better or alternative
+bindings for these and other languages:
+
+- Python: Various libraries and packages exist; search and ye shall
+ find. If you wouldn't like to do research, check out [mido](https://mido.readthedocs.io/en/stable/)
+- [SML](https://github.com/jh-midi/portmidi-sml2)
+- [OCaml](https://ocaml.org/p/portmidi/0.1)
+- [Haskell](https://hackage.haskell.org/package/PortMidi)
+- [Erlang](https://hexdocs.pm/portmidi/PortMidi.html)
+- [Julia](https://github.com/SteffenPL/PortMidi.jl)
+- [C#](https://github.com/net-core-audio/portmidi)
+- [Rust](https://musitdev.github.io/portmidi-rs/)
+- [Go](https://github.com/rakyll/portmidi)
+- [Odin](https://pkg.odin-lang.org/vendor/portmidi/)
+- [Serpent](https://sourceforge.net/projects/serpent/) - a real-time
+ Python-like language has PortMidi built-in, a MIDI-timestamp-aware
+ scheduler, and GUI support for device selection.
+- [Pd (Pure Data)](https://puredata.info/) uses PortMidi.
+
+
+## What's New?
+
+(Not so new, but significant:) Support for the **PmDefaults** program,
+which enabled a graphical interface to select default MIDI devices,
+has been removed for lack of interest. This allowed us to also remove
+C code to read and parse Java preference files on various systems,
+simplifying the library. **PmDefaults** allowed simple command-line
+programs to use `Pm_DefaultInputDeviceID()` and
+`Pm_DefaultOutputDeviceID()` rather than creating device selection
+interfaces. Now, you should either pass devices on the command line or
+create your own selection interface when building a GUI
+application. (See pm_tests for examples.) `Pm_DefaultInputDeviceID()`
+and `Pm_DefaultOutputDeviceID()` now return a valid device if
+possible, but they may not actually reflect any user preference.
+
+Haiku support in a minimal implementation. See TODO's in source.
+
+sndio is also minimally supported, allowing basic PortMidi functions
+in OpenBSD, FreeBSD, and NetBSD by setting USE_SNDIO for CMake, but
+not delayed/timestamped output and virtual devices.
+
+# Other Repositories
+
+PortMidi used to be part of the PortMedia suite, but this repo has
+been reduced to mostly just C/C++ code for PortMidi. You will find
+some other repositories in this PortMidi project set up for language
+bindings (volunteers and contributors are invited!). Other code
+removed from previous releases of PortMedia include:
+
+## PortSMF
+
+A Standard MIDI File (SMF) (and more) library is in the [portsmf
+repository](https://github.com/rbdannenberg/portsmf).
+
+PortSMF is a library for reading/writing/editing Standard MIDI
+Files. It is actually much more, with a general representation of
+events and updates with properties consisting of attributes and typed
+values. Familiar properties of pitch, time, duration, and channel are
+built into events and updates to make them faster to access and more
+compact.
+
+To my knowledge, PortSMF has the most complete and useful handling of
+MIDI tempo tracks. E.g., you can edit notes according to either beat
+or time, and you can edit tempo tracks, for example, flattening the
+tempo while preserving the beat alignment, preserving the real time
+while changing the tempo or stretching the tempo over some interval.
+
+In addition to Standard MIDI Files, PortSMF supports an ASCII
+representation called Allegro. PortSMF and Allegro are used for
+Audacity Note Tracks.
+
+## scorealign
+
+Scorealign used to be part of the PortMedia suite. It is now at the
+[scorealign repository](https://github.com/rbdannenberg/scorealign).
+
+Scorealign aligns audio-to-audio, audio-to-MIDI or MIDI-to-MIDI using
+dynamic time warping (DTW) of a computed chromagram
+representation. There are some added smoothing tricks to improve
+performance. This library is written in C and runs substantially
+faster than most other implementations, especially those written in
+MATLAB, due to the core DTW algorithm. Users should be warned that
+while chromagrams are robust features for alignment, they achieve
+robustness by operating at fairly high granularity, e.g., durations of
+around 100ms, which limits time precision. Other more recent
+algorithms can doubtless do better, but be cautious of claims, since
+it all depends on what assumptions you can make about the music.
diff --git a/portmidi/README.txt b/portmidi/README.txt
new file mode 100755
index 0000000..e09aa2e
--- /dev/null
+++ b/portmidi/README.txt
@@ -0,0 +1,88 @@
+README for PortMidi
+
+Roger B. Dannenberg
+
+Documentation for PortMidi is found in pm_common/portmidi.h.
+Documentation in HTML is available at portmidi.github.io/portmidi_docs/
+
+Additional documentation:
+ - README.md (overview, how to build, what's new)
+ - Windows: see pm_win/README_WIN.txt and pm_win/debugging_dlls.txt
+ - Linux: see pm_linux/README_LINUX.txt
+ - Mac OSX: see pm_mac/README_MAC.txt
+ - Other Languages: look for other repos at github.com/PortMidi,
+ and search README.md for pointers to other projects.
+
+---------- some notes on the design of PortMidi ----------
+
+POINTERS VS DEVICE NUMBERS
+
+When you open a MIDI port, PortMidi allocates a structure to
+maintain the state of the open device. Since every device is
+also listed in a table, you might think it would be simpler to
+use the table index rather than a pointer to identify a device.
+This would also help with error checking (it's hard to make
+sure a pointer is valid). PortMidi's design parallels that of
+PortAudio.
+
+ERROR HANDLING
+
+Error handling turned out to be much more complicated than expected.
+PortMidi functions return error codes that the caller can check.
+In addition, errors may occur asynchronously due to MIDI input.
+However, for Windows, there are virtually no errors that can
+occur if the code is correct and not passing bogus values. One
+exception is an error that the system is out of memory, but my
+guess is that one is unlikely to recover gracefully from that.
+Therefore, all errors in callbacks are guarded by assert(), which
+means not guarded at all in release configurations.
+
+Ordinarily, the caller checks for an error code. If the error is
+system-dependent, pmHostError is returned and the caller can
+call Pm_GetHostErrorText to get a text description of the error.
+
+Host error codes are system-specific and are recorded in the
+system-specific data allocated for each open MIDI port.
+However, if an error occurs on open or close,
+we cannot store the error with the device because there will be
+no device data (assuming PortMidi cleans up after devices that
+are not open). For open and close, we will convert the error
+to text, copy it to a global string, and set pm_hosterror, a
+global flag.
+
+Similarly, whenever a Read or Write operation returns pmHostError,
+the corresponding error string is copied to a global string
+and pm_hosterror is set. This makes getting error strings
+simple and uniform, although it does cost a string copy and some
+overhead even if the user does not want to look at the error data.
+
+The system-specific Read, Write, Poll, etc. implementations should
+check for asynchronous errors and return immediately if one is
+found so that these get reported. This happens in the Mac OS X
+code, where lots of things are happening in callbacks, but again,
+in Windows, there are no error codes recorded in callbacks.
+
+DEBUGGING
+
+If you are building a console application for research, we suggest
+compiling with the option PM_CHECK_ERRORS. This will 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.
+
+The Windows version (and perhaps others) also offers a DEBUG
+compile-time option. See README_WIN.txt.
+
+RELEASE
+
+To make a new release, update VERSION variable in CMakeLists.txt.
+
+Update CHANGELOG.txt. What's new?
+
diff --git a/portmidi/license.txt b/portmidi/license.txt
new file mode 100644
index 0000000..c757b37
--- /dev/null
+++ b/portmidi/license.txt
@@ -0,0 +1,40 @@
+/*
+ * PortMidi Portable Real-Time MIDI Library
+ *
+ * license.txt -- a copy of the PortMidi copyright notice and license information
+ *
+ * Latest version available at: http://sourceforge.net/projects/portmedia
+ *
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
+ * Copyright (c) 2001-2009 Roger B. Dannenberg
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortMidi license; however,
+ * the PortMusic community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
diff --git a/portmidi/packaging/PortMidiConfig.cmake.in b/portmidi/packaging/PortMidiConfig.cmake.in
new file mode 100644
index 0000000..0d24f4d
--- /dev/null
+++ b/portmidi/packaging/PortMidiConfig.cmake.in
@@ -0,0 +1,15 @@
+@PACKAGE_INIT@
+
+include(CMakeFindDependencyMacro)
+if(UNIX AND NOT APPLE AND NOT HAIKU AND (@LINUX_DEFINES@ MATCHES ".*PMALSA.*"))
+ find_dependency(ALSA)
+endif()
+
+if(NOT WIN32)
+ set(THREADS_PREFER_PTHREAD_FLAG ON)
+ find_package(Threads REQUIRED)
+endif()
+
+include("${CMAKE_CURRENT_LIST_DIR}/PortMidiTargets.cmake")
+
+check_required_components(PortMidi)
diff --git a/portmidi/packaging/portmidi.pc.in b/portmidi/packaging/portmidi.pc.in
new file mode 100644
index 0000000..e9d929c
--- /dev/null
+++ b/portmidi/packaging/portmidi.pc.in
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=@PKGCONFIG_LIBDIR@
+includedir=@PKGCONFIG_INCLUDEDIR@
+
+Name: @CMAKE_PROJECT_NAME@
+Description: @CMAKE_PROJECT_DESCRIPTION@
+Version: @CMAKE_PROJECT_VERSION@
+Cflags: -I${includedir}
+Libs: -L${libdir} -l@CMAKE_PROJECT_NAME@
+Requires.private: @PKGCONFIG_REQUIRES_PRIVATE@
diff --git a/portmidi/pm_common/CMakeLists.txt b/portmidi/pm_common/CMakeLists.txt
new file mode 100644
index 0000000..1ad54ad
--- /dev/null
+++ b/portmidi/pm_common/CMakeLists.txt
@@ -0,0 +1,167 @@
+# 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. 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})
+endif(APPLE OR WIN32)
+
+# 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})
+ foreach(FILE ${ARGN})
+ list(APPEND ${RESULT} "${PATH}${FILE}")
+ endforeach(FILE)
+endmacro(prepend_path)
+
+# UNIX needs pthread library
+if(NOT WIN32)
+ set(THREADS_PREFER_PTHREAD_FLAG ON)
+ find_package(Threads REQUIRED)
+endif()
+
+# Check for sndio
+if(USE_SNDIO)
+ include (FindPackageHandleStandardArgs)
+ find_path(SNDIO_INCLUDE_DIRS NAMES sndio.h)
+ find_library(SNDIO_LIBRARY sndio)
+ find_package_handle_standard_args(Sndio
+ REQUIRED_VARS SNDIO_LIBRARY SNDIO_INCLUDE_DIRS)
+endif(USE_SNDIO)
+
+# first include the appropriate system-dependent file:
+if(SNDIO_FOUND AND USE_SNDIO)
+ set(PM_LIB_PRIVATE_SRC
+ ${PMDIR}/porttime/ptlinux.c
+ ${PMDIR}/pm_sndio/pmsndio.c)
+ set(PM_NEEDED_LIBS Threads::Threads ${SNDIO_LIBRARY} PARENT_SCOPE)
+ target_link_libraries(portmidi PRIVATE Threads::Threads ${SNDIO_LIBRARY})
+ target_include_directories(portmidi PRIVATE ${SNDIO_INCLUDE_DIRS})
+elseif(UNIX AND APPLE)
+ set(Threads::Threads "" PARENT_SCOPE)
+ set(PM_LIB_PRIVATE_SRC
+ ${PMDIR}/porttime/ptmacosx_mach.c
+ ${PMDIR}/pm_mac/pmmac.c
+ ${PMDIR}/pm_mac/pmmacosxcm.c)
+ set(PM_NEEDED_LIBS
+ ${CMAKE_THREAD_LIBS_INIT}
+ -Wl,-framework,CoreAudio
+ -Wl,-framework,CoreFoundation
+ -Wl,-framework,CoreMidi
+ -Wl,-framework,CoreServices
+ PARENT_SCOPE)
+ target_link_libraries(portmidi PRIVATE
+ Threads::Threads
+ -Wl,-framework,CoreAudio
+ -Wl,-framework,CoreFoundation
+ -Wl,-framework,CoreMidi
+ -Wl,-framework,CoreServices
+ )
+ # 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)
+ 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)
+ 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)
+# endif()
+else()
+ message(FATAL_ERROR "Operating system not supported.")
+endif()
+
+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
+
+target_sources(portmidi PRIVATE ${PM_LIB_PRIVATE_SRC})
+
diff --git a/portmidi/pm_common/pminternal.h b/portmidi/pm_common/pminternal.h
new file mode 100755
index 0000000..8b3d8f5
--- /dev/null
+++ b/portmidi/pm_common/pminternal.h
@@ -0,0 +1,190 @@
+/** @file pminternal.h header for PortMidi implementations */
+
+/* this file is included by files that implement library internals */
+/* Here is a guide to implementers:
+ provide an initialization function similar to pm_winmm_init()
+ add your initialization function to pm_init()
+ Note that your init function should never require not-standard
+ libraries or fail in any way. If the interface is not available,
+ simply do not call pm_add_device. This means that non-standard
+ libraries should try to do dynamic linking at runtime using a DLL
+ and return without error if the DLL cannot be found or if there
+ is any other failure.
+ implement functions as indicated in pm_fns_type to open, read, write,
+ close, etc.
+ call pm_add_device() for each input and output device, passing it a
+ pm_fns_type structure.
+ assumptions about pm_fns_type functions are given below.
+ */
+
+/** @cond INTERNAL - add INTERNAL to Doxygen ENABLED_SECTIONS to include */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int pm_initialized; /* see note in portmidi.c */
+extern PmDeviceID pm_default_input_device_id;
+extern PmDeviceID pm_default_output_device_id;
+
+/* these are defined in system-specific file */
+void *pm_alloc(size_t s);
+void pm_free(void *ptr);
+
+/* 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;
+
+/* these do not use PmInternal because it is not defined yet... */
+typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi,
+ PmEvent *buffer);
+typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,
+ PmTimestamp timestamp);
+typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,
+ PmTimestamp timestamp);
+typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,
+ unsigned char byte, PmTimestamp timestamp);
+typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
+ PmEvent *buffer);
+typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi,
+ PmTimestamp timestamp);
+typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);
+/* pm_open_fn should clean up all memory and close the device if any part
+ 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 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 msg within sysex */
+ pm_write_flush_fn write_flush; /* send any accumulated but unsent data */
+ 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_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;
+
+
+/* when open fails, the dictionary gets this set of functions: */
+extern pm_fns_node pm_none_dictionary;
+
+typedef struct {
+ 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 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 pm_descriptors) */
+ short is_input; /* MIDI IN (true) or MIDI OUT (false) */
+ short is_removed; /* MIDI device was removed */
+ PmTimeProcPtr time_proc; /* where to get the time */
+ void *time_info; /* pass this to get_time() */
+ int32_t buffer_len; /* how big is the buffer or queue? */
+ PmQueue *queue;
+
+ int32_t latency; /* time delay in ms between timestamps and actual output */
+ /* set to zero to get immediate, simple blocking output */
+ /* if latency is zero, timestamps will be ignored; */
+ /* if midi input device, this field ignored */
+
+ int sysex_in_progress; /* when sysex status is seen, this flag becomes
+ * true until EOX is seen. When true, new data is appended to the
+ * stream of outgoing bytes. When overflow occurs, sysex data is
+ * dropped (until an EOX or non-real-timei status byte is seen) so
+ * that, if the overflow condition is cleared, we don't start
+ * sending data from the middle of a sysex message. If a sysex
+ * message is filtered, sysex_in_progress is false, causing the
+ * message to be dropped. */
+ PmMessage message; /* buffer for 4 bytes of sysex data */
+ int message_count; /* how many bytes in sysex_message so far */
+ int short_message_count; /* how many bytes are expected in short message */
+ unsigned char running_status; /* running status byte or zero if none */
+ int32_t filters; /* flags that filter incoming message classes */
+ int32_t channel_mask; /* filter incoming messages based on channel */
+ PmTimestamp last_msg_time; /* timestamp of last message */
+ PmTimestamp sync_time; /* time of last synchronization */
+ 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 *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,
+ * but this does not count time in the driver, so I don't know if it is
+ * important
+ */
+ unsigned char *fill_base; /* addr of ptr to sysex data */
+ uint32_t *fill_offset_ptr; /* offset of next sysex byte */
+ uint32_t fill_length; /* how many sysex bytes to write */
+} PmInternal;
+
+/* what is the length of this short message? */
+int pm_midi_length(PmMessage msg);
+
+/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */
+void pm_init(void);
+void pm_term(void);
+
+/* defined by portMidi, used by pmwinmm */
+PmError none_write_short(PmInternal *midi, PmEvent *buffer);
+PmError none_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp);
+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_interf(char *interf, pm_create_fn create_fn,
+ pm_delete_fn delete_fn);
+PmError pm_add_device(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);
+
+#define none_write_flush pm_fail_timestamp_fn
+#define none_sysex pm_fail_timestamp_fn
+#define none_poll pm_fail_fn
+#define success_poll pm_success_fn
+
+#define MIDI_REALTIME_MASK 0xf8
+#define is_real_time(msg) \
+ ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @endcond */
diff --git a/portmidi/pm_common/pmutil.c b/portmidi/pm_common/pmutil.c
new file mode 100755
index 0000000..a70fe2f
--- /dev/null
+++ b/portmidi/pm_common/pmutil.c
@@ -0,0 +1,284 @@
+/* pmutil.c -- some helpful utilities for building midi
+ applications that use PortMidi
+ */
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+
+#ifdef WIN32
+#define bzero(addr, siz) memset(addr, 0, siz)
+#endif
+
+// #define QUEUE_DEBUG 1
+#ifdef QUEUE_DEBUG
+#include "stdio.h"
+#endif
+
+typedef struct {
+ long head;
+ long tail;
+ long len;
+ long overflow;
+ int32_t msg_size; /* number of int32_t in a message including extra word */
+ int32_t peek_overflow;
+ int32_t *buffer;
+ int32_t *peek;
+ int32_t peek_flag;
+} PmQueueRep;
+
+
+PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg)
+{
+ int32_t int32s_per_msg =
+ (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) &
+ ~(sizeof(int32_t) - 1)) / sizeof(int32_t));
+ PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));
+ if (!queue) /* memory allocation failed */
+ return NULL;
+
+ /* need extra word per message for non-zero encoding */
+ queue->len = num_msgs * (int32s_per_msg + 1);
+ queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t));
+ bzero(queue->buffer, queue->len * sizeof(int32_t));
+ if (!queue->buffer) {
+ pm_free(queue);
+ return NULL;
+ } else { /* allocate the "peek" buffer */
+ queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t));
+ if (!queue->peek) {
+ /* free everything allocated so far and return */
+ pm_free(queue->buffer);
+ pm_free(queue);
+ return NULL;
+ }
+ }
+ bzero(queue->buffer, queue->len * sizeof(int32_t));
+ queue->head = 0;
+ queue->tail = 0;
+ /* msg_size is in words */
+ queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */
+ queue->overflow = FALSE;
+ queue->peek_overflow = FALSE;
+ queue->peek_flag = FALSE;
+ return queue;
+}
+
+
+PMEXPORT PmError Pm_QueueDestroy(PmQueue *q)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+
+ /* arg checking */
+ if (!queue || !queue->buffer || !queue->peek)
+ return pmBadPtr;
+
+ pm_free(queue->peek);
+ pm_free(queue->buffer);
+ pm_free(queue);
+ return pmNoError;
+}
+
+
+PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg)
+{
+ long head;
+ PmQueueRep *queue = (PmQueueRep *) q;
+ int i;
+ int32_t *msg_as_int32 = (int32_t *) msg;
+
+ /* arg checking */
+ if (!queue)
+ return pmBadPtr;
+ /* a previous peek operation encountered an overflow, but the overflow
+ * has not yet been reported to client, so do it now. No message is
+ * returned, but on the next call, we will return the peek buffer.
+ */
+ if (queue->peek_overflow) {
+ queue->peek_overflow = FALSE;
+ return pmBufferOverflow;
+ }
+ if (queue->peek_flag) {
+ memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t));
+ queue->peek_flag = FALSE;
+ return pmGotData;
+ }
+
+ head = queue->head;
+ /* if writer overflows, it writes queue->overflow = tail+1 so that
+ * when the reader gets to that position in the buffer, it can
+ * return the overflow condition to the reader. The problem is that
+ * at overflow, things have wrapped around, so tail == head, and the
+ * reader will detect overflow immediately instead of waiting until
+ * it reads everything in the buffer, wrapping around again to the
+ * point where tail == head. So the condition also checks that
+ * queue->buffer[head] is zero -- if so, then the buffer is now
+ * empty, and we're at the point in the msg stream where overflow
+ * occurred. It's time to signal overflow to the reader. If
+ * queue->buffer[head] is non-zero, there's a message there and we
+ * should read all the way around the buffer before signalling overflow.
+ * There is a write-order dependency here, but to fail, the overflow
+ * field would have to be written while an entire buffer full of
+ * writes are still pending. I'm assuming out-of-order writes are
+ * possible, but not that many.
+ */
+ if (queue->overflow == head + 1 && !queue->buffer[head]) {
+ queue->overflow = 0; /* non-overflow condition */
+ return pmBufferOverflow;
+ }
+
+ /* test to see if there is data in the queue -- test from back
+ * to front so if writer is simultaneously writing, we don't
+ * waste time discovering the write is not finished
+ */
+ for (i = queue->msg_size - 1; i >= 0; i--) {
+ if (!queue->buffer[head + i]) {
+ return pmNoData;
+ }
+ }
+ memcpy(msg, (char *) &queue->buffer[head + 1],
+ sizeof(int32_t) * (queue->msg_size - 1));
+ /* fix up zeros */
+ i = queue->buffer[head];
+ while (i < queue->msg_size) {
+ int32_t j;
+ i--; /* msg does not have extra word so shift down */
+ j = msg_as_int32[i];
+ msg_as_int32[i] = 0;
+ i = j;
+ }
+ /* signal that data has been removed by zeroing: */
+ bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size);
+
+ /* update head */
+ head += queue->msg_size;
+ if (head == queue->len) head = 0;
+ queue->head = head;
+ return pmGotData; /* success */
+}
+
+
+
+PMEXPORT PmError Pm_SetOverflow(PmQueue *q)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ long tail;
+ /* arg checking */
+ if (!queue)
+ return pmBadPtr;
+ /* no more enqueue until receiver acknowledges overflow */
+ if (queue->overflow) return pmBufferOverflow;
+ tail = queue->tail;
+ queue->overflow = tail + 1;
+ return pmBufferOverflow;
+}
+
+
+PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ long tail;
+ int i;
+ int32_t *src = (int32_t *) msg;
+ int32_t *ptr;
+ int32_t *dest;
+ int rslt;
+ if (!queue)
+ return pmBadPtr;
+ /* no more enqueue until receiver acknowledges overflow */
+ if (queue->overflow) return pmBufferOverflow;
+ rslt = Pm_QueueFull(q);
+ /* already checked above: if (rslt == pmBadPtr) return rslt; */
+ tail = queue->tail;
+ if (rslt) {
+ queue->overflow = tail + 1;
+ return pmBufferOverflow;
+ }
+
+ /* queue is has room for message, and overflow flag is cleared */
+ ptr = &queue->buffer[tail];
+ dest = ptr + 1;
+ for (i = 1; i < queue->msg_size; i++) {
+ int32_t j = src[i - 1];
+ if (!j) {
+ *ptr = i;
+ ptr = dest;
+ } else {
+ *dest = j;
+ }
+ dest++;
+ }
+ *ptr = i;
+ tail += queue->msg_size;
+ if (tail == queue->len) tail = 0;
+ queue->tail = tail;
+ return pmNoError;
+}
+
+
+PMEXPORT int Pm_QueueEmpty(PmQueue *q)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ return (!queue) || /* null pointer -> return "empty" */
+ (queue->buffer[queue->head] == 0 && !queue->peek_flag);
+}
+
+
+PMEXPORT int Pm_QueueFull(PmQueue *q)
+{
+ long tail;
+ int i;
+ PmQueueRep *queue = (PmQueueRep *) q;
+ /* arg checking */
+ if (!queue)
+ return pmBadPtr;
+ tail = queue->tail;
+ /* test to see if there is space in the queue */
+ for (i = 0; i < queue->msg_size; i++) {
+ if (queue->buffer[tail + i]) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+PMEXPORT void *Pm_QueuePeek(PmQueue *q)
+{
+ PmError rslt;
+ int32_t temp;
+ PmQueueRep *queue = (PmQueueRep *) q;
+ /* arg checking */
+ if (!queue)
+ return NULL;
+
+ if (queue->peek_flag) {
+ return queue->peek;
+ }
+ /* this is ugly: if peek_overflow is set, then Pm_Dequeue()
+ * returns immediately with pmBufferOverflow, but here, we
+ * want Pm_Dequeue() to really check for data. If data is
+ * there, we can return it
+ */
+ temp = queue->peek_overflow;
+ queue->peek_overflow = FALSE;
+ rslt = Pm_Dequeue(q, queue->peek);
+ queue->peek_overflow = temp;
+
+ if (rslt == 1) {
+ queue->peek_flag = TRUE;
+ return queue->peek;
+ } else if (rslt == pmBufferOverflow) {
+ /* when overflow is indicated, the queue is empty and the
+ * first message that was dropped by Enqueue (signalling
+ * pmBufferOverflow to its caller) would have been the next
+ * message in the queue. Pm_QueuePeek will return NULL, but
+ * remember that an overflow occurred. (see Pm_Dequeue)
+ */
+ queue->peek_overflow = TRUE;
+ }
+ return NULL;
+}
+
diff --git a/portmidi/pm_common/pmutil.h b/portmidi/pm_common/pmutil.h
new file mode 100755
index 0000000..46c618e
--- /dev/null
+++ b/portmidi/pm_common/pmutil.h
@@ -0,0 +1,184 @@
+/** @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.
+ */
+
+#ifndef PORTMIDI_PMUTIL_H
+#define PORTMIDI_PMUTIL_H
+
+#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;
+
+/** 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.
+
+ This implementation does not try to keep reader/writer data in
+ separate cache lines or prevent thrashing on cache lines.
+ However, this algorithm differs by doing inserts/removals in
+ units of messages rather than units of machine words. Some
+ performance improvement might be obtained by not clearing data
+ immediately after a read, but instead by waiting for the end
+ 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.
+ */
+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);
+
+/** 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.
+
+ @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.
+
+ @param queue a queue created by #Pm_QueueCreate().
+
+ @return non-zero iff the queue is empty, and @pmBadPtr if \p 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.
+
+ @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.
+ */
+PMEXPORT void *Pm_QueuePeek(PmQueue *queue);
+
+/** 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 */
+
+#endif // PORTMIDI_PMUTIL_H
diff --git a/portmidi/pm_common/portmidi.c b/portmidi/pm_common/portmidi.c
new file mode 100755
index 0000000..e78ee73
--- /dev/null
+++ b/portmidi/pm_common/portmidi.c
@@ -0,0 +1,1472 @@
+/* portmidi.c -- cross-platform MIDI I/O library */
+/* see license.txt for license */
+
+#include "stdlib.h"
+#include "string.h"
+#include "portmidi.h"
+#include "porttime.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include <assert.h>
+
+#define MIDI_CLOCK 0xf8
+#define MIDI_ACTIVE 0xfe
+#define MIDI_STATUS_MASK 0x80
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+#define MIDI_START 0xFA
+#define MIDI_STOP 0xFC
+#define MIDI_CONTINUE 0xFB
+#define MIDI_F9 0xF9
+#define MIDI_FD 0xFD
+#define MIDI_RESET 0xFF
+#define MIDI_NOTE_ON 0x90
+#define MIDI_NOTE_OFF 0x80
+#define MIDI_CHANNEL_AT 0xD0
+#define MIDI_POLY_AT 0xA0
+#define MIDI_PROGRAM 0xC0
+#define MIDI_CONTROL 0xB0
+#define MIDI_PITCHBEND 0xE0
+#define MIDI_MTC 0xF1
+#define MIDI_SONGPOS 0xF2
+#define MIDI_SONGSEL 0xF3
+#define MIDI_TUNE 0xF6
+
+#define is_empty(midi) ((midi)->tail == (midi)->head)
+
+/* these are not static so that (possibly) some system-dependent code
+ * could override the portmidi.c default which is to use the first
+ * device added using pm_add_device()
+ */
+PmDeviceID pm_default_input_device_id = -1;
+PmDeviceID pm_default_output_device_id = -1;
+
+/* this is not static so that pm_init can set it directly
+ * (see pmmac.c:pm_init())
+ */
+int pm_initialized = FALSE;
+
+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
+
+#include <stdio.h>
+
+#define STRING_MAX 80
+
+static void prompt_and_exit(void)
+{
+ char line[STRING_MAX];
+ printf("type ENTER...");
+ char *rslt = fgets(line, STRING_MAX, stdin);
+ /* this will clean up open ports: */
+ exit(-1);
+}
+
+static PmError pm_errmsg(PmError err)
+{
+ 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
+ */
+ printf("PortMidi found host error...\n %s\n", pm_hosterror_text);
+ pm_hosterror = FALSE;
+ pm_hosterror_text[0] = 0; /* clear the message */
+ prompt_and_exit();
+ } else if (err < 0) {
+ printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
+ prompt_and_exit();
+ }
+ return err;
+}
+#else
+#define pm_errmsg(err) err
+#endif
+
+
+int pm_midi_length(PmMessage msg)
+{
+ int status, high, low;
+ static int high_lengths[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */
+ 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */
+ };
+ static int low_lengths[] = {
+ 1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */
+ 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */
+ };
+
+ status = msg & 0xFF;
+ high = status >> 4;
+ low = status & 15;
+
+ return (high != 0xF) ? high_lengths[high] : low_lengths[low];
+}
+
+
+/*
+====================================================================
+system implementation of portmidi interface
+====================================================================
+*/
+
+int pm_descriptor_max = 0;
+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(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).
+ * 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 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, 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 = PM_DEVICEINFO_VERS;
+ 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 (pm_descriptors) {
+ memcpy(new_descriptors, pm_descriptors,
+ sizeof(descriptor_node) * pm_descriptor_max);
+ pm_free(pm_descriptors);
+ }
+ pm_descriptor_max += 32;
+ pm_descriptors = new_descriptors;
+ }
+ if (device_id == pm_descriptor_len) {
+ pm_descriptor_len++; /* extending array of pm_descriptors */
+ }
+ d = &pm_descriptors[device_id].pub;
+ d->interf = interf;
+ d->name = pm_alloc(strlen(name) + 1);
+ if (!d->name) {
+ return pmInsufficientMemory;
+ }
+#if defined(WIN32) && !defined(_WIN32)
+#pragma warning(suppress: 4996) // don't use suggested strncpy_s
+#endif
+ 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) */
+ d->opened = FALSE;
+
+ pm_descriptors[device_id].deleted = FALSE;
+
+ /* ID number passed to win32 multimedia API open */
+ pm_descriptors[device_id].descriptor = descriptor;
+
+ /* points to PmInternal, allows automatic device closing */
+ pm_descriptors[device_id].pm_internal = NULL;
+
+ pm_descriptors[device_id].dictionary = dictionary;
+
+ /* set the defaults to the first input and output we see */
+ if (is_input && pm_default_input_device_id == -1) {
+ pm_default_input_device_id = device_id;
+ } else if (!is_input && pm_default_output_device_id == -1) {
+ pm_default_output_device_id = device_id;
+ }
+
+ 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--;
+ }
+}
+
+
+/* utility to look up device, given a pattern,
+ note: pattern is modified
+ */
+int Pm_FindDevice(char *pattern, int is_input)
+{
+ int id = pmNoDevice;
+ int i;
+ /* first parse pattern into name, interf parts */
+ char *interf_pref = ""; /* initially assume it is not there */
+ char *name_pref = strstr(pattern, ", ");
+
+ if (name_pref) { /* found separator, adjust the pointer */
+ interf_pref = pattern;
+ name_pref[0] = 0;
+ name_pref += 2;
+ } else {
+ name_pref = pattern; /* whole string is the name pattern */
+ }
+ for (i = 0; i < pm_descriptor_len; i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (info->input == is_input &&
+ strstr(info->name, name_pref) &&
+ strstr(info->interf, interf_pref)) {
+ id = i;
+ break;
+ }
+ }
+ return id;
+}
+
+
+/*
+====================================================================
+portmidi implementation
+====================================================================
+*/
+
+PMEXPORT int Pm_CountDevices(void)
+{
+ Pm_Initialize();
+ /* no error checking -- Pm_Initialize() does not fail */
+ return pm_descriptor_len;
+}
+
+
+PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo(PmDeviceID id)
+{
+ Pm_Initialize(); /* no error check needed */
+ 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)
+{
+ return pmNoError;
+}
+
+/* none_write -- returns an error if called */
+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)
+{
+ return pmBadPtr;
+}
+
+PmError none_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp)
+{
+ return pmBadPtr;
+}
+
+/* pm_fail_fn -- generic function, returns error if called */
+PmError pm_fail_fn(PmInternal *midi)
+{
+ return pmBadPtr;
+}
+
+static PmError none_open(PmInternal *midi, void *driverInfo)
+{
+ return pmBadPtr;
+}
+
+static unsigned int none_check_host_error(PmInternal * midi)
+{
+ return FALSE;
+}
+
+PmTimestamp none_synchronize(PmInternal *midi)
+{
+ return 0;
+}
+
+#define none_abort pm_fail_fn
+#define none_close pm_fail_fn
+
+pm_fns_node pm_none_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ none_synchronize,
+ none_open,
+ none_abort,
+ none_close,
+ none_poll,
+ none_check_host_error,
+};
+
+
+PMEXPORT const char *Pm_GetErrorText(PmError errnum)
+{
+ const char *msg;
+
+ switch(errnum)
+ {
+ case pmNoError:
+ msg = "";
+ break;
+ case pmHostError:
+ msg = "PortMidi: Host error";
+ break;
+ case pmInvalidDeviceId:
+ msg = "PortMidi: Invalid device ID";
+ break;
+ case pmInsufficientMemory:
+ msg = "PortMidi: Insufficient memory";
+ break;
+ case pmBufferTooSmall:
+ msg = "PortMidi: Buffer too small";
+ break;
+ case pmBadPtr:
+ msg = "PortMidi: Bad pointer";
+ break;
+ case pmInternalError:
+ msg = "PortMidi: Internal PortMidi Error";
+ break;
+ case pmBufferOverflow:
+ msg = "PortMidi: Buffer overflow";
+ break;
+ case pmBadData:
+ msg = "PortMidi: Invalid MIDI message Data";
+ break;
+ case pmBufferMaxSize:
+ 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;
+ case pmNameConflict:
+ msg = "PortMidi: Cannot create virtual device: name is taken";
+ break;
+ case pmDeviceRemoved:
+ msg = "PortMidi: Output attempted after (USB) device removed";
+ break;
+ default:
+ msg = "PortMidi: Illegal error number";
+ break;
+ }
+ return msg;
+}
+
+
+/* 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)
+{
+ assert(msg);
+ assert(len > 0);
+ if (pm_hosterror) {
+#if defined(WIN32) && !defined(_WIN32)
+#pragma warning(suppress: 4996) // don't use suggested strncpy_s
+#endif
+ strncpy(msg, (char *) pm_hosterror_text, len);
+ pm_hosterror = FALSE;
+ pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it
+ might help with debugging */
+ msg[len - 1] = 0; /* make sure string is terminated */
+ } else {
+ msg[0] = 0; /* no string to return */
+ }
+}
+
+
+PMEXPORT int Pm_HasHostError(PortMidiStream * stream)
+{
+ if (pm_hosterror) return TRUE;
+ if (stream) {
+ PmInternal * midi = (PmInternal *) stream;
+ return (*midi->dictionary->check_host_error)(midi);
+ }
+ return FALSE;
+}
+
+
+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();
+ pm_initialized = TRUE;
+ }
+ return pmNoError;
+}
+
+
+PMEXPORT PmError Pm_Terminate(void)
+{
+ if (pm_initialized) {
+ pm_term();
+ /* 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_len = 0;
+ pm_descriptor_max = 0;
+ pm_interf_list_len = 0;
+ pm_initialized = FALSE;
+ }
+ return pmNoError;
+}
+
+
+/* Pm_Read -- read up to length messages from source into buffer */
+/*
+ * returns number of messages actually read, or error code
+ */
+PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ int n = 0;
+ PmError err = pmNoError;
+ pm_hosterror = FALSE;
+ /* arg checking */
+ if(midi == NULL)
+ err = pmBadPtr;
+ else if(!pm_descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ 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
+ * with data from the MIDI hardware; this depends on the implementation.
+ * We could call Pm_Poll here, but that would redo a lot of redundant
+ * parameter checking, so I copied some code from Pm_Poll to here: */
+ else err = (*(midi->dictionary->poll))(midi);
+
+ if (err != pmNoError) {
+ if (err == pmHostError) {
+ midi->dictionary->check_host_error(midi);
+ }
+ return pm_errmsg(err);
+ }
+
+ while (n < length) {
+ err = Pm_Dequeue(midi->queue, buffer++);
+ if (err == pmBufferOverflow) {
+ /* ignore the data we have retreived so far */
+ return pm_errmsg(pmBufferOverflow);
+ } else if (err == 0) { /* empty queue */
+ break;
+ }
+ n++;
+ }
+ return n;
+}
+
+PMEXPORT PmError Pm_Poll(PortMidiStream *stream)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err;
+
+ pm_hosterror = FALSE;
+ /* arg checking */
+ if(midi == NULL)
+ err = pmBadPtr;
+ else if (!pm_descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else if (!pm_descriptors[midi->device_id].pub.input)
+ err = pmBadPtr;
+ else
+ err = (*(midi->dictionary->poll))(midi);
+
+ if (err != pmNoError) {
+ return pm_errmsg(err);
+ }
+
+ return (PmError) !Pm_QueueEmpty(midi->queue);
+}
+
+
+/* this is called from Pm_Write and Pm_WriteSysEx to issue a
+ * call to the system-dependent end_sysex function and handle
+ * the error return
+ */
+static PmError pm_end_sysex(PmInternal *midi)
+{
+ PmError err = (*midi->dictionary->end_sysex)(midi, 0);
+ midi->sysex_in_progress = FALSE;
+ return err;
+}
+
+
+/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and
+ 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)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err = pmNoError;
+ int i;
+ int bits;
+
+ pm_hosterror = FALSE;
+ /* arg checking */
+ if (midi == NULL) {
+ err = pmBadPtr;
+ } else {
+ descriptor_type desc = &pm_descriptors[midi->device_id];
+ if (!desc || !desc->pub.opened ||
+ !desc->pub.output || !desc->pm_internal) {
+ err = pmBadPtr;
+ } else if (desc->pm_internal->is_removed) {
+ err = pmDeviceRemoved;
+ }
+ }
+ if (err != pmNoError) goto pm_write_error;
+
+ if (midi->latency == 0) {
+ midi->now = 0;
+ } else {
+ midi->now = (*(midi->time_proc))(midi->time_info);
+ if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) {
+ /* time to resync */
+ midi->now = (*midi->dictionary->synchronize)(midi);
+ midi->first_message = FALSE;
+ }
+ }
+ /* error recovery: when a sysex is detected, we call
+ * dictionary->begin_sysex() followed by calls to
+ * dictionary->write_byte() and dictionary->write_realtime()
+ * until an end-of-sysex is detected, when we call
+ * dictionary->end_sysex(). After an error occurs,
+ * Pm_Write() continues to call functions. For example,
+ * it will continue to call write_byte() even after
+ * an error sending a sysex message, and end_sysex() will be
+ * called when an EOX or non-real-time status is found.
+ * When errors are detected, Pm_Write() returns immediately,
+ * so it is possible that this will drop data and leave
+ * sysex messages in a partially transmitted state.
+ */
+ for (i = 0; i < length; i++) {
+ uint32_t msg = buffer[i].message;
+ bits = 0;
+ /* is this a sysex message? */
+ if (Pm_MessageStatus(msg) == MIDI_SYSEX) {
+ if (midi->sysex_in_progress) {
+ /* error: previous sysex was not terminated by EOX */
+ midi->sysex_in_progress = FALSE;
+ err = pmBadData;
+ goto pm_write_error;
+ }
+ midi->sysex_in_progress = TRUE;
+ if ((err = (*midi->dictionary->begin_sysex)(midi,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ bits = 8;
+ /* fall through to continue sysex processing */
+ } else if ((msg & MIDI_STATUS_MASK) &&
+ (Pm_MessageStatus(msg) != MIDI_EOX)) {
+ /* a non-sysex message */
+ if (midi->sysex_in_progress) {
+ /* this should be a realtime message */
+ if (is_real_time(msg)) {
+ if ((err = (*midi->dictionary->write_realtime)(midi,
+ &(buffer[i]))) != pmNoError)
+ goto pm_write_error;
+ } else {
+ midi->sysex_in_progress = FALSE;
+ err = pmBadData;
+ /* ignore any error from this, because we already have one */
+ /* pass 0 as timestamp -- it's ignored */
+ (*midi->dictionary->end_sysex)(midi, 0);
+ goto pm_write_error;
+ }
+ } else { /* regular short midi message */
+ if ((err = (*midi->dictionary->write_short)(midi,
+ &(buffer[i]))) != pmNoError)
+ goto pm_write_error;
+ continue;
+ }
+ }
+ if (midi->sysex_in_progress) { /* send sysex bytes until EOX */
+ /* see if we can accelerate data transfer */
+ if (bits == 0 && midi->fill_base && /* 4 bytes to copy */
+ (*midi->fill_offset_ptr) + 4 <= midi->fill_length &&
+ (msg & 0x80808080) == 0) { /* all data */
+ /* copy 4 bytes from msg to fill_base + fill_offset */
+ unsigned char *ptr = midi->fill_base +
+ *(midi->fill_offset_ptr);
+ ptr[0] = msg; ptr[1] = msg >> 8;
+ ptr[2] = msg >> 16; ptr[3] = msg >> 24;
+ (*midi->fill_offset_ptr) += 4;
+ continue;
+ }
+ /* no acceleration, so do byte-by-byte copying */
+ while (bits < 32) {
+ unsigned char midi_byte = (unsigned char) (msg >> bits);
+ if ((err = (*midi->dictionary->write_byte)(midi, midi_byte,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ if (midi_byte == MIDI_EOX) {
+ err = pm_end_sysex(midi);
+ if (err != pmNoError) goto error_exit;
+ break; /* from while loop */
+ }
+ bits += 8;
+ }
+ } else {
+ /* not in sysex mode, but message did not start with status */
+ err = pmBadData;
+ goto pm_write_error;
+ }
+ }
+ /* after all messages are processed, send the data */
+ if (!midi->sysex_in_progress)
+ err = (*midi->dictionary->write_flush)(midi, 0);
+pm_write_error:
+ if (err == pmHostError) {
+ midi->dictionary->check_host_error(midi);
+ }
+error_exit:
+ return pm_errmsg(err);
+}
+
+
+PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when,
+ PmMessage msg)
+{
+ PmEvent event;
+
+ event.timestamp = when;
+ event.message = msg;
+ return Pm_Write(stream, &event, 1);
+}
+
+
+PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when,
+ unsigned char *msg)
+{
+ /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */
+ /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */
+ #define BUFLEN ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage)))
+ PmEvent buffer[BUFLEN];
+ int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err = pmNoError;
+ /* the next byte in the buffer is represented by an index, bufx, and
+ a shift in bits */
+ int shift = 0;
+ int bufx = 0;
+ buffer[0].message = 0;
+ buffer[0].timestamp = when;
+
+ while (1) {
+ /* insert next byte into buffer */
+ buffer[bufx].message |= ((*msg) << shift);
+ shift += 8;
+ if (*msg++ == MIDI_EOX) break;
+ if (shift == 32) {
+ shift = 0;
+ bufx++;
+ if (bufx == buffer_size) {
+ err = Pm_Write(stream, buffer, buffer_size);
+ /* note: Pm_Write has already called errmsg() */
+ if (err) return err;
+ /* prepare to fill another buffer */
+ bufx = 0;
+ buffer_size = BUFLEN;
+ /* optimization: maybe we can just copy bytes */
+ if (midi->fill_base) {
+ while (*(midi->fill_offset_ptr) < midi->fill_length) {
+ midi->fill_base[(*midi->fill_offset_ptr)++] = *msg;
+ if (*msg++ == MIDI_EOX) {
+ err = pm_end_sysex(midi);
+ if (err != pmNoError) return pm_errmsg(err);
+ goto end_of_sysex;
+ }
+ }
+ /* I thought that I could do a pm_Write here and
+ * change this if to a loop, avoiding calls in Pm_Write
+ * to the slower write_byte, but since
+ * sysex_in_progress is true, this will not flush
+ * the buffer, and we'll infinite loop: */
+ /* err = Pm_Write(stream, buffer, 0);
+ if (err) return err; */
+ /* instead, the way this works is that Pm_Write calls
+ * write_byte on 4 bytes. The first, since the buffer
+ * is full, will flush the buffer and allocate a new
+ * one. This primes the buffer so
+ * that we can return to the loop above and fill it
+ * efficiently without a lot of function calls.
+ */
+ buffer_size = 1; /* get another message started */
+ }
+ }
+ buffer[bufx].message = 0;
+ buffer[bufx].timestamp = when;
+ }
+ /* keep inserting bytes until you find MIDI_EOX */
+ }
+end_of_sysex:
+ /* we're finished sending full buffers, but there may
+ * be a partial one left.
+ */
+ if (shift != 0) bufx++; /* add partial message to buffer len */
+ if (bufx) { /* bufx is number of PmEvents to send from buffer */
+ err = Pm_Write(stream, buffer, bufx);
+ if (err) return err;
+ }
+ return pmNoError;
+}
+
+
+
+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;
+ 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) {
+ return pmInsufficientMemory;
+ }
+ midi->device_id = device_id;
+ midi->is_input = is_input;
+ midi->is_removed = FALSE;
+ 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;
+ 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 = buffer_size; /* portMidi input storage */
+ midi->sysex_in_progress = FALSE;
+ midi->message = 0;
+ midi->message_count = 0;
+ midi->filters = (is_input ? PM_FILT_ACTIVE : 0);
+ midi->channel_mask = 0xFFFF;
+ midi->sync_time = 0;
+ midi->first_message = TRUE;
+ midi->api_info = NULL;
+ midi->fill_base = NULL;
+ midi->fill_offset_ptr = NULL;
+ midi->fill_length = 0;
+ 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;
+ 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;
+ pm_descriptors[inputDevice].pm_internal = NULL;
+ /* free portMidi data */
+ Pm_QueueDestroy(midi->queue);
+ pm_free(midi);
+ } else {
+ /* portMidi input open successful */
+ pm_descriptors[inputDevice].pub.opened = TRUE;
+ }
+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_OpenOutput(PortMidiStream** stream,
+ PmDeviceID outputDevice,
+ void *outputDriverInfo,
+ int32_t bufferSize,
+ PmTimeProcPtr time_proc,
+ void *time_info,
+ int32_t latency)
+{
+ PmInternal *midi;
+ PmError err = pmNoError;
+ pm_hosterror = FALSE;
+ *stream = NULL;
+
+ /* arg checking */
+ if (outputDevice < 0 || outputDevice >= pm_descriptor_len)
+ err = pmInvalidDeviceId;
+ else if (!pm_descriptors[outputDevice].pub.output)
+ err = pmInvalidDeviceId;
+ else if (pm_descriptors[outputDevice].pub.opened)
+ err = pmInvalidDeviceId;
+ if (err != pmNoError)
+ goto error_return;
+
+ /* 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;
+ }
+
+ /* open system dependent output device */
+ err = (*midi->dictionary->open)(midi, outputDriverInfo);
+ if (err) {
+ *stream = NULL;
+ pm_descriptors[outputDevice].pm_internal = NULL;
+ /* free portMidi data */
+ pm_free(midi);
+ } else {
+ /* portMidi input open successful */
+ pm_descriptors[outputDevice].pub.opened = TRUE;
+ }
+error_return:
+ /* note: system-dependent code must set pm_hosterror and
+ * pm_hosterror_text if a pmHostError occurs
+ */
+ return pm_errmsg(err);
+}
+
+
+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;
+ PmError err = pmNoError;
+
+ if (midi == NULL)
+ err = pmBadPtr;
+ else
+ midi->channel_mask = mask;
+
+ return pm_errmsg(err);
+}
+
+
+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 (!pm_descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else
+ midi->filters = filters;
+ return pm_errmsg(err);
+}
+
+
+PMEXPORT PmError Pm_Close(PortMidiStream *stream)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err = pmNoError;
+
+ pm_hosterror = FALSE;
+ /* arg checking */
+ 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_len)
+ err = pmBadPtr;
+ /* and the device should be in the opened state */
+ else if (!pm_descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+
+ if (err != pmNoError)
+ goto error_return;
+
+ /* close the device */
+ err = (*midi->dictionary->close)(midi);
+ /* even if an error occurred, continue with cleanup */
+ 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:
+ /* system dependent code must set pm_hosterror and
+ * pm_hosterror_text if a pmHostError occurs.
+ */
+ return pm_errmsg(err);
+}
+
+PmError Pm_Synchronize(PortMidiStream* stream)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err = pmNoError;
+ if (midi == NULL)
+ err = pmBadPtr;
+ else if (!pm_descriptors[midi->device_id].pub.output)
+ err = pmBadPtr;
+ else if (!pm_descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else
+ midi->first_message = TRUE;
+ return err;
+}
+
+PMEXPORT PmError Pm_Abort(PortMidiStream* stream)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err;
+ /* arg checking */
+ if (midi == NULL)
+ err = pmBadPtr;
+ else if (!pm_descriptors[midi->device_id].pub.output)
+ err = pmBadPtr;
+ else if (!pm_descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else
+ err = (*midi->dictionary->abort)(midi);
+
+ if (err == pmHostError) {
+ 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 */
+#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.
+ */
+
+
+/* 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)))
+
+/*
+ return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE))
+ || ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK))
+ || ((status == MIDI_START) && (filters & PM_FILT_PLAY))
+ || ((status == MIDI_STOP) && (filters & PM_FILT_PLAY))
+ || ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY))
+ || ((status == MIDI_F9) && (filters & PM_FILT_F9))
+ || ((status == MIDI_FD) && (filters & PM_FILT_FD))
+ || ((status == MIDI_RESET) && (filters & PM_FILT_RESET))
+ || ((status == MIDI_MTC) && (filters & PM_FILT_MTC))
+ || ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION))
+ || ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT))
+ || ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE));
+}*/
+
+
+/* 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))
+
+
+/*
+ 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_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH))
+ || ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM))
+ || ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL))
+ || ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND));
+
+}
+*/
+
+static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+ PmEvent event;
+
+ /* there may be nothing in the buffer */
+ if (midi->message_count == 0) return; /* nothing to flush */
+
+ event.message = midi->message;
+ event.timestamp = timestamp;
+ /* copied from pm_read_short, avoids filtering */
+ if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
+ midi->sysex_in_progress = FALSE;
+ }
+ midi->message_count = 0;
+ midi->message = 0;
+}
+
+
+/* pm_read_short and pm_read_bytes
+ are the interface between system-dependent MIDI input handlers
+ and the system-independent PortMIDI code.
+ The input handler MUST obey these rules:
+ 1) all short input messages must be sent to pm_read_short, which
+ enqueues them to a FIFO for the application.
+ 2) each buffer of sysex bytes should be reported by calling pm_read_bytes
+ (which sets midi->sysex_in_progress). After the eox byte,
+ pm_read_bytes will clear sysex_in_progress
+ */
+
+/* pm_read_short is the place where all input messages arrive from
+ system-dependent code such as pmwinmm.c. Here, the messages
+ are entered into the PortMidi input buffer.
+ */
+void pm_read_short(PmInternal *midi, PmEvent *event)
+{
+ int status;
+ /* arg checking */
+ assert(midi != NULL);
+ /* midi filtering is applied here */
+ status = Pm_MessageStatus(event->message);
+ if (!pm_status_filtered(status, midi->filters)
+ && (!is_real_time(status) ||
+ !pm_realtime_filtered(status, midi->filters))
+ && !pm_channel_filtered(status, midi->channel_mask)) {
+ /* if sysex is in progress and we get a status byte, it had
+ better be a realtime message or the starting SYSEX byte;
+ otherwise, we exit the sysex_in_progress state
+ */
+ if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) {
+ /* two choices: real-time or not. If it's real-time, then
+ * this should be delivered as a sysex byte because it is
+ * embedded in a sysex message
+ */
+ if (is_real_time(status)) {
+ midi->message |= (status << (8 * midi->message_count++));
+ if (midi->message_count == 4) {
+ pm_flush_sysex(midi, event->timestamp);
+ }
+ } else { /* otherwise, it's not real-time. This interrupts
+ * a sysex message in progress */
+ midi->sysex_in_progress = FALSE;
+ }
+ } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) {
+ midi->sysex_in_progress = FALSE;
+ }
+ }
+}
+
+
+/* pm_read_bytes -- a sequence of bytes has been read from a device.
+ * parse the bytes into PmEvents and put them in the queue.
+ * midi - the midi device
+ * data - the bytes
+ * len - the number of bytes
+ * timestamp - when were the bytes received?
+ *
+ * returns how many bytes processed, which is always the len parameter
+ */
+unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data,
+ int len, PmTimestamp timestamp)
+{
+ int i = 0; /* index into data, must not be unsigned (!) */
+ PmEvent event;
+ event.timestamp = timestamp;
+ assert(midi);
+
+ /* Since sysex messages may have embedded real-time messages, we
+ * cannot simply send every consecutive group of 4 bytes as sysex
+ * data. Instead, we insert each data byte into midi->message and
+ * keep count using midi->message_count. If we encounter a
+ * real-time message, it is sent immediately as a 1-byte PmEvent,
+ * while sysex bytes are sent as PmEvents in groups of 4 bytes
+ * until the sysex is either terminated by EOX (F7) or a
+ * non-real-time message is encountered, indicating that the EOX
+ * was dropped.
+ */
+
+ /* This is a finite state machine so that we can accept any number
+ * of bytes, even if they contain partial messages.
+ *
+ * midi->sysex_in_progress says we are expecting sysex message bytes
+ * (otherwise, expect a short message or real-time message)
+ * midi->message accumulates bytes to enqueue for application
+ * midi->message_count is the number of bytes accumulated
+ * midi->short_message_count is how many bytes we need in midi->message,
+ * therefore midi->message_count, before we have a complete message
+ * midi->running_status is running status or 0 if there is none
+ *
+ * Set running status when: A status byte < F0 is received.
+ * Clear running status when: A status byte from F0 through F7 is
+ * received.
+ * Ignore (drop) data bytes when running status is 0.
+ *
+ * Our output buffer (the application input buffer) can overflow
+ * at any time. If that occurs, we have to clear sysex_in_progress
+ * (otherwise, the buffer could be flushed and we could resume
+ * inserting sysex bytes into the buffer, resulting in a continuation
+ * of a sysex message even though a buffer full of bytes was dropped.)
+ *
+ * Since we have to parse everything and form <=4-byte PmMessages,
+ * we send all messages via pm_read_short, which filters messages
+ * according to midi->filters and clears sysex_in_progress on
+ * buffer overflow. This also provides a "short cut" for short
+ * messages that are already parsed, allowing API-specific code
+ * to bypass this more expensive state machine. What if we are
+ * getting a sysex message, but it is interrupted by a short
+ * message (status 80-EF) and a direct call to pm_read_short?
+ * Without some care, the state machine would still be in
+ * sysex_in_progress mode, and subsequent data bytes would be
+ * accumulated as more sysex data, which is wrong since you
+ * cannot have a short message in the middle of a sysex message.
+ * To avoid this problem, pm_read_short clears sysex_in_progress
+ * when a non-real-time short message arrives.
+ */
+
+ while (i < len) {
+ unsigned char byte = data[i++];
+ if (is_real_time(byte)) {
+ event.message = byte;
+ pm_read_short(midi, &event);
+ } else if (byte & MIDI_STATUS_MASK && byte != MIDI_EOX) {
+ midi->message = byte;
+ midi->message_count = 1;
+ if (byte == MIDI_SYSEX) {
+ midi->sysex_in_progress = TRUE;
+ } else {
+ midi->sysex_in_progress = FALSE;
+ midi->short_message_count = pm_midi_length(midi->message);
+ /* maybe we're done already with a 1-byte message: */
+ if (midi->short_message_count == 1) {
+ pm_read_short(midi, &event);
+ midi->message_count = 0;
+ }
+ }
+ } else if (midi->sysex_in_progress) { /* sysex data byte */
+ /* accumulate sysex message data or EOX */
+ midi->message |= (byte << (8 * midi->message_count++));
+ if (midi->message_count == 4 || byte == MIDI_EOX) {
+ event.message = midi->message;
+ /* enqueue if not filtered, and then if there is overflow,
+ stop sysex_in_progress */
+ if (!(midi->filters & PM_FILT_SYSEX) &&
+ Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
+ midi->sysex_in_progress = FALSE;
+ } else if (byte == MIDI_EOX) { /* continue unless EOX */
+ midi->sysex_in_progress = FALSE;
+ }
+ midi->message_count = 0;
+ midi->message = 0;
+ }
+ } else { /* no sysex in progress, must be short message */
+ if (midi->message_count == 0) { /* need a running status */
+ if (midi->running_status) {
+ midi->message = midi->running_status;
+ midi->message_count = 1;
+ } else { /* drop data byte - not sysex and no status byte */
+ continue;
+ }
+ }
+ midi->message |= (byte << (8 * midi->message_count++));
+ if (midi->message_count == midi->short_message_count) {
+ event.message = midi->message;
+ pm_read_short(midi, &event);
+ }
+ }
+ }
+ return i;
+}
diff --git a/portmidi/pm_common/portmidi.h b/portmidi/pm_common/portmidi.h
new file mode 100755
index 0000000..8696a73
--- /dev/null
+++ b/portmidi/pm_common/portmidi.h
@@ -0,0 +1,974 @@
+#ifndef PORTMIDI_PORTMIDI_H
+#define PORTMIDI_PORTMIDI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * PortMidi Portable Real-Time MIDI Library
+ * PortMidi API Header File
+ * Latest version available at: http://sourceforge.net/projects/portmedia
+ *
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
+ * Copyright (c) 2001-2006 Roger B. Dannenberg
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortMidi license; however,
+ * the PortMusic community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also
+ * requested that these non-binding requests be included along with the
+ * license above.
+ */
+
+/* CHANGELOG FOR PORTMIDI
+ * (see ../CHANGELOG.txt)
+ *
+ * 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_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. 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
+ @{
+*/
+
+#include <stdint.h>
+
+#ifdef _WINDLL
+#define PMEXPORT __declspec(dllexport)
+#else
+#define PMEXPORT
+#endif
+
+#ifndef FALSE
+ #define FALSE 0
+#endif
+#ifndef TRUE
+ #define TRUE 1
+#endif
+
+/* default size of buffers for sysex transmission: */
+#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024
+
+
+typedef enum {
+ 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
+ * output device when input is requested or
+ * input device when output is requested or
+ * device is already opened.
+ */
+ pmInsufficientMemory,
+ pmBufferTooSmall,
+ pmBufferOverflow,
+ 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. */
+ pmInternalError,
+ 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. */
+ pmDeviceRemoved /**< Output attempted after (USB) device was removed. */
+ /* 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 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);
+
+/**
+ Pm_Terminate() is the library termination function - call this after
+ using the library.
+*/
+PMEXPORT PmError Pm_Terminate(void);
+
+/** 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 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);
+
+
+/** Translate portmidi error number into human readable message.
+ These strings are constants (set at compile time) so client has
+ no need to allocate storage.
+*/
+PMEXPORT const char *Pm_GetErrorText(PmError errnum);
+
+/** 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);
+
+/** 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
+
+/** 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;
+
+/** MIDI system-dependent device or driver info is passed in this
+ structure, which is owned by the caller.
+*/
+#define PM_SYSDEPINFO_VERS 210
+
+enum PmSysDepPropertyKey {
+ pmKeyNone = 0, /**< a "noop" key value */
+ /** CoreMIDI Manufacturer name, value is string */
+ pmKeyCoreMidiManufacturer = 1,
+ /** Linux ALSA snd_seq_port_info_set_name, value is a string. Can be
+ passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput when opening
+ a device. The created port will be named accordingly and will be
+ visible for externally made connections (subscriptions). (Linux ALSA
+ ports are always enabled for this, but only get application-specific
+ names if you give it one.) This key/value is ignored when opening
+ virtual ports, which are named when they are created.) */
+ pmKeyAlsaPortName = 2,
+ /** Linux ALSA snd_seq_set_client_name, value is a string.
+ Can be passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput.
+ Pm_CreateVirtualInput or Pm_CreateVirtualOutput. Will override
+ any previously set client name and applies to all ports. */
+ pmKeyAlsaClientName = 3
+ /* if system-dependent code introduces more options, register
+ the key here to avoid conflicts. */
+};
+
+/** System-dependent information can be passed when creating and opening
+ ports using this data structure, which stores alternating keys and
+ values (addresses). See `pm_test/sendvirtual.c`, `pm_test/recvvirtual.c`,
+ and `pm_test/testio.c` for examples.
+ */
+typedef struct {
+ int structVersion; /**< @brief this structure version */
+ int length; /**< @brief number of properties in this structure */
+ struct {
+ enum PmSysDepPropertyKey key;
+ const void *value;
+ } properties[];
+} PmSysDepInfo;
+
+
+/** 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 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", so if there *is* a default, implementors should use
+ pm_add_device to add system default input and output devices
+ first.)
+
+ The recommended 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.
+
+ On linux, you can create virtual ports and use an external program
+ to set up inter-application and device connections.
+
+ 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.
+*/
+PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void);
+
+/** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */
+PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID(void);
+
+/** Find a device that matches a pattern.
+
+ @param pattern a substring of the device name, or if the pattern
+ contains the two-character separator ", ", then the first part of
+ the pattern represents a device interface substring and the second
+ part after the separator represents a device name substring.
+
+ @param is_input restricts the search to an input when true, or an
+ output when false.
+
+ @return the number of the first device whose device interface
+ contains the interface pattern (if any), whose device name
+ contains the name pattern, and whose direction (input or output)
+ matches the #is_input parameter. If no match is found, #pmNoDevice
+ (-1) is returned.
+*/
+PMEXPORT PmDeviceID Pm_FindDevice(char *pattern, int is_input);
+
+
+/** 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
+ @{
+*/
+/** Get a PmDeviceInfo structure describing a MIDI device.
+
+ @param id the device to be queried.
+
+ If \p id is out of range or if the device designates a deleted
+ virtual device, the function returns NULL.
+
+ 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);
+
+/** Open a MIDI device for input.
+
+ @param stream the address of a #PortMidiStream pointer which will
+ receive a pointer to the newly opened stream.
+
+ @param inputDevice the ID of the device to be opened (see #PmDeviceID).
+
+ @param inputSysDepInfo a pointer to an optional system-dependent
+ data structure (a #PmSysDepInfo struct) containing additional
+ information for device setup or handle processing. This parameter
+ is never required for correct operation. If not used, specify
+ NULL. Declared `void *` here for backward compatibility. Note that
+ with Linux ALSA, you can use this parameter to specify a client name
+ and port name.
+
+ @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.
+
+ @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.
+ \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.
+
+ @param time_info is a pointer passed to time_proc.
+
+ @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,
+ PmDeviceID inputDevice,
+ void *inputSysDepInfo,
+ int32_t bufferSize,
+ PmTimeProcPtr time_proc,
+ 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 inputSysDepInfo a pointer to an optional system-specific
+ data structure (a #PmSysDepInfo struct) containing additional
+ information for device setup or handle processing. This parameter
+ is never required for correct operation. If not used, specify
+ NULL. Declared `void *` here for backward compatibility. Note that
+ with Linux ALSA, you can use this parameter to specify a client name
+ and port name.
+
+ @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.
+
+ Any stream that is successfully opened should eventually be closed
+ by calling Pm_Close().
+*/
+PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream,
+ PmDeviceID outputDevice,
+ void *outputSysDepInfo,
+ int32_t bufferSize,
+ PmTimeProcPtr time_proc,
+ void *time_info,
+ 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 sysDepInfo contains interface-dependent additional
+ information (a #PmSysDepInfo struct), e.g., hints or options. This
+ parameter is never required for correct operation. If not used,
+ specify NULL. Declared `void *` here for backward compatibility.
+
+ @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 *sysDepInfo);
+
+/** 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 sysDepInfo contains interface-dependent additional
+ information (a #PmSysDepInfo struct), e.g., hints or options. This
+ parameter is never required for correct operation. If not used,
+ specify NULL. Declared `void *` here for backward compatibility.
+
+ @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 *sysDepInfo);
+
+/** 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
+ @{
+*/
+
+/* Filter bit-mask definitions */
+/** filter active sensing messages (0xFE): */
+#define PM_FILT_ACTIVE (1 << 0x0E)
+/** filter system exclusive messages (0xF0): */
+#define PM_FILT_SYSEX (1 << 0x00)
+/** filter MIDI clock message (0xF8) */
+#define PM_FILT_CLOCK (1 << 0x08)
+/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
+#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
+/** filter tick messages (0xF9) */
+#define PM_FILT_TICK (1 << 0x09)
+/** filter undefined FD messages */
+#define PM_FILT_FD (1 << 0x0D)
+/** filter undefined real-time messages */
+#define PM_FILT_UNDEFINED PM_FILT_FD
+/** filter reset messages (0xFF) */
+#define PM_FILT_RESET (1 << 0x0F)
+/** filter all real-time messages */
+#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \
+ PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)
+/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
+#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
+/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
+#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)
+/** 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)
+/** Program changes (0xC0-0xCF) */
+#define PM_FILT_PROGRAM (1 << 0x1C)
+/** Control Changes (CC's) (0xB0-0xBF)*/
+#define PM_FILT_CONTROL (1 << 0x1B)
+/** Pitch Bender (0xE0-0xEF*/
+#define PM_FILT_PITCHBEND (1 << 0x1E)
+/** MIDI Time Code (0xF1)*/
+#define PM_FILT_MTC (1 << 0x01)
+/** Song Position (0xF2) */
+#define PM_FILT_SONG_POSITION (1 << 0x02)
+/** Song Select (0xF3)*/
+#define PM_FILT_SONG_SELECT (1 << 0x03)
+/** 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)
+
+
+/* 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).
+
+ @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))
+
+/** 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))
+
+ Note that channels are numbered 0 to 15 (not 1 to 16). Most
+ synthesizer and interfaces number channels starting at 1, but
+ PortMidi numbers channels starting at 0.
+
+ All channels are allowed by default
+*/
+PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);
+
+/** 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);
+
+/** Close a midi stream, flush any pending buffers if possible.
+
+ @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.
+*/
+PMEXPORT PmError Pm_Synchronize(PortMidiStream* stream);
+
+
+/** Encode a short Midi message into a 32-bit word. If data1
+ and/or data2 are not present, use zero.
+*/
+#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 uint32_t PmMessage; /**< @brief see #PmEvent */
+/**
+ 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
+ messages used mainly for timing (see the MIDI spec). PortMidi retains
+ the order of non-real-time MIDI messages on both input and output, but
+ it does not specify exactly how real-time messages are processed. This
+ is particulary problematic for MIDI input, because the input parser
+ must either prepare to buffer an unlimited number of sysex message
+ bytes or to buffer an unlimited number of real-time messages that
+ arrive embedded in a long sysex message. To simplify things, the input
+ parser is allowed to pass real-time MIDI messages embedded within a
+ sysex message, and it is up to the client to detect, process, and
+ remove these messages as they arrive.
+
+ When receiving sysex messages, the sysex message is terminated
+ by either an EOX status byte (anywhere in the 4 byte messages) or
+ by a non-real-time status byte in the low order byte of the message.
+ If you get a non-real-time status byte but there was no EOX byte, it
+ means the sysex message was somehow truncated. This is not
+ considered an error; e.g., a missing EOX can result from the user
+ disconnecting a MIDI cable during sysex transmission.
+
+ A real-time message can occur within a sysex message. A real-time
+ message will always occupy a full PmEvent with the status byte in
+ the low-order byte of the PmEvent message field. (This implies that
+ the byte-order of sysex bytes and real-time message bytes may not
+ be preserved -- for example, if a real-time message arrives after
+ 3 bytes of a sysex message, the real-time message will be delivered
+ first. The first word of the sysex message will be delivered only
+ after the 4th byte arrives, filling the 4-byte PmEvent message field.
+
+ The timestamp field is observed when the output port is opened with
+ a non-zero latency. A timestamp of zero means "use the current time",
+ which in turn means to deliver the message with a delay of
+ latency (the latency parameter used when opening the output port.)
+ Do not expect PortMidi to sort data according to timestamps --
+ 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
+ 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.
+
+ On input, the timestamp ideally denotes the arrival time of the
+ status byte of the message. The first timestamp on sysex message
+ data will be valid. Subsequent timestamps may denote
+ when message bytes were actually received, or they may be simply
+ copies of the first timestamp.
+
+ Timestamps for nested messages: If a real-time message arrives in
+ the middle of some other message, it is enqueued immediately with
+ the timestamp corresponding to its arrival time. The interrupted
+ non-real-time message or 4-byte packet of sysex data will be enqueued
+ later. The timestamp of interrupted data will be equal to that of
+ the interrupting real-time message to insure that timestamps are
+ non-decreasing.
+ */
+typedef struct {
+ PmMessage message;
+ PmTimestamp timestamp;
+} PmEvent;
+
+/** @} */
+
+/** \defgroup grp_io Reading and Writing Midi Messages
+ @{
+*/
+/** Retrieve midi data into a buffer.
+
+ @param stream the open input stream.
+
+ @return the number of events read, or, if the result is negative,
+ a #PmError value will be returned.
+
+ The Buffer Overflow Problem
+
+ 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);
+
+/** 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);
+
+/** 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
+ - sysex messages that are converted into a sequence of PmEvent
+ structures, e.g. sending data from a file or forwarding them
+ 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);
+
+/** 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,
+ PmMessage msg);
+
+/** 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);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PORTMIDI_PORTMIDI_H */
diff --git a/portmidi/pm_haiku/pmhaiku.cpp b/portmidi/pm_haiku/pmhaiku.cpp
new file mode 100644
index 0000000..0c592f1
--- /dev/null
+++ b/portmidi/pm_haiku/pmhaiku.cpp
@@ -0,0 +1,473 @@
+/* pmhaiku.cpp -- PortMidi os-dependent code */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <vector>
+#include <MidiConsumer.h>
+#include <MidiEndpoint.h>
+#include <MidiProducer.h>
+#include <MidiRoster.h>
+#include <MidiSynth.h>
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+
+namespace {
+ struct PmInputConsumer : BMidiLocalConsumer {
+ PmInputConsumer(PmInternal *midi) :
+ BMidiLocalConsumer("PortMidi input consumer"),
+ midi(midi)
+ {
+ }
+
+
+ void Data(uchar *data, size_t length, bool atomic, bigtime_t time)
+ {
+ if (!atomic)
+ return; // should these be also supported?
+
+ if (data[0] == B_SYS_EX_START) {
+ pm_read_bytes(midi, data, length, time / 1000);
+ } else {
+ PmEvent event;
+ switch (length) {
+ case 1:
+ event.message = Pm_Message(data[0], 0, 0);
+ break;
+ case 2:
+ event.message = Pm_Message(data[0], data[1], 0);
+ break;
+ case 3:
+ event.message = Pm_Message(data[0], data[1], data[2]);
+ break;
+ default:
+ printf("Unexpected message length for short message, got %" B_PRIuSIZE "\n", length);
+ break;
+ }
+ event.timestamp = time / 1000;
+ pm_read_short(midi, &event);
+ }
+ }
+
+ private:
+ PmInternal *midi;
+ };
+
+ struct PmOutputInfo {
+ BMidiLocalProducer *producer;
+ std::vector<unsigned char> sysexBuffer;
+ };
+
+ struct PmSynthOutputInfo {
+ BMidiSynth midiSynth;
+ std::vector<unsigned char> sysexBuffer;
+ };
+
+
+ PmTimestamp synchronize(PmInternal *midi)
+ {
+ return 0;
+ }
+
+
+ PmError in_open(PmInternal *midi, void *driverInfo)
+ {
+ int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
+ BMidiProducer *producer = BMidiRoster::FindProducer(endpointID);
+ if (!producer)
+ return pmInvalidDeviceId;
+ PmInputConsumer *consumer = new PmInputConsumer(midi);
+ status_t status = producer->Connect(consumer);
+ if (status != B_OK) {
+ consumer->Release();
+ producer->Release();
+ strcpy(pm_hosterror_text, strerror(status));
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ midi->api_info = consumer;
+ producer->Release();
+ return pmNoError;
+ }
+
+
+ PmError in_abort(PmInternal *midi)
+ {
+ return pmNoError;
+ }
+
+
+ PmError in_close(PmInternal *midi)
+ {
+ int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
+ BMidiProducer *producer = BMidiRoster::FindProducer(endpointID);
+ if (!producer)
+ return pmInvalidDeviceId;
+ PmInputConsumer *consumer = (PmInputConsumer*)midi->api_info;
+ status_t status = producer->Disconnect(consumer);
+ if (status != B_OK) {
+ consumer->Release();
+ producer->Release();
+ strcpy(pm_hosterror_text, strerror(status));
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ consumer->Release();
+ midi->api_info = NULL;
+ producer->Release();
+ return pmNoError;
+ }
+
+
+ PmError out_open(PmInternal *midi, void *driverInfo)
+ {
+ int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
+ BMidiConsumer *consumer = BMidiRoster::FindConsumer(endpointID);
+ if (!consumer)
+ return pmInvalidDeviceId;
+ BMidiLocalProducer *producer = new BMidiLocalProducer("PortMidi output producer");
+ status_t status = producer->Connect(consumer);
+ if (status != B_OK) {
+ consumer->Release();
+ producer->Release();
+ strcpy(pm_hosterror_text, strerror(status));
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ PmOutputInfo *info = new PmOutputInfo;
+ info->producer = producer;
+ midi->api_info = info;
+ consumer->Release();
+ return pmNoError;
+ }
+
+
+ PmError out_abort(PmInternal *midi)
+ {
+ return pmNoError;
+ }
+
+
+ PmError out_close(PmInternal *midi)
+ {
+ int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
+ BMidiConsumer *consumer = BMidiRoster::FindConsumer(endpointID);
+ if (!consumer)
+ return pmInvalidDeviceId;
+ PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
+ status_t status = info->producer->Disconnect(consumer);
+ if (status != B_OK) {
+ consumer->Release();
+ midi->api_info = NULL;
+ info->producer->Release();
+ delete info;
+ strcpy(pm_hosterror_text, strerror(status));
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ consumer->Release();
+ midi->api_info = NULL;
+ info->producer->Release();
+ delete info;
+ return pmNoError;
+ }
+
+
+ PmError write_short(PmInternal *midi, PmEvent *buffer)
+ {
+ PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
+ uchar data[3];
+ data[0] = Pm_MessageStatus(buffer->message);
+ data[1] = Pm_MessageData1(buffer->message);
+ data[2] = Pm_MessageData2(buffer->message);
+ size_t length = pm_midi_length(data[0]);
+
+ info->producer->SprayData(data, length, true, buffer->timestamp * 1000);
+
+ // TODO: handle latency != 0
+ return pmNoError;
+ }
+
+
+ PmError begin_sysex(PmInternal *midi, PmTimestamp timestamp)
+ {
+ PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
+ info->sysexBuffer.clear();
+ return pmNoError;
+ }
+
+
+ PmError end_sysex(PmInternal *midi, PmTimestamp timestamp)
+ {
+ PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
+ info->producer->SpraySystemExclusive(&info->sysexBuffer[0], info->sysexBuffer.size(), timestamp * 1000);
+ info->sysexBuffer.clear();
+ return pmNoError;
+ }
+
+
+ PmError write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
+ {
+ PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
+ info->sysexBuffer.push_back(byte);
+ return pmNoError;
+ }
+
+
+ PmError write_realtime(PmInternal *midi, PmEvent *buffer)
+ {
+ PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
+ info->producer->SpraySystemRealTime(Pm_MessageStatus(buffer->message), buffer->timestamp * 1000);
+ return pmNoError;
+ }
+
+
+ PmError synth_open(PmInternal *midi, void *driverInfo)
+ {
+ PmSynthOutputInfo *info = new PmSynthOutputInfo;
+ info->midiSynth.EnableInput(true, true);
+ midi->api_info = info;
+ return pmNoError;
+ }
+
+
+ PmError synth_abort(PmInternal *midi)
+ {
+ return pmNoError;
+ }
+
+
+ PmError synth_close(PmInternal *midi)
+ {
+ PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
+ delete info;
+ midi->api_info = NULL;
+ return pmNoError;
+ }
+
+
+ PmError write_short_synth(PmInternal *midi, PmEvent *buffer)
+ {
+ PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
+ uchar data[3];
+ data[0] = Pm_MessageStatus(buffer->message);
+ data[1] = Pm_MessageData1(buffer->message);
+ data[2] = Pm_MessageData2(buffer->message);
+
+ switch(data[0] & 0xf0) {
+ case B_NOTE_OFF:
+ info->midiSynth.NoteOff((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
+ break;
+ case B_NOTE_ON:
+ info->midiSynth.NoteOn((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
+ break;
+ case B_KEY_PRESSURE:
+ info->midiSynth.KeyPressure((data[0] & 0x0f + 1), data[1], data[2], buffer->timestamp);
+ break;
+ case B_CONTROL_CHANGE:
+ info->midiSynth.ControlChange((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
+ break;
+ case B_PROGRAM_CHANGE:
+ info->midiSynth.ProgramChange((data[0] & 0x0f) + 1, data[1], buffer->timestamp);
+ break;
+ case B_CHANNEL_PRESSURE:
+ info->midiSynth.ChannelPressure((data[0] & 0x0f) + 1, data[1], buffer->timestamp);
+ break;
+ case B_PITCH_BEND:
+ info->midiSynth.PitchBend((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
+ break;
+ }
+
+ // TODO: handle latency != 0
+ return pmNoError;
+ }
+
+
+ PmError begin_sysex_synth(PmInternal *midi, PmTimestamp timestamp)
+ {
+ PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
+ info->sysexBuffer.clear();
+ return pmNoError;
+ }
+
+
+ PmError end_sysex_synth(PmInternal *midi, PmTimestamp timestamp)
+ {
+ PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
+ info->midiSynth.SystemExclusive(&info->sysexBuffer[0], info->sysexBuffer.size(), timestamp);
+ info->sysexBuffer.clear();
+ return pmNoError;
+ }
+
+
+ PmError write_byte_synth(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
+ {
+ PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
+ info->sysexBuffer.push_back(byte);
+ return pmNoError;
+ }
+
+
+ PmError write_realtime_synth(PmInternal *midi, PmEvent *buffer)
+ {
+ PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
+ info->midiSynth.SystemRealTime(Pm_MessageStatus(buffer->message), buffer->timestamp);
+ return pmNoError;
+ }
+
+
+ PmError write_flush(PmInternal *midi, PmTimestamp timestamp)
+ {
+ return pmNoError;
+ }
+
+
+ unsigned int check_host_error(PmInternal *midi)
+ {
+ return 0;
+ }
+
+
+ pm_fns_node pm_in_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ synchronize,
+ in_open,
+ in_abort,
+ in_close,
+ success_poll,
+ check_host_error
+ };
+
+ pm_fns_node pm_out_dictionary = {
+ write_short,
+ begin_sysex,
+ end_sysex,
+ write_byte,
+ write_realtime,
+ write_flush,
+ synchronize,
+ out_open,
+ out_abort,
+ out_close,
+ none_poll,
+ check_host_error
+ };
+
+
+ pm_fns_node pm_synth_dictionary = {
+ write_short_synth,
+ begin_sysex_synth,
+ end_sysex_synth,
+ write_byte_synth,
+ write_realtime_synth,
+ write_flush,
+ synchronize,
+ synth_open,
+ synth_abort,
+ synth_close,
+ none_poll,
+ check_host_error
+ };
+
+
+ PmError create_virtual(int is_input, const char *name, void *driverInfo)
+ {
+ BMidiEndpoint *endpoint = is_input ? (BMidiEndpoint*)new BMidiLocalProducer(name) : new BMidiLocalConsumer(name);
+ if (!endpoint->IsValid()) {
+ endpoint->Release();
+ strcpy(pm_hosterror_text, "Endpoint could not be created");
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ status_t status = endpoint->Register();
+ if (status != B_OK) {
+ endpoint->Release();
+ strcpy(pm_hosterror_text, strerror(status));
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ return pm_add_device(const_cast<char*>("Haiku MIDI kit"), name, is_input, TRUE, (void*)(intptr_t)endpoint->ID(), is_input ? &pm_in_dictionary : &pm_out_dictionary);
+ }
+
+ PmError delete_virtual(PmDeviceID id)
+ {
+ int32 endpointID = (int32)(intptr_t)pm_descriptors[id].descriptor;
+ BMidiEndpoint *endpoint = BMidiRoster::FindEndpoint(endpointID);
+ //TODO: handle connected producers and consumers
+ status_t status = endpoint->Unregister();
+ // release twice to actually free the endpoint (FindEndpoint increases the ref-count)
+ endpoint->Release();
+ endpoint->Release();
+ if (status != B_OK) {
+ strcpy(pm_hosterror_text, strerror(status));
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ return pmNoError;
+ }
+}
+
+extern "C" {
+ void pm_init()
+ {
+ pm_add_interf(const_cast<char*>("Haiku MIDI kit"), create_virtual, delete_virtual);
+
+ pm_add_device(const_cast<char*>("Haiku MIDI kit"), "Soft Synth", FALSE, FALSE, NULL, &pm_synth_dictionary);
+
+ int32 id = 0;
+ BMidiEndpoint *endpoint;
+
+ while ((endpoint = BMidiRoster::NextEndpoint(&id)) != NULL) {
+ bool isInput = endpoint->IsProducer();
+ pm_add_device(const_cast<char*>("Haiku MIDI kit"), endpoint->Name(), isInput, FALSE, (void*)(intptr_t)id, isInput ? &pm_in_dictionary : &pm_out_dictionary);
+ endpoint->Release();
+ }
+ }
+
+
+ void pm_term()
+ {
+ int i;
+ for (i = 0; i < pm_descriptor_len; i++) {
+ PmInternal *midi = pm_descriptors[i].pm_internal;
+ if (midi && midi->api_info) {
+ // device is still open, close it
+ (*midi->dictionary->close)(midi);
+ }
+ if (pm_descriptors[i].pub.is_virtual && !pm_descriptors[i].deleted) {
+ delete_virtual(i);
+ }
+ }
+ }
+
+
+ PmDeviceID Pm_GetDefaultInputDeviceID()
+ {
+ Pm_Initialize();
+ return pm_default_input_device_id;
+ }
+
+
+ PmDeviceID Pm_GetDefaultOutputDeviceID()
+ {
+ Pm_Initialize();
+ return pm_default_output_device_id;
+ }
+
+
+ void *pm_alloc(size_t s)
+ {
+ return malloc(s);
+ }
+
+
+ void pm_free(void *ptr)
+ {
+ free(ptr);
+ }
+}
diff --git a/portmidi/pm_java/CMakeLists.txt b/portmidi/pm_java/CMakeLists.txt
new file mode 100644
index 0000000..55a20f4
--- /dev/null
+++ b/portmidi/pm_java/CMakeLists.txt
@@ -0,0 +1,56 @@
+# pm_java/CMakeLists.txt -- builds pmjni
+
+find_package(Java)
+message(STATUS "Java_JAVA_EXECUTABLE is " ${Java_JAVA_EXECUTABLE})
+
+# Build pmjni
+# this CMakeLists.txt is only loaded if BUILD_JAVA_NATIVE_INTERFACE
+# This jni library includes portmidi sources to give just
+# one library for JPortMidi users to manage rather than two.
+if(UNIX)
+ include(FindJNI)
+ # message(STATUS "JAVA_JVM_LIB_PATH is " ${JAVA_JVM_LIB_PATH})
+ # message(STATUS "JAVA_INCLUDE_PATH is " ${JAVA_INCLUDE_PATH})
+ # note: should use JAVA_JVM_LIB_PATH, but it is not set properly
+ # note: user might need to set JAVA_INCLUDE_PATH manually
+ #
+ # this will probably break on BSD and other Unix systems; the fix
+ # depends on whether FindJNI can find Java or not. If yes, then
+ # we should try to rely on automatically set JAVA_INCLUDE_PATH and
+ # JAVA_INCLUDE_PATH2; if no, then we need to make both JAVA_INCLUDE_PATH
+ # and JAVA_INCLUDE_PATH2 set by user (will need clear documentation
+ # because JAVA_INCLUDE_PATH2 is pretty obscure)
+ set(JAVA_INCLUDE_PATH ${JAVA_INCLUDE_PATH-UNKNOWN}
+ CACHE STRING "where to find Java SDK include directory")
+ # libjvm.so is found relative to JAVA_INCLUDE_PATH:
+ if (HAIKU)
+ set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH}/haiku)
+ else()
+ set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH}/linux)
+ endif()
+elseif(WIN32)
+ include(FindJNI)
+ # note: should use JAVA_JVM_LIB_PATH, but it is not set properly
+ set(JAVAVM_LIB ${JAVA_INCLUDE_PATH}/../lib/jvm.lib)
+
+ set(JAVA_INCLUDE_PATHS ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2})
+ # message(STATUS "JAVA_INCLUDE_PATHS: " ${JAVA_INCLUDE_PATHS})
+ # message(STATUS "JAVAVM_LIB: " ${JAVAVM_LIB})
+endif()
+
+add_library(pmjni SHARED pmjni/pmjni.c)
+target_sources(pmjni PRIVATE ${PM_LIB_PUBLIC_SRC} ${PM_LIB_PRIVATE_SRC})
+message(STATUS "Java paths ${JAVA_INCLUDE_PATHS}")
+# message(STATUS "Java pmjni src: pmjni/pmjni.c ${PM_LIB_SHARED_SRC} "
+# "${PM_LIB_PRIVATE_SRC}")
+target_include_directories(pmjni PUBLIC ${JAVA_INCLUDE_PATHS})
+target_link_libraries(pmjni ${PM_NEEDED_LIBS})
+set_target_properties(pmjni PROPERTIES
+ VERSION ${LIBRARY_VERSION}
+ SOVERSION ${LIBRARY_SOVERSION}
+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
+ EXECUTABLE_EXTENSION "jnilib"
+ MACOSX_RPATH ON)
+
diff --git a/portmidi/pm_java/README.txt b/portmidi/pm_java/README.txt
new file mode 100644
index 0000000..d1e5ad5
--- /dev/null
+++ b/portmidi/pm_java/README.txt
@@ -0,0 +1,62 @@
+README.txt
+Roger B. Dannenberg
+16 Jun 2009
+updated 2021
+
+This directory implements a JNI library so that Java programs can use
+the PortMidi API. This was mainly created to implement PmDefaults, a
+program to set default input and output devices for PortMidi
+applications. Because it is rarely used, PmDefaults was dropped from
+PortMidi starting with v3. I recommend you implement per-application
+preferences and store default PortMidi device numbers for input and
+output there. (Or better yet, store device *names* since numbers can
+change if you plug in or remove USB devices.)
+
+Even without PmDefaults, a PortMidi API for Java is probably an
+improvement over other Java libraries, but there is very little MIDI
+development in Java, so I have not maintained this API. The only thing
+probably seriously wrong now is an interface to the
+Pm_CreateVirtualInput and Pm_CreateVirtualOutput functions, which are
+new additions.
+
+I will leave the code here, and if there is a demand, please either
+update it or let your needs be known. Perhaps I or someone can help.
+
+==================================================================
+
+BUILDING Java EXTERNAL LIBRARY
+
+You must have a JDK installed (Java development kit including javac
+(the Java compiler), jni.h, etc.
+
+Test java on the command line, e.g., type: javac -version
+
+Enable these options in the main CMakeLists.txt file (run CMake
+from your top-level repository directory):
+ BUILD_JAVA_NATIVE_INTERFACE
+In my Ubuntu linux with jdk-15, ccmake was unable to find my JDK, so
+I have to manually set CMake variables as follows (type 't' to see
+these in ccmake):
+ JAVA_AWT_INCLUDE_PATH /usr/lib/jvm/jdk-15/include
+ JAVA_AWT_LIBRARY /usr/lib/jvm/jdk-15/lib
+ JAVA_INCLUDE_PATH /usr/lib/jvm/jdk-15/include
+ JAVA_INCLUDE_PATH2 /usr/lib/jvm/jdk-15/include
+ JAVA_JVM_LIBRARY /usr/lib/jvm/jdk-15/lib
+Of course, your paths may differ.
+
+
+---- old implementation notes ----
+
+For Windows, we use the free software JavaExe.exe. The copy here was
+downloaded from
+
+http://software.techrepublic.com.com/abstract.aspx?kw=javaexe&docid=767485
+
+I found this page by visiting http://software.techrepublic.com.com and
+searching in the "Software" category for "JavaExe"
+
+JavaExe works by placing the JavaExe.exe file in the directory with the
+Java application jar file and then *renaming* JavaExe.exe to the name
+of the jar file, but keeping the .exe extension. (See make.bat for this
+step.) Documentation for JavaExe can be obtained by downloading the
+whole program from the URL(s) above.
diff --git a/portmidi/pm_java/jportmidi/JPortMidi.java b/portmidi/pm_java/jportmidi/JPortMidi.java
new file mode 100644
index 0000000..7116e19
--- /dev/null
+++ b/portmidi/pm_java/jportmidi/JPortMidi.java
@@ -0,0 +1,541 @@
+package jportmidi;
+
+/* PortMidi is a general class intended for any Java program using
+ the PortMidi library. It encapsulates JPortMidiApi with a more
+ object-oriented interface. A single PortMidi object can manage
+ up to one input stream and one output stream.
+
+ This class is not safely callable from multiple threads. It
+ is the client's responsibility to periodically call the Poll
+ method which checks for midi input and handles it.
+*/
+
+import jportmidi.*;
+import jportmidi.JPortMidiApi.*;
+
+public class JPortMidi {
+
+ // timecode to send message immediately
+ public final int NOW = 0;
+
+ // midi codes
+ public final int MIDI_NOTE_OFF = 0x80;
+ public final int MIDI_NOTE_ON = 0x90;
+ public final int CTRL_ALL_OFF = 123;
+ public final int MIDI_PITCH_BEND = 0xE0;
+ public final int MIDI_CLOCK = 0xF8;
+ public final int MIDI_CONTROL = 0xB0;
+ public final int MIDI_PROGRAM = 0xC0;
+ public final int MIDI_START = 0xFA;
+ public final int MIDI_STOP = 0xFC;
+ public final int MIDI_POLY_TOUCH = 0xA0;
+ public final int MIDI_TOUCH = 0xD0;
+
+ // error code -- cannot refresh device list while stream is open:
+ public final int pmStreamOpen = -5000;
+ public final int pmOutputNotOpen = -4999;
+
+ // access to JPortMidiApi is through a single, global instance
+ private static JPortMidiApi pm;
+ // a reference count tracks how many objects have it open
+ private static int pmRefCount = 0;
+ private static int openCount = 0;
+
+ public int error; // user can check here for error codes
+ private PortMidiStream input;
+ private PortMidiStream output;
+ private PmEvent buffer;
+ protected int timestamp; // remember timestamp from incoming messages
+ protected boolean trace = false; // used to print midi msgs for debugging
+
+
+ public JPortMidi() throws JPortMidiException {
+ if (pmRefCount == 0) {
+ pm = new JPortMidiApi();
+ pmRefCount++;
+ System.out.println("Calling Pm_Initialize");
+ checkError(pm.Pm_Initialize());
+ System.out.println("Called Pm_Initialize");
+ }
+ buffer = new PmEvent();
+ }
+
+ public boolean getTrace() { return trace; }
+
+ // set the trace flag and return previous value
+ public boolean setTrace(boolean flag) {
+ boolean previous = trace;
+ trace = flag;
+ return previous;
+ }
+
+ // WARNING: you must not call this if any devices are open
+ public void refreshDeviceLists()
+ throws JPortMidiException
+ {
+ if (openCount > 0) {
+ throw new JPortMidiException(pmStreamOpen,
+ "RefreshDeviceLists called while stream is open");
+ }
+ if (trace) System.out.println("Pm_Terminate");
+ checkError(pm.Pm_Terminate());
+ if (trace) System.out.println("Pm_Initialize");
+ checkError(pm.Pm_Initialize());
+ }
+
+ // there is no control over when/whether this is called, but it seems
+ // to be a good idea to close things when this object is collected
+ public void finalize() {
+ if (input != null) {
+ error = pm.Pm_Close(input);
+ }
+ if (input != null) {
+ int rslt = pm.Pm_Close(output);
+ // we may lose an error code from closing output, but don't
+ // lose any real error from closing input...
+ if (error == pm.pmNoError) error = rslt;
+ }
+ pmRefCount--;
+ if (pmRefCount == 0) {
+ error = pm.Pm_Terminate();
+ }
+ }
+
+ int checkError(int err) throws JPortMidiException
+ {
+ // note that Pm_Read and Pm_Write return positive result values
+ // which are not errors, so compare with >=
+ if (err >= pm.pmNoError) return err;
+ if (err == pm.pmHostError) {
+ throw new JPortMidiException(err, pm.Pm_GetHostErrorText());
+ } else {
+ throw new JPortMidiException(err, pm.Pm_GetErrorText(err));
+ }
+ }
+
+ // ******** ACCESS TO TIME ***********
+
+ public void timeStart(int resolution) throws JPortMidiException {
+ checkError(pm.Pt_TimeStart(resolution));
+ }
+
+ public void timeStop() throws JPortMidiException {
+ checkError(pm.Pt_TimeStop());
+ }
+
+ public int timeGet() {
+ return pm.Pt_Time();
+ }
+
+ public boolean timeStarted() {
+ return pm.Pt_TimeStarted();
+ }
+
+ // ******* QUERY DEVICE INFORMATION *********
+
+ public int countDevices() throws JPortMidiException {
+ return checkError(pm.Pm_CountDevices());
+ }
+
+ public int getDefaultInputDeviceID() throws JPortMidiException {
+ return checkError(pm.Pm_GetDefaultInputDeviceID());
+ }
+
+ public int getDefaultOutputDeviceID() throws JPortMidiException {
+ return checkError(pm.Pm_GetDefaultOutputDeviceID());
+ }
+
+ public String getDeviceInterf(int i) {
+ return pm.Pm_GetDeviceInterf(i);
+ }
+
+ public String getDeviceName(int i) {
+ return pm.Pm_GetDeviceName(i);
+ }
+
+ public boolean getDeviceInput(int i) {
+ return pm.Pm_GetDeviceInput(i);
+ }
+
+ public boolean getDeviceOutput(int i) {
+ return pm.Pm_GetDeviceOutput(i);
+ }
+
+ // ********** MIDI INTERFACE ************
+
+ public boolean isOpenInput() {
+ return input != null;
+ }
+
+ public void openInput(int inputDevice, int bufferSize)
+ throws JPortMidiException
+ {
+ openInput(inputDevice, "", bufferSize);
+ }
+
+ public void openInput(int inputDevice, String inputDriverInfo, int bufferSize)
+ throws JPortMidiException
+ {
+ if (isOpenInput()) pm.Pm_Close(input);
+ else input = new PortMidiStream();
+ if (trace) {
+ System.out.println("openInput " + getDeviceName(inputDevice));
+ }
+ checkError(pm.Pm_OpenInput(input, inputDevice,
+ inputDriverInfo, bufferSize));
+ // if no exception, then increase count of open streams
+ openCount++;
+ }
+
+ public boolean isOpenOutput() {
+ return output != null;
+ }
+
+ public void openOutput(int outputDevice, int bufferSize, int latency)
+ throws JPortMidiException
+ {
+ openOutput(outputDevice, "", bufferSize, latency);
+ }
+
+ public void openOutput(int outputDevice, String outputDriverInfo,
+ int bufferSize, int latency) throws JPortMidiException {
+ if (isOpenOutput()) pm.Pm_Close(output);
+ else output = new PortMidiStream();
+ if (trace) {
+ System.out.println("openOutput " + getDeviceName(outputDevice));
+ }
+ checkError(pm.Pm_OpenOutput(output, outputDevice, outputDriverInfo,
+ bufferSize, latency));
+ // if no exception, then increase count of open streams
+ openCount++;
+ }
+
+ public void setFilter(int filters) throws JPortMidiException {
+ if (input == null) return; // no effect if input not open
+ checkError(pm.Pm_SetFilter(input, filters));
+ }
+
+ public void setChannelMask(int mask) throws JPortMidiException {
+ if (input == null) return; // no effect if input not open
+ checkError(pm.Pm_SetChannelMask(input, mask));
+ }
+
+ public void abort() throws JPortMidiException {
+ if (output == null) return; // no effect if output not open
+ checkError(pm.Pm_Abort(output));
+ }
+
+ // In keeping with the idea that this class represents an input and output,
+ // there are separate Close methods for input and output streams, avoiding
+ // the need for clients to ever deal directly with a stream object
+ public void closeInput() throws JPortMidiException {
+ if (input == null) return; // no effect if input not open
+ checkError(pm.Pm_Close(input));
+ input = null;
+ openCount--;
+ }
+
+ public void closeOutput() throws JPortMidiException {
+ if (output == null) return; // no effect if output not open
+ checkError(pm.Pm_Close(output));
+ output = null;
+ openCount--;
+ }
+
+ // Poll should be called by client to process input messages (if any)
+ public void poll() throws JPortMidiException {
+ if (input == null) return; // does nothing until input is opened
+ while (true) {
+ int rslt = pm.Pm_Read(input, buffer);
+ checkError(rslt);
+ if (rslt == 0) return; // no more messages
+ handleMidiIn(buffer);
+ }
+ }
+
+ public void writeShort(int when, int msg) throws JPortMidiException {
+ if (output == null)
+ throw new JPortMidiException(pmOutputNotOpen,
+ "Output stream not open");
+ if (trace) {
+ System.out.println("writeShort: " + Integer.toHexString(msg));
+ }
+ checkError(pm.Pm_WriteShort(output, when, msg));
+ }
+
+ public void writeSysEx(int when, byte msg[]) throws JPortMidiException {
+ if (output == null)
+ throw new JPortMidiException(pmOutputNotOpen,
+ "Output stream not open");
+ if (trace) {
+ System.out.print("writeSysEx: ");
+ for (int i = 0; i < msg.length; i++) {
+ System.out.print(Integer.toHexString(msg[i]));
+ }
+ System.out.print("\n");
+ }
+ checkError(pm.Pm_WriteSysEx(output, when, msg));
+ }
+
+ public int midiChanMessage(int chan, int status, int data1, int data2) {
+ return (((data2 << 16) & 0xFF0000) |
+ ((data1 << 8) & 0xFF00) |
+ (status & 0xF0) |
+ (chan & 0xF));
+ }
+
+ public int midiMessage(int status, int data1, int data2) {
+ return ((((data2) << 16) & 0xFF0000) |
+ (((data1) << 8) & 0xFF00) |
+ ((status) & 0xFF));
+ }
+
+ public void midiAllOff(int channel) throws JPortMidiException {
+ midiAllOff(channel, NOW);
+ }
+
+ public void midiAllOff(int chan, int when) throws JPortMidiException {
+ writeShort(when, midiChanMessage(chan, MIDI_CONTROL, CTRL_ALL_OFF, 0));
+ }
+
+ public void midiPitchBend(int chan, int value) throws JPortMidiException {
+ midiPitchBend(chan, value, NOW);
+ }
+
+ public void midiPitchBend(int chan, int value, int when)
+ throws JPortMidiException {
+ writeShort(when,
+ midiChanMessage(chan, MIDI_PITCH_BEND, value, value >> 7));
+ }
+
+ public void midiClock() throws JPortMidiException {
+ midiClock(NOW);
+ }
+
+ public void midiClock(int when) throws JPortMidiException {
+ writeShort(when, midiMessage(MIDI_CLOCK, 0, 0));
+ }
+
+ public void midiControl(int chan, int control, int value)
+ throws JPortMidiException {
+ midiControl(chan, control, value, NOW);
+ }
+
+ public void midiControl(int chan, int control, int value, int when)
+ throws JPortMidiException {
+ writeShort(when, midiChanMessage(chan, MIDI_CONTROL, control, value));
+ }
+
+ public void midiNote(int chan, int pitch, int vel)
+ throws JPortMidiException {
+ midiNote(chan, pitch, vel, NOW);
+ }
+
+ public void midiNote(int chan, int pitch, int vel, int when)
+ throws JPortMidiException {
+ writeShort(when, midiChanMessage(chan, MIDI_NOTE_ON, pitch, vel));
+ }
+
+ public void midiProgram(int chan, int program)
+ throws JPortMidiException {
+ midiProgram(chan, program, NOW);
+ }
+
+ public void midiProgram(int chan, int program, int when)
+ throws JPortMidiException {
+ writeShort(when, midiChanMessage(chan, MIDI_PROGRAM, program, 0));
+ }
+
+ public void midiStart()
+ throws JPortMidiException {
+ midiStart(NOW);
+ }
+
+ public void midiStart(int when)
+ throws JPortMidiException {
+ writeShort(when, midiMessage(MIDI_START, 0, 0));
+ }
+
+ public void midiStop()
+ throws JPortMidiException {
+ midiStop(NOW);
+ }
+
+ public void midiStop(int when)
+ throws JPortMidiException {
+ writeShort(when, midiMessage(MIDI_STOP, 0, 0));
+ }
+
+ public void midiPolyTouch(int chan, int key, int value)
+ throws JPortMidiException {
+ midiPolyTouch(chan, key, value, NOW);
+ }
+
+ public void midiPolyTouch(int chan, int key, int value, int when)
+ throws JPortMidiException {
+ writeShort(when, midiChanMessage(chan, MIDI_POLY_TOUCH, key, value));
+ }
+
+ public void midiTouch(int chan, int value)
+ throws JPortMidiException {
+ midiTouch(chan, value, NOW);
+ }
+
+ public void midiTouch(int chan, int value, int when)
+ throws JPortMidiException {
+ writeShort(when, midiChanMessage(chan, MIDI_TOUCH, value, 0));
+ }
+
+ // ****** now we implement the message handlers ******
+
+ // an array for incoming sysex messages that can grow.
+ // The downside is that after getting a message, we
+
+ private byte sysexBuffer[] = null;
+ private int sysexBufferIndex = 0;
+
+ void sysexBufferReset() {
+ sysexBufferIndex = 0;
+ if (sysexBuffer == null) sysexBuffer = new byte[256];
+ }
+
+ void sysexBufferCheck() {
+ if (sysexBuffer.length < sysexBufferIndex + 4) {
+ byte bigger[] = new byte[sysexBuffer.length * 2];
+ for (int i = 0; i < sysexBufferIndex; i++) {
+ bigger[i] = sysexBuffer[i];
+ }
+ sysexBuffer = bigger;
+ }
+ // now we have space to write some bytes
+ }
+
+ // call this to insert Sysex and EOX status bytes
+ // call sysexBufferAppendBytes to insert anything else
+ void sysexBufferAppendStatus(byte status) {
+ sysexBuffer[sysexBufferIndex++] = status;
+ }
+
+ void sysexBufferAppendBytes(int msg, int len) {
+ for (int i = 0; i < len; i++) {
+ byte b = (byte) msg;
+ if ((msg & 0x80) != 0) {
+ if (b == 0xF7) { // end of sysex
+ sysexBufferAppendStatus(b);
+ sysex(sysexBuffer, sysexBufferIndex);
+ return;
+ }
+ // recursively handle embedded real-time messages
+ PmEvent buffer = new PmEvent();
+ buffer.timestamp = timestamp;
+ buffer.message = b;
+ handleMidiIn(buffer);
+ } else {
+ sysexBuffer[sysexBufferIndex++] = b;
+ }
+ msg = msg >> 8;
+ }
+ }
+
+ void sysexBegin(int msg) {
+ sysexBufferReset(); // start from 0, we have at least 256 bytes now
+ sysexBufferAppendStatus((byte) (msg & 0xFF)); // first byte is special
+ sysexBufferAppendBytes(msg >> 8, 3); // process remaining bytes
+ }
+
+ public void handleMidiIn(PmEvent buffer)
+ {
+ if (trace) {
+ System.out.println("handleMidiIn: " +
+ Integer.toHexString(buffer.message));
+ }
+ // rather than pass timestamps to every handler, where typically
+ // timestamps are ignored, just save the timestamp as a member
+ // variable where methods can access it if they want it
+ timestamp = buffer.timestamp;
+ int status = buffer.message & 0xFF;
+ if (status < 0x80) {
+ sysexBufferCheck(); // make enough space
+ sysexBufferAppendBytes(buffer.message, 4); // process 4 bytes
+ return;
+ }
+ int command = status & 0xF0;
+ int channel = status & 0x0F;
+ int data1 = (buffer.message >> 8) & 0xFF;
+ int data2 = (buffer.message >> 16) & 0xFF;
+ switch (command) {
+ case MIDI_NOTE_OFF:
+ noteOff(channel, data1, data2); break;
+ case MIDI_NOTE_ON:
+ if (data2 > 0) {
+ noteOn(channel, data1, data2); break;
+ } else {
+ noteOff(channel, data1);
+ }
+ break;
+ case MIDI_CONTROL:
+ control(channel, data1, data2); break;
+ case MIDI_POLY_TOUCH:
+ polyTouch(channel, data1, data2); break;
+ case MIDI_TOUCH:
+ touch(channel, data1); break;
+ case MIDI_PITCH_BEND:
+ pitchBend(channel, (data1 + (data2 << 7)) - 8192); break;
+ case MIDI_PROGRAM:
+ program(channel, data1); break;
+ case 0xF0:
+ switch (channel) {
+ case 0: sysexBegin(buffer.message); break;
+ case 1: mtcQuarterFrame(data1);
+ case 2: songPosition(data1 + (data2 << 7)); break;
+ case 3: songSelect(data1); break;
+ case 4: /* unused */ break;
+ case 5: /* unused */ break;
+ case 6: tuneRequest(); break;
+ case 7: sysexBufferAppendBytes(buffer.message, buffer.message); break;
+ case 8: clock(); break;
+ case 9: tick(); break;
+ case 0xA: clockStart(); break;
+ case 0xB: clockContinue(); break;
+ case 0xC: clockStop(); break;
+ case 0xD: /* unused */ break;
+ case 0xE: activeSense(); break;
+ case 0xF: reset(); break;
+ }
+ }
+ }
+
+ // the value ranges from +8181 to -8192. The interpretation is
+ // synthesizer dependent. Often the range is +/- one whole step
+ // (two semitones), but the range is usually adjustable within
+ // the synthesizer.
+ void pitchBend(int channel, int value) { return; }
+ void control(int channel, int control, int value) { return; }
+ void noteOn(int channel, int pitch, int velocity) { return; }
+ // you can handle velocity in note-off if you want, but the default
+ // is to drop the velocity and call the simpler NoteOff handler
+ void noteOff(int channel, int pitch, int velocity) {
+ noteOff(channel, pitch);
+ }
+ // if the subclass wants to implement NoteOff with velocity, it
+ // should override the following to make sure all NoteOffs are handled
+ void noteOff(int channel, int pitch) { return; }
+ void program(int channel, int program) { return; }
+ // the byte array may be bigger than the message, length tells how
+ // many bytes in the array are part of the message
+ void sysex(byte[] msg, int length) { return; }
+ void polyTouch(int channel, int key, int value) { return; }
+ void touch(int channel, int value) { return; }
+ void mtcQuarterFrame(int value) { return; }
+ // the value is a 14-bit integer representing 16th notes
+ void songPosition(int value) { return; }
+ void songSelect(int value) { return; }
+ void tuneRequest() { return; }
+ void clock() { return; } // represents 1/24th of a quarter note
+ void tick() { return; } // represents 10ms
+ void clockStart() { return; }
+ void clockStop() { return; }
+ void clockContinue() { return; }
+ void activeSense() { return; }
+ void reset() { return; }
+}
diff --git a/portmidi/pm_java/jportmidi/JPortMidiApi.java b/portmidi/pm_java/jportmidi/JPortMidiApi.java
new file mode 100644
index 0000000..45dd9d9
--- /dev/null
+++ b/portmidi/pm_java/jportmidi/JPortMidiApi.java
@@ -0,0 +1,117 @@
+package jportmidi;
+
+public class JPortMidiApi {
+ public static class PortMidiStream {
+ private long address;
+ }
+ public static class PmEvent {
+ public int message;
+ public int timestamp;
+ }
+
+ // PmError bindings
+ public final int pmNoError = 0;
+ public final int pmNoData = 0;
+ public final int pmGotData = -1;
+ public final int pmHostError = -10000;
+ public final int pmInvalidDeviceId = -9999;
+ public final int pmInsufficientMemory = -9998;
+ public final int pmBufferTooSmall = -9997;
+ public final int pmBufferOverflow = -9996;
+ public final int pmBadPtr = -9995;
+ public final int pmBadData = -9994;
+ public final int pmInternalError = -9993;
+ public final int pmBufferMaxSize = -9992;
+
+ static public native int Pm_Initialize();
+ static public native int Pm_Terminate();
+ static public native int Pm_HasHostError(PortMidiStream stream);
+ static public native String Pm_GetErrorText(int errnum);
+ static public native String Pm_GetHostErrorText();
+ final int pmNoDevice = -1;
+ static public native int Pm_CountDevices();
+ static public native int Pm_GetDefaultInputDeviceID();
+ static public native int Pm_GetDefaultOutputDeviceID();
+ static public native String Pm_GetDeviceInterf(int i);
+ static public native String Pm_GetDeviceName(int i);
+ static public native boolean Pm_GetDeviceInput(int i);
+ static public native boolean Pm_GetDeviceOutput(int i);
+ static public native int Pm_OpenInput(PortMidiStream stream,
+ int inputDevice,
+ String inputDriverInfo,
+ int bufferSize);
+ static public native int Pm_OpenOutput(PortMidiStream stream,
+ int outputDevice,
+ String outnputDriverInfo,
+ int bufferSize,
+ int latency);
+ final static public int PM_FILT_ACTIVE = (1 << 0x0E);
+ final static public int PM_FILT_SYSEX = (1 << 0x00);
+ final static public int PM_FILT_CLOCK = (1 << 0x08);
+ final static public int PM_FILT_PLAY =
+ (1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B);
+ final static public int PM_FILT_TICK = (1 << 0x09);
+ final static public int PM_FILT_FD = (1 << 0x0D);
+ final static public int PM_FILT_UNDEFINED = PM_FILT_FD;
+ final static public int PM_FILT_RESET = (1 << 0x0F);
+ final static public int PM_FILT_REALTIME =
+ PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK;
+ final static public int PM_FILT_NOTE = (1 << 0x19) | (1 << 0x18);
+ final static public int PM_FILT_CHANNEL_AFTERTOUCH = (1 << 0x1D);
+ final static public int PM_FILT_POLY_AFTERTOUCH = (1 << 0x1A);
+ final static public int PM_FILT_AFTERTOUCH =
+ (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH);
+ final static public int PM_FILT_PROGRAM = (1 << 0x1C);
+ final static public int PM_FILT_CONTROL = (1 << 0x1B);
+ final static public int PM_FILT_PITCHBEND = (1 << 0x1E);
+ final static public int PM_FILT_MTC = (1 << 0x01);
+ final static public int PM_FILT_SONG_POSITION = (1 << 0x02);
+ final static public int PM_FILT_SONG_SELECT = (1 << 0x03);
+ final static public int PM_FILT_TUNE = (1 << 0x06);
+ final static public int PM_FILT_SYSTEMCOMMON =
+ (PM_FILT_MTC | PM_FILT_SONG_POSITION |
+ PM_FILT_SONG_SELECT | PM_FILT_TUNE);
+ static public native int Pm_SetFilter(PortMidiStream stream, int filters);
+ static public int Pm_Channel(int channel) { return 1 << channel; }
+ final static public native int Pm_SetChannelMask(PortMidiStream stream,
+ int mask);
+ final static public native int Pm_Abort(PortMidiStream stream);
+ final static public native int Pm_Close(PortMidiStream stream);
+ static public int Pm_Message(int status, int data1, int data2) {
+ return (((data2 << 16) & 0xFF0000) |
+ ((data1 << 8) & 0xFF00) |
+ (status & 0xFF));
+ }
+ static public int Pm_MessageStatus(int msg) {
+ return msg & 0xFF;
+ }
+ static public int Pm_MessageData1(int msg) {
+ return (msg >> 8) & 0xFF;
+ }
+ static public int Pm_MessageData2(int msg) {
+ return (msg >> 16) & 0xFF;
+ }
+ // only supports reading one buffer at a time
+ static public native int Pm_Read(PortMidiStream stream, PmEvent buffer);
+ static public native int Pm_Poll(PortMidiStream stream);
+ // only supports writing one buffer at a time
+ static public native int Pm_Write(PortMidiStream stream, PmEvent buffer);
+ static public native int Pm_WriteShort(PortMidiStream stream,
+ int when, int msg);
+ static public native int Pm_WriteSysEx(PortMidiStream stream,
+ int when, byte msg[]);
+
+ public final int ptNoError = 0;
+ public final int ptAlreadyStarted = -10000;
+ public final int ptAlreadyStopped = -9999;
+ public final int PtInsufficientMemory = -9998;
+ static public native int Pt_TimeStart(int resolution);
+ static public native int Pt_TimeStop();
+ static public native int Pt_Time();
+ static public native boolean Pt_TimeStarted();
+ static {
+ System.out.println("Loading pmjni");
+ System.loadLibrary("pmjni");
+ System.out.println("done loading pmjni");
+ }
+}
diff --git a/portmidi/pm_java/jportmidi/JPortMidiException.java b/portmidi/pm_java/jportmidi/JPortMidiException.java
new file mode 100644
index 0000000..9be8aaf
--- /dev/null
+++ b/portmidi/pm_java/jportmidi/JPortMidiException.java
@@ -0,0 +1,12 @@
+// JPortMidiException -- thrown by JPortMidi methods
+
+package jportmidi;
+
+public class JPortMidiException extends Exception {
+ public int error = 0;
+ public JPortMidiException(int err, String msg) {
+ super(msg);
+ error = err;
+ }
+}
+
diff --git a/portmidi/pm_java/make.bat b/portmidi/pm_java/make.bat
new file mode 100644
index 0000000..ff15c2b
--- /dev/null
+++ b/portmidi/pm_java/make.bat
@@ -0,0 +1,50 @@
+@echo off
+
+rem This is an out-of-date script for Windows to build a Java application
+rem (PmDefaults) with PortMidi external library.xb
+
+rem Compile the java PortMidi interface classes.
+javac jportmidi/*.java
+
+rem Compile the pmdefaults application.
+javac -classpath . pmdefaults/*.java
+
+rem Temporarily copy the portmusic_logo.png file here to add to the jar file.
+copy pmdefaults\portmusic_logo.png . > nul
+
+rem Create a directory to hold the distribution.
+mkdir win32
+
+rem Attempt to copy the interface DLL to the distribution directory.
+
+if exist "..\release\pmjni.dll" goto have-dll
+
+echo "ERROR: pmjni.dll not found!"
+exit /b 1
+
+:have-dll
+copy "..\release\pmjni.dll" win32\pmjni.dll > nul
+
+rem Create a java archive (jar) file of the distribution.
+jar cmf pmdefaults\manifest.txt win32\pmdefaults.jar pmdefaults\*.class portmusic_logo.png jportmidi\*.class
+
+rem Clean up the temporary image file now that it is in the jar file.
+del portmusic_logo.png
+
+rem Copy the java execution code obtained from
+rem http://devwizard.free.fr/html/en/JavaExe.html to the distribution
+rem directory. The copy also renames the file to our desired executable
+rem name.
+copy JavaExe.exe win32\pmdefaults.exe > nul
+
+rem Integrate the icon into the executable using UpdateRsrcJavaExe from
+rem http://devwizard.free.fr
+UpdateRsrcJavaExe -run -exe=win32\pmdefaults.exe -ico=pmdefaults\pmdefaults.ico
+
+rem Copy the 32-bit windows read me file to the distribution directory.
+copy pmdefaults\readme-win32.txt win32\README.txt > nul
+
+rem Copy the license file to the distribution directory.
+copy pmdefaults\pmdefaults-license.txt win32\license.txt > nul
+
+echo "You can run pmdefaults.exe in win32"
diff --git a/portmidi/pm_java/pmjni/jportmidi_JportMidiApi.h b/portmidi/pm_java/pmjni/jportmidi_JportMidiApi.h
new file mode 100644
index 0000000..2208be6
--- /dev/null
+++ b/portmidi/pm_java/pmjni/jportmidi_JportMidiApi.h
@@ -0,0 +1,293 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class jportmidi_JPortMidiApi */
+
+#ifndef _Included_jportmidi_JPortMidiApi
+#define _Included_jportmidi_JPortMidiApi
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef jportmidi_JPortMidiApi_PM_FILT_ACTIVE
+#define jportmidi_JPortMidiApi_PM_FILT_ACTIVE 16384L
+#undef jportmidi_JPortMidiApi_PM_FILT_SYSEX
+#define jportmidi_JPortMidiApi_PM_FILT_SYSEX 1L
+#undef jportmidi_JPortMidiApi_PM_FILT_CLOCK
+#define jportmidi_JPortMidiApi_PM_FILT_CLOCK 256L
+#undef jportmidi_JPortMidiApi_PM_FILT_PLAY
+#define jportmidi_JPortMidiApi_PM_FILT_PLAY 7168L
+#undef jportmidi_JPortMidiApi_PM_FILT_TICK
+#define jportmidi_JPortMidiApi_PM_FILT_TICK 512L
+#undef jportmidi_JPortMidiApi_PM_FILT_FD
+#define jportmidi_JPortMidiApi_PM_FILT_FD 8192L
+#undef jportmidi_JPortMidiApi_PM_FILT_UNDEFINED
+#define jportmidi_JPortMidiApi_PM_FILT_UNDEFINED 8192L
+#undef jportmidi_JPortMidiApi_PM_FILT_RESET
+#define jportmidi_JPortMidiApi_PM_FILT_RESET 32768L
+#undef jportmidi_JPortMidiApi_PM_FILT_REALTIME
+#define jportmidi_JPortMidiApi_PM_FILT_REALTIME 16641L
+#undef jportmidi_JPortMidiApi_PM_FILT_NOTE
+#define jportmidi_JPortMidiApi_PM_FILT_NOTE 50331648L
+#undef jportmidi_JPortMidiApi_PM_FILT_CHANNEL_AFTERTOUCH
+#define jportmidi_JPortMidiApi_PM_FILT_CHANNEL_AFTERTOUCH 536870912L
+#undef jportmidi_JPortMidiApi_PM_FILT_POLY_AFTERTOUCH
+#define jportmidi_JPortMidiApi_PM_FILT_POLY_AFTERTOUCH 67108864L
+#undef jportmidi_JPortMidiApi_PM_FILT_AFTERTOUCH
+#define jportmidi_JPortMidiApi_PM_FILT_AFTERTOUCH 603979776L
+#undef jportmidi_JPortMidiApi_PM_FILT_PROGRAM
+#define jportmidi_JPortMidiApi_PM_FILT_PROGRAM 268435456L
+#undef jportmidi_JPortMidiApi_PM_FILT_CONTROL
+#define jportmidi_JPortMidiApi_PM_FILT_CONTROL 134217728L
+#undef jportmidi_JPortMidiApi_PM_FILT_PITCHBEND
+#define jportmidi_JPortMidiApi_PM_FILT_PITCHBEND 1073741824L
+#undef jportmidi_JPortMidiApi_PM_FILT_MTC
+#define jportmidi_JPortMidiApi_PM_FILT_MTC 2L
+#undef jportmidi_JPortMidiApi_PM_FILT_SONG_POSITION
+#define jportmidi_JPortMidiApi_PM_FILT_SONG_POSITION 4L
+#undef jportmidi_JPortMidiApi_PM_FILT_SONG_SELECT
+#define jportmidi_JPortMidiApi_PM_FILT_SONG_SELECT 8L
+#undef jportmidi_JPortMidiApi_PM_FILT_TUNE
+#define jportmidi_JPortMidiApi_PM_FILT_TUNE 64L
+#undef jportmidi_JPortMidiApi_PM_FILT_SYSTEMCOMMON
+#define jportmidi_JPortMidiApi_PM_FILT_SYSTEMCOMMON 78L
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_Initialize
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Initialize
+ (JNIEnv *, jclass);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_Terminate
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Terminate
+ (JNIEnv *, jclass);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_HasHostError
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1HasHostError
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_GetErrorText
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetErrorText
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_GetHostErrorText
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetHostErrorText
+ (JNIEnv *, jclass);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_CountDevices
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1CountDevices
+ (JNIEnv *, jclass);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_GetDefaultInputDeviceID
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultInputDeviceID
+ (JNIEnv *, jclass);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_GetDefaultOutputDeviceID
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultOutputDeviceID
+ (JNIEnv *, jclass);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_GetDeviceInterf
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInterf
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_GetDeviceName
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceName
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_GetDeviceInput
+ * Signature: (I)Z
+ */
+JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInput
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_GetDeviceOutput
+ * Signature: (I)Z
+ */
+JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceOutput
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_OpenInput
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;ILjava/lang/String;I)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenInput
+ (JNIEnv *, jclass, jobject, jint, jstring, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_OpenOutput
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;ILjava/lang/String;II)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenOutput
+ (JNIEnv *, jclass, jobject, jint, jstring, jint, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_SetFilter
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetFilter
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_SetChannelMask
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetChannelMask
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_Abort
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Abort
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_Close
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Close
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_Read
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;Ljportmidi/JPortMidiApi/PmEvent;)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Read
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_Poll
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Poll
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_Write
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;Ljportmidi/JPortMidiApi/PmEvent;)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Write
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_WriteShort
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;II)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteShort
+ (JNIEnv *, jclass, jobject, jint, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pm_WriteSysEx
+ * Signature: (Ljportmidi/JPortMidiApi/PortMidiStream;I[B)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteSysEx
+ (JNIEnv *, jclass, jobject, jint, jbyteArray);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pt_TimeStart
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStart
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pt_TimeStop
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStop
+ (JNIEnv *, jclass);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pt_Time
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1Time
+ (JNIEnv *, jclass);
+
+/*
+ * Class: jportmidi_JPortMidiApi
+ * Method: Pt_TimeStarted
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStarted
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* Header for class jportmidi_JPortMidiApi_PmEvent */
+
+#ifndef _Included_jportmidi_JPortMidiApi_PmEvent
+#define _Included_jportmidi_JPortMidiApi_PmEvent
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* Header for class jportmidi_JPortMidiApi_PortMidiStream */
+
+#ifndef _Included_jportmidi_JPortMidiApi_PortMidiStream
+#define _Included_jportmidi_JPortMidiApi_PortMidiStream
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/portmidi/pm_java/pmjni/pmjni.c b/portmidi/pm_java/pmjni/pmjni.c
new file mode 100644
index 0000000..c60cffb
--- /dev/null
+++ b/portmidi/pm_java/pmjni/pmjni.c
@@ -0,0 +1,354 @@
+#include "portmidi.h"
+#include "porttime.h"
+#include "jportmidi_JportMidiApi.h"
+#include <stdio.h>
+
+// these macros assume JNIEnv *env is declared and valid:
+//
+#define CLASS(c, obj) jclass c = (*env)->GetObjectClass(env, obj)
+#define ADDRESS_FID(fid, c) \
+ jfieldID fid = (*env)->GetFieldID(env, c, "address", "J")
+// Uses Java Long (64-bit) to make sure there is room to store a
+// pointer. Cast this to a C long (either 32 or 64 bit) to match
+// the size of a pointer. Finally cast int to pointer. All this
+// is supposed to avoid C compiler warnings and (worse) losing
+// address bits.
+#define PMSTREAM(obj, fid) ((PmStream *) (intptr_t) (*env)->GetLongField(env, obj, fid))
+// Cast stream to long to convert integer to pointer, then expand
+// integer to 64-bit jlong. This avoids compiler warnings.
+#define SET_PMSTREAM(obj, fid, stream) \
+ (*env)->SetLongField(env, obj, fid, (jlong) (intptr_t) stream)
+
+
+/*
+ * Method: Pm_Initialize
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Initialize
+ (JNIEnv *env, jclass cl)
+{
+ return Pm_Initialize();
+}
+
+
+/*
+ * Method: Pm_Terminate
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Terminate
+ (JNIEnv *env, jclass cl)
+{
+ return Pm_Terminate();
+}
+
+
+/*
+ * Method: Pm_HasHostError
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1HasHostError
+ (JNIEnv *env, jclass cl, jobject jstream)
+{
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ return Pm_HasHostError(PMSTREAM(jstream, fid));
+}
+
+
+/*
+ * Method: Pm_GetErrorText
+ */
+JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetErrorText
+ (JNIEnv *env, jclass cl, jint i)
+{
+ return (*env)->NewStringUTF(env, Pm_GetErrorText(i));
+}
+
+
+/*
+ * Method: Pm_GetHostErrorText
+ */
+JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetHostErrorText
+ (JNIEnv *env, jclass cl)
+{
+ char msg[PM_HOST_ERROR_MSG_LEN];
+ Pm_GetHostErrorText(msg, PM_HOST_ERROR_MSG_LEN);
+ return (*env)->NewStringUTF(env, msg);
+}
+
+
+/*
+ * Method: Pm_CountDevices
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1CountDevices
+ (JNIEnv *env, jclass cl)
+{
+ return Pm_CountDevices();
+}
+
+
+/*
+ * Method: Pm_GetDefaultInputDeviceID
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultInputDeviceID
+ (JNIEnv *env, jclass cl)
+{
+ return Pm_GetDefaultInputDeviceID();
+}
+
+
+/*
+ * Method: Pm_GetDefaultOutputDeviceID
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDefaultOutputDeviceID
+ (JNIEnv *env, jclass cl)
+{
+ return Pm_GetDefaultOutputDeviceID();
+}
+
+
+/*
+ * Method: Pm_GetDeviceInterf
+ */
+JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInterf
+ (JNIEnv *env, jclass cl, jint i)
+{
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (!info) return NULL;
+ return (*env)->NewStringUTF(env, info->interf);
+}
+
+
+/*
+ * Method: Pm_GetDeviceName
+ */
+JNIEXPORT jstring JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceName
+ (JNIEnv *env, jclass cl, jint i)
+{
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (!info) return NULL;
+ return (*env)->NewStringUTF(env, info->name);
+}
+
+
+/*
+ * Method: Pm_GetDeviceInput
+ */
+JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceInput
+ (JNIEnv *env, jclass cl, jint i)
+{
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (!info) return (jboolean) 0;
+ return (jboolean) info->input;
+}
+
+
+/*
+ * Method: Pm_GetDeviceOutput
+ */
+JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pm_1GetDeviceOutput
+ (JNIEnv *env, jclass cl, jint i)
+{
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (!info) return (jboolean) 0;
+ return (jboolean) info->output;
+}
+
+
+/*
+ * Method: Pm_OpenInput
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenInput
+ (JNIEnv *env, jclass cl,
+ jobject jstream, jint index, jstring extras, jint bufsiz)
+{
+ PmError rslt;
+ PortMidiStream *stream;
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ rslt = Pm_OpenInput(&stream, index, NULL, bufsiz, NULL, NULL);
+ SET_PMSTREAM(jstream, fid, stream);
+ return rslt;
+}
+
+
+/*
+ * Method: Pm_OpenOutput
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1OpenOutput
+ (JNIEnv *env, jclass cl, jobject jstream, jint index, jstring extras,
+ jint bufsiz, jint latency)
+{
+ PmError rslt;
+ PortMidiStream *stream;
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ rslt = Pm_OpenOutput(&stream, index, NULL, bufsiz, NULL, NULL, latency);
+ SET_PMSTREAM(jstream, fid, stream);
+ return rslt;
+}
+
+
+/*
+ * Method: Pm_SetFilter
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetFilter
+ (JNIEnv *env, jclass cl, jobject jstream, jint filters)
+{
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ return Pm_SetFilter(PMSTREAM(jstream, fid), filters);
+}
+
+
+/*
+ * Method: Pm_SetChannelMask
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1SetChannelMask
+ (JNIEnv *env, jclass cl, jobject jstream, jint mask)
+{
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ return Pm_SetChannelMask(PMSTREAM(jstream, fid), mask);
+}
+
+
+/*
+ * Method: Pm_Abort
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Abort
+ (JNIEnv *env, jclass cl, jobject jstream)
+{
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ return Pm_Abort(PMSTREAM(jstream, fid));
+}
+
+
+/*
+ * Method: Pm_Close
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Close
+ (JNIEnv *env, jclass cl, jobject jstream)
+{
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ return Pm_Close(PMSTREAM(jstream, fid));
+}
+
+
+/*
+ * Method: Pm_Read
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Read
+ (JNIEnv *env, jclass cl, jobject jstream, jobject jpmevent)
+{
+ CLASS(jstream_class, jstream);
+ ADDRESS_FID(address_fid, jstream_class);
+ jclass jpmevent_class = (*env)->GetObjectClass(env, jpmevent);
+ jfieldID message_fid =
+ (*env)->GetFieldID(env, jpmevent_class, "message", "I");
+ jfieldID timestamp_fid =
+ (*env)->GetFieldID(env, jpmevent_class, "timestamp", "I");
+ PmEvent buffer;
+ PmError rslt = Pm_Read(PMSTREAM(jstream, address_fid), &buffer, 1);
+ (*env)->SetIntField(env, jpmevent, message_fid, buffer.message);
+ (*env)->SetIntField(env, jpmevent, timestamp_fid, buffer.timestamp);
+ return rslt;
+}
+
+
+/*
+ * Method: Pm_Poll
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Poll
+ (JNIEnv *env, jclass cl, jobject jstream)
+{
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ return Pm_Poll(PMSTREAM(jstream, fid));
+}
+
+
+/*
+ * Method: Pm_Write
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1Write
+ (JNIEnv *env, jclass cl, jobject jstream, jobject jpmevent)
+{
+ CLASS(jstream_class, jstream);
+ ADDRESS_FID(address_fid, jstream_class);
+ jclass jpmevent_class = (*env)->GetObjectClass(env, jpmevent);
+ jfieldID message_fid =
+ (*env)->GetFieldID(env, jpmevent_class, "message", "I");
+ jfieldID timestamp_fid =
+ (*env)->GetFieldID(env, jpmevent_class, "timestamp", "I");
+ // note that we call WriteShort because it's simpler than constructing
+ // a buffer and passing it to Pm_Write
+ return Pm_WriteShort(PMSTREAM(jstream, address_fid),
+ (*env)->GetIntField(env, jpmevent, timestamp_fid),
+ (*env)->GetIntField(env, jpmevent, message_fid));
+}
+
+
+/*
+ * Method: Pm_WriteShort
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteShort
+ (JNIEnv *env, jclass cl, jobject jstream, jint when, jint msg)
+{
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ return Pm_WriteShort(PMSTREAM(jstream, fid), when, msg);
+}
+
+
+/*
+ * Method: Pm_WriteSysEx
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pm_1WriteSysEx
+ (JNIEnv *env, jclass cl, jobject jstream, jint when, jbyteArray jmsg)
+{
+ CLASS(c, jstream);
+ ADDRESS_FID(fid, c);
+ jbyte *bytes = (*env)->GetByteArrayElements(env, jmsg, 0);
+ PmError rslt = Pm_WriteSysEx(PMSTREAM(jstream, fid), when,
+ (unsigned char *) bytes);
+ (*env)->ReleaseByteArrayElements(env, jmsg, bytes, 0);
+ return rslt;
+}
+
+/*
+ * Method: Pt_TimeStart
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStart
+ (JNIEnv *env, jclass c, jint resolution)
+{
+ return Pt_Start(resolution, NULL, NULL);
+}
+
+/*
+ * Method: Pt_TimeStop
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStop
+ (JNIEnv *env, jclass c)
+ {
+ return Pt_Stop();
+ }
+
+/*
+ * Method: Pt_Time
+ */
+JNIEXPORT jint JNICALL Java_jportmidi_JPortMidiApi_Pt_1Time
+ (JNIEnv *env, jclass c)
+ {
+ return Pt_Time();
+ }
+
+/*
+ * Method: Pt_TimeStarted
+ */
+JNIEXPORT jboolean JNICALL Java_jportmidi_JPortMidiApi_Pt_1TimeStarted
+ (JNIEnv *env, jclass c)
+{
+ return Pt_Started();
+}
+
+
diff --git a/portmidi/pm_java/pmjni/pmjni.rc b/portmidi/pm_java/pmjni/pmjni.rc
new file mode 100644
index 0000000..1b7522b
--- /dev/null
+++ b/portmidi/pm_java/pmjni/pmjni.rc
@@ -0,0 +1,63 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/portmidi/pm_linux/README_LINUX.txt b/portmidi/pm_linux/README_LINUX.txt
new file mode 100755
index 0000000..cfbc43f
--- /dev/null
+++ b/portmidi/pm_linux/README_LINUX.txt
@@ -0,0 +1,99 @@
+README_LINUX.txt for PortMidi
+Roger Dannenberg
+6 Dec 2012, revised May 2022
+
+Contents:
+ To make PortMidi
+ The pmdefaults program
+ Setting LD_LIBRARY_PATH
+ A note about amd64
+ Using autoconf
+ Using configure
+ Changelog
+
+
+See ../README.md for general instructions.
+
+THE pmdefaults PROGRAM
+
+(This may be obsolete. It is older than `../README.md` which
+also discusses pmdefaults, and Java support may be removed
+unless someone claims they use it... -RBD)
+
+You should install pmdefaults. It provides a graphical interface
+for selecting default MIDI IN and OUT devices so that you don't
+have to build device selection interfaces into all your programs
+and so users have a single place to set a preference.
+
+Follow the instructions above to run ccmake, making sure that
+CMAKE_BUILD_TYPE is Release. Run make as described above. Then:
+
+sudo make install
+
+This will install PortMidi libraries and the pmdefault program.
+You must alos have the environment variable LD_LIBRARY_PATH set
+to include /usr/local/lib (where libpmjni.so is installed).
+
+Now, you can run pmdefault.
+
+
+SETTING LD_LIBRARY_PATH
+
+pmdefaults will not work unless LD_LIBRARY_PATH includes a
+directory (normally /usr/local/lib) containing libpmjni.so,
+installed as described above.
+
+To set LD_LIBRARY_PATH, you might want to add this to your
+~/.profile (if you use the bash shell):
+
+LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
+export LD_LIBRARY_PATH
+
+
+A NOTE ABOUT AMD64:
+
+When compiling portmidi under linux on an AMD64, I had to add the -fPIC
+flag to the gcc flags.
+
+Reason: when trying to build John Harrison's pyPortMidi gcc bailed out
+with this error:
+
+./linux/libportmidi.a(pmlinux.o): relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC
+./linux/libportmidi.a: could not read symbols: Bad value
+collect2: ld returned 1 exit status
+error: command 'gcc' failed with exit status 1
+
+What they said:
+http://www.gentoo.org/proj/en/base/amd64/howtos/index.xml?part=1&chap=3
+On certain architectures (AMD64 amongst them), shared libraries *must*
+be "PIC-enabled".
+
+CHANGELOG
+
+27-may-2022 Roger B. Dannenberg
+ Some updates to this file.
+
+6-dec-2012 Roger B. Dannenberg
+ Copied notes on Autoconf from Audacity sources
+
+22-jan-2010 Roger B. Dannenberg
+ Updated instructions about Java paths
+
+14-oct-2009 Roger B. Dannenberg
+ Using CMake now for building and configuration
+
+29-aug-2006 Roger B. Dannenberg
+ Fixed PortTime to join with time thread for clean exit.
+
+28-aug-2006 Roger B. Dannenberg
+ Updated this documentation.
+
+08-Jun-2004 Roger B. Dannenberg
+ Updated code to use new system abstraction.
+
+12-Apr-2003 Roger B. Dannenberg
+ Fixed pm_test/test.c to filter clocks and active messages.
+ Integrated changes from Clemens Ladisch:
+ cleaned up pmlinuxalsa.c
+ record timestamp on sysex input
+ deallocate some resources previously left open
diff --git a/portmidi/pm_linux/pmlinux.c b/portmidi/pm_linux/pmlinux.c
new file mode 100755
index 0000000..3766427
--- /dev/null
+++ b/portmidi/pm_linux/pmlinux.c
@@ -0,0 +1,68 @@
+/* pmlinux.c -- PortMidi os-dependent code */
+
+/* This file only needs to implement pm_init(), which calls various
+ routines to register the available midi devices. This file must
+ be separate from the main portmidi.c file because it is system
+ dependent, and it is separate from, pmlinuxalsa.c, because it
+ might need to register non-alsa devices as well.
+
+ NOTE: if you add non-ALSA support, you need to fix :alsa_poll()
+ in pmlinuxalsa.c, which assumes all input devices are ALSA.
+ */
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+
+#ifdef PMALSA
+ #include "pmlinuxalsa.h"
+#endif
+
+#ifdef PMNULL
+ #include "pmlinuxnull.h"
+#endif
+
+#if !(defined(PMALSA) || defined(PMNULL))
+#error One of PMALSA or PMNULL must be defined
+#endif
+
+void pm_init()
+{
+ /* Note: it is not an error for PMALSA to fail to initialize.
+ * It may be a design error that the client cannot query what subsystems
+ * are working properly other than by looking at the list of available
+ * devices.
+ */
+#ifdef PMALSA
+ pm_linuxalsa_init();
+#endif
+#ifdef PMNULL
+ pm_linuxnull_init();
+#endif
+}
+
+void pm_term(void)
+{
+ #ifdef PMALSA
+ pm_linuxalsa_term();
+ #endif
+ #ifdef PMNULL
+ pm_linuxnull_term();
+ #endif
+}
+
+PmDeviceID Pm_GetDefaultInputDeviceID() {
+ Pm_Initialize();
+ return pm_default_input_device_id;
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID() {
+ Pm_Initialize();
+ return pm_default_output_device_id;
+}
+
+void *pm_alloc(size_t s) { return malloc(s); }
+
+void pm_free(void *ptr) { free(ptr); }
+
diff --git a/portmidi/pm_linux/pmlinuxalsa.c b/portmidi/pm_linux/pmlinuxalsa.c
new file mode 100755
index 0000000..b2e43e1
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxalsa.c
@@ -0,0 +1,893 @@
+/*
+ * pmlinuxalsa.c -- system specific definitions
+ *
+ * written by:
+ * Roger Dannenberg (port to Alsa 0.9.x)
+ * Clemens Ladisch (provided code examples and invaluable consulting)
+ * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation)
+ */
+
+/* omit this code if PMALSA is not defined -- this mechanism allows
+ * selection of different MIDI interfaces (at compile time).
+ */
+#ifdef PMALSA
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "pmlinuxalsa.h"
+#include "string.h"
+#include "porttime.h"
+
+#include <alsa/asoundlib.h>
+
+/* I used many print statements to debug this code. I left them in the
+ * source, and you can turn them on by changing false to true below:
+ */
+#define VERBOSE_ON 0
+#define VERBOSE if (VERBOSE_ON)
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9
+#error needs ALSA 0.9.0 or later
+#endif
+
+/* to store client/port in the device descriptor */
+#define MAKE_DESCRIPTOR(client, port) ((void*)(long)(((client) << 8) | (port)))
+#define GET_DESCRIPTOR_CLIENT(info) ((((long)(info)) >> 8) & 0xff)
+#define GET_DESCRIPTOR_PORT(info) (((long)(info)) & 0xff)
+
+#define BYTE unsigned char
+
+extern pm_fns_node pm_linuxalsa_in_dictionary;
+extern pm_fns_node pm_linuxalsa_out_dictionary;
+
+static snd_seq_t *seq = NULL; // all input comes here,
+ // output queue allocated on seq
+static int queue, queue_used; /* one for all ports, reference counted */
+
+#define PORT_IS_CLOSED -999999
+
+typedef struct alsa_info_struct {
+ int is_virtual;
+ int client;
+ int port;
+ int this_port;
+ int in_sysex;
+ snd_midi_event_t *parser;
+} alsa_info_node, *alsa_info_type;
+
+
+/* get_alsa_error_text -- copy error text to potentially short string */
+/**/
+static void get_alsa_error_text(char *msg, int len, int err)
+{
+ int errlen = strlen(snd_strerror(err));
+ if (errlen > 0 && errlen < len) {
+ strcpy(msg, snd_strerror(err));
+ } else if (len > 20) {
+ sprintf(msg, "Alsa error %d", err);
+ } else {
+ msg[0] = 0;
+ }
+}
+
+
+static PmError check_hosterror(int err)
+{
+ if (err < 0) {
+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+
+/* queue is shared by both input and output, reference counted */
+static PmError alsa_use_queue(void)
+{
+ int err = 0;
+ if (queue_used == 0) {
+ snd_seq_queue_tempo_t *tempo;
+
+ queue = snd_seq_alloc_queue(seq);
+ if (queue < 0) {
+ return check_hosterror(queue);
+ }
+ snd_seq_queue_tempo_alloca(&tempo);
+ snd_seq_queue_tempo_set_tempo(tempo, 480000);
+ snd_seq_queue_tempo_set_ppq(tempo, 480);
+ err = snd_seq_set_queue_tempo(seq, queue, tempo);
+ if (err < 0) {
+ return check_hosterror(err);
+ }
+ snd_seq_start_queue(seq, queue, NULL);
+ snd_seq_drain_output(seq);
+ }
+ ++queue_used;
+ return pmNoError;
+}
+
+
+static void alsa_unuse_queue(void)
+{
+ if (--queue_used == 0) {
+ snd_seq_stop_queue(seq, queue, NULL);
+ snd_seq_drain_output(seq);
+ snd_seq_free_queue(seq, queue);
+ VERBOSE printf("queue freed\n");
+ }
+}
+
+
+/* midi_message_length -- how many bytes in a message? */
+static int midi_message_length(PmMessage message)
+{
+ message &= 0xff;
+ if (message < 0x80) {
+ return 0;
+ } else if (message < 0xf0) {
+ static const int length[] = {3, 3, 3, 3, 2, 2, 3};
+ return length[(message - 0x80) >> 4];
+ } else {
+ static const int length[] = {
+ -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1};
+ return length[message - 0xf0];
+ }
+}
+
+
+static alsa_info_type alsa_info_create(int client_port, long id, int is_virtual)
+{
+ alsa_info_type info = (alsa_info_type) pm_alloc(sizeof(alsa_info_node));
+ info->is_virtual = is_virtual;
+ info->this_port = id;
+ info->client = GET_DESCRIPTOR_CLIENT(client_port);
+ info->port = GET_DESCRIPTOR_PORT(client_port);
+ info->in_sysex = 0;
+ return info;
+}
+
+
+/* search system dependent extra parameters for string */
+static const char *get_sysdep_name(enum PmSysDepPropertyKey key,
+ PmSysDepInfo *info)
+{
+ /* the version where all current properties were introduced is 210 */
+ if (info && info->structVersion >= 210) {
+ int i;
+ for (i = 0; i < info->length; i++) { /* search for key */
+ if (info->properties[i].key == key) {
+ return info->properties[i].value;
+ }
+ }
+ }
+ return NULL;
+}
+
+
+static void maybe_set_client_name(PmSysDepInfo *driverInfo)
+{
+ if (!seq) { // make sure seq is created and we have info
+ return;
+ }
+
+ const char *client_name = get_sysdep_name(pmKeyAlsaClientName,
+ (PmSysDepInfo *) driverInfo);
+ if (client_name) {
+ snd_seq_set_client_name(seq, client_name);
+ printf("maybe_set_client_name set client to %s\n", client_name);
+ }
+}
+
+
+static PmError alsa_out_open(PmInternal *midi, void *driverInfo)
+{
+ int id = midi->device_id;
+ void *client_port = pm_descriptors[id].descriptor;
+ alsa_info_type ainfo = alsa_info_create((long) client_port, id,
+ pm_descriptors[id].pub.is_virtual);
+ snd_seq_port_info_t *pinfo;
+ int err = 0;
+ int using_the_queue = 0;
+
+ if (!ainfo) return pmInsufficientMemory;
+ midi->api_info = ainfo;
+
+ snd_seq_port_info_alloca(&pinfo);
+ if (!ainfo->is_virtual) {
+ snd_seq_port_info_set_port(pinfo, id);
+ snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ);
+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SND_SEQ_PORT_TYPE_APPLICATION);
+ const char *port_name = get_sysdep_name(pmKeyAlsaPortName,
+ (PmSysDepInfo *) driverInfo);
+ if (port_name) {
+ snd_seq_port_info_set_name(pinfo, port_name);
+ }
+ snd_seq_port_info_set_port_specified(pinfo, 1);
+
+ err = snd_seq_create_port(seq, pinfo);
+ if (err < 0) goto free_ainfo;
+
+ }
+
+ err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &ainfo->parser);
+ if (err < 0) goto free_this_port;
+
+ if (midi->latency > 0) { /* must delay output using a queue */
+ err = alsa_use_queue();
+ if (err < 0) goto free_parser;
+ using_the_queue++;
+ }
+
+ if (!ainfo->is_virtual) {
+ err = snd_seq_connect_to(seq, ainfo->this_port, ainfo->client,
+ ainfo->port);
+ if (err < 0) goto unuse_queue; /* clean up and return on error */
+ }
+
+ maybe_set_client_name(driverInfo);
+
+ return pmNoError;
+
+ unuse_queue:
+ if (using_the_queue > 0) /* only for latency>0 case */
+ alsa_unuse_queue();
+ free_parser:
+ snd_midi_event_free(ainfo->parser);
+ free_this_port:
+ snd_seq_delete_port(seq, ainfo->this_port);
+ free_ainfo:
+ pm_free(ainfo);
+ return check_hosterror(err);
+}
+
+
+static PmError alsa_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp)
+{
+ alsa_info_type info = (alsa_info_type) midi->api_info;
+ snd_seq_event_t ev;
+ int err = 0;
+
+ snd_seq_ev_clear(&ev);
+ if (snd_midi_event_encode_byte(info->parser, byte, &ev) == 1) {
+ if (info->is_virtual) {
+ snd_seq_ev_set_subs(&ev);
+ } else {
+ snd_seq_ev_set_dest(&ev, info->client, info->port);
+ }
+ snd_seq_ev_set_source(&ev, info->this_port);
+ if (midi->latency > 0) {
+ /* compute relative time of event = timestamp - now + latency */
+ PmTimestamp now = (midi->time_proc ?
+ midi->time_proc(midi->time_info) :
+ Pt_Time());
+ int when = timestamp;
+ /* if timestamp is zero, send immediately */
+ /* otherwise compute time delay and use delay if positive */
+ if (when == 0) when = now;
+ when = (when - now) + midi->latency;
+ if (when < 0) when = 0;
+ VERBOSE printf("timestamp %d now %d latency %d, ",
+ (int) timestamp, (int) now, midi->latency);
+ VERBOSE printf("scheduling event after %d\n", when);
+ /* message is sent in relative ticks, where 1 tick = 1 ms */
+ snd_seq_ev_schedule_tick(&ev, queue, 1, when);
+ /* NOTE: for cases where the user does not supply a time function,
+ we could optimize the code by not starting Pt_Time and using
+ the alsa tick time instead. I didn't do this because it would
+ entail changing the queue management to start the queue tick
+ count when PortMidi is initialized and keep it running until
+ PortMidi is terminated. (This should be simple, but it's not
+ how the code works now.) -RBD */
+ } else { /* send event out without queueing */
+ VERBOSE printf("direct\n");
+ /* ev.queue = SND_SEQ_QUEUE_DIRECT;
+ ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */
+ snd_seq_ev_set_direct(&ev);
+ }
+ VERBOSE printf("sending event, timestamp %d (%d+%dns) (%s, %s)\n",
+ ev.time.tick, ev.time.time.tv_sec, ev.time.time.tv_nsec,
+ (ev.flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),
+ (ev.flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));
+ err = snd_seq_event_output(seq, &ev);
+ }
+ return check_hosterror(err);
+}
+
+
+static PmError alsa_out_close(PmInternal *midi)
+{
+ alsa_info_type info = (alsa_info_type) midi->api_info;
+ int err = 0;
+ int error2 = 0;
+ if (!info) return pmBadPtr;
+
+ if (info->this_port != PORT_IS_CLOSED) {
+ if (!info->is_virtual) {
+ err = snd_seq_disconnect_to(seq, info->this_port,
+ info->client, info->port);
+ /* even if there was an error, we still try to delete the port */
+ error2 = snd_seq_delete_port(seq, info->this_port);
+
+ if (!err) { /* retain original error if there was one */
+ err = error2; /* otherwise, use port delete status */
+ }
+ }
+ }
+ if (midi->latency > 0) alsa_unuse_queue();
+ snd_midi_event_free(info->parser);
+ midi->api_info = NULL; /* destroy the pointer to signify "closed" */
+ pm_free(info);
+ return check_hosterror(err);
+}
+
+
+static PmError alsa_create_virtual(int is_input, const char *name,
+ void *device_info)
+{
+ snd_seq_port_info_t *pinfo;
+ int err;
+ int client, port;
+
+ /* we need the id to set the port. */
+ PmDeviceID id = pm_add_device("ALSA", name, is_input, TRUE, NULL,
+ (is_input ? &pm_linuxalsa_in_dictionary :
+ &pm_linuxalsa_out_dictionary));
+ if (id < 0) { /* error -- out of memory? */
+ return id;
+ }
+ snd_seq_port_info_alloca(&pinfo);
+ snd_seq_port_info_set_capability(pinfo,
+ (is_input ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE :
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ));
+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SND_SEQ_PORT_TYPE_APPLICATION);
+ snd_seq_port_info_set_name(pinfo, name);
+ snd_seq_port_info_set_port(pinfo, id);
+ snd_seq_port_info_set_port_specified(pinfo, 1);
+ /* next 3 lines needed to generate timestamp - PaulLiu */
+ snd_seq_port_info_set_timestamping(pinfo, 1);
+ snd_seq_port_info_set_timestamp_real(pinfo, 0);
+ snd_seq_port_info_set_timestamp_queue(pinfo, queue);
+
+ err = snd_seq_create_port(seq, pinfo);
+ if (err < 0) {
+ pm_undo_add_device(id);
+ return check_hosterror(err);
+ }
+
+ client = snd_seq_port_info_get_client(pinfo);
+ port = snd_seq_port_info_get_port(pinfo);
+ pm_descriptors[id].descriptor = MAKE_DESCRIPTOR(client, port);
+ return id;
+}
+
+
+ static PmError alsa_delete_virtual(PmDeviceID id)
+ {
+ int err = snd_seq_delete_port(seq, id);
+ return check_hosterror(err);
+ }
+
+
+static PmError alsa_in_open(PmInternal *midi, void *driverInfo)
+{
+ int id = midi->device_id;
+ void *client_port = pm_descriptors[id].descriptor;
+ alsa_info_type ainfo = alsa_info_create((long) client_port, id,
+ pm_descriptors[id].pub.is_virtual);
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_subscribe_t *sub;
+ snd_seq_addr_t addr;
+ int err = 0;
+ int is_virtual = pm_descriptors[id].pub.is_virtual;
+
+ if (!ainfo) return pmInsufficientMemory;
+ midi->api_info = ainfo;
+
+ err = alsa_use_queue();
+ if (err < 0) goto free_ainfo;
+
+ snd_seq_port_info_alloca(&pinfo);
+ if (is_virtual) {
+ ainfo->is_virtual = TRUE;
+ if (snd_seq_get_port_info(seq, ainfo->port, pinfo)) {
+ pinfo = NULL;
+ goto free_ainfo;
+ }
+ } else {
+ /* create a port for this alsa client (seq) where the port
+ number matches the portmidi device ID of the input device */
+ snd_seq_port_info_set_port(pinfo, id);
+ snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_WRITE);
+
+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SND_SEQ_PORT_TYPE_APPLICATION);
+ snd_seq_port_info_set_port_specified(pinfo, 1);
+
+ const char *port_name = get_sysdep_name(pmKeyAlsaPortName,
+ (PmSysDepInfo *) driverInfo);
+ if (port_name) {
+ snd_seq_port_info_set_name(pinfo, port_name);
+ }
+
+ err = snd_seq_create_port(seq, pinfo);
+ if (err < 0) goto free_queue;
+
+ /* forward messages from input to this alsa client, so this
+ * alsa client is the destination, and the destination port is the
+ * port we just created using the device ID as port number
+ */
+ snd_seq_port_subscribe_alloca(&sub);
+ addr.client = snd_seq_client_id(seq);
+ addr.port = ainfo->this_port;
+ snd_seq_port_subscribe_set_dest(sub, &addr);
+
+ /* forward from the sender which is the device named by
+ client and port */
+ addr.client = ainfo->client;
+ addr.port = ainfo->port;
+ snd_seq_port_subscribe_set_sender(sub, &addr);
+ snd_seq_port_subscribe_set_time_update(sub, 1);
+ /* this doesn't seem to work: messages come in with real timestamps */
+ snd_seq_port_subscribe_set_time_real(sub, 0);
+ err = snd_seq_subscribe_port(seq, sub);
+ if (err < 0) goto free_this_port; /* clean up and return on error */
+ }
+
+ maybe_set_client_name(driverInfo);
+
+ return pmNoError;
+ free_this_port:
+ snd_seq_delete_port(seq, ainfo->this_port);
+ free_queue:
+ alsa_unuse_queue();
+ free_ainfo:
+ pm_free(ainfo);
+ return check_hosterror(err);
+}
+
+static PmError alsa_in_close(PmInternal *midi)
+{
+ int err = 0;
+ alsa_info_type info = (alsa_info_type) midi->api_info;
+ if (!info) return pmBadPtr;
+ /* virtual ports stay open because the represent devices */
+ if (!info->is_virtual && info->this_port != PORT_IS_CLOSED) {
+ err = snd_seq_delete_port(seq, info->this_port);
+ }
+ alsa_unuse_queue();
+ midi->api_info = NULL;
+ pm_free(info);
+ return check_hosterror(err);
+}
+
+
+static PmError alsa_abort(PmInternal *midi)
+{
+ /* NOTE: ALSA documentation is vague. This is supposed to
+ * remove any pending output messages. If you can test and
+ * confirm this code is correct, please update this comment. -RBD
+ */
+ /* Unfortunately, I can't even compile it -- my ALSA version
+ * does not implement snd_seq_remove_events_t, so this does
+ * not compile. I'll try again, but it looks like I'll need to
+ * upgrade my entire Linux OS -RBD
+ */
+ /*
+ info_type info = (info_type) midi->api_info;
+ snd_seq_remove_events_t info;
+ snd_seq_addr_t addr;
+ addr.client = info->client;
+ addr.port = info->port;
+ snd_seq_remove_events_set_dest(&info, &addr);
+ snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);
+ pm_hosterror = snd_seq_remove_events(seq, &info);
+ if (pm_hosterror) {
+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
+ pm_hosterror);
+ return pmHostError;
+ }
+ */
+ printf("WARNING: alsa_abort not implemented\n");
+ return pmNoError;
+}
+
+
+static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)
+{
+ int err;
+ alsa_info_type info = (alsa_info_type) midi->api_info;
+ if (!info) return pmBadPtr;
+ VERBOSE printf("snd_seq_drain_output: %p\n", seq);
+ err = snd_seq_drain_output(seq);
+ return check_hosterror(err);
+}
+
+
+static PmError alsa_write_short(PmInternal *midi, PmEvent *event)
+{
+ int bytes = midi_message_length(event->message);
+ PmMessage msg = event->message;
+ int i;
+ alsa_info_type info = (alsa_info_type) midi->api_info;
+ if (!info) return pmBadPtr;
+ for (i = 0; i < bytes; i++) {
+ unsigned char byte = msg;
+ VERBOSE printf("sending 0x%x\n", byte);
+ alsa_write_byte(midi, byte, event->timestamp);
+ if (pm_hosterror) break;
+ msg >>= 8; /* shift next byte into position */
+ }
+ if (pm_hosterror) return pmHostError;
+ return pmNoError;
+}
+
+
+/* alsa_sysex -- implements begin_sysex and end_sysex */
+PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) {
+ return pmNoError;
+}
+
+
+static PmTimestamp alsa_synchronize(PmInternal *midi)
+{
+ return 0; /* linux implementation does not use this synchronize function */
+ /* Apparently, Alsa data is relative to the time you send it, and there
+ is no reference. If this is true, this is a serious shortcoming of
+ Alsa. If not true, then PortMidi has a serious shortcoming -- it
+ should be scheduling relative to Alsa's time reference. */
+}
+
+
+static void handle_event(snd_seq_event_t *ev)
+{
+ int device_id = ev->dest.port;
+ PmInternal *midi = pm_descriptors[device_id].pm_internal;
+ // There is a race condition when closing a device and
+ // continuing to poll other open devices. The closed device may
+ // have outstanding events from before the close operation.
+ if (!midi) {
+ return;
+ }
+ PmEvent pm_ev;
+ PmTimestamp timestamp = midi->time_proc(midi->time_info);
+
+ /* time stamp should be in ticks, using our queue where 1 tick = 1ms */
+ /* assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);
+ * Currently, event timestamp is ignored. See long note below. */
+
+ VERBOSE {
+ /* translate time to time_proc basis */
+ snd_seq_queue_status_t *queue_status;
+ snd_seq_queue_status_alloca(&queue_status);
+ snd_seq_get_queue_status(seq, queue, queue_status);
+ printf("handle_event: alsa_now %d, "
+ "event timestamp %d (%d+%dns) (%s, %s)\n",
+ snd_seq_queue_status_get_tick_time(queue_status),
+ ev->time.tick, ev->time.time.tv_sec, ev->time.time.tv_nsec,
+ (ev->flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),
+ (ev->flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));
+ /* OLD: portmidi timestamp is (now - alsa_now) + alsa_timestamp */
+ /* timestamp = (*time_proc)(midi->time_info) + ev->time.tick -
+ snd_seq_queue_status_get_tick_time(queue_status); */
+ }
+ /* CURRENT: portmidi timestamp is "now". In a test, timestamps from
+ * hardware (MIDI over USB) were timestamped with the current ALSA
+ * time (snd_seq_queue_status_get_tick_time) and flags indicating
+ * absolute ticks, but timestamps from another application's virtual
+ * port, sent direct with 0 absolute ticks, were received with a
+ * large value that is apparently the time since the start time of
+ * the other application. Without any reference to our local time,
+ * this seems useless. PortMidi is supposed to return the local
+ * PortMidi time of the arrival of the message, so the best we can
+ * do is set the timestamp to our local clock. This seems to be a
+ * design flaw in ALSA -- I pointed this out a decade ago, but if
+ * there is a workaround, I'd still like to know. Maybe there is a
+ * way to use absolute real time and maybe that's sharable across
+ * applications by referencing the system time?
+ */
+ pm_ev.timestamp = timestamp;
+ switch (ev->type) {
+ case SND_SEQ_EVENT_NOTEON:
+ pm_ev.message = Pm_Message(0x90 | ev->data.note.channel,
+ ev->data.note.note & 0x7f,
+ ev->data.note.velocity & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_NOTEOFF:
+ pm_ev.message = Pm_Message(0x80 | ev->data.note.channel,
+ ev->data.note.note & 0x7f,
+ ev->data.note.velocity & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_KEYPRESS:
+ pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel,
+ ev->data.note.note & 0x7f,
+ ev->data.note.velocity & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CONTROLLER:
+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
+ ev->data.control.param & 0x7f,
+ ev->data.control.value & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_PGMCHANGE:
+ pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel,
+ ev->data.control.value & 0x7f, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CHANPRESS:
+ pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel,
+ ev->data.control.value & 0x7f, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_PITCHBEND:
+ pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel,
+ (ev->data.control.value + 0x2000) & 0x7f,
+ ((ev->data.control.value + 0x2000) >> 7) & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CONTROL14:
+ if (ev->data.control.param < 0x20) {
+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
+ ev->data.control.param,
+ (ev->data.control.value >> 7) & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
+ ev->data.control.param + 0x20,
+ ev->data.control.value & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ } else {
+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
+ ev->data.control.param & 0x7f,
+ ev->data.control.value & 0x7f);
+
+ pm_read_short(midi, &pm_ev);
+ }
+ break;
+ case SND_SEQ_EVENT_SONGPOS:
+ pm_ev.message = Pm_Message(0xf2,
+ ev->data.control.value & 0x7f,
+ (ev->data.control.value >> 7) & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_SONGSEL:
+ pm_ev.message = Pm_Message(0xf3,
+ ev->data.control.value & 0x7f, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_QFRAME:
+ pm_ev.message = Pm_Message(0xf1,
+ ev->data.control.value & 0x7f, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_START:
+ pm_ev.message = Pm_Message(0xfa, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CONTINUE:
+ pm_ev.message = Pm_Message(0xfb, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_STOP:
+ pm_ev.message = Pm_Message(0xfc, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CLOCK:
+ pm_ev.message = Pm_Message(0xf8, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_TUNE_REQUEST:
+ pm_ev.message = Pm_Message(0xf6, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_RESET:
+ pm_ev.message = Pm_Message(0xff, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_SENSING:
+ pm_ev.message = Pm_Message(0xfe, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_SYSEX: {
+ const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;
+ /* assume there is one sysex byte to process */
+ pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);
+ break;
+ }
+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: {
+ /* this happens if you have an input port open and the
+ * device or application with virtual ports closes. We
+ * mark the port as closed to avoid closing a 2nd time
+ * when Pm_Close() is called.
+ */
+ alsa_info_type info = (alsa_info_type) midi->api_info;
+ /* printf("SND_SEQ_EVENT_UNSUBSCRIBE message\n"); */
+ info->this_port = PORT_IS_CLOSED;
+ break;
+ }
+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+ break; /* someone connected to a virtual output port, not reported */
+ default:
+ printf("portmidi handle_event: not handled type %x\n", ev->type);
+ break;
+ }
+}
+
+
+static PmError alsa_poll(PmInternal *midi)
+{
+ if (!midi) {
+ return pmBadPtr;
+ }
+ snd_seq_event_t *ev;
+ /* expensive check for input data, gets data from device: */
+ while (snd_seq_event_input_pending(seq, TRUE) > 0) {
+ /* cheap check on local input buffer */
+ while (snd_seq_event_input_pending(seq, FALSE) > 0) {
+ /* check for and ignore errors, e.g. input overflow */
+ /* note: if there's overflow, this should be reported
+ * all the way through to client. Since input from all
+ * devices is merged, we need to find all input devices
+ * and set all to the overflow state.
+ * NOTE: this assumes every input is ALSA based.
+ */
+ int rslt = snd_seq_event_input(seq, &ev);
+ if (rslt >= 0) {
+ handle_event(ev);
+ } else if (rslt == -ENOSPC) {
+ int i;
+ for (i = 0; i < pm_descriptor_len; i++) {
+ if (pm_descriptors[i].pub.input) {
+ PmInternal *midi_i = pm_descriptors[i].pm_internal;
+ /* careful, device may not be open! */
+ if (midi_i) Pm_SetOverflow(midi_i->queue);
+ }
+ }
+ }
+ }
+ }
+ return pmNoError;
+}
+
+
+static unsigned int alsa_check_host_error(PmInternal *midi)
+{
+ return FALSE;
+}
+
+
+pm_fns_node pm_linuxalsa_in_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ alsa_synchronize,
+ alsa_in_open,
+ alsa_abort,
+ alsa_in_close,
+ alsa_poll,
+ alsa_check_host_error
+};
+
+pm_fns_node pm_linuxalsa_out_dictionary = {
+ alsa_write_short,
+ alsa_sysex,
+ alsa_sysex,
+ alsa_write_byte,
+ alsa_write_short, /* short realtime message */
+ alsa_write_flush,
+ alsa_synchronize,
+ alsa_out_open,
+ alsa_abort,
+ alsa_out_close,
+ none_poll,
+ alsa_check_host_error
+};
+
+
+/* pm_strdup -- copy a string to the heap. Use this rather than strdup so
+ * that we call pm_alloc, not malloc. This allows portmidi to avoid
+ * malloc which might cause priority inversion. Probably ALSA is going
+ * to call malloc anyway, so this extra work here may be pointless.
+ */
+char *pm_strdup(const char *s)
+{
+ int len = strlen(s);
+ char *dup = (char *) pm_alloc(len + 1);
+ strcpy(dup, s);
+ return dup;
+}
+
+
+PmError pm_linuxalsa_init(void)
+{
+ int err;
+ snd_seq_client_info_t *cinfo;
+ snd_seq_port_info_t *pinfo;
+ unsigned int caps;
+
+ /* Register interface ALSA with create_virtual fn */
+ pm_add_interf("ALSA", &alsa_create_virtual, &alsa_delete_virtual);
+
+ /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this
+ * would cause messages to be dropped if the ALSA buffer fills up.
+ * The correct behavior is for writes to block until there is
+ * room to send all the data. The client should normally allocate
+ * a large enough buffer to avoid blocking on output.
+ * Now that blocking is enabled, the seq_event_input() will block
+ * if there is no input data. This is not what we want, so must
+ * call seq_event_input_pending() to avoid blocking.
+ */
+ err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
+ if (err < 0) goto error_return;
+
+ snd_seq_client_info_alloca(&cinfo);
+ snd_seq_port_info_alloca(&pinfo);
+
+ snd_seq_client_info_set_client(cinfo, -1);
+ while (snd_seq_query_next_client(seq, cinfo) == 0) {
+ snd_seq_port_info_set_client(pinfo,
+ snd_seq_client_info_get_client(cinfo));
+ snd_seq_port_info_set_port(pinfo, -1);
+ while (snd_seq_query_next_port(seq, pinfo) == 0) {
+ if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)
+ continue; /* ignore Timer and Announce ports on client 0 */
+ caps = snd_seq_port_info_get_capability(pinfo);
+ if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ |
+ SND_SEQ_PORT_CAP_SUBS_WRITE)))
+ continue; /* ignore if you cannot read or write port */
+ if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) {
+ if (pm_default_output_device_id == -1)
+ pm_default_output_device_id = pm_descriptor_len;
+ pm_add_device("ALSA",
+ pm_strdup(snd_seq_port_info_get_name(pinfo)),
+ FALSE, FALSE,
+ MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
+ snd_seq_port_info_get_port(pinfo)),
+ &pm_linuxalsa_out_dictionary);
+ }
+ if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {
+ if (pm_default_input_device_id == -1)
+ pm_default_input_device_id = pm_descriptor_len;
+ pm_add_device("ALSA",
+ pm_strdup(snd_seq_port_info_get_name(pinfo)),
+ TRUE, FALSE,
+ MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
+ snd_seq_port_info_get_port(pinfo)),
+ &pm_linuxalsa_in_dictionary);
+ }
+ }
+ }
+ return pmNoError;
+ error_return:
+ pm_linuxalsa_term(); /* clean up */
+ return check_hosterror(err);
+}
+
+
+void pm_linuxalsa_term(void)
+{
+ if (seq) {
+ snd_seq_close(seq);
+ pm_free(pm_descriptors);
+ pm_descriptors = NULL;
+ pm_descriptor_len = 0;
+ pm_descriptor_max = 0;
+ }
+}
+
+#endif
diff --git a/portmidi/pm_linux/pmlinuxalsa.h b/portmidi/pm_linux/pmlinuxalsa.h
new file mode 100755
index 0000000..d4bff16
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxalsa.h
@@ -0,0 +1,6 @@
+/* pmlinuxalsa.h -- system-specific definitions */
+
+PmError pm_linuxalsa_init(void);
+void pm_linuxalsa_term(void);
+
+
diff --git a/portmidi/pm_linux/pmlinuxnull.c b/portmidi/pm_linux/pmlinuxnull.c
new file mode 100644
index 0000000..257f3d6
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxnull.c
@@ -0,0 +1,31 @@
+/*
+ * pmlinuxnull.c -- system specific definitions
+ *
+ * written by:
+ * Roger Dannenberg
+ *
+ * If there is no ALSA, you can define PMNULL and build PortMidi. It will
+ * not report any devices, so you will not be able to open any, but if
+ * you wanted to disable MIDI from some application, this could be used.
+ * Mainly, this code shows the possibility of supporting multiple
+ * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux.
+ * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA.
+ */
+
+#ifdef PMNULL
+
+#include "portmidi.h"
+#include "pmlinuxnull.h"
+
+
+PmError pm_linuxnull_init(void)
+{
+ return pmNoError;
+}
+
+
+void pm_linuxnull_term(void)
+{
+}
+
+#endif
diff --git a/portmidi/pm_linux/pmlinuxnull.h b/portmidi/pm_linux/pmlinuxnull.h
new file mode 100644
index 0000000..9835825
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxnull.h
@@ -0,0 +1,6 @@
+/* pmlinuxnull.h -- system-specific definitions */
+
+PmError pm_linuxnull_init(void);
+void pm_linuxnull_term(void);
+
+
diff --git a/portmidi/pm_mac/Makefile.osx b/portmidi/pm_mac/Makefile.osx
new file mode 100755
index 0000000..2044381
--- /dev/null
+++ b/portmidi/pm_mac/Makefile.osx
@@ -0,0 +1,125 @@
+# MAKEFILE FOR PORTMIDI
+
+# Roger B. Dannenberg
+# Sep 2009
+
+# NOTE: PortMidi is currently built and tested with CMake.
+# This makefile is probably broken, but if you want to
+# directly use make, start here, and please contribute any
+# fixes.
+
+# NOTE: you can use
+# make -f pm_mac/Makefile.osx configuration=Release
+# to override the default Debug configuration
+configuration=Release
+
+PF=/usr/local
+
+# For debugging, define PM_CHECK_ERRORS
+ifeq ($(configuration),Release)
+ CONFIG = Release
+else
+ CONFIG = Debug
+endif
+
+current: all
+
+all: $(CONFIG)/CMakeCache.txt
+ cd $(CONFIG); make
+
+$(CONFIG)/CMakeCache.txt:
+ rm -f $(CONFIG)/CMakeCache.txt
+ mkdir -p $(CONFIG)
+ cd $(CONFIG); cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$(CONFIG)
+
+
+**** For instructions: make -f pm_mac/Makefile.osx help ****\n'
+
+help:
+ echo $$'\n\n\
+This is help for portmidi/pm_mac/Makefile.osx\n\n\
+Installation path for dylib is $(PF)\n\
+To build Release version libraries and test applications,\n \
+make -f pm_mac/Makefile.osx\n\
+To build Debug version libraries and test applications,\n \
+make -f pm_mac/Makefile.osx configuration=Debug\n\
+To install universal dynamic library,\n \
+sudo make -f pm_mac/Makefile.osx install\n\
+To install universal dynamic library with xcode,\n \
+make -f pm_mac/Makefile.osx install-with-xcode\n\
+To make PmDefaults Java application,\n \
+make -f pm_mac/Makefile.osx pmdefaults\n\n \
+configuration = $(configuration)\n'
+
+
+clean:
+ rm -f *.o *~ core* */*.o */*/*.o */*~ */core* pm_test/*/pm_dll.dll
+ rm -f *.opt *.ncb *.plg pm_win/Debug/pm_dll.lib pm_win/Release/pm_dll.lib
+ rm -f pm_test/*.opt pm_test/*.ncb
+ rm -f pm_java/pmjni/*.o pm_java/pmjni/*~ pm_java/*.h
+ rm -rf Release/CMakeFiles Debug/CMakeFiles
+ rm -rf pm_mac/pmdefaults/lib pm_mac/pmdefaults/src
+
+cleaner: clean
+ rm -rf pm_mac/build
+ rm -rf pm_mac/Debug pm_mac/Release pm_test/Debug pm_test/Release
+ rm -f Debug/*.dylib Release/*.dylib
+ rm -f pm_java/pmjni/Debug/*.jnilib
+ rm -f pm_java/pmjni/Release/*.jnilib
+
+cleanest: cleaner
+ rm -f Debug/CMakeCache.txt Release/CMakeCache.txt
+ rm -f CMakeCache.txt
+ rm -f Debug/libportmidi_s.a Release/libportmidi_s.a
+ rm -f pm_test/Debug/test pm_test/Debug/sysex pm_test/Debug/midithread
+ rm -f pm_test/Debug/latency pm_test/Debug/midithru
+ rm -f pm_test/Debug/qtest pm_test/Debug/mm
+ rm -f pm_test/Release/test pm_test/Release/sysex pm_test/Release/midithread
+ rm -f pm_test/Release/latency pm_test/Release/midithru
+ rm -f pm_test/Release/qtest pm_test/Release/mm
+ rm -f pm_java/*/*.class
+ rm -f pm_java/pmjni/jportmidi_JPortMidiApi_PortMidiStream.h
+
+backup: cleanest
+ cd ..; zip -r portmidi.zip portmidi
+
+install: porttime/porttime.h pm_common/portmidi.h \
+ $(CONFIG)/libportmidi.dylib
+ install porttime/porttime.h $(PF)/include/
+ install pm_common/portmidi.h $(PF)/include
+ install $(CONFIG)/libportmidi.dylib $(PF)/lib/
+
+# note - this uses xcode to build and install portmidi universal binaries
+install-with-xcode:
+ sudo xcodebuild -project pm_mac/pm_mac.xcodeproj \
+ -configuration Release install DSTROOT=/
+
+##### build pmdefault ######
+
+pm_java/pmjni/jportmidi_JPortMidiApi.h: pm_java/jportmidi/JPortMidiApi.class
+ cd pm_java; javah jportmidi.JPortMidiApi
+ mv pm_java/jportmidi_JportMidiApi.h pm_java/pmjni
+
+JAVASRC = pmdefaults/PmDefaultsFrame.java \
+ pmdefaults/PmDefaults.java \
+ jportmidi/JPortMidiApi.java jportmidi/JPortMidi.java \
+ jportmidi/JPortMidiException.java
+
+# this compiles ALL of the java code
+pm_java/jportmidi/JPortMidiApi.class: $(JAVASRC:%=pm_java/%)
+ cd pm_java; javac $(JAVASRC)
+
+$(CONFIG)/libpmjni.dylib:
+ mkdir -p $(CONFIG)
+ cd $(CONFIG); make -f ../pm_mac/$(MAKEFILE)
+
+pmdefaults: $(CONFIG)/libpmjni.dylib pm_java/jportmidi/JPortMidiApi.class
+ifeq ($(CONFIG),Debug)
+ echo "Error: you cannot build pmdefaults in a Debug configuration \n\
+ You should use configuration=Release in the Makefile command line. "
+ @exit 2
+endif
+ xcodebuild -project pm_mac/pm_mac.xcodeproj \
+ -configuration Release -target PmDefaults
+ echo "pmdefaults java application is made"
+
diff --git a/portmidi/pm_mac/README_MAC.txt b/portmidi/pm_mac/README_MAC.txt
new file mode 100644
index 0000000..41e8341
--- /dev/null
+++ b/portmidi/pm_mac/README_MAC.txt
@@ -0,0 +1,65 @@
+README_MAC.txt for PortMidi
+Roger Dannenberg
+20 nov 2009
+
+revised Mar 2024 to remove pmdefaults references
+revised Jan 2022 for the PortMidi/portmidi repo on github.com
+revised 20 Sep 2010 for Xcode 4.3.2 and CMake 2.8.8
+
+This documents how I build PortMidi for macOS. It's not the only way,
+and command-line/scripting enthusiasts will say it's not even a good
+way. Feel free to contribute your approach if you are willing to
+describe it carefully and test it.
+
+Install Xcode and the CMake application, CMake.app. I use the GUI
+version of CMake which makes it easy to see/edit variables and
+options.
+
+==== USING CMAKE ====
+
+Run CMake.app and select your portmidi repo working directory as the
+location for source and build. (Yes, I use so called "in-tree"
+builds -- it doesn't hurt, but I don't think it is necessary.)
+
+Default settings should all be fine, but select options under BUILD if
+you wish:
+
+BUILD_NATIVE_JAVA_INTERFACE to build a Java interface (JNI) library.
+
+BUILD_PORTMIDI_TESTS to create some test programs. Of particular
+interest are test/mm, a handy command-line MIDI Input Monitor, and
+test/testio, a simple command-line program to send or receive some
+MIDI notes in case you need a quick test: What devices do I have? Does
+this input work? Does this output work?
+
+I disable BUILD_SHARED_LIBS and always link statically: Static linking only
+adds about 40KB to any application and then you don't have to worry
+about versions, instally, copying or finding the dynamic link library,
+etc.
+
+To make sure you link statically, I rename the library to
+libportmidi_static.a. To do this, set PM_STATIC_LIB_NAME (in CMake,
+under the "PM" group) to "portmidi_static", and of course your
+application will have to specify portmidi_static as the library to
+link to.
+
+If you are building simple command-line applications, you might want
+to enable PM_CHECK_ERRORS. If you do, then calls into the PortMidi
+library will print error messages and exit in the event of an error
+(such as trying to open a device that does not exist). This saves you
+from having to check for errors everytime you call a library function
+or getting confused when errors are detected but not reported. For
+high-quality applications, do NOT enable PM_CHECK_ERRORS -- any
+failure could immediately abort your whole application, which is not
+very friendly to users.
+
+Click on Configure (maybe a couple of times).
+
+Click on Generate and make an Xcode project.
+
+Open portmidi/portmidi.xcodeproj with Xcode and build what you
+need. The simplest thing is to build the ALL_BUILD target. Be careful
+to specify a Debug or Release depending on what you want. "ALL_BUILD"
+is a misnomer -- it only builds the version you select.
+
+
diff --git a/portmidi/pm_mac/pmmac.c b/portmidi/pm_mac/pmmac.c
new file mode 100755
index 0000000..48ac17a
--- /dev/null
+++ b/portmidi/pm_mac/pmmac.c
@@ -0,0 +1,44 @@
+/* pmmac.c -- PortMidi os-dependent code */
+
+/* This file only needs to implement:
+pm_init(), which calls various routines to register the
+available midi devices,
+Pm_GetDefaultInputDeviceID(), and
+Pm_GetDefaultOutputDeviceID().
+It is seperate from pmmacosxcm because we might want to register
+non-CoreMIDI devices.
+*/
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "pmmacosxcm.h"
+
+void pm_init(void)
+{
+ pm_macosxcm_init();
+}
+
+
+void pm_term(void)
+{
+ pm_macosxcm_term();
+}
+
+PmDeviceID Pm_GetDefaultInputDeviceID(void)
+{
+ Pm_Initialize();
+ return pm_default_input_device_id;
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID(void) {
+ Pm_Initialize();
+ return pm_default_output_device_id;
+}
+
+void *pm_alloc(size_t s) { return malloc(s); }
+
+void pm_free(void *ptr) { free(ptr); }
+
+
diff --git a/portmidi/pm_mac/pmmacosxcm.c b/portmidi/pm_mac/pmmacosxcm.c
new file mode 100755
index 0000000..e8b196c
--- /dev/null
+++ b/portmidi/pm_mac/pmmacosxcm.c
@@ -0,0 +1,1179 @@
+/*
+ * Platform interface to the MacOS X CoreMIDI framework
+ *
+ * Jon Parise <jparise at cmu.edu>
+ * and subsequent work by Andrew Zeldis and Zico Kolter
+ * and Roger B. Dannenberg
+ *
+ * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $
+ */
+
+/* Notes:
+
+ Since the input and output streams are represented by
+ MIDIEndpointRef values and almost no other state, we store the
+ MIDIEndpointRef on pm_descriptors[midi->device_id].descriptor.
+
+ OS X does not seem to have an error-code-to-text function, so we
+ will just use text messages instead of error codes.
+
+ Virtual device input synchronization: Once we create a virtual
+ device, it is always "on" and receiving messages, but it must drop
+ messages unless the device has been opened with Pm_OpenInput. To
+ open, the main thread should create all the data structures, then
+ call OSMemoryBarrier so that writes are observed, then set
+ is_opened = TRUE. To close without locks, we need to get the
+ callback to set is_opened to FALSE before we free data structures;
+ otherwise, there's a race condition where closing could delete
+ structures in use by the virtual_read_callback function. We send
+ 8 MIDI resets (FF) in a single packet to our own port to signal
+ the virtual_read_callback to close it. Then, we wait for the
+ callback to recognize the "close" packet and reset is_opened.
+
+ Device scanning is done when you first open an application.
+ PortMIDI does not actively update the devices. Instead, you must
+ Pm_Terminate() and Pm_Initialize(), basically starting over. But
+ CoreMIDI does not have a way to shut down(!), and even
+ MIDIClientDispose() somehow retains state (and docs say do not
+ call it even if it worked). The solution, apparently, is to
+ call CFRunLoopRunInMode(), which somehow updates CoreMIDI
+ state.
+
+ But when do we call CFRunLoopRunInMode()? I tried calling it
+ in midi_in_poll() which is called when you call Pm_Read() since
+ that is called often. I observed that this caused the program
+ to block for as long as 50ms and fairly often for 2 or 3ms.
+ What was Apple thinking? Is it really OK to design systems that
+ can only function with a tricky multi-threaded, non-blocking
+ priority-based solution, and then not provide a proof of concept
+ or documentation? Or is Apple's design really flawed? If anyone
+ at Apple reads this, please let me know -- I'm curious.
+
+ But I digress... Here's the PortMidi approach: Since
+ CFRunLoopRunInMode() is potentially a non-realtime operation,
+ we only call it in Pm_Initialize(), where other calls to look
+ up devices and device names are quite slow to begin with. Again,
+ PortMidi does not actively scan for new or deleted devices, so
+ if devices change, you won't see it until the next Pm_Terminate
+ and Pm_Initialize.
+
+ Calling CFRunLoopRunInMode() once is probably not enough. There
+ might be better way, but it seems to work to just call it 100
+ times and insert 20 1ms delays (in case some inter-process
+ communication or synchronization is going on).
+ This adds 20ms to the wall time of Pm_Initialize(), but it
+ typically runs 30ms to much more (~4s), so this has little impact.
+ */
+
+#include <stdlib.h>
+
+/* turn on lots of debugging print statements */
+#define CM_DEBUG if (0)
+/* #define CM_DEBUG if (1) */
+
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "porttime.h"
+#include "pmmacosxcm.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <CoreServices/CoreServices.h>
+#include <CoreMIDI/MIDIServices.h>
+#include <CoreAudio/HostTime.h>
+#include <unistd.h>
+#include <libkern/OSAtomic.h>
+
+#define PACKET_BUFFER_SIZE 1024
+/* maximum overall data rate (OS X limits MIDI rate in case there
+ * is a cycle among IAC ports.
+ */
+
+#define MAX_BYTES_PER_S 5400
+
+/* Apple reports that packets are dropped when the MIDI bytes/sec
+ exceeds 15000. This is computed by "tracking the number of MIDI
+ bytes scheduled into 1-second buckets over the last six seconds and
+ averaging these counts." This was confirmed in measurements
+ (2021) with pm_test/fast.c and pm_test/fastrcv.c Now, in 2022, with
+ macOS 12, pm_test/fast{rcv}.c show problems begin at 6000 bytes/sec.
+ Previously, we set MAX_BYTES_PER_S to 14000. This is reduced to
+ 5400 based on testing (which shows 5700 is too high) to fix the
+ packet loss problem that showed up with macOS 12.
+
+ Experiments show this restriction applies to IAC bus MIDI, but not
+ to hardware interfaces. (I measured 0.5 Mbps each way over USB to a
+ Teensy 3.2 microcontroller implementing a USB MIDI loopback. Maybe
+ it would get 1 Mbps one-way, which would make the CoreMIDI
+ restriction 18x slower than USB. Maybe other USB MIDI
+ implementations are faster -- USB top speed for other protocols is
+ certainly higher than 1 Mbps!)
+
+ This is apparently based on timestamps, not on real time, so we
+ have to avoid constructing packets that schedule high speed output
+ regardless of when writes occur. The solution is to alter
+ timestamps to limit data rates. This adds a slight time
+ distortion, e.g. an 11 note chord with all notes on the same
+ timestamp will be altered so that the last message is delayed by
+ 11 messages x 3 bytes/message / 5400 bytes/second = 6.1 ms.
+ Note that this is about 2x MIDI speed, but at least 18x slower
+ than USB MIDI.
+
+ Altering timestamps creates another problem, which is that a sender
+ that exceeds the maximum rate can queue up an unbounded number of
+ messages. With non-USB MIDI devices, you could be writing 5x faster
+ to CoreMIDI than the hardware interface can send, causing an
+ unbounded backlog, not to mention that the output stream will be a
+ steady byte stream (e.g., one 3-byte MIDI message every 0.55 ms),
+ losing any original timing or rhythm. PortMidi does not guarantee
+ delivery if, over the long run, you write faster than the hardware
+ can send.
+
+ The LIMIT_RATE symbol, if defined (which is the default), enables
+ code to modify timestamps for output to an IAC device as follows:
+
+ Before a packet is formed, the message timestamp is set to the
+ maximum of the PortMidi timestamp (converted to CoreMIDI time)
+ and min_next_time. After each send, min_next_time is updated to
+ the packet time + packet length * delay_per_byte, which limits
+ the scheduled bytes-per-second. Also, after each packet list
+ flush, min_next_time is updated to the maximum of min_next_time
+ and the real time, which prevents many bytes to be scheduled in
+ the past. (We could more directly just say packets are never
+ scheduled in the past, but we prefer to get the current time -- a
+ system call -- only when we perform the more expensive operation
+ of flushing packets, so that's when we update min_next_time to
+ the current real time. If we are sending a lot, we have to flush
+ a lot, so the time will be updated frequently when it matters.)
+
+ This possible adjustment to timestamps can distort accurate
+ timestamps by up to 0.556 us per 3-byte MIDI message.
+
+ Nothing blocks the sender from queueing up an arbitrary number of
+ messages. Timestamps should be used for accurate timing by sending
+ timestamped messages a little ahead of real time, not for
+ scheduling an entire MIDI sequence at once!
+ */
+#define LIMIT_RATE 1
+
+#define SYSEX_BUFFER_SIZE 128
+/* What is the maximum PortMidi device number for an IAC device? A
+ * cleaner design would be to not use the endpoint as our device
+ * representation. Instead, we could have a private extensible struct
+ * to keep all device information, including whether the device is
+ * implemented with the AppleMIDIIACDriver, which we need because we
+ * have to limit the data rate to this particular driver to avoid
+ * dropping messages. Rather than rewrite a lot of code, I am just
+ * allocating 64 bytes to flag which devices are IAC ones. If an IAC
+ * device number is greater than 63, PortMidi will fail to limit
+ * writes to it, but will not complain and will not access memory
+ * outside the 64-element array of char.
+ */
+#define MAX_IAC_NUM 63
+
+#define VERBOSE_ON 1
+#define VERBOSE if (VERBOSE_ON)
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+#define MIDI_CLOCK 0xf8
+#define MIDI_STATUS_MASK 0x80
+
+// "Ref"s are pointers on 32-bit machines and ints on 64 bit machines
+// NULL_REF is our representation of either 0 or NULL
+#ifdef __LP64__
+#define NULL_REF 0
+#else
+#define NULL_REF NULL
+#endif
+
+static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */
+static MIDIPortRef portIn = NULL_REF; /* Input port handle */
+static MIDIPortRef portOut = NULL_REF; /* Output port handle */
+static char isIAC[MAX_IAC_NUM + 1]; /* is device an IAC device */
+
+extern pm_fns_node pm_macosx_in_dictionary;
+extern pm_fns_node pm_macosx_out_dictionary;
+
+typedef struct coremidi_info_struct {
+ int is_virtual; /* virtual device (TRUE) or actual device (FALSE)? */
+ UInt64 delta; /* difference between stream time and real time in ns */
+ int sysex_mode; /* middle of sending sysex */
+ uint32_t sysex_word; /* accumulate data when receiving sysex */
+ uint32_t sysex_byte_count; /* count how many received */
+ char error[PM_HOST_ERROR_MSG_LEN];
+ char callback_error[PM_HOST_ERROR_MSG_LEN];
+ Byte packetBuffer[PACKET_BUFFER_SIZE];
+ MIDIPacketList *packetList; /* a pointer to packetBuffer */
+ MIDIPacket *packet;
+ Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */
+ MIDITimeStamp sysex_timestamp; /* host timestamp to use with sysex data */
+ /* allow for running status (is running status possible here? -rbd): -cpr */
+ UInt64 min_next_time; /* when can the next send take place? (host time) */
+ int isIACdevice;
+ Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */
+ UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */
+} coremidi_info_node, *coremidi_info_type;
+
+/* private function declarations */
+MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); // returns host time
+PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); // returns ms
+
+char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag);
+
+static PmError check_hosterror(OSStatus err, const char *msg)
+{
+ if (err != noErr) {
+ snprintf(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, "Host error %ld: %s", (long) err, msg);
+ pm_hosterror = TRUE;
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+
+static PmTimestamp midi_synchronize(PmInternal *midi)
+{
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ UInt64 pm_stream_time_2 = // current time in ns
+ AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+ PmTimestamp real_time; // in ms
+ UInt64 pm_stream_time; // in ns
+ /* if latency is zero and this is an output, there is no
+ time reference and midi_synchronize should never be called */
+ assert(midi->time_proc);
+ assert(midi->is_input || midi->latency != 0);
+ do {
+ /* read real_time between two reads of stream time */
+ pm_stream_time = pm_stream_time_2;
+ real_time = (*midi->time_proc)(midi->time_info);
+ pm_stream_time_2 = AudioConvertHostTimeToNanos(
+ AudioGetCurrentHostTime());
+ /* repeat if more than 0.5 ms has elapsed */
+ } while (pm_stream_time_2 > pm_stream_time + 500000);
+ info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
+ midi->sync_time = real_time;
+ return real_time;
+}
+
+
+/* called when MIDI packets are received */
+static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi)
+{
+ PmTimestamp timestamp;
+ MIDIPacket *packet;
+ unsigned int packetIndex;
+ uint32_t now;
+ /* Retrieve the context for this connection */
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ assert(info);
+
+ CM_DEBUG printf("read_callback: numPackets %d: ", newPackets->numPackets);
+
+ /* synchronize time references every 100ms */
+ now = (*midi->time_proc)(midi->time_info);
+ if (midi->first_message || midi->sync_time + 100 /*ms*/ < now) {
+ /* time to resync */
+ now = midi_synchronize(midi);
+ midi->first_message = FALSE;
+ }
+
+ packet = (MIDIPacket *) &newPackets->packet[0];
+ /* hardware devices get untimed messages and apply timestamps. We
+ * want to preserve them because they should be more accurate than
+ * applying the current time here. virtual devices just pass on the
+ * packet->timeStamp, which could be anything. PortMidi says the
+ * PortMidi timestamp is the time the message is received. We do not
+ * know if we are receiving from a device driver or a virtual device.
+ * PortMidi sends to virtual devices get a current timestamp, so we
+ * can treat them as the receive time. If the timestamp is zero,
+ * suggested by CoreMIDI as the value to use for immediate delivery,
+ * then we plug in `now` which is obtained above. If another
+ * application sends bogus non-zero timestamps, we will convert them
+ * to this port's reference time and pass them as event.timestamp.
+ * Receiver beware.
+ */
+ CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) "
+ "status %x length %d\n",
+ AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),
+ AudioGetCurrentHostTime(),
+ packet->data[0], packet->length);
+ for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
+ /* Set the timestamp and dispatch this message */
+ CM_DEBUG printf(" packet->timeStamp %lld ns %lld host\n",
+ packet->timeStamp,
+ AudioConvertHostTimeToNanos(packet->timeStamp));
+ if (packet->timeStamp == 0) {
+ timestamp = now;
+ } else {
+ timestamp = (PmTimestamp) /* explicit conversion */ (
+ (AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) /
+ (UInt64) 1000000);
+ }
+ pm_read_bytes(midi, packet->data, packet->length, timestamp);
+ packet = MIDIPacketNext(packet);
+ }
+}
+
+/* callback for real devices - redirects to read_callback */
+static void device_read_callback(const MIDIPacketList *newPackets,
+ void *refCon, void *connRefCon)
+{
+ read_callback(newPackets, (PmInternal *) connRefCon);
+}
+
+
+/* callback for virtual devices - redirects to read_callback */
+static void virtual_read_callback(const MIDIPacketList *newPackets,
+ void *refCon, void *connRefCon)
+{
+ /* this refCon is the device ID -- if there is a valid ID and
+ the pm_descriptors table has a non-null pointer to a PmInternal,
+ then then device is open and should receive this data */
+ PmDeviceID id = (PmDeviceID) (size_t) refCon;
+ if (id >= 0 && id < pm_descriptor_len) {
+ if (pm_descriptors[id].pub.opened) {
+ /* check for close request (7 reset status bytes): */
+ if (newPackets->numPackets == 1 &&
+ newPackets->packet[0].length == 8 &&
+ /* CoreMIDI declares packets with 4-byte alignment, so we
+ * should be safe to test for 8 0xFF's as 2 32-bit values: */
+ *(SInt32 *) &newPackets->packet[0].data[0] == -1 &&
+ *(SInt32 *) &newPackets->packet[0].data[4] == -1) {
+ CM_DEBUG printf("got close request packet\n");
+ pm_descriptors[id].pub.opened = FALSE;
+ return;
+ } else {
+ read_callback(newPackets, pm_descriptors[id].pm_internal);
+ }
+ }
+ }
+}
+
+
+/* allocate and initialize our internal coremidi connection info */
+static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input)
+{
+ coremidi_info_type info = (coremidi_info_type)
+ pm_alloc(sizeof(coremidi_info_node));
+ if (!info) {
+ return NULL;
+ }
+ info->is_virtual = is_virtual;
+ info->delta = 0;
+ info->sysex_mode = FALSE;
+ info->sysex_word = 0;
+ info->sysex_byte_count = 0;
+ info->packet = NULL;
+ info->min_next_time = 0;
+ info->isIACdevice = FALSE;
+ info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency();
+ info->host_ticks_per_byte =
+ (UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S));
+ info->packetList = (is_input ? NULL :
+ (MIDIPacketList *) info->packetBuffer);
+ return info;
+}
+
+
+static PmError midi_in_open(PmInternal *midi, void *driverInfo)
+{
+ MIDIEndpointRef endpoint;
+ coremidi_info_type info;
+ OSStatus macHostError;
+ int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;
+
+ /* if this is an external device, descriptor is a MIDIEndpointRef.
+ * if this is a virtual device for this application, descriptor is NULL.
+ */
+ if (!is_virtual) {
+ endpoint = (MIDIEndpointRef) (intptr_t)
+ pm_descriptors[midi->device_id].descriptor;
+ if (endpoint == NULL_REF) {
+ return pmInvalidDeviceId;
+ }
+ }
+
+ info = create_macosxcm_info(is_virtual, TRUE);
+ midi->api_info = info;
+ if (!info) {
+ return pmInsufficientMemory;
+ }
+ if (!is_virtual) {
+ macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
+ if (macHostError != noErr) {
+ midi->api_info = NULL;
+ pm_free(info);
+ return check_hosterror(macHostError,
+ "MIDIPortConnectSource() in midi_in_open()");
+ }
+ }
+ return pmNoError;
+}
+
+static PmError midi_in_close(PmInternal *midi)
+{
+ MIDIEndpointRef endpoint;
+ OSStatus macHostError;
+ PmError err = pmNoError;
+
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+
+ if (!info) return pmBadPtr;
+
+ endpoint = (MIDIEndpointRef) (intptr_t)
+ pm_descriptors[midi->device_id].descriptor;
+ if (endpoint == NULL_REF) {
+ return pmBadPtr;
+ }
+
+ if (!info->is_virtual) {
+ /* shut off the incoming messages before freeing data structures */
+ macHostError = MIDIPortDisconnectSource(portIn, endpoint);
+ /* If the source closes, you get paramErr == -50 here. It seems
+ * possible to monitor changes like sources closing by getting
+ * notifications ALL changes, but the CoreMIDI documentation is
+ * really terrible overall, and it seems easier to just ignore
+ * this host error.
+ */
+ if (macHostError != noErr && macHostError != -50) {
+ pm_hosterror = TRUE;
+ err = check_hosterror(macHostError,
+ "MIDIPortDisconnectSource() in midi_in_close()");
+ }
+ } else {
+ /* make "close virtual port" message */
+ SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF;
+ /* memory requirements: packet count (4), timestamp (8), length (2),
+ * data (8). Total: 22, but we allocate plenty more:
+ */
+ Byte packetBuffer[64];
+ MIDIPacketList *plist = (MIDIPacketList *) packetBuffer;
+ MIDIPacket *packet = MIDIPacketListInit(plist);
+ MIDIPacketListAdd(plist, 64, packet, 0, 8,
+ (const Byte *) &close_port_bytes);
+ macHostError = MIDISend(portOut, endpoint, plist);
+ if (macHostError != noErr) {
+ err = check_hosterror(macHostError, "MIDISend() (PortMidi close "
+ "port packet) in midi_in_close()");
+ }
+ /* when packet is delivered, callback thread will clear opened;
+ * we must wait for that before removing the input queues etc.
+ * Maybe this could use signals of some kind, but if signals use
+ * locks, locks can cause priority inversion problems, so we will
+ * just sleep as needed. On the MIDI timescale, inserting a 0.5ms
+ * latency should be OK, as the application has no business
+ * opening/closing devices during time-critical moments.
+ *
+ * We expect the MIDI thread to close the device quickly (<0.5ms),
+ * but we wait up to 50ms in case something terrible happens like
+ * getting paged out in the middle of deliving packets to this
+ * virtual device. If there is still no response, we time out and
+ * force the close without the MIDI thread (even this will probably
+ * succeed - the problem would be: this thread proceeds to delete
+ * the input queues, and the freed memory is reallocated and
+ * overwritten so that queues are no longer usable. Meanwhile,
+ * the MIDI thread has already begun to deliver packets, so the
+ * check for opened == TRUE passed, but MIDI thread does not insert
+ * into queue until queue is freed, reallocated and overwritten.
+ */
+ for (int i = 0; i < 100; i++) { /* up to 50ms delay */
+ if (!pm_descriptors[midi->device_id].pub.opened) {
+ break;
+ }
+ usleep(500); /* 0.5ms */
+ }
+ pm_descriptors[midi->device_id].pub.opened = FALSE; /* force it */
+ }
+ midi->api_info = NULL;
+ pm_free(info);
+ return err;
+}
+
+
+static PmError midi_out_open(PmInternal *midi, void *driverInfo)
+{
+ coremidi_info_type info;
+ int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;
+
+ info = create_macosxcm_info(is_virtual, FALSE);
+ if (midi->device_id <= MAX_IAC_NUM) {
+ info->isIACdevice = isIAC[midi->device_id];
+ CM_DEBUG printf("midi_out_open isIACdevice %d\n", info->isIACdevice);
+ }
+ midi->api_info = info;
+ if (!info) {
+ return pmInsufficientMemory;
+ }
+ return pmNoError;
+}
+
+
+static PmError midi_out_close(PmInternal *midi)
+{
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ if (!info) return pmBadPtr;
+ midi->api_info = NULL;
+ pm_free(info);
+ return pmNoError;
+}
+
+
+/* MIDIDestinationCreate apparently cannot create a virtual device
+ * without a callback and a "refCon" parameter, but when we create
+ * a virtual device, we do not want a PortMidi stream yet -- that
+ * should wait for the user to open the stream. So, for the refCon,
+ * use the PortMidi device ID. The callback will check if the
+ * device is opened within PortMidi, and if so, use the pm_descriptors
+ * table to locate the corresponding PmStream.
+ */
+static PmError midi_create_virtual(int is_input, const char *name,
+ void *device_info)
+{
+ OSStatus macHostError;
+ MIDIEndpointRef endpoint;
+ CFStringRef nameRef;
+ PmDeviceID id = pm_add_device("CoreMIDI", name, is_input, TRUE, NULL,
+ (is_input ? &pm_macosx_in_dictionary :
+ &pm_macosx_out_dictionary));
+ if (id < 0) { /* error -- out of memory or name conflict? */
+ return id;
+ }
+
+ nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ if (is_input) {
+ macHostError = MIDIDestinationCreate(client, nameRef,
+ virtual_read_callback, (void *) (intptr_t) id, &endpoint);
+ } else {
+ macHostError = MIDISourceCreate(client, nameRef, &endpoint);
+ }
+ CFRelease(nameRef);
+
+ if (macHostError != noErr) {
+ /* undo the device we just allocated */
+ pm_undo_add_device(id);
+ return check_hosterror(macHostError, (is_input ?
+ "MIDIDestinationCreateWithProtocol() in midi_create_virtual()" :
+ "MIDISourceCreateWithProtocol() in midi_create_virtual()"));
+ }
+
+ /* Do we have a manufacturer name? If not, set to "PortMidi" */
+ const char *mfr_name = "PortMidi";
+ PmSysDepInfo *info = (PmSysDepInfo *) device_info;
+ /* the version where pmKeyCoreMidiManufacturer was introduced is 210 */
+ if (info && info->structVersion >= 210) {
+ int i;
+ for (i = 0; i < info->length; i++) { /* search for key */
+ if (info->properties[i].key == pmKeyCoreMidiManufacturer) {
+ mfr_name = info->properties[i].value;
+ break;
+ } /* no other keys are recognized; they are ignored */
+ }
+ }
+ nameRef = CFStringCreateWithCString(NULL, mfr_name, kCFStringEncodingUTF8);
+ MIDIObjectSetStringProperty(endpoint, kMIDIPropertyManufacturer, nameRef);
+ CFRelease(nameRef);
+
+ pm_descriptors[id].descriptor = (void *) (intptr_t) endpoint;
+ return id;
+}
+
+
+static PmError midi_delete_virtual(PmDeviceID id)
+{
+ MIDIEndpointRef endpoint;
+ OSStatus macHostError;
+
+ endpoint = (MIDIEndpointRef) (long) pm_descriptors[id].descriptor;
+ if (endpoint == NULL_REF) {
+ return pmBadPtr;
+ }
+ macHostError = MIDIEndpointDispose(endpoint);
+ return check_hosterror(macHostError,
+ "MIDIEndpointDispose() in midi_in_close()");
+}
+
+
+static PmError midi_abort(PmInternal *midi)
+{
+ OSStatus macHostError;
+ MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)
+ pm_descriptors[midi->device_id].descriptor;
+ macHostError = MIDIFlushOutput(endpoint);
+ return check_hosterror(macHostError,
+ "MIDIFlushOutput() in midi_abort()");
+}
+
+
+static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
+{
+ OSStatus macHostError = 0;
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)
+ pm_descriptors[midi->device_id].descriptor;
+ assert(info);
+ assert(endpoint);
+ if (info->packet != NULL) {
+ /* out of space, send the buffer and start refilling it */
+ /* update min_next_time each flush to support rate limit */
+ UInt64 host_now = AudioGetCurrentHostTime();
+ if (host_now > info->min_next_time)
+ info->min_next_time = host_now;
+ if (info->is_virtual) {
+ macHostError = MIDIReceived(endpoint, info->packetList);
+ } else {
+ macHostError = MIDISend(portOut, endpoint, info->packetList);
+ }
+ info->packet = NULL; /* indicate no data in packetList now */
+ }
+ return check_hosterror(macHostError, (info->is_virtual ?
+ "MIDIReceived() in midi_write()" :
+ "MIDISend() in midi_write()"));
+}
+
+
+static PmError send_packet(PmInternal *midi, Byte *message,
+ unsigned int messageLength, MIDITimeStamp timestamp)
+{
+ PmError err;
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ assert(info);
+
+ CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns "
+ "(host %lld)\n",
+ message[0], info->packet, messageLength, timestamp,
+ AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),
+ AudioGetCurrentHostTime());
+ info->packet = MIDIPacketListAdd(info->packetList,
+ sizeof(info->packetBuffer), info->packet,
+ timestamp, messageLength, message);
+#if LIMIT_SEND_RATE
+ info->byte_count += messageLength;
+#endif
+ if (info->packet == NULL) {
+ /* out of space, send the buffer and start refilling it */
+ /* make midi->packet non-null to fool midi_write_flush into sending */
+ info->packet = (MIDIPacket *) 4;
+ /* timestamp is 0 because midi_write_flush ignores timestamp since
+ * timestamps are already in packets. The timestamp parameter is here
+ * because other API's need it. midi_write_flush can be called
+ * from system-independent code that must be cross-API.
+ */
+ if ((err = midi_write_flush(midi, 0)) != pmNoError) return err;
+ info->packet = MIDIPacketListInit(info->packetList);
+ assert(info->packet); /* if this fails, it's a programming error */
+ info->packet = MIDIPacketListAdd(info->packetList,
+ sizeof(info->packetBuffer), info->packet,
+ timestamp, messageLength, message);
+ assert(info->packet); /* can't run out of space on first message */
+ }
+ return pmNoError;
+}
+
+
+static PmError midi_write_short(PmInternal *midi, PmEvent *event)
+{
+ PmTimestamp when = event->timestamp;
+ PmMessage what = event->message;
+ MIDITimeStamp timestamp;
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ Byte message[4];
+ unsigned int messageLength;
+
+ if (info->packet == NULL) {
+ info->packet = MIDIPacketListInit(info->packetList);
+ /* this can never fail, right? failure would indicate something
+ unrecoverable */
+ assert(info->packet);
+ }
+
+ /* PortMidi specifies that incoming timestamps are the receive
+ * time. Devices attach their receive times, but virtual devices
+ * do not. Instead, they pass along whatever timestamp was sent to
+ * them. We do not know if we are connected to real or virtual
+ * device. To avoid wild timestamps on the receiving end, we
+ * consider 2 cases: PortMidi timestamp is zero or latency is
+ * zero. Both mean send immediately, so we attach the current time
+ * which will go out immediately and arrive with a sensible
+ * timestamp (not zero and not zero mapped to the client's local
+ * time). Otherwise, we assume the timestamp is reasonable. It
+ * might be slighly in the past, but we pass it along after
+ * translation to MIDITimeStamp units.
+ *
+ * Compute timestamp: use current time if timestamp is zero or
+ * latency is zero. Both mean no timing and send immediately.
+ */
+ if (when == 0 || midi->latency == 0) {
+ timestamp = AudioGetCurrentHostTime();
+ } else { /* translate PortMidi time + latency to CoreMIDI time */
+ timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
+ info->delta;
+ timestamp = AudioConvertNanosToHostTime(timestamp);
+ }
+
+ message[0] = Pm_MessageStatus(what);
+ message[1] = Pm_MessageData1(what);
+ message[2] = Pm_MessageData2(what);
+ messageLength = pm_midi_length((int32_t) what);
+
+#ifdef LIMIT_RATE
+ /* Make sure we go forward in time. */
+ if (timestamp < info->min_next_time) {
+ timestamp = info->min_next_time;
+ }
+ /* Note that if application is way behind and slowly catching up, then
+ * timestamps could be increasing faster than real time, and since
+ * timestamps are used to estimate data rate, our estimate could be
+ * low, causing CoreMIDI to drop packets. This seems very unlikely.
+ */
+ if (info->isIACdevice || info->is_virtual) {
+ info->min_next_time = timestamp + messageLength *
+ info->host_ticks_per_byte;
+ }
+#endif
+ /* Add this message to the packet list */
+ return send_packet(midi, message, messageLength, timestamp);
+}
+
+
+static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when)
+{
+ UInt64 when_ns;
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ assert(info);
+ info->sysex_byte_count = 0;
+
+ /* compute timestamp */
+ if (when == 0) when = midi->now;
+ /* if latency == 0, midi->now is not valid. We will just set it to zero */
+ if (midi->latency == 0) when = 0;
+ when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
+ info->delta;
+ info->sysex_timestamp =
+ (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
+ UInt64 now; /* only make system time call when writing a virtual port */
+ if (info->is_virtual && info->sysex_timestamp <
+ (now = AudioGetCurrentHostTime())) {
+ info->sysex_timestamp = now;
+ }
+
+ if (info->packet == NULL) {
+ info->packet = MIDIPacketListInit(info->packetList);
+ /* this can never fail, right? failure would indicate something
+ unrecoverable */
+ assert(info->packet);
+ }
+ return pmNoError;
+}
+
+
+static PmError midi_end_sysex(PmInternal *midi, PmTimestamp when)
+{
+ PmError err;
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ assert(info);
+
+#ifdef LIMIT_RATE
+ /* make sure we go foreward in time */
+ if (info->sysex_timestamp < info->min_next_time)
+ info->sysex_timestamp = info->min_next_time;
+
+ if (info->isIACdevice) {
+ info->min_next_time = info->sysex_timestamp + info->sysex_byte_count *
+ info->host_ticks_per_byte;
+ }
+#endif
+
+ /* now send what's in the buffer */
+ err = send_packet(midi, info->sysex_buffer, info->sysex_byte_count,
+ info->sysex_timestamp);
+ info->sysex_byte_count = 0;
+ if (err != pmNoError) {
+ info->packet = NULL; /* flush everything in the packet list */
+ }
+ return err;
+}
+
+
+static PmError midi_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp)
+{
+ coremidi_info_type info = (coremidi_info_type) midi->api_info;
+ assert(info);
+ if (info->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
+ PmError err = midi_end_sysex(midi, timestamp);
+ if (err != pmNoError) return err;
+ }
+ info->sysex_buffer[info->sysex_byte_count++] = byte;
+ return pmNoError;
+}
+
+
+static PmError midi_write_realtime(PmInternal *midi, PmEvent *event)
+{
+ /* to send a realtime message during a sysex message, first
+ flush all pending sysex bytes into packet list */
+ PmError err = midi_end_sysex(midi, 0);
+ if (err != pmNoError) return err;
+ /* then we can just do a normal midi_write_short */
+ return midi_write_short(midi, event);
+}
+
+
+static unsigned int midi_check_host_error(PmInternal *midi)
+{
+ return FALSE;
+}
+
+
+MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
+{
+ UInt64 nanos;
+ if (timestamp <= 0) {
+ return (MIDITimeStamp)0;
+ } else {
+ nanos = (UInt64)timestamp * (UInt64)1000000;
+ return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
+ }
+}
+
+
+PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
+{
+ UInt64 nanos;
+ nanos = AudioConvertHostTimeToNanos(timestamp);
+ return (PmTimestamp)(nanos / (UInt64)1000000);
+}
+
+
+//
+// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html
+//////////////////////////////////////
+// Obtain the name of an endpoint without regard for whether it has connections.
+// The result should be released by the caller.
+CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal,
+ int *iac_flag)
+{
+ CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
+ CFStringRef str;
+ *iac_flag = FALSE;
+
+ // begin with the endpoint's name
+ str = NULL;
+ MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
+ if (str != NULL) {
+ CFStringAppend(result, str);
+ CFRelease(str);
+ }
+ MIDIEntityRef entity = NULL_REF;
+ MIDIEndpointGetEntity(endpoint, &entity);
+ if (entity == NULL_REF) {
+ // probably virtual
+ return result;
+ }
+ if (!isExternal) { /* detect IAC devices */
+ //extern const CFStringRef kMIDIPropertyDriverOwner;
+ MIDIObjectGetStringProperty(entity, kMIDIPropertyDriverOwner, &str);
+ if (str != NULL) {
+ char s[32]; /* driver name may truncate, but that's OK */
+ CFStringGetCString(str, s, 31, kCFStringEncodingUTF8);
+ s[31] = 0; /* make sure it is terminated just to be safe */
+ CM_DEBUG printf("driver %s\n", s);
+ *iac_flag = (strcmp(s, "com.apple.AppleMIDIIACDriver") == 0);
+ }
+ }
+
+ if (CFStringGetLength(result) == 0) {
+ // endpoint name has zero length -- try the entity
+ str = NULL;
+ MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
+ if (str != NULL) {
+ CFStringAppend(result, str);
+ CFRelease(str);
+ }
+ }
+ // now consider the device's name
+ MIDIDeviceRef device = NULL_REF;
+ MIDIEntityGetDevice(entity, &device);
+ if (device == NULL_REF)
+ return result;
+
+ str = NULL;
+ MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
+ if (CFStringGetLength(result) == 0) {
+ CFRelease(result);
+ return str;
+ }
+ if (str != NULL) {
+ // if an external device has only one entity, throw away
+ // the endpoint name and just use the device name
+ if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {
+ CFRelease(result);
+ return str;
+ } else {
+ if (CFStringGetLength(str) == 0) {
+ CFRelease(str);
+ return result;
+ }
+ // does the entity name already start with the device name?
+ // (some drivers do this though they shouldn't)
+ // if so, do not prepend
+ if (CFStringCompareWithOptions(result, /* endpoint name */
+ str, /* device name */
+ CFRangeMake(0, CFStringGetLength(str)), 0) !=
+ kCFCompareEqualTo) {
+ // prepend the device name to the entity name
+ if (CFStringGetLength(result) > 0)
+ CFStringInsert(result, 0, CFSTR(" "));
+ CFStringInsert(result, 0, str);
+ }
+ CFRelease(str);
+ }
+ }
+ return result;
+}
+
+
+// Obtain the name of an endpoint, following connections.
+// The result should be released by the caller.
+static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint,
+ int *iac_flag)
+{
+ CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
+ CFStringRef str;
+ OSStatus err;
+ long i;
+
+ // Does the endpoint have connections?
+ CFDataRef connections = NULL;
+ long nConnected = 0;
+ bool anyStrings = false;
+ err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID,
+ &connections);
+ if (connections != NULL) {
+ // It has connections, follow them
+ // Concatenate the names of all connected devices
+ nConnected = CFDataGetLength(connections) /
+ (int32_t) sizeof(MIDIUniqueID);
+ if (nConnected) {
+ const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
+ for (i = 0; i < nConnected; ++i, ++pid) {
+ MIDIUniqueID id = EndianS32_BtoN(*pid);
+ MIDIObjectRef connObject;
+ MIDIObjectType connObjectType;
+ err = MIDIObjectFindByUniqueID(id, &connObject,
+ &connObjectType);
+ if (err == noErr) {
+ if (connObjectType == kMIDIObjectType_ExternalSource ||
+ connObjectType == kMIDIObjectType_ExternalDestination) {
+ // Connected to an external device's endpoint (>=10.3)
+ str = EndpointName((MIDIEndpointRef)(connObject), true,
+ iac_flag);
+ } else {
+ // Connected to an external device (10.2)
+ // (or something else, catch-all)
+ str = NULL;
+ MIDIObjectGetStringProperty(connObject,
+ kMIDIPropertyName, &str);
+ }
+ if (str != NULL) {
+ if (anyStrings)
+ CFStringAppend(result, CFSTR(", "));
+ else anyStrings = true;
+ CFStringAppend(result, str);
+ CFRelease(str);
+ }
+ }
+ }
+ }
+ CFRelease(connections);
+ }
+ if (anyStrings)
+ return result; // caller should release result
+
+ CFRelease(result);
+
+ // Here, either the endpoint had no connections, or we failed to
+ // obtain names for any of them.
+ return EndpointName(endpoint, false, iac_flag);
+}
+
+
+char *cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag)
+{
+ /* Thanks to Dan Wilcox for fixes for Unicode handling */
+ CFStringRef fullName = ConnectedEndpointName(endpoint, iac_flag);
+ CFIndex utf16_len = CFStringGetLength(fullName) + 1;
+ CFIndex max_byte_len = CFStringGetMaximumSizeForEncoding(
+ utf16_len, kCFStringEncodingUTF8) + 1;
+ char* pmname = (char *) pm_alloc(CFStringGetLength(fullName) + 1);
+
+ /* copy the string into our buffer; note that there may be some wasted
+ space, but the total waste is not large */
+ CFStringGetCString(fullName, pmname, max_byte_len, kCFStringEncodingUTF8);
+
+ /* clean up */
+ if (fullName) CFRelease(fullName);
+ return pmname;
+}
+
+
+pm_fns_node pm_macosx_in_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ none_synchronize,
+ midi_in_open,
+ midi_abort,
+ midi_in_close,
+ success_poll,
+ midi_check_host_error
+};
+
+pm_fns_node pm_macosx_out_dictionary = {
+ midi_write_short,
+ midi_begin_sysex,
+ midi_end_sysex,
+ midi_write_byte,
+ midi_write_realtime,
+ midi_write_flush,
+ midi_synchronize,
+ midi_out_open,
+ midi_abort,
+ midi_out_close,
+ success_poll,
+ midi_check_host_error
+};
+
+
+/* We do nothing with callbacks, but generating the callbacks also
+ * updates CoreMIDI state. Callback may not be essential, but calling
+ * the CFRunLoopRunInMode is necessary.
+ */
+void cm_notify(const MIDINotification *msg, void *refCon)
+{
+ /* for debugging, trace change notifications:
+ const char *descr[] = {
+ "undefined (0)",
+ "kMIDIMsgSetupChanged",
+ "kMIDIMsgObjectAdded",
+ "kMIDIMsgObjectRemoved",
+ "kMIDIMsgPropertyChanged",
+ "kMIDIMsgThruConnectionsChanged",
+ "kMIDIMsgSerialPortOwnerChanged",
+ "kMIDIMsgIOError"};
+
+ printf("MIDI Notify, messageID %d (%s)\n", (int) msg->messageID,
+ descr[(int) msg->messageID]);
+ */
+ return;
+}
+
+
+PmError pm_macosxcm_init(void)
+{
+ ItemCount numInputs, numOutputs, numDevices;
+ MIDIEndpointRef endpoint;
+ OSStatus macHostError = noErr;
+ char *error_text;
+
+ memset(isIAC, 0, sizeof(isIAC)); /* initialize all FALSE */
+
+ /* Register interface CoreMIDI with create_virtual fn */
+ pm_add_interf("CoreMIDI", &midi_create_virtual, &midi_delete_virtual);
+ /* no check for error return because this always succeeds */
+
+ /* Determine the number of MIDI devices on the system */
+ numDevices = MIDIGetNumberOfDevices();
+
+ /* Return prematurely if no devices exist on the system
+ Note that this is not an error. There may be no devices.
+ Pm_CountDevices() will return zero, which is correct and
+ useful information
+ */
+ if (numDevices <= 0) {
+ return pmNoError;
+ }
+
+ /* Initialize the client handle */
+ if (client == NULL_REF) {
+ macHostError = MIDIClientCreate(CFSTR("PortMidi"), &cm_notify, NULL,
+ &client);
+ } else { /* see notes above on device scanning */
+ for (int i = 0; i < 100; i++) {
+ // look for any changes before scanning for devices
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
+ if (i % 5 == 0) Pt_Sleep(1); /* insert 20 delays */
+ }
+ }
+ if (macHostError != noErr) {
+ error_text = "MIDIClientCreate() in pm_macosxcm_init()";
+ goto error_return;
+ }
+ numInputs = MIDIGetNumberOfSources();
+ numOutputs = MIDIGetNumberOfDestinations();
+
+ /* Create the input port */
+ macHostError = MIDIInputPortCreate(client, CFSTR("Input port"),
+ device_read_callback, NULL, &portIn);
+ if (macHostError != noErr) {
+ error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
+ goto error_return;
+ }
+
+ /* Create the output port */
+ macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
+ if (macHostError != noErr) {
+ error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
+ goto error_return;
+ }
+
+ /* Iterate over the MIDI input devices */
+ for (int i = 0; i < numInputs; i++) {
+ int iac_flag;
+ endpoint = MIDIGetSource(i);
+ if (endpoint == NULL_REF) {
+ continue;
+ }
+ /* Register this device with PortMidi */
+ pm_add_device("CoreMIDI",
+ cm_get_full_endpoint_name(endpoint, &iac_flag), TRUE, FALSE,
+ (void *) (intptr_t) endpoint, &pm_macosx_in_dictionary);
+ }
+
+ /* Iterate over the MIDI output devices */
+ for (int i = 0; i < numOutputs; i++) {
+ int iac_flag;
+ PmDeviceID id;
+ endpoint = MIDIGetDestination(i);
+ if (endpoint == NULL_REF) {
+ continue;
+ }
+ /* Register this device with PortMidi */
+ id = pm_add_device("CoreMIDI",
+ cm_get_full_endpoint_name(endpoint, &iac_flag), FALSE, FALSE,
+ (void *) (intptr_t) endpoint, &pm_macosx_out_dictionary);
+ /* if this is an IAC device, tuck that info away for write functions */
+ if (iac_flag && id <= MAX_IAC_NUM) {
+ isIAC[id] = TRUE;
+ }
+ }
+ return pmNoError;
+
+error_return:
+ pm_macosxcm_term(); /* clear out any opened ports */
+ return check_hosterror(macHostError, error_text);
+}
+
+void pm_macosxcm_term(void)
+{
+ /* docs say do not explicitly dispose of client
+ if (client != NULL_REF) MIDIClientDispose(client); */
+ if (portIn != NULL_REF) MIDIPortDispose(portIn);
+ if (portOut != NULL_REF) MIDIPortDispose(portOut);
+}
diff --git a/portmidi/pm_mac/pmmacosxcm.h b/portmidi/pm_mac/pmmacosxcm.h
new file mode 100755
index 0000000..166a0b7
--- /dev/null
+++ b/portmidi/pm_mac/pmmacosxcm.h
@@ -0,0 +1,4 @@
+/* system-specific definitions */
+
+PmError pm_macosxcm_init(void);
+void pm_macosxcm_term(void);
diff --git a/portmidi/pm_sndio/pmsndio.c b/portmidi/pm_sndio/pmsndio.c
new file mode 100644
index 0000000..0c1ea11
--- /dev/null
+++ b/portmidi/pm_sndio/pmsndio.c
@@ -0,0 +1,365 @@
+/* pmsndio.c -- PortMidi os-dependent code */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sndio.h>
+#include <string.h>
+#include <poll.h>
+#include <errno.h>
+#include <pthread.h>
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "porttime.h"
+
+#define NDEVS 9
+#define SYSEX_MAXLEN 1024
+
+#define SYSEX_START 0xf0
+#define SYSEX_END 0xf7
+
+extern pm_fns_node pm_sndio_in_dictionary;
+extern pm_fns_node pm_sndio_out_dictionary;
+
+/* length of voice and common messages (status byte included) */
+unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 };
+unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 };
+
+struct mio_dev {
+ char name[16];
+ struct mio_hdl *hdl;
+ int mode;
+ char errmsg[PM_HOST_ERROR_MSG_LEN];
+ pthread_t thread;
+} devs[NDEVS];
+
+static void set_mode(struct mio_dev *, unsigned int);
+
+void pm_init()
+{
+ int i, j, k = 0;
+ char devices[][16] = {"midithru", "rmidi", "midi", "snd"};
+
+ /* default */
+ strcpy(devs[0].name, MIO_PORTANY);
+ pm_add_device("SNDIO", devs[k].name, TRUE, FALSE, (void *) &devs[k],
+ &pm_sndio_in_dictionary);
+ pm_add_device("SNDIO", devs[k].name, FALSE, FALSE, (void *) &devs[k],
+ &pm_sndio_out_dictionary);
+ k++;
+
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 2; j++) {
+ sprintf(devs[k].name, "%s/%d", devices[i], j);
+ pm_add_device("SNDIO", devs[k].name, TRUE, FALSE, (void *) &devs[k],
+ &pm_sndio_in_dictionary);
+ pm_add_device("SNDIO", devs[k].name, FALSE, FALSE, (void *) &devs[k],
+ &pm_sndio_out_dictionary);
+ k++;
+ }
+ }
+
+ // this is set when we return to Pm_Initialize, but we need it
+ // now in order to (successfully) call Pm_CountDevices()
+ pm_initialized = TRUE;
+ pm_default_input_device_id = 0;
+ pm_default_output_device_id = 1;
+}
+
+void pm_term(void)
+{
+ int i;
+ for(i = 0; i < NDEVS; i++) {
+ if (devs[i].mode != 0) {
+ set_mode(&devs[i], 0);
+ if (devs[i].thread) {
+ pthread_join(devs[i].thread, NULL);
+ devs[i].thread = NULL;
+ }
+ }
+ }
+}
+
+PmDeviceID Pm_GetDefaultInputDeviceID() {
+ Pm_Initialize();
+ return pm_default_input_device_id;
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID() {
+ Pm_Initialize();
+ return pm_default_output_device_id;
+}
+
+void *pm_alloc(size_t s) { return malloc(s); }
+
+void pm_free(void *ptr) { free(ptr); }
+
+/* midi_message_length -- how many bytes in a message? */
+static int midi_message_length(PmMessage message)
+{
+ unsigned char st = message & 0xff;
+ if (st >= 0xf8)
+ return 1;
+ else if (st >= 0xf0)
+ return common_len[st & 7];
+ else if (st >= 0x80)
+ return voice_len[(st >> 4) & 7];
+ else
+ return 0;
+}
+
+void* input_thread(void *param)
+{
+ PmInternal *midi = (PmInternal*)param;
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+ struct pollfd pfd[1];
+ nfds_t nfds;
+ unsigned char st = 0, c = 0;
+ int rc, revents, idx = 0, len = 0;
+ size_t todo = 0;
+ unsigned char buf[0x200], *p;
+ PmEvent pm_ev, pm_ev_rt;
+ unsigned char sysex_data[SYSEX_MAXLEN];
+
+ while(dev->mode & MIO_IN) {
+ if (todo == 0) {
+ nfds = mio_pollfd(dev->hdl, pfd, POLLIN);
+ rc = poll(pfd, nfds, 100);
+ if (rc < 0) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ revents = mio_revents(dev->hdl, pfd);
+ if (!(revents & POLLIN))
+ continue;
+
+ todo = mio_read(dev->hdl, buf, sizeof(buf));
+ if (todo == 0)
+ continue;
+ p = buf;
+ }
+ c = *p++;
+ todo--;
+
+ if (c >= 0xf8) {
+ pm_ev_rt.message = c;
+ pm_ev_rt.timestamp = Pt_Time();
+ pm_read_short(midi, &pm_ev_rt);
+ } else if (c == SYSEX_END) {
+ /* note: PortMidi is designed to avoid the need for SYSEX_MAXLEN.
+ With the new implementation of pm_read_bytes, it would be
+ better to simply call pm_read_bytes() and let it parse buf,
+ which can contain any number of whole or partial messages with
+ interleaved realtime messages. I did not change the code because
+ I cannot test it. -RBD */
+ if (st == SYSEX_START) {
+ sysex_data[idx++] = c;
+ pm_read_bytes(midi, sysex_data, idx, Pt_Time());
+ }
+ st = 0;
+ idx = 0;
+ } else if (c == SYSEX_START) {
+ st = c;
+ idx = 0;
+ sysex_data[idx++] = c;
+ } else if (c >= 0xf0) {
+ pm_ev.message = c;
+ len = common_len[c & 7];
+ st = c;
+ idx = 1;
+ } else if (c >= 0x80) {
+ pm_ev.message = c;
+ len = voice_len[(c >> 4) & 7];
+ st = c;
+ idx = 1;
+ } else if (st == SYSEX_START) {
+ if (idx == SYSEX_MAXLEN) {
+ fprintf(stderr, "the message is too long\n");
+ idx = st = 0;
+ } else {
+ sysex_data[idx++] = c;
+ }
+ } else if (st) {
+ if (idx == 0 && st != SYSEX_START)
+ pm_ev.message |= (c << (8 * idx++));
+ pm_ev.message |= (c << (8 * idx++));
+ if (idx == len) {
+ pm_read_short(midi, &pm_ev);
+ if (st >= 0xf0)
+ st = 0;
+ idx = 0;
+ }
+ }
+ }
+
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static void set_mode(struct mio_dev *dev, unsigned int mode) {
+ if (dev->mode != 0)
+ mio_close(dev->hdl);
+ dev->mode = 0;
+ if (mode != 0)
+ dev->hdl = mio_open(dev->name, mode, 0);
+ if (dev->hdl)
+ dev->mode = mode;
+}
+
+static PmError sndio_out_open(PmInternal *midi, void *driverInfo)
+{
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+
+ if (dev->mode & MIO_OUT)
+ return pmNoError;
+
+ set_mode(dev, dev->mode | MIO_OUT);
+ if (!(dev->mode & MIO_OUT)) {
+ snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN,
+ "mio_open (output) failed: %s\n", dev->name);
+ return pmHostError;
+ }
+
+ return pmNoError;
+}
+
+static PmError sndio_in_open(PmInternal *midi, void *driverInfo)
+{
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+
+ if (dev->mode & MIO_IN)
+ return pmNoError;
+
+ set_mode(dev, dev->mode | MIO_IN);
+ if (!(dev->mode & MIO_IN)) {
+ snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN,
+ "mio_open (input) failed: %s\n", dev->name);
+ return pmHostError;
+ }
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_create(&dev->thread, &attr, input_thread, ( void* )midi);
+ return pmNoError;
+}
+
+static PmError sndio_out_close(PmInternal *midi)
+{
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+
+ if (dev->mode & MIO_OUT)
+ set_mode(dev, dev->mode & ~MIO_OUT);
+ return pmNoError;
+}
+
+static PmError sndio_in_close(PmInternal *midi)
+{
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+
+ if (dev->mode & MIO_IN) {
+ set_mode(dev, dev->mode & ~MIO_IN);
+ pthread_join(dev->thread, NULL);
+ dev->thread = NULL;
+ }
+ return pmNoError;
+}
+
+static PmError sndio_abort(PmInternal *midi)
+{
+ return pmNoError;
+}
+
+static PmTimestamp sndio_synchronize(PmInternal *midi)
+{
+ return 0;
+}
+
+static PmError do_write(struct mio_dev *dev, const void *addr, size_t nbytes)
+{
+ size_t w = mio_write(dev->hdl, addr, nbytes);
+
+ if (w != nbytes) {
+ snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN,
+ "mio_write failed, bytes written:%zu\n", w);
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+static PmError sndio_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp)
+{
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+
+ return do_write(dev, &byte, 1);
+}
+
+static PmError sndio_write_short(PmInternal *midi, PmEvent *event)
+{
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+ int nbytes = midi_message_length(event->message);
+
+ if (midi->latency > 0) {
+ /* XXX the event should be queued for later playback */
+ return do_write(dev, &event->message, nbytes);
+ } else {
+ return do_write(dev, &event->message, nbytes);
+ }
+ return pmNoError;
+}
+
+static PmError sndio_write_flush(PmInternal *midi, PmTimestamp timestamp)
+{
+ return pmNoError;
+}
+
+PmError sndio_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+ return pmNoError;
+}
+
+static unsigned int sndio_has_host_error(PmInternal *midi)
+{
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+
+ return (dev->errmsg[0] != '\0');
+}
+
+static void sndio_get_host_error(PmInternal *midi, char *msg, unsigned int len)
+{
+ struct mio_dev *dev = pm_descriptors[midi->device_id].descriptor;
+
+ strlcpy(msg, dev->errmsg, len);
+ dev->errmsg[0] = '\0';
+}
+
+pm_fns_node pm_sndio_in_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ sndio_synchronize,
+ sndio_in_open,
+ sndio_abort,
+ sndio_in_close,
+ success_poll,
+ sndio_has_host_error,
+};
+
+pm_fns_node pm_sndio_out_dictionary = {
+ sndio_write_short,
+ sndio_sysex,
+ sndio_sysex,
+ sndio_write_byte,
+ sndio_write_short,
+ sndio_write_flush,
+ sndio_synchronize,
+ sndio_out_open,
+ sndio_abort,
+ sndio_out_close,
+ none_poll,
+ sndio_has_host_error,
+};
+
diff --git a/portmidi/pm_sndio/pmsndio.h b/portmidi/pm_sndio/pmsndio.h
new file mode 100644
index 0000000..4096d9b
--- /dev/null
+++ b/portmidi/pm_sndio/pmsndio.h
@@ -0,0 +1,5 @@
+/* pmsndio.h */
+
+extern PmDeviceID pm_default_input_device_id;
+extern PmDeviceID pm_default_output_device_id;
+
diff --git a/portmidi/pm_test/CMakeLists.txt b/portmidi/pm_test/CMakeLists.txt
new file mode 100644
index 0000000..ae0fe48
--- /dev/null
+++ b/portmidi/pm_test/CMakeLists.txt
@@ -0,0 +1,46 @@
+# CMake file to build tests in this directory: pm_test
+
+# set the build directory to be in portmidi, not in portmidi/pm_test
+# this is required for Xcode:
+if(APPLE)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
+endif(APPLE)
+
+# if(WIN32)
+# if(NOT BUILD_SHARED_LIBS)
+ # /MDd is multithread debug DLL, /MTd is multithread debug
+ # /MD is multithread DLL, /MT is multithread. Change to static:
+# include(../pm_win/static.cmake)
+# endif()
+# endif(WIN32)
+
+if(HAIKU)
+ add_compile_options(-fPIC) # Haiku x86_64 needs this explicitly
+endif()
+
+macro(add_test name)
+ add_executable(${name} ${name}.c)
+ target_link_libraries(${name} PRIVATE portmidi)
+ set_property(TARGET ${name} PROPERTY MSVC_RUNTIME_LIBRARY
+ "MultiThreaded$<$<CONFIG:Debug>:Debug>${MSVCRT_DLL}")
+endmacro(add_test)
+
+add_test(testio)
+add_test(midithread)
+add_test(midithru)
+add_test(sysex)
+add_test(latency)
+add_test(mm)
+add_test(midiclock)
+add_test(qtest)
+add_test(fast)
+add_test(fastrcv)
+add_test(pmlist)
+if(WIN32)
+# windows does not implement Pm_CreateVirtualInput or Pm_CreateVirtualOutput
+else(WIN32)
+add_test(recvvirtual)
+add_test(sendvirtual)
+add_test(multivirtual)
+add_test(virttest)
+endif(WIN32)
diff --git a/portmidi/pm_test/README.txt b/portmidi/pm_test/README.txt
new file mode 100644
index 0000000..6c0c7ab
--- /dev/null
+++ b/portmidi/pm_test/README.txt
@@ -0,0 +1,363 @@
+README.txt - for pm_test directory
+
+These are all test programs for PortMidi
+
+Because device numbers depend on the system, there is no automated
+script to run all tests on PortMidi.
+
+To run the full set of tests manually:
+
+Note: everything is run from the ../Debug or ../Release directory.
+Actual or example input is marked with >>, e.g., >>0 means type 0<ENTER>
+Comments are shown in square brackets [like this]
+
+1. ./qtest -- output should show a bunch of tests and no error message.
+
+2. ./testio [test input]
+Latency in ms: >>0
+enter your choice... >>1
+Type input number: >>6 [pick a working input device]
+[play some notes, look for note-on (0x90) with pitch and velocity data]
+
+3. ./testio [test input (fail w/assert)]
+Latency in ms: >>0
+enter your choice... >>2
+Type input number: >>6 [pick a working input device]
+[play some notes, program will abort after 5 messages
+(this test only applies to a Debug build, otherwise
+the assert() macro is disabled.)]
+
+4. ./testio [test input (fail w/NULL assign)]
+Latency in ms: >>0
+enter your choice... >>3
+Type input number: >>6 [pick a working input device]
+[play some notes, program will Segmentation fault after 5 messages
+(this test may not Segfault in the Release build; if not
+try testing with a Debug build.)]
+
+5. ./testio [test output, no latency]
+Latency in ms: >>0
+enter your choice... >>4
+Type output number: >>2 [pick a working output device]
+>> [type ENTER when prompted (7 times)]
+[hear note on, note off, note on, note off, chord]
+
+6. ./testio [test output, latency > 0]
+Latency in ms: >>300
+enter your choice... >>4
+Type output number: >>2 [pick a working output device]
+>> [type ENTER when prompted (7 times)]
+[hear note on, note off, note on, note off, arpeggiated chord
+ (delay of 300ms should be apparent)]
+
+7. ./testio [for both, no latency]
+Latency in ms: >>0
+enter your choice... >>5
+Type input number: >>6 [pick a working input device]
+Type output number: >>2 [pick a working output device]
+[play notes on input, hear them on output]
+
+8. ./testio [for both, latency > 0]
+Latency in ms: >>300
+enter your choice... >>5
+Type input number: >>6 [pick a working input device]
+Type output number: >>2 [pick a working output device]
+[play notes on input, hear them on output (delay of 300ms is apparent)]
+
+9. ./testio [stream test]
+Latency in ms: >>0 [does not matter]
+enter your choice... >>6
+Type output number: >>2 [pick a working output device]
+>> [type ENTER to start]
+[hear 4 notes: C D E F# at one note per second, then all turn off]
+ready to close and terminate... (type ENTER) :>> [type ENTER (twice)]
+
+10. ./testio [isochronous out]
+Latency in ms: >>300
+enter your choice... >>7
+Type output number: >>2 [pick a working output device]
+ready to send program 1 change... (type ENTER): >> [type ENTER]
+[hear 80 notes, exactly 4 notes per second, no jitter]
+
+11. ./latency [no MIDI, histogram]
+Choose timer period (in ms, >= 1): >>1
+? >>1 [No MIDI traffic option]
+[wait about 10 seconds]
+>> [type ENTER]
+[output should be something like ... Maximum latency: 1 milliseconds]
+
+12. ./latency [MIDI input, histogram]
+Choose timer period (in ms, >= 1): >>1
+? >>2 [MIDI input option]
+Midi input device number: >>6 [pick a working input device]
+[wait about 5 seconds, play input for 10 seconds ]
+>> [type ENTER]
+[output should be something like ... Maximum latency: 3 milliseconds]
+
+13. ./latency [MIDI output, histogram]
+Choose timer period (in ms, >= 1): >>1
+? >>3 [MIDI output option]
+Midi output device number: >>2 [pick a working output device]
+Midi output should be sent every __ callback iterations: >>50
+[wait until you hear notes for 5 or 10 seconds]
+>> [type ENTER to stop]
+[output should be something like ... Maximum latency: 2 milliseconds]
+
+14. ./latency [MIDI input and output, histogram]
+Choose timer period (in ms, >= 1): >>1
+? >>4 [MIDI input and output option]
+Midi input device number: >>6 [pick a working input device]
+Midi output device number: >>2 [pick a working output device]
+Midi output should be sent every __ callback iterations: >>50
+[wait until you hear notes, simultaneously play notes for 5 or 10 seconds]
+>> [type ENTER to stop]
+[output should be something like ... Maximum latency: 1 milliseconds]
+
+15. ./mm [test with device input]
+Type input device number: >>6 [pick a working input device]
+[play some notes, see notes printed]
+>>q [Type q ENTER when finished to exit]
+
+16. ./midithread -i 6 -o 2 [use working input/output device numbers]
+>>5 [enter a transposition number]
+[play some notes, hear parallel 4ths]
+>>q [quit after ENTER a couple of times]
+
+17. ./midiclock [in one shell]
+ ./mm [in another shell]
+[Goal is send clock messages to MIDI monitor program. This requires
+ either a hardware loopback (MIDI cable from OUT to IN on interface)
+ or a software loopback (macOS IAC bus or ALSA MIDI Through Port)]
+[For midiclock application:]
+ Type output device number: >>0 [pick a device with loopback]
+ Type ENTER to start MIDI CLOCK: >> [type ENTER]
+[For mm application:]
+ Type input device number: >>1 [pick device with loopback]
+ [Wait a few seconds]
+ >>s [to get Clock Count]
+ >>s [expect to get a higher Clock Count]
+[For midiclock application:]
+ >>c [turn off clocks]
+[For mm application:]
+ >>s [to get Clock Count]
+ >>s [expect to Clock Count stays the same]
+[For midiclock application:]
+ >>t [turn on time code, see Time Code Quarter Frame messages from mm]
+ >>q [to quit]
+[For mm application:]
+ >>q [to quit]
+
+18. ./midithru -i 6 -o 2 [use working input/output device numbers]
+[Play notes on input evice; notes are sent immediately and also with a
+ 2 sec delay to the output device; program terminates in 60 seconds or
+ when you play B3 (B below Middle C)]
+>> [ENTER to exit]
+
+19. ./recvvirtual -h [in one shell, macOS and Linux only]
+ ./recvvirtual -m vvv [for mac, or -c vvv -p vvvport for linux]
+ ./testio [in another shell]
+[For testio application:]
+ Latency in ms: >>0
+ enter your choice... >>4 [test output]
+ Type output number: >>9 [select the "portmidi (output)" device]
+ [type ENTER to each prompt, see that recvvirtual "Got message 0"
+ through "Got message 9"]
+ >> [ENTER to quit]
+[For recvvirtual application:]
+ >> [ENTER to quit]
+
+20. ./sendvirtual -h [in one shell, macOS and Linux only]
+ ./sendvirtual -m vvv [for mac, or -c vvv -p vvvport for linux]
+ ./mm [in another shell]
+[For mm application:]
+ Type input device number: >>10 [select the "portmidi" device]
+[For sendvirtual application:]
+ Type ENTER to send messages: >> [type ENTER]
+ [see NoteOn and off messages received by mm for Key 60-64]
+ >> [ENTER to quit]
+[For mm application:]
+ >>q [and ENTER twice to quit]
+
+21. ./sysex [no latency]
+[This requires either a hardware loopback (MIDI cable from OUT to IN
+ on interface) or a software loopback (macOS IAC bus or ALSA MIDI
+ Through Port)]
+>>l [for loopback test]
+Type output device number: >>0 [pick output device to loopback]
+Latency in milliseconds: >>0
+Type input device number: >>0 [pick input device for loopback]
+[Program will send 100,000 bytes. After awhile, program will quit.
+ You can read the Cummulative bytes/sec value.]
+
+22. ./sysex [latency > 0]
+[This requires either a hardware loopback (MIDI cable from OUT to IN
+ on interface) or a software loopback (macOS IAC bus or ALSA MIDI
+ Through Port)]
+>>l [for loopback test]
+Type output device number: >>0 [pick output device to loopback]
+Latency in milliseconds: >>100
+Type input device number: >>0 [pick input device for loopback]
+[Program will send 100,000 bytes. After awhile, program will quit. You
+ can read the Cummulative bytes/sec value; it is affected by latency.]
+
+23. ./fast [no latency]
+ ./fastrcv [in another shell]
+[This is a speed check, especially for macOSX IAC bus connections,
+ which are known to drop messages if you send messages too fast.
+ fast and fastrcv must use a loopback to function.]
+[In fastrcv:]
+ Input device number: >>1 [pick a non-hardware device if possible]
+[In fast:]
+ Latency in ms: >>0
+ Rate in messages per second: >>10000
+ Duration in seconds: >>10
+ Output device number: >>0 [pick a non-hardware device if possible]
+ sending output...
+[see message counts and times; on Linux you should get about 10000
+ messages/s; on macOS you should get about 1800 messages/s; Windows
+ does not have software ports, so data rate might be limited by the
+ loopback device you use.]
+
+Check output of fastrcv: there should be no errors, just msg/sec.]
+
+24. ./fast [latency > 0]
+ ./fastrcv [in another shell]
+[This is a speed check, especially for macOSX IAC bus connections,
+ which are known to drop messages if you send messages too fast.
+ fast and fastrcv must use a loopback to function.]
+[In fastrcv:]
+ Input device number: >>1 [pick a non-hardware device if possible]
+[In fast:]
+ Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400]
+ Rate in messages per second: >>10000
+ Duration in seconds: >>10
+ Output device number: >>0 [pick a non-hardware device if possible]
+ sending output...
+[see message counts and times; on Linux you should get about 10000
+ messages/s; on macOS you should get about 1800 messages/s; Windows
+ does not have software ports, so data rate might be limited by the
+ loopback device you use.]
+
+Check output of fastrcv: there should be no errors, just msg/sec.]
+
+25. ./fast [virtual output port, latency = 0, macOS and Linux only]
+ ./fastrcv [in another shell]
+[Start fast first:]
+ Latency in ms: >>0
+ Rate in messages per second: >>10000
+ Duration in seconds: >>10
+ Output device number: >>9 [enter number listed for "Create virtual
+ port named 'fast' (output)"]
+ Pausing so you can connect a receiver to the newly created
+ "fast" port. Type ENTER to proceed:
+[In fastrcv:]
+ Input device number: >>3 [pick the device named "fast (input)"]
+[In fast:]
+ >> [type ENTER to start]
+[see message counts and times as above ]
+
+Check output of fastrcv: there should be no errors, just msg/sec.]
+
+26. ./fast [virtual output port, latency > 0, macOS and Linux only]
+ ./fastrcv [in another shell]
+[Start fast first:]
+ Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400]
+ Rate in messages per second: >>10000
+ Duration in seconds: >>10
+ Output device number: >>9 [enter number listed for "Create virtual
+ port named 'fast' (output)"]
+ Pausing so you can connect a receiver to the newly created
+ "fast" port. Type ENTER to proceed:
+[In fastrcv:]
+ Input device number: >>3 [pick the device named "fast (input)"]
+[In fast:]
+ >> [type ENTER to start]
+[see message counts and times as above ]
+
+Check output of fastrcv: there should be no errors, just msg/sec.]
+
+27. ./fast [latency = 0, macOS and Linux only]
+ ./fastrcv [virtual input port, in another shell]
+[In fastrcv:]
+ Input device number: >>8 [enter number listed for "Create virtual
+ port named 'fastrcv' (input)"]
+[In fast:]
+ Latency in ms: >>0
+ Rate in messages per second: >>10000
+ Duration in seconds: >>10
+ Output device number: >>7 [pick the device named "fastrcv (output)"]
+ sending output...
+[see message counts and times as above ]
+
+Check output of fastrcv: there should be no errors, just msg/sec.]
+
+28. ./fast [latency > 0, macOS and Linux only]
+ ./fastrcv [virtual input port, in another shell]
+[In fastrcv:]
+ Input device number: >>8 [enter number listed for "Create virtual
+ port named 'fastrcv' (input)"]
+[In fast:]
+ Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400]
+ Rate in messages per second: >>10000
+ Duration in seconds: >>10
+ Output device number: >>7 [pick the device named "fastrcv (output)"]
+ sending output...
+[see message counts and times as above ]
+
+Check output of fastrcv: there should be no errors, just msg/sec.]
+
+29. ./midithru -v -n [virtual input and output, macOS and Linux only]
+ ./fast [latency = 0]
+ ./fastrcv [in another shell]
+[Start midithru first, it will run for 60 seconds]
+[In fastrcv:]
+ Input device number: >>3 [pick the device named
+ port named "midithru (input)"]
+[In fast:]
+ Latency in ms: >>0
+ Rate in messages per second: >>10000
+ Duration in seconds: >>10
+ Output device number: >>8 [pick the device named "midithru (output)"]
+ sending output...
+[see message counts and times as above, on Mac, output from fast to
+ midithru AND output from midithru to fastrcv are rate limited, so
+ as in other tests, it will take more than 10s to receive all the
+ messages and the receiving message rate will be about 1800 messages/second]
+
+30. ./multivirtual [macOS and Linux only]
+ ./testio
+ ./testio
+[Start multivirtual first]
+[In first testio:]
+ Latency in ms: >>0
+ enter your choice... >>5 [test both]
+ Type input number: >>1 [pick portmidi1 (input)
+ Type output number: >>4 [pick portmidi1 (output)
+[In second testio:]
+ Latency in ms: >>10
+ enter your choice... >>5 [test both]
+ Type input number: >>2 [pick portmidi2 (input)
+ Type output number: >>5 [pick portmidi2 (output)
+[In multivirtual:]
+ Type ENTER to send messages: >> [type ENTER to start]
+[see that each testio gets 11 messages (0 to 10) at reasonable times
+ (e.g. 2077 to 7580, and the "@" times (real times) should match the
+ timestamps). multivirtual should also report reasonable times and
+ line near the end of output should be "Got 11 messages from
+ portmidi1 and 11 from portmidi2; expected 11."]
+
+31. ./multivirtual [macOS and Linux only]
+ ./multivirtual
+[Second instance should report "PortMidi call failed...
+ PortMidi: Cannot create virtual device: name is taken"]
+
+32. pmlist
+ ./pmlist [check the output]
+ [plug in or remove a device]
+ >> [type RETURN]
+ [check for changes in device list]
+ >>q
+
+
+
+
diff --git a/portmidi/pm_test/fast.c b/portmidi/pm_test/fast.c
new file mode 100644
index 0000000..102697e
--- /dev/null
+++ b/portmidi/pm_test/fast.c
@@ -0,0 +1,290 @@
+/* fast.c -- send many MIDI messages very fast.
+ *
+ * This is a stress test created to explore reports of
+ * pm_write() call blocking (forever) on Linux when
+ * sending very dense MIDI sequences.
+ *
+ * Modified 8 Aug 2017 with -n to send expired timestamps
+ * to test a theory about why Linux ALSA hangs in Audacity.
+ *
+ * Modified 9 Aug 2017 with -m, -p to test when timestamps are
+ * wrapping from negative to positive or positive to negative.
+ *
+ * Roger B. Dannenberg, Aug 2017
+ */
+
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define DEVICE_INFO NULL
+#define DRIVER_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+#define STRING_MAX 80 /* used for console input */
+// need to get declaration for Sleep()
+#ifdef WIN32
+#include "windows.h"
+#else
+#include <unistd.h>
+#define Sleep(n) usleep(n * 1000)
+#endif
+
+
+int32_t latency = 0;
+int32_t msgrate = 0;
+int deviceno = -9999;
+int duration = 0;
+int expired_timestamps = FALSE;
+int use_timeoffset = 0;
+
+/* read a number from console */
+/**/
+int get_number(const char *prompt)
+{
+ int n = 0, i;
+ fputs(prompt, stdout);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ while (getchar() != '\n') ;
+ }
+ return i;
+}
+
+
+/* get_time -- the time reference. Normally, this will be the default
+ * time, Pt_Time(), but if you use the -p or -m option, the time
+ * reference will start at an offset of -10s for -m, or
+ * maximum_time - 10s for -p, so that we can observe what happens
+ * with negative time or when time changes sign or wraps (by
+ * generating output for more than 10s).
+ */
+PmTimestamp get_time(void *info)
+{
+ PmTimestamp now = (PmTimestamp) (Pt_Time() + use_timeoffset);
+ return now;
+}
+
+
+void fast_test()
+{
+ PmStream *midi;
+ char line[STRING_MAX];
+ int pause = FALSE; /* pause if this is a virtual output port */
+ PmError err = pmNoError;
+ /* output buffer size should be a little more than
+ msgrate * latency / 1000. PortMidi will guarantee
+ a minimum of latency / 2 */
+ int buffer_size = msgrate * latency / 900;
+ PmTimestamp start, now;
+ int msgcnt = 0;
+ int polling_count = 0;
+ int pitch = 60;
+ int printtime = 1000;
+
+ /* It is recommended to start timer before PortMidi */
+ TIME_START;
+
+ /* open output device */
+ if (deviceno == Pm_CountDevices()) {
+ deviceno = Pm_CreateVirtualOutput("fast", NULL, DEVICE_INFO);
+ if (deviceno >= 0) {
+ err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size,
+ get_time, NULL, latency);
+ pause = TRUE;
+ }
+ } else if (err >= pmNoError) {
+ err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size,
+ get_time, NULL, latency);
+ }
+ if (err == pmHostError) {
+ Pm_GetHostErrorText(line, STRING_MAX);
+ printf("PortMidi found host error...\n %s\n", line);
+ goto done;
+ } else if (err < 0) {
+ printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
+ goto done;
+ }
+ printf("Midi Output opened with %ld ms latency.\n", (long) latency);
+ if (pause) {
+ printf("Pausing so you can connect a receiver to the newly created\n"
+ " \"fast\" port. Type ENTER to proceed: ");
+ while (getchar() != '\n') ;
+ }
+ /* wait a sec after printing previous line */
+ start = get_time(NULL) + 1000;
+ while (start > get_time(NULL)) {
+ Sleep(10);
+ }
+ printf("sending output...\n");
+ fflush(stdout); /* make sure message goes to console */
+
+ /* every 10ms send on/off pairs at timestamps set to current time */
+ now = get_time(NULL);
+ /* if expired_timestamps, we want to send timestamps that have
+ * expired. They should be sent immediately, but there's a suggestion
+ * that negative delay might cause problems in the ALSA implementation
+ * so this is something we can test using the -n flag.
+ */
+ if (expired_timestamps) {
+ now = now - 2 * latency;
+ }
+
+ while (((PmTimestamp) (now - start)) < duration * 1000 || pitch != 60) {
+ /* how many messages do we send? Total should be
+ * (elapsed * rate) / 1000
+ */
+ int send_total = (((PmTimestamp) ((now - start))) * msgrate) / 1000;
+ /* always send until pitch would be 60 so if we run again, the
+ next pitch (60) will be expected */
+ if (msgcnt < send_total) {
+ if ((msgcnt & 1) == 0) {
+ Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100));
+ } else {
+ Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0));
+ /* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */
+ pitch = (pitch - 59) % 12 + 60;
+ }
+ msgcnt += 1;
+ if (((PmTimestamp) (now - start)) >= printtime) {
+ printf("%d at %dms, polling count %d\n", msgcnt, now - start,
+ polling_count);
+ fflush(stdout); /* make sure message goes to console */
+ printtime += 1000; /* next msg in 1s */
+ }
+ }
+ now = get_time(NULL);
+ polling_count++;
+ }
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close and terminate... (type RETURN):");
+ while (getchar() != '\n') ;
+
+ Pm_Close(midi);
+ done:
+ Pm_Terminate();
+ printf("done closing and terminating...\n");
+}
+
+
+void show_usage()
+{
+ printf("Usage: fast [-h] [-l latency] [-r rate] [-d device] [-s dur] "
+ "[-n] [-p] [-m]\n"
+ ", where latency is in ms,\n"
+ " rate is messages per second,\n"
+ " device is the PortMidi device number,\n"
+ " dur is the length of the test in seconds,\n"
+ " -n means send timestamps in the past,\n"
+ " -p means use a large positive time offset,\n"
+ " -m means use a large negative time offset, and\n"
+ " -h means help.\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int default_in;
+ int default_out;
+ char *deflt;
+ int i = 0;
+ int latency_valid = FALSE;
+ int rate_valid = FALSE;
+ int device_valid = FALSE;
+ int dur_valid = FALSE;
+
+ if (sizeof(void *) == 8)
+ printf("Apparently this is a 64-bit machine.\n");
+ else if (sizeof(void *) == 4)
+ printf ("Apparently this is a 32-bit machine.\n");
+
+ if (argc <= 1) {
+ show_usage();
+ } else {
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0) {
+ show_usage();
+ } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ latency = atoi(argv[i]);
+ printf("Latency will be %ld\n", (long) latency);
+ latency_valid = TRUE;
+ } else if (strcmp(argv[i], "-r") == 0) {
+ i = i + 1;
+ msgrate = atoi(argv[i]);
+ printf("Rate will be %d messages/second\n", msgrate);
+ rate_valid = TRUE;
+ } else if (strcmp(argv[i], "-d") == 0) {
+ i = i + 1;
+ deviceno = atoi(argv[i]);
+ printf("Device will be %d\n", deviceno);
+ } else if (strcmp(argv[i], "-s") == 0) {
+ i = i + 1;
+ duration = atoi(argv[i]);
+ printf("Duration will be %d seconds\n", duration);
+ dur_valid = TRUE;
+ } else if (strcmp(argv[i], "-n") == 0) {
+ printf("Sending expired timestamps (-n)\n");
+ expired_timestamps = TRUE;
+ } else if (strcmp(argv[i], "-p") == 0) {
+ printf("Time offset set to 2147473648 (-p)\n");
+ use_timeoffset = 2147473648;
+ } else if (strcmp(argv[i], "-m") == 0) {
+ printf("Time offset set to -10000 (-m)\n");
+ use_timeoffset = -10000;
+ } else {
+ show_usage();
+ }
+ }
+ }
+
+ if (!latency_valid) {
+ // coerce to known size
+ latency = (int32_t) get_number("Latency in ms: ");
+ }
+
+ if (!rate_valid) {
+ // coerce from "%d" to known size
+ msgrate = (int32_t) get_number("Rate in messages per second: ");
+ }
+
+ if (!dur_valid) {
+ duration = get_number("Duration in seconds: ");
+ }
+
+ /* list device information */
+ default_in = Pm_GetDefaultInputDeviceID();
+ default_out = Pm_GetDefaultOutputDeviceID();
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (info->output) {
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (i == deviceno) {
+ device_valid = TRUE;
+ deflt = "selected ";
+ } else if (i == default_out) {
+ deflt = "default ";
+ } else {
+ deflt = "";
+ }
+ printf(" (%soutput)\n", deflt);
+ }
+ }
+ printf("%d: Create virtual port named \"fast\"", i);
+ if (i == deviceno) {
+ device_valid = TRUE;
+ deflt = "selected ";
+ } else {
+ deflt = "";
+ }
+ printf(" (%soutput)\n", deflt);
+
+ if (!device_valid) {
+ deviceno = get_number("Output device number: ");
+ }
+
+ fast_test();
+ return 0;
+}
diff --git a/portmidi/pm_test/fastrcv.c b/portmidi/pm_test/fastrcv.c
new file mode 100644
index 0000000..dabf9fa
--- /dev/null
+++ b/portmidi/pm_test/fastrcv.c
@@ -0,0 +1,255 @@
+/* fastrcv.c -- send many MIDI messages very fast.
+ *
+ * This is a stress test created to explore reports of
+ * pm_write() call blocking (forever) on Linux when
+ * sending very dense MIDI sequences.
+ *
+ * Modified 8 Aug 2017 with -n to send expired timestamps
+ * to test a theory about why Linux ALSA hangs in Audacity.
+ *
+ * Roger B. Dannenberg, Aug 2017
+ */
+
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define INPUT_BUFFER_SIZE 1000 /* big to avoid losing any input */
+#define DEVICE_INFO NULL
+#define DRIVER_INFO NULL
+#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
+#define TIME_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+#define STRING_MAX 80 /* used for console input */
+// need to get declaration for Sleep()
+#ifdef WIN32
+#include "windows.h"
+#else
+#include <unistd.h>
+#define Sleep(n) usleep(n * 1000)
+#endif
+
+
+int deviceno = -9999;
+int verbose = FALSE;
+
+
+static void prompt_and_exit(void)
+{
+ printf("type ENTER...");
+ while (getchar() != '\n') ;
+ /* this will clean up open ports: */
+ exit(-1);
+}
+
+
+static PmError checkerror(PmError err)
+{
+ if (err == pmHostError) {
+ /* it seems pointless to allocate memory and copy the string,
+ * so I will do the work of Pm_GetHostErrorText directly
+ */
+ char errmsg[80];
+ Pm_GetHostErrorText(errmsg, 80);
+ printf("PortMidi found host error...\n %s\n", errmsg);
+ prompt_and_exit();
+ } else if (err < 0) {
+ printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
+ prompt_and_exit();
+ }
+ return err;
+}
+
+
+/* read a number from console */
+/**/
+int get_number(const char *prompt)
+{
+ int n = 0, i;
+ fputs(prompt, stdout);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ while (getchar() != '\n') ;
+ }
+ return i;
+}
+
+
+void fastrcv_test()
+{
+ PmStream * midi;
+ PmError status, length;
+ PmEvent buffer[1];
+ PmTimestamp start;
+ /* every 10ms read all messages, keep counts */
+ /* every 1000ms, print report */
+ int msgcnt = 0;
+ /* expect repeating sequence of 60 through 71, alternating on/off */
+ int expected_pitch = 60;
+ int expected_on = TRUE;
+ int report_time;
+ PmTimestamp last_timestamp = -1;
+ PmTimestamp last_delta = -1;
+
+ /* It is recommended to start timer before PortMidi */
+ TIME_START;
+
+ /* open output device */
+ if (deviceno == Pm_CountDevices()) {
+ int id = Pm_CreateVirtualInput("fastrcv", NULL, DEVICE_INFO);
+ if (id < 0) checkerror(id); /* error reporting */
+ checkerror(Pm_OpenInput(&midi, id, DRIVER_INFO,
+ INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO));
+ } else {
+ Pm_OpenInput(&midi, deviceno, DRIVER_INFO, INPUT_BUFFER_SIZE,
+ TIME_PROC, TIME_INFO);
+ }
+ printf("Midi Input opened.\n");
+
+ /* wait a sec after printing previous line */
+ start = Pt_Time() + 1000;
+ while (start > Pt_Time()) {
+ Sleep(10);
+ }
+
+ report_time = Pt_Time() + 1000; /* report every 1s */
+ while (TRUE) {
+ PmTimestamp now = Pt_Time();
+ status = Pm_Poll(midi);
+ if (status == TRUE) {
+ length = Pm_Read(midi, buffer, 1);
+ if (length > 0) {
+ int status = Pm_MessageStatus(buffer[0].message);
+ if (status == 0x80) { /* convert NoteOff to NoteOn, vel=0 */
+ status = 0x90;
+ buffer[0].message = Pm_Message(status,
+ Pm_MessageData1(buffer[0].message), 0);
+ }
+ /* only listen to NOTEON messages */
+ if (status == 0x90) {
+ int pitch = Pm_MessageData1(buffer[0].message);
+ int vel = Pm_MessageData2(buffer[0].message);
+ int is_on = (vel > 0);
+ if (verbose) {
+ printf("Note pitch %d vel %d\n", pitch, vel);
+ }
+ msgcnt++;
+ if (pitch != expected_pitch || expected_on != is_on) {
+ printf("Unexpected note-on: pitch %d vel %d, "
+ "expected: pitch %d Note%s\n", pitch, vel,
+ expected_pitch, (expected_on ? "On" : "Off"));
+ }
+ if (is_on) {
+ expected_on = FALSE;
+ expected_pitch = pitch;
+ } else {
+ expected_on = TRUE;
+ expected_pitch = (pitch + 1) % 72;
+ if (expected_pitch < 60) expected_pitch = 60;
+ }
+ if (last_timestamp >= 0) {
+ last_delta = buffer[0].timestamp - last_timestamp;
+ }
+ last_timestamp = buffer[0].timestamp;
+ }
+ }
+ }
+ if (now >= report_time) {
+ printf("%d msgs/sec", msgcnt);
+ /* if available, print the last timestamp and last delta time */
+ if (last_timestamp >= 0) {
+ printf(" last timestamp %d", (int) last_timestamp);
+ last_timestamp = -1;
+ }
+ if (last_delta >= 0) {
+ printf(" last delta time %d", (int) last_delta);
+ last_delta = -1;
+ }
+ printf("\n");
+ report_time += 1000;
+ msgcnt = 0;
+ }
+ }
+}
+
+
+void show_usage()
+{
+ printf("Usage: fastrcv [-h] [-v] [-d device], where\n"
+ "device is the PortMidi device number,\n"
+ "-h means help,\n"
+ "-v means verbose (print messages)\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int default_in;
+ int default_out;
+ char *deflt;
+
+ int i = 0;
+ int test_input = 0, test_output = 0, test_both = 0;
+ int stream_test = 0;
+ int device_valid = FALSE;
+
+ if (sizeof(void *) == 8)
+ printf("Apparently this is a 64-bit machine.\n");
+ else if (sizeof(void *) == 4)
+ printf ("Apparently this is a 32-bit machine.\n");
+
+ if (argc <= 1) {
+ show_usage();
+ } else {
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0) {
+ show_usage();
+ } else if (strcmp(argv[i], "-v") == 0) {
+ verbose = TRUE;
+ } else if (strcmp(argv[i], "-d") == 0) {
+ i = i + 1;
+ deviceno = atoi(argv[i]);
+ printf("Device will be %d\n", deviceno);
+ } else {
+ show_usage();
+ }
+ }
+ }
+
+ /* list device information */
+ default_in = Pm_GetDefaultInputDeviceID();
+ default_out = Pm_GetDefaultOutputDeviceID();
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (!info->output) {
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (i == deviceno) {
+ device_valid = TRUE;
+ deflt = "selected ";
+ } else if (i == default_out) {
+ deflt = "default ";
+ } else {
+ deflt = "";
+ }
+ printf(" (%sinput)\n", deflt);
+ }
+ }
+ printf("%d: Create virtual port named \"fastrcv\"", i);
+ if (i == deviceno) {
+ device_valid = TRUE;
+ deflt = "selected ";
+ } else {
+ deflt = "";
+ }
+ printf(" (%sinput)\n", deflt);
+
+ if (!device_valid) {
+ deviceno = get_number("Input device number: ");
+ }
+
+ fastrcv_test();
+ return 0;
+}
diff --git a/portmidi/pm_test/latency.c b/portmidi/pm_test/latency.c
new file mode 100755
index 0000000..06ea80d
--- /dev/null
+++ b/portmidi/pm_test/latency.c
@@ -0,0 +1,287 @@
+/* latency.c -- measure latency of OS */
+
+#include "porttime.h"
+#include "portmidi.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+/* Latency is defined here to mean the time starting when a
+ process becomes ready to run, and ending when the process
+ actually runs. Latency is due to contention for the
+ processor, usually due to other processes, OS activity
+ including device drivers handling interrupts, and
+ waiting for the scheduler to suspend the currently running
+ process and activate the one that is waiting.
+
+ Latency can affect PortMidi applications: if a process fails
+ to wake up promptly, MIDI input may sit in the input buffer
+ waiting to be handled, and MIDI output may not be generated
+ with accurate timing. Using the latency parameter when
+ opening a MIDI output port allows the caller to defer timing
+ to PortMidi, which in most implementations will pass the
+ data on to the OS. By passing timestamps and data to the
+ OS kernel, device driver, or even hardware, there are fewer
+ sources of latency that can affect the ultimate timing of
+ the data. On the other hand, the application must generate
+ and deliver the data ahead of the timestamp. The amount by
+ which data is computed early must be at least as large as
+ the worst-case latency to avoid timing problems.
+
+ Latency is even more important in audio applications. If an
+ application lets an audio output buffer underflow, an audible
+ pop or click is produced. Audio input buffers can overflow,
+ causing data to be lost. In general the audio buffers must
+ be large enough to buffer the worst-case latency that the
+ application will encounter.
+
+ This program measures latency by recording the difference
+ between the scheduled callback time and the current real time.
+ We do not really know the scheduled callback time, so we will
+ record the differences between the real time of each callback
+ and the real time of the previous callback. Differences that
+ are larger than the scheduled difference are recorded. Smaller
+ differences indicate the system is recovering from an earlier
+ latency, so these are ignored.
+ Since printing by the callback process can cause all sorts of
+ delays, this program records latency observations in a
+ histogram. When the program is stopped, the histogram is
+ printed to the console.
+
+ Optionally the system can be tested under a load of MIDI input,
+ MIDI output, or both. If MIDI input is selected, the callback
+ thread will read any waiting MIDI events each iteration. You
+ must generate events on this interface for the test to actually
+ put any appreciable load on PortMidi. If MIDI output is
+ selected, alternating note on and note off events are sent each
+ X iterations, where you specify X. For example, with a timer
+ callback period of 2ms and X=1, a MIDI event is sent every 2ms.
+
+
+ INTERPRETING RESULTS: Time is quantized to 1ms, so there is
+ some uncertainty due to rounding. A microsecond latency that
+ spans the time when the clock is incremented will be reported
+ as a latency of 1. On the other hand, a latency of almost
+ 1ms that falls between two clock ticks will be reported as
+ zero. In general, if the highest nonzero bin is numbered N,
+ then the maximum latency is N+1.
+
+CHANGE LOG
+
+18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive
+ MIDI during test, and made period user-settable.
+ */
+
+#define HIST_LEN 21 /* how many 1ms bins in the histogram */
+
+#define STRING_MAX 80 /* used for console input */
+
+#define INPUT_BUFFER_SIZE 100
+#define OUTPUT_BUFFER_SIZE 0
+
+#ifndef max
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef min
+#define min(a, b) ((a) <= (b) ? (a) : (b))
+#endif
+
+int get_number(const char *prompt);
+
+PtTimestamp previous_callback_time = 0;
+
+int period; /* milliseconds per callback */
+
+int histogram[HIST_LEN];
+int max_latency = 0; /* worst latency observed */
+int out_of_range = 0; /* how many points outside of HIST_LEN? */
+
+int test_in, test_out; /* test MIDI in and/or out? */
+int output_period; /* output MIDI every __ iterations if test_out true */
+int iteration = 0;
+PmStream *in, *out;
+int note_on = 0; /* is the note currently on? */
+
+/* callback function for PortTime -- computes histogram */
+void pt_callback(PtTimestamp timestamp, void *userData)
+{
+ PtTimestamp difference = timestamp - previous_callback_time - period;
+ previous_callback_time = timestamp;
+
+ /* allow 5 seconds for the system to settle down */
+ if (timestamp < 5000) return;
+
+ iteration++;
+ /* send a note on/off if user requested it */
+ if (test_out && (iteration % output_period == 0)) {
+ PmEvent buffer[1];
+ buffer[0].timestamp = Pt_Time();
+ if (note_on) {
+ /* note off */
+ buffer[0].message = Pm_Message(0x90, 60, 0);
+ note_on = 0;
+ } else {
+ /* note on */
+ buffer[0].message = Pm_Message(0x90, 60, 100);
+ note_on = 1;
+ }
+ Pm_Write(out, buffer, 1);
+ iteration = 0;
+ }
+
+ /* read all waiting events (if user requested) */
+ if (test_in) {
+ PmError status;
+ PmEvent buffer[1];
+ do {
+ status = Pm_Poll(in);
+ if (status == TRUE) {
+ Pm_Read(in,buffer,1);
+ }
+ } while (status == TRUE);
+ }
+
+ if (difference < 0) return; /* ignore when system is "catching up" */
+
+ /* update the histogram */
+ if (difference < HIST_LEN) {
+ histogram[difference]++;
+ } else {
+ out_of_range++;
+ }
+
+ if (max_latency < difference) max_latency = difference;
+}
+
+
+int main()
+{
+ int i;
+ int len;
+ int choice;
+ PtTimestamp stop;
+ printf("Latency histogram.\n");
+ period = 0;
+ while (period < 1) {
+ period = get_number("Choose timer period (in ms, >= 1): ");
+ }
+ printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n",
+ "1. No MIDI traffic",
+ "2. MIDI input",
+ "3. MIDI output",
+ "4. MIDI input and output");
+ choice = get_number("? ");
+ switch (choice) {
+ case 1: test_in = 0; test_out = 0; break;
+ case 2: test_in = 1; test_out = 0; break;
+ case 3: test_in = 0; test_out = 1; break;
+ case 4: test_in = 1; test_out = 1; break;
+ default: assert(0);
+ }
+ if (test_in || test_out) {
+ /* list device information */
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if ((test_in && info->input) ||
+ (test_out && info->output)) {
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (info->input) printf(" (input)");
+ if (info->output) printf(" (output)");
+ printf("\n");
+ }
+ }
+ /* open stream(s) */
+ if (test_in) {
+ int i = get_number("MIDI input device number: ");
+ Pm_OpenInput(&in,
+ i,
+ NULL,
+ INPUT_BUFFER_SIZE,
+ (PmTimestamp (*)(void *)) Pt_Time,
+ NULL);
+ /* turn on filtering; otherwise, input might overflow in the
+ 5-second period before timer callback starts reading midi */
+ Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+ }
+ if (test_out) {
+ int i = get_number("MIDI output device number: ");
+ PmEvent buffer[1];
+ Pm_OpenOutput(&out,
+ i,
+ NULL,
+ OUTPUT_BUFFER_SIZE,
+ (PmTimestamp (*)(void *)) Pt_Time,
+ NULL,
+ 0); /* no latency scheduling */
+
+ /* send a program change to force a status byte -- this fixes
+ a problem with a buggy linux MidiSport driver, and shouldn't
+ hurt anything else
+ */
+ buffer[0].timestamp = 0;
+ buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */
+ Pm_Write(out, buffer, 1);
+
+ output_period = get_number(
+ "MIDI out should be sent every __ callback iterations: ");
+
+ assert(output_period >= 1);
+ }
+ }
+
+ printf("Latency measurements will start in 5 seconds. "
+ "Type return to stop: ");
+ Pt_Start(period, &pt_callback, 0);
+ while (getchar() != '\n') ;
+ stop = Pt_Time();
+ Pt_Stop();
+
+ /* courteously turn off the last note, if necessary */
+ if (note_on) {
+ PmEvent buffer[1];
+ buffer[0].timestamp = Pt_Time();
+ buffer[0].message = Pm_Message(0x90, 60, 0);
+ Pm_Write(out, buffer, 1);
+ }
+
+ /* print the histogram */
+ printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001);
+ printf("Latency(ms) Number of occurrences\n");
+ /* avoid printing beyond last non-zero histogram entry */
+ len = min(HIST_LEN, max_latency + 1);
+ for (i = 0; i < len; i++) {
+ printf("%2d %10d\n", i, histogram[i]);
+ }
+ printf("Number of points greater than %dms: %d\n",
+ HIST_LEN - 1, out_of_range);
+ printf("Maximum latency: %d milliseconds\n", max_latency);
+ printf("\nNote that due to rounding, actual latency can be 1ms higher\n");
+ printf("than the numbers reported here.\n");
+ printf("Type return to exit...");
+ while (getchar() != '\n') ;
+
+ if(choice == 2)
+ Pm_Close(in);
+ else if(choice == 3)
+ Pm_Close(out);
+ else if(choice == 4)
+ {
+ Pm_Close(in);
+ Pm_Close(out);
+ }
+ return 0;
+}
+
+
+/* read a number from console */
+int get_number(const char *prompt)
+{
+ int n = 0, i;
+ fputs(prompt, stdout);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ while (getchar() != '\n') ;
+ }
+ return i;
+}
diff --git a/portmidi/pm_test/midiclock.c b/portmidi/pm_test/midiclock.c
new file mode 100644
index 0000000..f0a6897
--- /dev/null
+++ b/portmidi/pm_test/midiclock.c
@@ -0,0 +1,282 @@
+/* miditime.c -- a test program that sends midi clock and MTC */
+
+#include "portmidi.h"
+#include "porttime.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#ifndef false
+#define false 0
+#define true 1
+#endif
+
+#define private static
+typedef int boolean;
+
+#define MIDI_TIME_CLOCK 0xf8
+#define MIDI_START 0xfa
+#define MIDI_CONTINUE 0xfb
+#define MIDI_STOP 0xfc
+#define MIDI_Q_FRAME 0xf1
+
+#define OUTPUT_BUFFER_SIZE 0
+#define DRIVER_INFO NULL
+#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
+#define TIME_INFO NULL
+#define LATENCY 0
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+#define STRING_MAX 80 /* used for console input */
+
+/* to determine ms per clock:
+ * time per beat in seconds = 60 / tempo
+ * multiply by 1000 to get time per beat in ms: 60000 / tempo
+ * divide by 24 CLOCKs per beat: (60000/24) / tempo
+ * simplify: 2500 / tempo
+ */
+#define TEMPO_TO_CLOCK 2500.0
+
+boolean done = false;
+PmStream *midi;
+/* shared flags to control callback output generation: */
+boolean clock_running = false;
+boolean send_start_stop = false;
+boolean time_code_running = false;
+boolean active = false; /* tells callback to do its thing */
+float tempo = 60.0F;
+/* protocol for handing off portmidi to callback thread:
+ main owns portmidi
+ main sets active = true: ownership transfers to callback
+ main sets active = false: main requests ownership
+ callback sees active == false, yields ownership back to main
+ main waits 2ms to make sure callback has a chance to yield
+ (stop making PortMidi calls), then assumes it can close
+ PortMidi
+ */
+
+/* timer_poll -- the timer callback function */
+/*
+ * All MIDI sends take place here
+ */
+void timer_poll(PtTimestamp timestamp, void *userData)
+{
+ static int callback_owns_portmidi = false;
+ static PmTimestamp clock_start_time = 0;
+ static double next_clock_time = 0;
+ /* SMPTE time */
+ static int frames = 0;
+ static int seconds = 0;
+ static int minutes = 0;
+ static int hours = 0;
+ static int mtc_count = 0; /* where are we in quarter frame sequence? */
+ static int smpte_start_time = 0;
+ static double next_smpte_time = 0;
+ #define QUARTER_FRAME_PERIOD (1.0 / 120.0) /* 30fps, 1/4 frame */
+
+ if (callback_owns_portmidi && !active) {
+ /* main is requesting (by setting active to false) that we shut down */
+ callback_owns_portmidi = false;
+ return;
+ }
+ if (!active) return; /* main still getting ready or it's closing down */
+ callback_owns_portmidi = true; /* main is ready, we have portmidi */
+ if (send_start_stop) {
+ if (clock_running) {
+ Pm_WriteShort(midi, 0, MIDI_STOP);
+ } else {
+ Pm_WriteShort(midi, 0, MIDI_START);
+ clock_start_time = timestamp;
+ next_clock_time = TEMPO_TO_CLOCK / tempo;
+ }
+ clock_running = !clock_running;
+ send_start_stop = false; /* until main sets it again */
+ /* note that there's a slight race condition here: main could
+ set send_start_stop asynchronously, but we assume user is
+ typing slower than the clock rate */
+ }
+ if (clock_running) {
+ if ((timestamp - clock_start_time) > next_clock_time) {
+ Pm_WriteShort(midi, 0, MIDI_TIME_CLOCK);
+ next_clock_time += TEMPO_TO_CLOCK / tempo;
+ }
+ }
+ if (time_code_running) {
+ int data = 0; // initialization avoids compiler warning
+ if ((timestamp - smpte_start_time) < next_smpte_time)
+ return;
+ switch (mtc_count) {
+ case 0: /* frames low nibble */
+ data = frames;
+ break;
+ case 1: /* frames high nibble */
+ data = frames >> 4;
+ break;
+ case 2: /* frames seconds low nibble */
+ data = seconds;
+ break;
+ case 3: /* frames seconds high nibble */
+ data = seconds >> 4;
+ break;
+ case 4: /* frames minutes low nibble */
+ data = minutes;
+ break;
+ case 5: /* frames minutes high nibble */
+ data = minutes >> 4;
+ break;
+ case 6: /* hours low nibble */
+ data = hours;
+ break;
+ case 7: /* hours high nibble */
+ data = hours >> 4;
+ break;
+ }
+ data &= 0xF; /* take only 4 bits */
+ Pm_WriteShort(midi, 0,
+ Pm_Message(MIDI_Q_FRAME, (mtc_count << 4) + data, 0));
+ mtc_count = (mtc_count + 1) & 7; /* wrap around */
+ if (mtc_count == 0) { /* update time by two frames */
+ frames += 2;
+ if (frames >= 30) {
+ frames = 0;
+ seconds++;
+ if (seconds >= 60) {
+ seconds = 0;
+ minutes++;
+ if (minutes >= 60) {
+ minutes = 0;
+ hours++;
+ /* just let hours wrap if it gets that far */
+ }
+ }
+ }
+ }
+ next_smpte_time += QUARTER_FRAME_PERIOD;
+ } else { /* time_code_running is false */
+ smpte_start_time = timestamp;
+ /* so that when it finally starts, we'll be in sync */
+ }
+}
+
+
+/* read a number from console */
+/**/
+int get_number(const char *prompt)
+{
+ int n = 0, i;
+ fputs(prompt, stdout);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ while (getchar() != '\n') ;
+ }
+ return i;
+}
+
+/****************************************************************************
+* showhelp
+* Effect: print help text
+****************************************************************************/
+
+private void showhelp()
+{
+ printf("\n");
+ printf("t toggles sending MIDI Time Code (MTC)\n");
+ printf("c toggles sending MIDI CLOCK (initially on)\n");
+ printf("m to set tempo (from 1bpm to 300bpm)\n");
+ printf("q quits\n");
+ printf("\n");
+}
+
+/****************************************************************************
+* doascii
+* Inputs:
+* char c: input character
+* Effect: interpret to control output
+****************************************************************************/
+
+private void doascii(char c)
+{
+ if (isupper(c)) c = tolower(c);
+ if (c == 'q') done = true;
+ else if (c == 'c') {
+ printf("%s MIDI CLOCKs\n", (clock_running ? "Stopping" : "Starting"));
+ send_start_stop = true;
+ } else if (c == 't') {
+ printf("%s MIDI Time Code\n",
+ (time_code_running ? "Stopping" : "Starting"));
+ time_code_running = !time_code_running;
+ } else if (c == 'm') {
+ int input_tempo = get_number("Enter new tempo (bpm): ");
+ if (input_tempo >= 1 && input_tempo <= 300) {
+ printf("Changing tempo to %d\n", input_tempo);
+ tempo = (float) input_tempo;
+ } else {
+ printf("Tempo range is 1 to 300, current tempo is %g bpm\n",
+ tempo);
+ }
+ } else {
+ showhelp();
+ }
+}
+
+
+/* main - prompt for parameters, start processing */
+/*
+ * Prompt user to type return.
+ * Then send START and MIDI CLOCK for 60 beats/min.
+ * Commands:
+ * t - toggle sending MIDI Time Code (MTC)
+ * c - toggle sending MIDI CLOCK
+ * m - set tempo
+ * q - quit
+ */
+int main(int argc, char **argv)
+{
+ int outp;
+ PmError err;
+ int i;
+ if (argc > 1) {
+ printf("Warning: command line arguments ignored\n");
+ }
+ showhelp();
+ /* use porttime callback to send midi */
+ Pt_Start(1, timer_poll, 0);
+ /* list device information */
+ printf("MIDI output devices:\n");
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (info->output) printf("%d: %s, %s\n", i, info->interf, info->name);
+ }
+ outp = get_number("Type output device number: ");
+ err = Pm_OpenOutput(&midi, outp, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
+ TIME_PROC, TIME_INFO, LATENCY);
+ if (err) {
+ puts(Pm_GetErrorText(err));
+ goto error_exit_no_device;
+ }
+ active = true;
+
+ printf("Type ENTER to start MIDI CLOCK:\n");
+ while (getchar() != '\n') ;
+ send_start_stop = true; /* send START and then CLOCKs */
+
+ while (!done) {
+ doascii(getchar());
+ while (getchar() != '\n') ;
+ }
+
+ active = false;
+ Pt_Sleep(2); /* this is to allow callback to complete -- it's
+ real time, so it's either ok and it runs on
+ time, or there's no point to synchronizing
+ with it */
+ /* now we "own" portmidi again */
+ Pm_Close(midi);
+ error_exit_no_device:
+ Pt_Stop();
+ Pm_Terminate();
+ exit(0);
+}
+
diff --git a/portmidi/pm_test/midithread.c b/portmidi/pm_test/midithread.c
new file mode 100755
index 0000000..ea0613b
--- /dev/null
+++ b/portmidi/pm_test/midithread.c
@@ -0,0 +1,343 @@
+/* midithread.c -- example program showing how to do midi processing
+ in a preemptive thread
+
+ Notes: if you handle midi I/O from your main program, there will be
+ some delay before handling midi messages whenever the program is
+ doing something like file I/O, graphical interface updates, etc.
+
+ To handle midi with minimal delay, you should do all midi processing
+ in a separate, high priority thread. A convenient way to get a high
+ priority thread in windows is to use the timer callback provided by
+ the PortTime library. That is what we show here.
+
+ If the high priority thread writes to a file, prints to the console,
+ or does just about anything other than midi processing, this may
+ create delays, so all this processing should be off-loaded to the
+ "main" process or thread. Communication between threads can be tricky.
+ If one thread is writing at the same time the other is reading, very
+ tricky race conditions can arise, causing programs to behave
+ incorrectly, but only under certain timing conditions -- a terrible
+ thing to debug. Advanced programmers know this as a synchronization
+ problem. See any operating systems textbook for the complete story.
+
+ To avoid synchronization problems, a simple, reliable approach is
+ to communicate via messages. PortMidi offers a message queue as a
+ datatype, and operations to insert and remove messages. Use two
+ queues as follows: midi_to_main transfers messages from the midi
+ thread to the main thread, and main_to_midi transfers messages from
+ the main thread to the midi thread. Queues are safe for use between
+ threads as long as ONE thread writes and ONE thread reads. You must
+ NEVER allow two threads to write to the same queue.
+
+ This program transposes incoming midi data by an amount controlled
+ by the main program. To change the transposition, type an integer
+ followed by return. The main program sends this via a message queue
+ to the midi thread. To quit, type 'q' followed by return.
+
+ The midi thread can also send a pitch to the main program on request.
+ Type 'm' followed by return to wait for the next midi message and
+ print the pitch.
+
+ This program illustrates:
+ Midi processing in a high-priority thread.
+ Communication with a main process via message queues.
+
+ */
+
+#include "stdio.h"
+#include "stdlib.h"
+#include "string.h"
+#include "assert.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "porttime.h"
+
+/* if INPUT_BUFFER_SIZE is 0, PortMidi uses a default value */
+#define INPUT_BUFFER_SIZE 0
+
+#define OUTPUT_BUFFER_SIZE 100
+#define DRIVER_INFO NULL
+#define TIME_PROC NULL
+#define TIME_INFO NULL
+/* use zero latency because we want output to be immediate */
+#define LATENCY 0
+
+#define STRING_MAX 80
+
+/**********************************/
+/* DATA USED ONLY BY process_midi */
+/* (except during initialization) */
+/**********************************/
+
+int active = FALSE;
+int monitor = FALSE;
+int midi_thru = TRUE;
+
+int transpose;
+PmStream *midi_in;
+PmStream *midi_out;
+
+/****************************/
+/* END OF process_midi DATA */
+/****************************/
+
+/* shared queues */
+PmQueue *midi_to_main;
+PmQueue *main_to_midi;
+
+#define QUIT_MSG 1000
+#define MONITOR_MSG 1001
+#define THRU_MSG 1002
+
+/* timer interrupt for processing midi data */
+void process_midi(PtTimestamp timestamp, void *userData)
+{
+ PmError result;
+ PmEvent buffer; /* just one message at a time */
+ int32_t msg;
+
+ /* do nothing until initialization completes */
+ if (!active)
+ return;
+
+ /* check for messages */
+ do {
+ result = Pm_Dequeue(main_to_midi, &msg);
+ if (result) {
+ if (msg >= -127 && msg <= 127)
+ transpose = msg;
+ else if (msg == QUIT_MSG) {
+ /* acknowledge receipt of quit message */
+ Pm_Enqueue(midi_to_main, &msg);
+ active = FALSE;
+ return;
+ } else if (msg == MONITOR_MSG) {
+ /* main has requested a pitch. monitor is a flag that
+ * records the request:
+ */
+ monitor = TRUE;
+ } else if (msg == THRU_MSG) {
+ /* toggle Thru on or off */
+ midi_thru = !midi_thru;
+ }
+ }
+ } while (result);
+
+ /* see if there is any midi input to process */
+ do {
+ result = Pm_Poll(midi_in);
+ if (result) {
+ int status, data1, data2;
+ if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow)
+ continue;
+ if (midi_thru)
+ Pm_Write(midi_out, &buffer, 1);
+ /* unless there was overflow, we should have a message now */
+ status = Pm_MessageStatus(buffer.message);
+ data1 = Pm_MessageData1(buffer.message);
+ data2 = Pm_MessageData2(buffer.message);
+ if ((status & 0xF0) == 0x90 ||
+ (status & 0xF0) == 0x80) {
+
+ /* this is a note-on or note-off, so transpose and send */
+ data1 += transpose;
+
+ /* keep within midi pitch range, keep proper pitch class */
+ while (data1 > 127)
+ data1 -= 12;
+ while (data1 < 0)
+ data1 += 12;
+
+ /* send the message */
+ buffer.message = Pm_Message(status, data1, data2);
+ Pm_Write(midi_out, &buffer, 1);
+
+ /* if monitor is set, send the pitch to the main thread */
+ if (monitor) {
+ Pm_Enqueue(midi_to_main, &data1);
+ monitor = FALSE; /* only send one pitch per request */
+ }
+ }
+ }
+ } while (result);
+}
+
+void exit_with_message(char *msg)
+{
+ printf("%s\n", msg);
+ while (getchar() != '\n') ;
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ int32_t n;
+ const PmDeviceInfo *info;
+ char line[STRING_MAX];
+ int spin;
+ int done = FALSE;
+ int i;
+ int input = -1, output = -1;
+
+ printf("Usage: midithread [-i input] [-o output]\n"
+ "where input and output are portmidi device numbers\n");
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-i") == 0) {
+ i++;
+ input = atoi(argv[i]);
+ printf("Input device number: %d\n", input);
+ } else if (strcmp(argv[i], "-o") == 0) {
+ i++;
+ output = atoi(argv[i]);
+ printf("Output device number: %d\n", output);
+ } else {
+ return -1;
+ }
+ }
+ printf("begin PortMidi multithread test...\n");
+
+ /* note that it is safe to call PortMidi from the main thread for
+ initialization and opening devices. You should not make any
+ calls to PortMidi from this thread once the midi thread begins.
+ to make PortMidi calls.
+ */
+
+ /* make the message queues */
+ /* messages can be of any size and any type, but all messages in
+ * a given queue must have the same size. We'll just use int32_t's
+ * for our messages in this simple example
+ */
+ midi_to_main = Pm_QueueCreate(32, sizeof(int32_t));
+ assert(midi_to_main != NULL);
+ main_to_midi = Pm_QueueCreate(32, sizeof(int32_t));
+ assert(main_to_midi != NULL);
+
+ /* a little test of enqueue and dequeue operations. Ordinarily,
+ * you would call Pm_Enqueue from one thread and Pm_Dequeue from
+ * the other. Since the midi thread is not running, this is safe.
+ */
+ n = 1234567890;
+ Pm_Enqueue(midi_to_main, &n);
+ n = 987654321;
+ Pm_Enqueue(midi_to_main, &n);
+ Pm_Dequeue(midi_to_main, &n);
+ if (n != 1234567890) {
+ exit_with_message("Pm_Dequeue produced unexpected result.");
+ }
+ Pm_Dequeue(midi_to_main, &n);
+ if(n != 987654321) {
+ exit_with_message("Pm_Dequeue produced unexpected result.");
+ }
+
+ /* always start the timer before you start midi */
+ Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
+ /* the timer will call our function, process_midi() every millisecond */
+
+ Pm_Initialize();
+
+ output = (output < 0 ? Pm_GetDefaultOutputDeviceID() : output);
+ info = Pm_GetDeviceInfo(output);
+ if (info == NULL) {
+ printf("Could not open output device (%d).", output);
+ exit_with_message("");
+ }
+ printf("Opening output device %s %s\n", info->interf, info->name);
+
+ /* use zero latency because we want output to be immediate */
+ Pm_OpenOutput(&midi_out,
+ output,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ LATENCY);
+
+ input = (input < 0 ? Pm_GetDefaultInputDeviceID() : input);
+ info = Pm_GetDeviceInfo(input);
+ if (info == NULL) {
+ printf("Could not open default input device (%d).", input);
+ exit_with_message("");
+ }
+ printf("Opening input device %s %s\n", info->interf, info->name);
+ Pm_OpenInput(&midi_in,
+ input,
+ DRIVER_INFO,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+
+ active = TRUE; /* enable processing in the midi thread -- yes, this
+ is a shared variable without synchronization, but
+ this simple assignment is safe */
+
+ printf("Enter midi input; it will be transformed as specified by...\n");
+ printf("Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or\n"
+ "type a number to specify transposition.\n"
+ "Must terminate with [ENTER]\n");
+
+ while (!done) {
+ int32_t msg;
+ int input;
+ int len;
+ if (!fgets(line, STRING_MAX, stdin)) break; /* no stdin? */
+ /* remove the newline: */
+ len = (int) strlen(line);
+ if (len > 0) line[len - 1] = 0; /* overwrite the newline char */
+ if (strcmp(line, "q") == 0) {
+ msg = QUIT_MSG;
+ Pm_Enqueue(main_to_midi, &msg);
+ /* wait for acknowlegement */
+ do {
+ spin = Pm_Dequeue(midi_to_main, &msg);
+ } while (spin == 0); /* spin */ ;
+ done = TRUE; /* leave the command loop and wrap up */
+ } else if (strcmp(line, "m") == 0) {
+ msg = MONITOR_MSG;
+ Pm_Enqueue(main_to_midi, &msg);
+ printf("Waiting for note...\n");
+ do {
+ spin = Pm_Dequeue(midi_to_main, &msg);
+ } while (spin == 0); /* spin */ ;
+ // convert int32_t to long for safe printing
+ printf("... pitch is %ld\n", (long) msg);
+ } else if (strcmp(line, "t") == 0) {
+ /* reading midi_thru asynchronously could give incorrect results,
+ e.g. if you type "t" twice before the midi thread responds to
+ the first one, but we'll do it this way anyway. Perhaps a more
+ correct way would be to wait for an acknowledgement message
+ containing the new state. */
+ printf("Setting THRU %s\n", (midi_thru ? "off" : "on"));
+ msg = THRU_MSG;
+ Pm_Enqueue(main_to_midi, &msg);
+ } else if (sscanf(line, "%d", &input) == 1) {
+ if (input >= -127 && input <= 127) {
+ /* send transposition value, make sur */
+ printf("Transposing by %d\n", input);
+ msg = (int32_t) input;
+ Pm_Enqueue(main_to_midi, &msg);
+ } else {
+ printf("Transposition must be within -127...127\n");
+ }
+ } else {
+ printf("%s\n%s\n",
+ "Type 'q[ENTER]' to quit, 'm[ENTER]' to monitor next pitch, or",
+ "enter a number to specify transposition.");
+ }
+ }
+
+ /* at this point, midi thread is inactive and we need to shut down
+ * the midi input and output
+ */
+ Pt_Stop(); /* stop the timer */
+ Pm_QueueDestroy(midi_to_main);
+ Pm_QueueDestroy(main_to_midi);
+
+ /* Belinda! if close fails here, some memory is deleted, right??? */
+ Pm_Close(midi_in);
+ Pm_Close(midi_out);
+
+ fputs("finished portMidi multithread test.\n"
+ "type ENTER to quit:", stdout);
+ while (getchar() != '\n') ;
+ return 0;
+}
diff --git a/portmidi/pm_test/midithru.c b/portmidi/pm_test/midithru.c
new file mode 100755
index 0000000..94b4f13
--- /dev/null
+++ b/portmidi/pm_test/midithru.c
@@ -0,0 +1,455 @@
+/* midithru.c -- example program implementing background thru processing */
+
+/* suppose you want low-latency midi-thru processing, but your
+ application wants to take advantage of the input buffer and
+ timestamped data so that it does not have to operate with very low
+ latency.
+
+ This program illustrates how to use a timer callback from PortTime
+ to implement a low-latency process that handles midi thru,
+ including correctly merging midi data from the application with
+ midi data from the input port.
+
+ The main application, which runs in the main program thread, will
+ use an interface similar to that of PortMidi, but since PortMidi
+ does not allow concurrent threads to share access to a stream, the
+ application will call private methods that transfer MIDI messages
+ to and from the timer thread using lock-free queues. All PortMidi
+ API calls are made from the timer thread.
+ */
+
+/* DESIGN
+
+All setup will be done by the main thread. Then, all direct access to
+PortMidi will be handed off to the timer callback thread.
+
+After this hand-off, the main thread will get/send messages via a queue.
+
+The goal is to send incoming messages to the midi output while merging
+any midi data generated by the application. Sysex is a problem here
+because you cannot insert (merge) a midi message while a sysex is in
+progress. There are at least three ways to implement midi thru with
+sysex messages:
+
+1) Turn them off. If your application does not need them, turn them off
+ with Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_SYSEX). You will
+ not receive sysex (or active sensing messages), so you will not have
+ to handle them.
+
+2) Make them atomic. As you receive sysex messages, copy the data into
+ a (big) buffer. Ideally, expand the buffer as needed -- sysex messages
+ do not have any maximum length. Even more ideally, use a list structure
+ and real-time memory allocation to avoid latency in the timer thread.
+ When a full sysex message is received, send it to the midi output all
+ at once.
+
+3) Process sysex incrementally. Send sysex data to midi output as it
+ arrives. Block any non-real-time messages from the application until
+ the sysex message completes. There is the risk that an incomplete
+ sysex message will block messages forever, so implement a 5-second
+ timeout: if no sysex data is seen for 5 seconds, release the block,
+ possibly losing the rest of the sysex message.
+
+ Application messages must be processed similarly: once started, a
+ sysex message will block MIDI THRU processing. We will assume that
+ the application will not abort a sysex message, so timeouts are not
+ necessary here.
+
+This code implements (3).
+
+Latency is also an issue. PortMidi requires timestamps to be in
+non-decreasing order. Since we'll be operating with a low-latency
+timer thread, we can just set the latency to zero meaning timestamps
+are ignored by PortMidi. This will allow thru to go through with
+minimal latency. The application, however, needs to use timestamps
+because we assume it is high latency (the whole purpose of this
+example is to illustrate how to get low-latency thru with a high-latency
+application.) So the callback thread will implement midi timing by
+observing timestamps. The current timestamp will be available in the
+global variable current_timestamp.
+
+*/
+
+
+#include "stdio.h"
+#include "stdlib.h"
+#include "string.h"
+#include "assert.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "porttime.h"
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+#define STRING_MAX 80 /* used for console input */
+
+/* active is set true when midi processing should start, must be
+ * volatile to force thread to check for updates by other thread */
+int active = FALSE;
+/* process_midi_exit_flag is set when the timer thread shuts down;
+ * must be volatile so it is re-read in the while loop that waits on it */
+volatile int process_midi_exit_flag;
+
+PmStream *midi_in;
+PmStream *midi_out;
+
+/* shared queues */
+#define IN_QUEUE_SIZE 1024
+#define OUT_QUEUE_SIZE 1024
+PmQueue *in_queue;
+PmQueue *out_queue;
+/* this is volatile because it is set in the process_midi callback and
+ * the main thread reads it to sense elapsed time. Without volatile, the
+ * optimizer can put it in a register and not see the updates.
+ */
+volatile PmTimestamp current_timestamp = 0;
+int thru_sysex_in_progress = FALSE;
+int app_sysex_in_progress = FALSE;
+PmTimestamp last_timestamp = 0;
+
+
+static void prompt_and_exit(void)
+{
+ printf("type ENTER...");
+ while (getchar() != '\n') ;
+ /* this will clean up open ports: */
+ exit(-1);
+}
+
+
+static PmError checkerror(PmError err)
+{
+ if (err == pmHostError) {
+ /* it seems pointless to allocate memory and copy the string,
+ * so I will do the work of Pm_GetHostErrorText directly
+ */
+ char errmsg[80];
+ Pm_GetHostErrorText(errmsg, 80);
+ printf("PortMidi found host error...\n %s\n", errmsg);
+ prompt_and_exit();
+ } else if (err < 0) {
+ printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
+ prompt_and_exit();
+ }
+ return err;
+}
+
+
+/* time proc parameter for Pm_MidiOpen */
+PmTimestamp midithru_time_proc(void *info)
+{
+ return current_timestamp;
+}
+
+
+/* timer interrupt for processing midi data.
+ Incoming data is delivered to main program via in_queue.
+ Outgoing data from main program is delivered via out_queue.
+ Incoming data from midi_in is copied with low latency to midi_out.
+ Sysex messages from either source block messages from the other.
+ */
+void process_midi(PtTimestamp timestamp, void *userData)
+{
+ PmError result;
+ PmEvent buffer; /* just one message at a time */
+
+ current_timestamp++; /* update every millisecond */
+
+ /* do nothing until initialization completes */
+ if (!active) {
+ /* this flag signals that no more midi processing will be done */
+ process_midi_exit_flag = TRUE;
+ return;
+ }
+
+ /* see if there is any midi input to process */
+ if (!app_sysex_in_progress) {
+ do {
+ result = Pm_Poll(midi_in);
+ if (result) {
+ int status;
+ PmError rslt = Pm_Read(midi_in, &buffer, 1);
+ if (rslt == pmBufferOverflow)
+ continue;
+ assert(rslt == 1);
+
+ /* record timestamp of most recent data */
+ last_timestamp = current_timestamp;
+
+ /* the data might be the end of a sysex message that
+ has timed out, in which case we must ignore it.
+ It's a continuation of a sysex message if status
+ is actually a data byte (high-order bit is zero). */
+ status = Pm_MessageStatus(buffer.message);
+ if (((status & 0x80) == 0) && !thru_sysex_in_progress) {
+ continue; /* ignore this data */
+ }
+
+ /* implement midi thru */
+ /* note that you could output to multiple ports or do other
+ processing here if you wanted
+ */
+ /* printf("thru: %x\n", buffer.message); */
+ Pm_Write(midi_out, &buffer, 1);
+
+ /* send the message to the application */
+ /* you might want to filter clock or active sense messages here
+ to avoid sending a bunch of junk to the application even if
+ you want to send it to MIDI THRU
+ */
+ Pm_Enqueue(in_queue, &buffer);
+
+ /* sysex processing */
+ if (status == MIDI_SYSEX) thru_sysex_in_progress = TRUE;
+ else if ((status & 0xF8) != 0xF8) {
+ /* not MIDI_SYSEX and not real-time, so */
+ thru_sysex_in_progress = FALSE;
+ }
+ if (thru_sysex_in_progress && /* look for EOX */
+ (((buffer.message & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 8) & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 16) & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) {
+ thru_sysex_in_progress = FALSE;
+ }
+ }
+ } while (result);
+ }
+
+
+ /* see if there is application midi data to process */
+ while (!Pm_QueueEmpty(out_queue)) {
+ /* see if it is time to output the next message */
+ PmEvent *next = (PmEvent *) Pm_QueuePeek(out_queue);
+ assert(next); /* must be non-null because queue is not empty */
+ if (next->timestamp <= current_timestamp) {
+ /* time to send a message, first make sure it's not blocked */
+ int status = Pm_MessageStatus(next->message);
+ if ((status & 0xF8) == 0xF8) {
+ ; /* real-time messages are not blocked */
+ } else if (thru_sysex_in_progress) {
+ /* maybe sysex has timed out (output becomes unblocked) */
+ if (last_timestamp + 5000 < current_timestamp) {
+ thru_sysex_in_progress = FALSE;
+ } else break; /* output is blocked, so exit loop */
+ }
+ Pm_Dequeue(out_queue, &buffer);
+ Pm_Write(midi_out, &buffer, 1);
+
+ /* inspect message to update app_sysex_in_progress */
+ if (status == MIDI_SYSEX) app_sysex_in_progress = TRUE;
+ else if ((status & 0xF8) != 0xF8) {
+ /* not MIDI_SYSEX and not real-time, so */
+ app_sysex_in_progress = FALSE;
+ }
+ if (app_sysex_in_progress && /* look for EOX */
+ (((buffer.message & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 8) & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 16) & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) {
+ app_sysex_in_progress = FALSE;
+ }
+ } else break; /* wait until indicated timestamp */
+ }
+}
+
+
+void exit_with_message(char *msg)
+{
+#define STRING_MAX 80
+ printf("%s\nType ENTER...", msg);
+ while (getchar() != '\n') ;
+ exit(1);
+}
+
+
+void initialize(int input, int output, int virtual)
+/* set up midi processing thread and open midi streams */
+{
+ /* note that it is safe to call PortMidi from the main thread for
+ initialization and opening devices. You should not make any
+ calls to PortMidi from this thread once the midi thread begins.
+ to make PortMidi calls.
+ */
+
+ /* note that this routine provides minimal error checking. If
+ you use the PortMidi library compiled with PM_CHECK_ERRORS,
+ then error messages will be printed and the program will exit
+ if an error is encountered. Otherwise, you should add some
+ error checking to this code.
+ */
+
+ const PmDeviceInfo *info;
+
+ /* make the message queues */
+ in_queue = Pm_QueueCreate(IN_QUEUE_SIZE, sizeof(PmEvent));
+ assert(in_queue != NULL);
+ out_queue = Pm_QueueCreate(OUT_QUEUE_SIZE, sizeof(PmEvent));
+ assert(out_queue != NULL);
+
+ /* always start the timer before you start midi */
+ Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
+ /* the timer will call our function, process_midi() every millisecond */
+
+ Pm_Initialize();
+
+ if (output < 0) {
+ if (!virtual) {
+ output = Pm_GetDefaultOutputDeviceID();
+ }
+ }
+ if (output >= 0) {
+ info = Pm_GetDeviceInfo(output);
+ if (info == NULL) {
+ printf("Could not open default output device (%d).", output);
+ exit_with_message("");
+ }
+
+ printf("Opening output device %s %s\n", info->interf, info->name);
+
+ /* use zero latency because we want output to be immediate */
+ Pm_OpenOutput(&midi_out,
+ output,
+ NULL /* driver info */,
+ OUT_QUEUE_SIZE,
+ &midithru_time_proc,
+ NULL /* time info */,
+ 0 /* Latency */);
+ } else { /* send to virtual port */
+ int id;
+ printf("Opening virtual output device \"midithru\"\n");
+ id = Pm_CreateVirtualOutput("midithru", NULL, NULL);
+ if (id < 0) checkerror(id); /* error reporting */
+ checkerror(Pm_OpenOutput(&midi_out, id, NULL, OUT_QUEUE_SIZE,
+ &midithru_time_proc, NULL, 0));
+ }
+ if (input < 0) {
+ if (!virtual) {
+ input = Pm_GetDefaultInputDeviceID();
+ }
+ }
+ if (input >= 0) {
+ info = Pm_GetDeviceInfo(input);
+ if (info == NULL) {
+ printf("Could not open default input device (%d).", input);
+ exit_with_message("");
+ }
+
+ printf("Opening input device %s %s\n", info->interf, info->name);
+ Pm_OpenInput(&midi_in,
+ input,
+ NULL /* driver info */,
+ 0 /* use default input size */,
+ &midithru_time_proc,
+ NULL /* time info */);
+ } else { /* receive from virtual port */
+ int id;
+ printf("Opening virtual input device \"midithru\"\n");
+ id = Pm_CreateVirtualInput("midithru", NULL, NULL);
+ if (id < 0) checkerror(id); /* error reporting */
+ checkerror(Pm_OpenInput(&midi_in, id, NULL, 0,
+ &midithru_time_proc, NULL));
+ }
+ /* Note: if you set a filter here, then this will filter what goes
+ to the MIDI THRU port. You may not want to do this.
+ */
+ Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+
+ active = TRUE; /* enable processing in the midi thread -- yes, this
+ is a shared variable without synchronization, but
+ this simple assignment is safe */
+
+}
+
+
+void finalize()
+{
+ /* the timer thread could be in the middle of accessing PortMidi stuff */
+ /* to detect that it is done, we first clear process_midi_exit_flag and
+ then wait for the timer thread to set it
+ */
+ process_midi_exit_flag = FALSE;
+ active = FALSE;
+ /* busy wait for flag from timer thread that it is done */
+ while (!process_midi_exit_flag) ;
+ /* at this point, midi thread is inactive and we need to shut down
+ * the midi input and output
+ */
+ Pt_Stop(); /* stop the timer */
+ Pm_QueueDestroy(in_queue);
+ Pm_QueueDestroy(out_queue);
+
+ Pm_Close(midi_in);
+ Pm_Close(midi_out);
+
+ Pm_Terminate();
+}
+
+
+int main(int argc, char *argv[])
+{
+ PmTimestamp last_time = 0;
+ PmEvent buffer;
+ int i;
+ int input = -1, output = -1;
+ int virtual = FALSE;
+ int delay_enable = TRUE;
+
+ printf("Usage: midithru [-i input] [-o output] [-v] [-n]\n"
+ "where input and output are portmidi device numbers\n"
+ "if -v and input and/or output are not specified,\n"
+ "then virtual ports are created and used instead.\n"
+ "-n turns off the default MIDI delay effect.\n");
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-i") == 0) {
+ i++;
+ input = atoi(argv[i]);
+ printf("Input device number: %d\n", input);
+ } else if (strcmp(argv[i], "-o") == 0) {
+ i++;
+ output = atoi(argv[i]);
+ printf("Output device number: %d\n", output);
+ } else if (strcmp(argv[i], "-v") == 0) {
+ virtual = TRUE;
+ } else if (strcmp(argv[i], "-n") == 0) {
+ delay_enable = FALSE;
+ printf("delay_effect is disabled\n");
+ } else {
+ return -1;
+ }
+ }
+ printf("begin PortMidi midithru program...\n");
+
+ initialize(input, output, virtual); /* set up and start midi processing */
+
+ printf("This program will run for 60 seconds, "
+ "or until you play B below middle C,\n"
+ "All input is sent immediately, implementing software MIDI THRU.\n"
+ "Also, all input is echoed with a 2 second delay.\n");
+
+ while (current_timestamp < 60000) {
+ /* just to make the point that this is not a low-latency process,
+ spin until half a second has elapsed */
+ last_time = last_time + 500;
+ while (last_time > current_timestamp) ;
+
+ /* now read data and send it after changing timestamps */
+ while (Pm_Dequeue(in_queue, &buffer) == 1) {
+ /* printf("timestamp %d\n", buffer.timestamp); */
+ /* printf("message %x\n", buffer.message); */
+ if (delay_enable) {
+ buffer.timestamp = buffer.timestamp + 2000; /* delay */
+ Pm_Enqueue(out_queue, &buffer);
+ }
+ /* play B3 to break out of loop */
+ if (Pm_MessageStatus(buffer.message) == 0x90 &&
+ Pm_MessageData1(buffer.message) == 59) {
+ goto quit_now;
+ }
+ }
+ }
+quit_now:
+ finalize();
+ exit_with_message("finished PortMidi midithru program.");
+ return 0; /* never executed, but keeps the compiler happy */
+}
diff --git a/portmidi/pm_test/mm.c b/portmidi/pm_test/mm.c
new file mode 100755
index 0000000..ab9d32e
--- /dev/null
+++ b/portmidi/pm_test/mm.c
@@ -0,0 +1,595 @@
+/* mm.c -- midi monitor */
+
+/*****************************************************************************
+* Change Log
+* Date | Change
+*-----------+-----------------------------------------------------------------
+* 7-Apr-86 | Created changelog
+* 31-Jan-90 | GWL : use new cmdline
+* 5-Apr-91 | JDW : Further changes
+* 16-Feb-92 | GWL : eliminate label mmexit:; add error recovery
+* 18-May-92 | GWL : continuous clocks, etc.
+* 17-Jan-94 | GWL : option to display notes
+* 20-Nov-06 | RBD : port mm.c from CMU Midi Toolkit to PortMidi
+* | mm.c -- revealing MIDI secrets for over 20 years!
+*****************************************************************************/
+
+#include "stdlib.h"
+#include "ctype.h"
+#include "string.h"
+#include "stdio.h"
+#include "porttime.h"
+#include "portmidi.h"
+
+#define STRING_MAX 80
+
+#define MIDI_CODE_MASK 0xf0
+#define MIDI_CHN_MASK 0x0f
+/*#define MIDI_REALTIME 0xf8
+ #define MIDI_CHAN_MODE 0xfa */
+#define MIDI_OFF_NOTE 0x80
+#define MIDI_ON_NOTE 0x90
+#define MIDI_POLY_TOUCH 0xa0
+#define MIDI_CTRL 0xb0
+#define MIDI_CH_PROGRAM 0xc0
+#define MIDI_TOUCH 0xd0
+#define MIDI_BEND 0xe0
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_Q_FRAME 0xf1
+#define MIDI_SONG_POINTER 0xf2
+#define MIDI_SONG_SELECT 0xf3
+#define MIDI_TUNE_REQ 0xf6
+#define MIDI_EOX 0xf7
+#define MIDI_TIME_CLOCK 0xf8
+#define MIDI_START 0xfa
+#define MIDI_CONTINUE 0xfb
+#define MIDI_STOP 0xfc
+#define MIDI_ACTIVE_SENSING 0xfe
+#define MIDI_SYS_RESET 0xff
+
+#define MIDI_ALL_SOUND_OFF 0x78
+#define MIDI_RESET_CONTROLLERS 0x79
+#define MIDI_LOCAL 0x7a
+#define MIDI_ALL_OFF 0x7b
+#define MIDI_OMNI_OFF 0x7c
+#define MIDI_OMNI_ON 0x7d
+#define MIDI_MONO_ON 0x7e
+#define MIDI_POLY_ON 0x7f
+
+
+#define private static
+
+#ifndef false
+#define false 0
+#define true 1
+#endif
+
+typedef int boolean;
+
+int debug = false; /* never set, but referenced by userio.c */
+PmStream *midi_in; /* midi input */
+boolean active = false; /* set when midi_in is ready for reading */
+boolean in_sysex = false; /* we are reading a sysex message */
+boolean inited = false; /* suppress printing during command line parsing */
+boolean done = false; /* when true, exit */
+boolean notes = true; /* show notes? */
+boolean controls = true; /* show continuous controllers */
+boolean bender = true; /* record pitch bend etc.? */
+boolean excldata = true; /* record system exclusive data? */
+boolean verbose = true; /* show text representation? */
+boolean realdata = true; /* record real time messages? */
+boolean clksencnt = true; /* clock and active sense count on */
+boolean chmode = true; /* show channel mode messages */
+boolean pgchanges = true; /* show program changes */
+boolean flush = false; /* flush all pending MIDI data */
+
+uint32_t filter = 0; /* remember state of midi filter */
+
+uint32_t clockcount = 0; /* count of clocks */
+uint32_t actsensecount = 0; /* cout of active sensing bytes */
+uint32_t notescount = 0; /* #notes since last request */
+uint32_t notestotal = 0; /* total #notes */
+
+char val_format[] = " Val %d\n";
+
+/*****************************************************************************
+* Imported variables
+*****************************************************************************/
+
+extern int abort_flag;
+
+/*****************************************************************************
+* Routines local to this module
+*****************************************************************************/
+
+private void mmexit(int code);
+private void output(PmMessage data);
+private int put_pitch(int p);
+private void showhelp();
+private void showbytes(PmMessage data, int len, boolean newline);
+private void showstatus(boolean flag);
+private void doascii(char c);
+private int get_number(const char *prompt);
+
+
+/* read a number from console */
+/**/
+int get_number(const char *prompt)
+{
+ int n = 0, i;
+ fputs(prompt, stdout);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ while (getchar() != '\n') ;
+ }
+ return i;
+}
+
+
+void receive_poll(PtTimestamp timestamp, void *userData)
+{
+ PmEvent event;
+ int count;
+ if (!active) return;
+ while ((count = Pm_Read(midi_in, &event, 1))) {
+ if (count == 1) output(event.message);
+ else puts(Pm_GetErrorText(count));
+ }
+}
+
+
+/****************************************************************************
+* main
+* Effect: prompts for parameters, starts monitor
+****************************************************************************/
+
+int main(int argc, char **argv)
+{
+ char *argument;
+ int inp;
+ PmError err;
+ int i;
+ if (argc > 1) { /* first arg can change defaults */
+ argument = argv[1];
+ while (*argument) doascii(*argument++);
+ }
+ showhelp();
+ /* use porttime callback to empty midi queue and print */
+ Pt_Start(1, receive_poll, 0);
+ /* list device information */
+ puts("MIDI input devices:");
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (info->input) printf("%d: %s, %s\n", i, info->interf, info->name);
+ }
+ inp = get_number("Type input device number: ");
+ err = Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL);
+ if (err) {
+ puts(Pm_GetErrorText(err));
+ Pt_Stop();
+ mmexit(1);
+ }
+ Pm_SetFilter(midi_in, filter);
+ inited = true; /* now can document changes, set filter */
+ printf("Midi Monitor ready.\n");
+ active = true;
+ while (!done) {
+ doascii(getchar());
+ while (getchar() != '\n') ;
+ }
+ active = false;
+ Pm_Close(midi_in);
+ Pt_Stop();
+ Pm_Terminate();
+ mmexit(0);
+ return 0; // make the compiler happy be returning a value
+}
+
+
+/****************************************************************************
+* doascii
+* Inputs:
+* char c: input character
+* Effect: interpret to revise flags
+****************************************************************************/
+
+private void doascii(char c)
+{
+ if (isupper(c)) c = tolower(c);
+ if (c == 'q') done = true;
+ else if (c == 'b') {
+ bender = !bender;
+ filter ^= PM_FILT_PITCHBEND;
+ if (inited)
+ printf("Pitch Bend, etc. %s\n", (bender ? "ON" : "OFF"));
+ } else if (c == 'c') {
+ controls = !controls;
+ filter ^= PM_FILT_CONTROL;
+ if (inited)
+ printf("Control Change %s\n", (controls ? "ON" : "OFF"));
+ } else if (c == 'h') {
+ pgchanges = !pgchanges;
+ filter ^= PM_FILT_PROGRAM;
+ if (inited)
+ printf("Program Changes %s\n", (pgchanges ? "ON" : "OFF"));
+ } else if (c == 'n') {
+ notes = !notes;
+ filter ^= PM_FILT_NOTE;
+ if (inited)
+ printf("Notes %s\n", (notes ? "ON" : "OFF"));
+ } else if (c == 'x') {
+ excldata = !excldata;
+ filter ^= PM_FILT_SYSEX;
+ if (inited)
+ printf("System Exclusive data %s\n", (excldata ? "ON" : "OFF"));
+ } else if (c == 'r') {
+ realdata = !realdata;
+ filter ^= (PM_FILT_PLAY | PM_FILT_RESET | PM_FILT_TICK | PM_FILT_UNDEFINED);
+ if (inited)
+ printf("Real Time messages %s\n", (realdata ? "ON" : "OFF"));
+ } else if (c == 'k') {
+ clksencnt = !clksencnt;
+ if (inited) {
+ printf("Clock and Active Sense Counting %s\n", (clksencnt ? "ON" : "OFF"));
+ printf("Resetting Clock and Active Sense counts.\n");
+ clockcount = actsensecount = 0;
+ }
+ } else if (c == 's') {
+ if (inited) {
+ printf("Clock Count %ld\nActive Sense Count %ld\n",
+ (long) clockcount, (long) actsensecount);
+ }
+ } else if (c == 't') {
+ notestotal+=notescount;
+ if (inited)
+ printf("This Note Count %ld\nTotal Note Count %ld\n",
+ (long) notescount, (long) notestotal);
+ notescount=0;
+ } else if (c == 'v') {
+ verbose = !verbose;
+ if (inited)
+ printf("Verbose %s\n", (verbose ? "ON" : "OFF"));
+ } else if (c == 'm') {
+ chmode = !chmode;
+ if (inited)
+ printf("Channel Mode Messages %s", (chmode ? "ON" : "OFF"));
+ } else {
+ if (inited) {
+ if (c == ' ') {
+ PmEvent event;
+ while (Pm_Read(midi_in, &event, 1)) ; /* flush midi input */
+ printf("...FLUSHED MIDI INPUT\n\n");
+ } else showhelp();
+ }
+ }
+ if (inited) Pm_SetFilter(midi_in, filter);
+}
+
+
+
+private void mmexit(int code)
+{
+ /* if this is not being run from a console, maybe we should wait for
+ * the user to read error messages before exiting
+ */
+ exit(code);
+}
+
+
+/****************************************************************************
+* output
+* Inputs:
+* data: midi message buffer holding one command or 4 bytes of sysex msg
+* Effect: format and print midi data
+****************************************************************************/
+
+char vel_format[] = " Vel %d\n";
+
+private void output(PmMessage data)
+{
+ int command; /* the current command */
+ int chan; /* the midi channel of the current event */
+ int len; /* used to get constant field width */
+
+ /* printf("output data %8x; ", data); */
+
+ command = Pm_MessageStatus(data) & MIDI_CODE_MASK;
+ chan = Pm_MessageStatus(data) & MIDI_CHN_MASK;
+
+ if (in_sysex || Pm_MessageStatus(data) == MIDI_SYSEX) {
+#define sysex_max 16
+ int i;
+ PmMessage data_copy = data;
+ in_sysex = true;
+ /* look for MIDI_EOX in first 3 bytes
+ * if realtime messages are embedded in sysex message, they will
+ * be printed as if they are part of the sysex message
+ */
+ for (i = 0; (i < 4) && ((data_copy & 0xFF) != MIDI_EOX); i++)
+ data_copy >>= 8;
+ if (i < 4) {
+ in_sysex = false;
+ i++; /* include the EOX byte in output */
+ }
+ showbytes(data, i, verbose);
+ if (verbose) printf("System Exclusive\n");
+ } else if (command == MIDI_ON_NOTE && Pm_MessageData2(data) != 0) {
+ notescount++;
+ if (notes) {
+ showbytes(data, 3, verbose);
+ if (verbose) {
+ printf("NoteOn Chan %2d Key %3d ", chan, Pm_MessageData1(data));
+ len = put_pitch(Pm_MessageData1(data));
+ printf(vel_format + len, Pm_MessageData2(data));
+ }
+ }
+ } else if ((command == MIDI_ON_NOTE /* && Pm_MessageData2(data) == 0 */ ||
+ command == MIDI_OFF_NOTE)) {
+ if (notes) {
+ showbytes(data, 3, verbose);
+ if (verbose) {
+ printf("NoteOff Chan %2d Key %3d ", chan,
+ Pm_MessageData1(data));
+ len = put_pitch(Pm_MessageData1(data));
+ printf(vel_format + len, Pm_MessageData2(data));
+ }
+ }
+ } else if (command == MIDI_CH_PROGRAM) {
+ if (pgchanges) {
+ showbytes(data, 2, verbose);
+ if (verbose) {
+ printf(" ProgChg Chan %2d Prog %2d\n", chan,
+ Pm_MessageData1(data) + 1);
+ }
+ }
+ } else if (command == MIDI_CTRL) {
+ /* controls 121 (MIDI_RESET_CONTROLLER) to 127 are channel
+ * mode messages. */
+ if (Pm_MessageData1(data) < MIDI_ALL_SOUND_OFF) {
+ if (controls) {
+ showbytes(data, 3, verbose);
+ if (verbose) {
+ printf("CtrlChg Chan %2d Ctrl %2d Val %2d\n",
+ chan, Pm_MessageData1(data), Pm_MessageData2(data));
+ }
+ } else /* channel mode */ if (chmode) {
+ showbytes(data, 3, verbose);
+ if (verbose) {
+ switch (Pm_MessageData1(data)) {
+ case MIDI_ALL_SOUND_OFF:
+ printf("All Sound Off, Chan %2d\n", chan);
+ break;
+ case MIDI_RESET_CONTROLLERS:
+ printf("Reset All Controllers, Chan %2d\n", chan);
+ break;
+ case MIDI_LOCAL:
+ printf("LocCtrl Chan %2d %s\n",
+ chan, Pm_MessageData2(data) ? "On" : "Off");
+ break;
+ case MIDI_ALL_OFF:
+ printf("All Off Chan %2d\n", chan);
+ break;
+ case MIDI_OMNI_OFF:
+ printf("OmniOff Chan %2d\n", chan);
+ break;
+ case MIDI_OMNI_ON:
+ printf("Omni On Chan %2d\n", chan);
+ break;
+ case MIDI_MONO_ON:
+ printf("Mono On Chan %2d\n", chan);
+ if (Pm_MessageData2(data))
+ printf(" to %d received channels\n",
+ Pm_MessageData2(data));
+ else
+ printf(" to all received channels\n");
+ break;
+ case MIDI_POLY_ON:
+ printf("Poly On Chan %2d\n", chan);
+ break;
+ }
+ }
+ }
+ }
+ } else if (command == MIDI_POLY_TOUCH) {
+ if (bender) {
+ showbytes(data, 3, verbose);
+ if (verbose) {
+ printf("P.Touch Chan %2d Key %2d ", chan,
+ Pm_MessageData1(data));
+ len = put_pitch(Pm_MessageData1(data));
+ printf(val_format + len, Pm_MessageData2(data));
+ }
+ }
+ } else if (command == MIDI_TOUCH) {
+ if (bender) {
+ showbytes(data, 2, verbose);
+ if (verbose) {
+ printf(" A.Touch Chan %2d Val %2d\n", chan,
+ Pm_MessageData1(data));
+ }
+ }
+ } else if (command == MIDI_BEND) {
+ if (bender) {
+ showbytes(data, 3, verbose);
+ if (verbose) {
+ printf("P.Bend Chan %2d Val %2d\n", chan,
+ (Pm_MessageData1(data) + (Pm_MessageData2(data)<<7)));
+ }
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_SONG_POINTER) {
+ showbytes(data, 3, verbose);
+ if (verbose) {
+ printf(" Song Position %d\n",
+ (Pm_MessageData1(data) + (Pm_MessageData2(data)<<7)));
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_SONG_SELECT) {
+ showbytes(data, 2, verbose);
+ if (verbose) {
+ printf(" Song Select %d\n", Pm_MessageData1(data));
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_TUNE_REQ) {
+ showbytes(data, 1, verbose);
+ if (verbose) {
+ printf(" Tune Request\n");
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_Q_FRAME) {
+ if (realdata) {
+ showbytes(data, 2, verbose);
+ if (verbose) {
+ printf(" Time Code Quarter Frame Type %d Values %d\n",
+ (Pm_MessageData1(data) & 0x70) >> 4,
+ Pm_MessageData1(data) & 0xf);
+ }
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_START) {
+ if (realdata) {
+ showbytes(data, 1, verbose);
+ if (verbose) {
+ printf(" Start\n");
+ }
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_CONTINUE) {
+ if (realdata) {
+ showbytes(data, 1, verbose);
+ if (verbose) {
+ printf(" Continue\n");
+ }
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_STOP) {
+ if (realdata) {
+ showbytes(data, 1, verbose);
+ if (verbose) {
+ printf(" Stop\n");
+ }
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_SYS_RESET) {
+ if (realdata) {
+ showbytes(data, 1, verbose);
+ if (verbose) {
+ printf(" System Reset\n");
+ }
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_TIME_CLOCK) {
+ clockcount++;
+ if (clksencnt) {
+ showbytes(data, 1, verbose);
+ if (verbose) {
+ printf(" Clock\n");
+ }
+ }
+ } else if (Pm_MessageStatus(data) == MIDI_ACTIVE_SENSING) {
+ actsensecount++;
+ if (clksencnt) {
+ showbytes(data, 1, verbose);
+ if (verbose) {
+ printf(" Active Sensing\n");
+ }
+ }
+ } else showbytes(data, 3, verbose);
+ fflush(stdout);
+}
+
+
+/****************************************************************************
+* put_pitch
+* Inputs:
+* int p: pitch number
+* Effect: write out the pitch name for a given number
+****************************************************************************/
+
+private int put_pitch(int p)
+{
+ char result[8];
+ static char *ptos[] = {
+ "c", "cs", "d", "ef", "e", "f", "fs", "g",
+ "gs", "a", "bf", "b" };
+ /* note octave correction below */
+ sprintf(result, "%s%d", ptos[p % 12], (p / 12) - 1);
+ fputs(result, stdout);
+ return (int) strlen(result);
+}
+
+
+/****************************************************************************
+* showbytes
+* Effect: print hex data, precede with newline if asked
+****************************************************************************/
+
+char nib_to_hex[] = "0123456789ABCDEF";
+
+private void showbytes(PmMessage data, int len, boolean newline)
+{
+ int count = 0;
+ int i;
+
+/* if (newline) {
+ putchar('\n');
+ count++;
+ } */
+ for (i = 0; i < len; i++) {
+ putchar(nib_to_hex[(data >> 4) & 0xF]);
+ putchar(nib_to_hex[data & 0xF]);
+ count += 2;
+ if (count > 72) {
+ putchar('.');
+ putchar('.');
+ putchar('.');
+ break;
+ }
+ data >>= 8;
+ }
+ putchar(' ');
+}
+
+
+
+/****************************************************************************
+* showhelp
+* Effect: print help text
+****************************************************************************/
+
+private void showhelp()
+{
+ printf("\n");
+ printf(" Item Reported Range Item Reported Range\n");
+ printf(" ------------- ----- ------------- -----\n");
+ printf(" Channels 1 - 16 Programs 1 - 128\n");
+ printf(" Controllers 0 - 127 After Touch 0 - 127\n");
+ printf(" Loudness 0 - 127 Pitch Bend 0 - 16383, "
+ "center = 8192\n");
+ printf(" Pitches 0 - 127, 60 = c4 = middle C\n");
+ printf(" \n");
+ printf("n toggles notes");
+ showstatus(notes);
+ printf("t displays noteon count since last t\n");
+ printf("b toggles pitch bend, aftertouch");
+ showstatus(bender);
+ printf("c toggles continuous control");
+ showstatus(controls);
+ printf("h toggles program changes");
+ showstatus(pgchanges);
+ printf("x toggles system exclusive");
+ showstatus(excldata);
+ printf("k toggles clock and sense messages, clears counts");
+ showstatus(clksencnt);
+ printf("r toggles other real time messages & SMPTE");
+ showstatus(realdata);
+ printf("s displays clock and sense count since last k\n");
+ printf("m toggles channel mode messages");
+ showstatus(chmode);
+ printf("v toggles verbose text");
+ showstatus(verbose);
+ printf("q quits\n");
+ printf("\n");
+}
+
+/****************************************************************************
+* showstatus
+* Effect: print status of flag
+****************************************************************************/
+
+private void showstatus(boolean flag)
+{
+ printf(", now %s\n", flag ? "ON" : "OFF");
+}
diff --git a/portmidi/pm_test/multivirtual.c b/portmidi/pm_test/multivirtual.c
new file mode 100644
index 0000000..b90d860
--- /dev/null
+++ b/portmidi/pm_test/multivirtual.c
@@ -0,0 +1,223 @@
+/* multivirtual.c -- test for creating two input and two output virtual ports */
+/*
+ * Roger B. Dannenberg
+ * Oct 2021
+ */
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define OUTPUT_BUFFER_SIZE 0
+#define DEVICE_INFO NULL
+#define DRIVER_INFO NULL
+#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
+#define TIME_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+int latency = 0;
+
+static void prompt_and_exit(void)
+{
+ printf("type ENTER...");
+ while (getchar() != '\n') ;
+ /* this will clean up open ports: */
+ exit(-1);
+}
+
+
+static PmError checkerror(PmError err)
+{
+ if (err == pmHostError) {
+ /* it seems pointless to allocate memory and copy the string,
+ * so I will do the work of Pm_GetHostErrorText directly
+ */
+ char errmsg[80];
+ Pm_GetHostErrorText(errmsg, 80);
+ printf("PortMidi found host error...\n %s\n", errmsg);
+ prompt_and_exit();
+ } else if (err < 0) {
+ printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
+ prompt_and_exit();
+ }
+ return err;
+}
+
+static int msg_count[2] = {0, 0};
+
+void poll_input(PmStream *in, int which)
+{
+ PmEvent buffer[1];
+ int pitch, expected, length;
+ PmError status = Pm_Poll(in);
+ if (status == TRUE) {
+ length = Pm_Read(in, buffer, 1);
+ if (length > 0) {
+ printf("Got message %d from portmidi%d: "
+ "time %ld, %2x %2x %2x\n",
+ msg_count[which], which + 1, (long) buffer[0].timestamp,
+ (status = Pm_MessageStatus(buffer[0].message)),
+ (pitch = Pm_MessageData1(buffer[0].message)),
+ Pm_MessageData2(buffer[0].message));
+ if (status == 0x90) { /* 1 & 2 are on/off 60, 3 & 4 are 61, etc. */
+ expected = (((msg_count[which] - 1) / 2) % 12) + 60 +
+ which * 12;
+ if (pitch != expected) {
+ printf("WARNING: expected pitch %d, got pitch %d\n",
+ expected, pitch);
+ }
+ }
+ msg_count[which]++;
+ } else {
+ assert(0);
+ }
+ }
+}
+
+
+void wait_until(PmTimestamp when, PmStream *in1, PmStream *in2)
+{
+ while (when > Pt_Time()) {
+ poll_input(in1, 0);
+ poll_input(in2, 1);
+ Pt_Sleep(10);
+ }
+}
+
+
+/* create one virtual output device and one input device */
+void init(const char *name, PmStream **midi_out, PmStream **midi_in,
+ int *id_out, int *id_in)
+{
+ PmEvent buffer[1];
+
+ *id_out = checkerror(Pm_CreateVirtualOutput(name, NULL, DEVICE_INFO));
+ checkerror(Pm_OpenOutput(midi_out, *id_out, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
+ TIME_PROC, TIME_INFO, latency));
+ printf("Virtual Output \"%s\" id %d created and opened.\n", name, *id_out);
+
+ *id_in = checkerror(Pm_CreateVirtualInput(name, NULL, DRIVER_INFO));
+ checkerror(Pm_OpenInput(midi_in, *id_in, NULL, 0, NULL, NULL));
+ printf("Virtual Input \"%s\" id %d created and opened.\n", name, *id_in);
+ Pm_SetFilter(*midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
+ /* empty the buffer after setting filter, just in case anything
+ got through */
+ while (Pm_Read(*midi_in, buffer, 1)) ;
+}
+
+
+void main_test(int num)
+{
+ PmStream *midi1_out;
+ PmStream *midi2_out;
+ PmStream *midi1_in;
+ PmStream *midi2_in;
+ int id1_out;
+ int id2_out;
+ int id1_in;
+ int id2_in;
+ int32_t next_time;
+ PmEvent buffer[1];
+ int pitch = 60;
+ int expected_count = num + 1; /* add 1 for MIDI Program message */
+
+ /* It is recommended to start timer before Midi; otherwise, PortMidi may
+ start the timer with its (default) parameters
+ */
+ TIME_START;
+
+ init("portmidi1", &midi1_out, &midi1_in, &id1_out, &id1_in);
+ init("portmidi2", &midi2_out, &midi2_in, &id2_out, &id2_in);
+
+ printf("Type ENTER to send messages: ");
+ while (getchar() != '\n') ;
+
+ buffer[0].timestamp = Pt_Time();
+#define PROGRAM 0
+ buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
+ Pm_Write(midi1_out, buffer, 1);
+ Pm_Write(midi2_out, buffer, 1);
+ next_time = Pt_Time() + 1000; /* wait 1s */
+ while (num > 0) {
+ wait_until(next_time, midi1_in, midi2_in);
+ Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 100));
+ Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 100));
+ printf("Note On pitch %d\n", pitch);
+ num--;
+ next_time += 500;
+
+ wait_until(next_time, midi1_in, midi2_in);
+ Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 0));
+ Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 0));
+ printf("Note Off pitch %d\n", pitch);
+ num--;
+ pitch = (pitch + 1) % 12 + 60;
+ next_time += 500;
+ }
+ wait_until(next_time, midi1_in, midi2_in); /* get final note-offs */
+
+ printf("Got %d messages from portmidi1 and %d from portmidi2; "
+ "expected %d.\n", msg_count[0], msg_count[1], expected_count);
+
+ /* close devices (this not explicitly needed in most implementations) */
+ printf("ready to close...");
+ checkerror(Pm_Close(midi1_out));
+ checkerror(Pm_Close(midi2_out));
+ checkerror(Pm_Close(midi1_in));
+ checkerror(Pm_Close(midi2_in));
+ printf("done closing.\nNow delete the virtual devices...");
+ checkerror(Pm_DeleteVirtualDevice(id1_out));
+ checkerror(Pm_DeleteVirtualDevice(id1_in));
+ checkerror(Pm_DeleteVirtualDevice(id2_out));
+ checkerror(Pm_DeleteVirtualDevice(id2_in));
+ printf("done deleting.\n");
+}
+
+
+void show_usage()
+{
+ printf("Usage: multivirtual [-h] [-l latency-in-ms] [n]\n"
+ " -h for this message,\n"
+ " -l ms designates latency for precise timing (default 0),\n"
+ " n is number of message to send each output, not counting\n"
+ " initial program change.\n"
+ "sends change program to 1, then one note per second with 0.5s on,\n"
+ "0.5s off, for n/2 seconds to both output ports portmidi1 and\n"
+ "portmidi2. portmidi1 gets pitches from C4 (60). portmidi2 gets\n"
+ "pitches an octave higher. Latency >0 uses the device driver for \n"
+ "precise timing (see PortMidi documentation). Inputs print what\n"
+ "they get and print WARNING if they get something unexpected.\n"
+ "The expected test is use two instances of testio to loop\n"
+ "portmidi1 back to portmidi1 and portmidi2 back to portmidi2.\n");
+ exit(0);
+}
+
+
+int main(int argc, char *argv[])
+{
+ int num = 10;
+ int i;
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0) {
+ show_usage();
+ } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ latency = atoi(argv[i]);
+ printf("Latency will be %d\n", latency);
+ } else {
+ num = atoi(argv[1]);
+ if (num <= 0) {
+ show_usage();
+ }
+ printf("Sending %d messages.\n", num);
+ }
+ }
+
+ main_test(num);
+
+ printf("finished sendvirtual test...type ENTER to quit...");
+ while (getchar() != '\n') ;
+ return 0;
+}
diff --git a/portmidi/pm_test/pmlist.c b/portmidi/pm_test/pmlist.c
new file mode 100644
index 0000000..5e3d1db
--- /dev/null
+++ b/portmidi/pm_test/pmlist.c
@@ -0,0 +1,63 @@
+/* pmlist.c -- list portmidi devices and numbers
+ *
+ * This program lists devices. When you type return, it
+ * restarts portmidi and lists devices again. It is mainly
+ * a test for shutting down and restarting.
+ *
+ * Roger B. Dannenberg, Feb 2022
+ */
+
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define DEVICE_INFO NULL
+#define DRIVER_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+#define STRING_MAX 80 /* used for console input */
+
+void show_usage()
+{
+ printf("Usage: pmlist [-h]\n -h means help.\n"
+ " Type return to rescan and list devices, q<ret> to quit\n");
+}
+
+
+int main(int argc, char *argv[])
+{
+ if (argc > 1) {
+ show_usage();
+ exit(0);
+ }
+
+ while (1) {
+ char input[STRING_MAX];
+ const char *deflt;
+ const char *in_or_out;
+ int default_in, default_out, i;
+
+ // Pm_Initialize();
+ /* list device information */
+ default_in = Pm_GetDefaultInputDeviceID();
+ default_out = Pm_GetDefaultOutputDeviceID();
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ printf("%d: %s, %s", i, info->interf, info->name);
+ deflt = "";
+ if (i == default_out || i == default_in) {
+ deflt = "default ";
+ }
+ in_or_out = (info->input ? "input" : "output");
+ printf(" (%s%s)\n", deflt, in_or_out);
+ }
+ if (fgets(input, STRING_MAX, stdin) && input[0] == 'q') {
+ return 0;
+ }
+ Pm_Terminate();
+ }
+ return 0;
+}
diff --git a/portmidi/pm_test/qtest.c b/portmidi/pm_test/qtest.c
new file mode 100644
index 0000000..14d803e
--- /dev/null
+++ b/portmidi/pm_test/qtest.c
@@ -0,0 +1,174 @@
+#include "portmidi.h"
+#include "pmutil.h"
+#include "stdlib.h"
+#include "stdio.h"
+
+
+/* make_msg -- make a psuedo-random message of length n whose content
+ * is purely a function of i
+ */
+void make_msg(long msg[], int n, int i)
+{
+ int j;
+ for (j = 0; j < n; j++) {
+ msg[j] = i % (j + 5);
+ }
+}
+
+
+/* print_msg -- print the content of msg of length n to stdout */
+/**/
+void print_msg(long msg[], int n)
+{
+ int i;
+ for (i = 0; i < n; i++) {
+ printf(" %li", msg[i]);
+ }
+}
+
+
+/* cmp_msg -- compare two messages of length n */
+/**/
+int cmp_msg(long msg[], long msg2[], int n, int i)
+{
+ int j;
+ for (j = 0; j < n; j++) {
+ if (msg[j] != msg2[j]) {
+ printf("Received message %d doesn't match sent message\n", i);
+ printf("in: "); print_msg(msg, n); printf("\n");
+ printf("out:"); print_msg(msg2, n); printf("\n");
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+int main()
+{
+ int msg_len;
+ for (msg_len = 4; msg_len < 100; msg_len += 5) {
+ PmQueue *queue = Pm_QueueCreate(100, msg_len * sizeof(long));
+ int i;
+ long msg[100];
+ long msg2[100];
+
+ printf("msg_len = %d\n", msg_len);
+ if (!queue) {
+ printf("Could not allocate queue\n");
+ return 1;
+ }
+
+ /* insert/remove 1000 messages */
+ printf("test 1\n");
+ for (i = 0; i < 1357; i++) {
+ make_msg(msg, msg_len, i);
+ if (Pm_Enqueue(queue, msg)) {
+ printf("Pm_Enqueue error\n");
+ return 1;
+ }
+ if (Pm_Dequeue(queue, msg2) != 1) {
+ printf("Pm_Dequeue error\n");
+ return 1;
+ }
+ if (!cmp_msg(msg, msg2, msg_len, i)) {
+ return 1;
+ }
+ }
+
+ /* make full */
+ printf("test 2\n");
+ for (i = 0; i < 100; i++) {
+ make_msg(msg, msg_len, i);
+ if (Pm_Enqueue(queue, msg)) {
+ printf("Pm_Enqueue error\n");
+ return 1;
+ }
+ }
+
+ /* alternately remove and insert */
+ for (i = 100; i < 1234; i++) {
+ make_msg(msg, msg_len, i - 100); /* what we expect */
+ if (Pm_Dequeue(queue, msg2) != 1) {
+ printf("Pm_Dequeue error\n");
+ return 1;
+ }
+ if (!cmp_msg(msg, msg2, msg_len, i)) {
+ return 1;
+ }
+ make_msg(msg, msg_len, i);
+ if (Pm_Enqueue(queue, msg)) {
+ printf("Pm_Enqueue error\n");
+ return 1;
+ }
+ }
+
+ /* remove all */
+ while (!Pm_QueueEmpty(queue)) {
+ make_msg(msg, msg_len, i - 100); /* what we expect */
+ if (Pm_Dequeue(queue, msg2) != 1) {
+ printf("Pm_Dequeue error\n");
+ return 1;
+ }
+ if (!cmp_msg(msg, msg2, msg_len, i)) {
+ return 1;
+ }
+ i++;
+ }
+ if (i != 1334) {
+ printf("Message count error\n");
+ return 1;
+ }
+
+ /* now test overflow */
+ printf("test 3\n");
+ for (i = 0; i < 110; i++) {
+ make_msg(msg, msg_len, i);
+ if (Pm_Enqueue(queue, msg) == pmBufferOverflow) {
+ break; /* this is supposed to execute after 100 messages */
+ }
+ }
+ for (i = 0; i < 100; i++) {
+ make_msg(msg, msg_len, i);
+ if (Pm_Dequeue(queue, msg2) != 1) {
+ printf("Pm_Dequeue error\n");
+ return 1;
+ }
+ if (!cmp_msg(msg, msg2, msg_len, i)) {
+ return 1;
+ }
+ }
+ /* we should detect overflow after removing 100 messages */
+ if (Pm_Dequeue(queue, msg2) != pmBufferOverflow) {
+ printf("Pm_Dequeue overflow expected\n");
+ return 1;
+ }
+
+ /* after overflow is detected (and cleared), sender can
+ * send again
+ */
+ /* see if function is restored, also test peek */
+ printf("test 4\n");
+ for (i = 0; i < 1357; i++) {
+ long *peek;
+ make_msg(msg, msg_len, i);
+ if (Pm_Enqueue(queue, msg)) {
+ printf("Pm_Enqueue error\n");
+ return 1;
+ }
+ peek = (long *) Pm_QueuePeek(queue);
+ if (!cmp_msg(msg, peek, msg_len, i)) {
+ return 1;
+ }
+ if (Pm_Dequeue(queue, msg2) != 1) {
+ printf("Pm_Dequeue error\n");
+ return 1;
+ }
+ if (!cmp_msg(msg, msg2, msg_len, i)) {
+ return 1;
+ }
+ }
+ Pm_QueueDestroy(queue);
+ }
+ return 0;
+}
diff --git a/portmidi/pm_test/recvvirtual.c b/portmidi/pm_test/recvvirtual.c
new file mode 100644
index 0000000..f8d9848
--- /dev/null
+++ b/portmidi/pm_test/recvvirtual.c
@@ -0,0 +1,175 @@
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define INPUT_BUFFER_SIZE 100
+#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
+#define TIME_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+#define STRING_MAX 80 /* used for console input */
+
+char *portname = "portmidi";
+
+PmSysDepInfo *sysdepinfo = NULL;
+char *port_name = "portmidi";
+
+static void set_sysdepinfo(char m_or_p, const char *name)
+{
+ if (!sysdepinfo) {
+ // allocate some space we will alias with open-ended PmDriverInfo:
+ // there is space for 4 parameters:
+ static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8];
+ sysdepinfo = (PmSysDepInfo *) dimem;
+ // build the driver info structure:
+ sysdepinfo->structVersion = PM_SYSDEPINFO_VERS;
+ sysdepinfo->length = 0;
+ }
+ if (sysdepinfo->length > 1) {
+ printf("Error: sysdepinfo was allocated to hold 2 parameters\n");
+ exit(1);
+ }
+ int i = sysdepinfo->length++;
+ enum PmSysDepPropertyKey k = pmKeyNone;
+ if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer;
+ else if (m_or_p == 'p') k = pmKeyAlsaPortName;
+ else if (m_or_p == 'c') k = pmKeyAlsaClientName;
+ sysdepinfo->properties[i].key = k;
+ sysdepinfo->properties[i].value = name;
+}
+
+
+static void prompt_and_exit(void)
+{
+ printf("type ENTER...");
+ while (getchar() != '\n') ;
+ /* this will clean up open ports: */
+ exit(-1);
+}
+
+
+static PmError checkerror(PmError err)
+{
+ if (err == pmHostError) {
+ /* it seems pointless to allocate memory and copy the string,
+ * so I will do the work of Pm_GetHostErrorText directly
+ */
+ char errmsg[80];
+ Pm_GetHostErrorText(errmsg, 80);
+ printf("PortMidi found host error...\n %s\n", errmsg);
+ prompt_and_exit();
+ } else if (err < 0) {
+ printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
+ prompt_and_exit();
+ }
+ return err;
+}
+
+
+void main_test_input(int num)
+{
+ PmStream *midi;
+ PmError status, length;
+ PmEvent buffer[1];
+ int id;
+ int i = 0; /* count messages as they arrive */
+ /* It is recommended to start timer before Midi; otherwise, PortMidi may
+ start the timer with its (default) parameters
+ */
+ TIME_START;
+
+ /* create a virtual input device */
+ id = checkerror(Pm_CreateVirtualInput(port_name, NULL, sysdepinfo));
+ checkerror(Pm_OpenInput(&midi, id, sysdepinfo, 0, NULL, NULL));
+
+ printf("Midi Input opened. Reading %d Midi messages...\n", num);
+ Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
+ /* empty the buffer after setting filter, just in case anything
+ got through */
+ while (Pm_Poll(midi)) {
+ Pm_Read(midi, buffer, 1);
+ }
+ /* now start paying attention to messages */
+ while (i < num) {
+ status = Pm_Poll(midi);
+ if (status == TRUE) {
+ length = Pm_Read(midi, buffer, 1);
+ if (length > 0) {
+ printf("Got message %d: time %ld, %2lx %2lx %2lx\n",
+ i,
+ (long) buffer[0].timestamp,
+ (long) Pm_MessageStatus(buffer[0].message),
+ (long) Pm_MessageData1(buffer[0].message),
+ (long) Pm_MessageData2(buffer[0].message));
+ i++;
+ } else {
+ assert(0);
+ }
+ }
+ }
+
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close...");
+ Pm_Close(midi);
+ printf("done closing.\nNow delete the virtual device...");
+ checkerror(Pm_DeleteVirtualDevice(id));
+ printf("done deleting.\n");
+}
+
+
+void show_usage()
+{
+ printf("Usage: recvvirtual [-h] [-m manufacturer] [-c clientname] "
+ "[-p portname] [n]\n"
+ " -h for this message,\n"
+ " -m name designates a manufacturer name (macOS only),\n"
+ " -c name designates a client name (linux only),\n"
+ " -p name designates a port name (linux only),\n"
+ " n is number of message to wait for.\n");
+ exit(0);
+}
+
+
+int main(int argc, char *argv[])
+{
+ char line[STRING_MAX];
+ int num = 10;
+ int i;
+ if (argc <= 1) {
+ show_usage();
+ }
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0) {
+ show_usage();
+ } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ set_sysdepinfo('m', argv[i]);
+ printf("Manufacturer name will be %s\n", argv[i]);
+ } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ port_name = argv[i];
+ set_sysdepinfo('p', port_name);
+ printf("Port name will be %s\n", port_name);
+ } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ set_sysdepinfo('c', argv[i]);
+ printf("Client name will be %s\n", argv[i]);
+ } else {
+ num = atoi(argv[i]);
+ if (num <= 0) {
+ printf("Zero value or non-number for n\n");
+ show_usage();
+ }
+ printf("Waiting for %d messages.\n", num);
+ }
+ }
+
+ main_test_input(num);
+
+ printf("finished portMidi test...type ENTER to quit...");
+ while (getchar() != '\n') ;
+ return 0;
+}
diff --git a/portmidi/pm_test/sendvirtual.c b/portmidi/pm_test/sendvirtual.c
new file mode 100644
index 0000000..a60a48f
--- /dev/null
+++ b/portmidi/pm_test/sendvirtual.c
@@ -0,0 +1,194 @@
+/* sendvirtual.c -- test for creating a virtual device and sending to it */
+/*
+ * Roger B. Dannenberg
+ * Sep 2021
+ */
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define OUTPUT_BUFFER_SIZE 0
+#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
+#define TIME_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+int latency = 0;
+PmSysDepInfo *sysdepinfo = NULL;
+char *port_name = "portmidi";
+
+static void set_sysdepinfo(char m_or_p, const char *name)
+{
+ if (!sysdepinfo) {
+ // allocate some space we will alias with open-ended PmDriverInfo:
+ // there is space for 4 parameters:
+ static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8];
+ sysdepinfo = (PmSysDepInfo *) dimem;
+ // build the driver info structure:
+ sysdepinfo->structVersion = PM_SYSDEPINFO_VERS;
+ sysdepinfo->length = 0;
+ }
+ if (sysdepinfo->length > 1) {
+ printf("Error: sysdepinfo was allocated to hold 2 parameters\n");
+ exit(1);
+ }
+ int i = sysdepinfo->length++;
+ enum PmSysDepPropertyKey k = pmKeyNone;
+ if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer;
+ else if (m_or_p == 'p') k = pmKeyAlsaPortName;
+ else if (m_or_p == 'c') k = pmKeyAlsaClientName;
+ sysdepinfo->properties[i].key = k;
+ sysdepinfo->properties[i].value = name;
+}
+
+
+static void prompt_and_exit(void)
+{
+ printf("type ENTER...");
+ while (getchar() != '\n') ;
+ /* this will clean up open ports: */
+ exit(-1);
+}
+
+
+static PmError checkerror(PmError err)
+{
+ if (err == pmHostError) {
+ /* it seems pointless to allocate memory and copy the string,
+ * so I will do the work of Pm_GetHostErrorText directly
+ */
+ char errmsg[80];
+ Pm_GetHostErrorText(errmsg, 80);
+ printf("PortMidi found host error...\n %s\n", errmsg);
+ prompt_and_exit();
+ } else if (err < 0) {
+ printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
+ prompt_and_exit();
+ }
+ return err;
+}
+
+
+void wait_until(PmTimestamp when)
+{
+ PtTimestamp now = Pt_Time();
+ if (when > now) {
+ Pt_Sleep(when - now);
+ }
+}
+
+
+void main_test_output(int num)
+{
+ PmStream *midi;
+ int32_t next_time;
+ PmEvent buffer[1];
+ PmTimestamp timestamp;
+ int pitch = 60;
+ int id;
+
+ /* It is recommended to start timer before Midi; otherwise, PortMidi may
+ start the timer with its (default) parameters
+ */
+ TIME_START;
+
+ /* create a virtual output device */
+ id = checkerror(Pm_CreateVirtualOutput(port_name, NULL, sysdepinfo));
+ checkerror(Pm_OpenOutput(&midi, id, sysdepinfo, OUTPUT_BUFFER_SIZE,
+ TIME_PROC, TIME_INFO, latency));
+
+ printf("Midi Output Virtual Device \"%s\" created.\n", port_name);
+ printf("Type ENTER to send messages: ");
+ while (getchar() != '\n') ;
+
+ buffer[0].timestamp = Pt_Time();
+#define PROGRAM 0
+ buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
+ Pm_Write(midi, buffer, 1);
+ next_time = Pt_Time() + 1000; /* wait 1s */
+ while (num > 0) {
+ wait_until(next_time);
+ Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 100));
+ printf("Note On pitch %d\n", pitch);
+ num--;
+ next_time += 500;
+
+ wait_until(next_time);
+ Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 0));
+ printf("Note Off pitch %d\n", pitch);
+ num--;
+ pitch = (pitch + 1) % 12 + 60;
+ next_time += 500;
+ }
+
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close...");
+ Pm_Close(midi);
+ printf("done closing.\nNow delete the virtual device...");
+ checkerror(Pm_DeleteVirtualDevice(id));
+ printf("done deleting.\n");
+}
+
+
+void show_usage()
+{
+ printf("Usage: sendvirtual [-h] [-l latency-in-ms] [-m manufacturer] "
+ "[-c clientname] [-p portname] [n]\n"
+ " -h for this message,\n"
+ " -l ms designates latency for precise timing (default 0),\n"
+ " -m name designates a manufacturer name (macOS only),\n"
+ " -c name designates a client name (linux only),\n"
+ " -p name designates a port name (linux only),\n"
+ " n is number of message to send.\n"
+ "sends change program to 1, then one note per second with 0.5s on,\n"
+ "0.5s off, for n/2 seconds. Latency >0 uses the device driver for \n"
+ "precise timing (see PortMidi documentation).\n");
+ exit(0);
+}
+
+
+int main(int argc, char *argv[])
+{
+ int num = 10;
+ int i;
+ if (argc <= 1) {
+ show_usage();
+ }
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0) {
+ show_usage();
+ } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ latency = atoi(argv[i]);
+ printf("Latency will be %d\n", latency);
+ } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ set_sysdepinfo('m', argv[i]);
+ printf("Manufacturer name will be %s\n", argv[i]);
+ } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ port_name = argv[i];
+ set_sysdepinfo('p', port_name);
+ printf("Port name will be %s\n", port_name);
+ } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ set_sysdepinfo('c', argv[i]);
+ printf("Client name will be %s\n", argv[i]);
+ } else {
+ num = atoi(argv[i]);
+ if (num <= 0) {
+ printf("Zero value or non-number for n\n");
+ show_usage();
+ }
+ printf("Sending %d messages.\n", num);
+ }
+ }
+
+ main_test_output(num);
+
+ printf("finished sendvirtual test...type ENTER to quit...");
+ while (getchar() != '\n') ;
+ return 0;
+}
diff --git a/portmidi/pm_test/sysex.c b/portmidi/pm_test/sysex.c
new file mode 100755
index 0000000..c2c7187
--- /dev/null
+++ b/portmidi/pm_test/sysex.c
@@ -0,0 +1,556 @@
+/* sysex.c -- example program showing how to send and receive sysex
+ messages
+
+ Messages are stored in a file using 2-digit hexadecimal numbers,
+ one per byte, separated by blanks, with up to 32 numbers per line:
+ F0 14 A7 4B ...
+
+ */
+
+#include "stdio.h"
+#include "stdlib.h"
+#include "assert.h"
+#include "portmidi.h"
+#include "porttime.h"
+#include "string.h"
+#ifdef WIN32
+// need to get declaration for Sleep()
+#include "windows.h"
+#else
+#include <unistd.h>
+#define Sleep(n) usleep(n * 1000)
+#endif
+
+// enable some extra printing
+#ifndef VERBOSE
+#define VERBOSE 0
+#endif
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+#define STRING_MAX 80
+
+#ifndef true
+#define true 1
+#define false 0
+#endif
+
+int latency = 0;
+
+/* read a number from console */
+/**/
+int get_number(const char *prompt)
+{
+ int n = 0, i;
+ fputs(prompt, stdout);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ while (getchar() != '\n') ;
+ }
+ return i;
+}
+
+
+/* loopback test -- send/rcv from 2 to 1000 bytes of random midi data */
+/**/
+void loopback_test()
+{
+ int outp;
+ int inp;
+ PmStream *midi_in;
+ PmStream *midi_out;
+ unsigned char msg[1024];
+ int32_t len;
+ int i;
+ int data;
+ PmEvent event;
+ int shift;
+ long total_bytes = 0;
+ int32_t begin_time;
+
+ Pt_Start(1, 0, 0);
+
+ printf("Connect a midi cable from an output port to an input port.\n");
+ printf("This test will send random data via sysex message from output\n");
+ printf("to input and check that the correct data was received.\n");
+ outp = get_number("Type output device number: ");
+ /* Open output with 1ms latency -- when latency is non-zero, the Win32
+ implementation supports sending sysex messages incrementally in a
+ series of buffers. This is nicer than allocating a big buffer for the
+ message, and it also seems to work better. Either way works.
+ */
+ while ((latency = get_number(
+ "Latency in milliseconds (0 to send data immediatedly,\n"
+ " >0 to send timestamped messages): ")) < 0);
+ Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
+ inp = get_number("Type input device number: ");
+ /* since we are going to send and then receive, make sure the input buffer
+ is large enough for the entire message */
+ Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL);
+
+ srand((unsigned int) Pt_Time()); /* seed for random numbers */
+
+ begin_time = Pt_Time();
+ while (total_bytes < 100000) {
+ PmError count;
+ int32_t start_time;
+ int error_position = -1; /* 0; -1; -1 for continuous */
+ int expected = 0;
+ int actual = 0;
+ /* this modification will run until an error is detected */
+ /* set error_position above to 0 for interactive, -1 for */
+ /* continuous */
+ if (error_position >= 0) {
+ int c;
+ printf("Type return to send message, q to quit: ");
+ while ((c = getchar()) != '\n') {
+ if (c == 'q') goto cleanup;
+ }
+ }
+
+ /* compose the message */
+ len = rand() % 998 + 2; /* len only counts data bytes */
+ msg[0] = (char) MIDI_SYSEX; /* start of SYSEX message */
+ /* data bytes go from 1 to len */
+ for (i = 0; i < len; i++) {
+/* pick whether data is sequential or random... (docs say random) */
+#define DATA_EXPR (i+1)
+// #define DATA_EXPR rand()
+ msg[i + 1] = DATA_EXPR & 0x7f; /* MIDI data */
+ }
+ /* final EOX goes in len+1, total of len+2 bytes in msg */
+ msg[len + 1] = (char) MIDI_EOX;
+
+ /* sanity check: before we send, there should be no queued data */
+ count = Pm_Read(midi_in, &event, 1);
+
+ if (count != 0) {
+ printf("Before sending anything, a MIDI message was found in\n");
+ printf("the input buffer. Please try again.\n");
+ break;
+ }
+
+ /* send the message two ways: 1) Pm_WriteSysEx, 2) Pm_Write */
+ if (total_bytes & 1) {
+ printf("Sending %d byte sysex msg via Pm_WriteSysEx.\n", len + 2);
+ Pm_WriteSysEx(midi_out, 0, msg);
+ } else {
+ PmEvent event = {0, 0};
+ int bits = 0;
+ printf("Sending %d byte sysex msg via Pm_Write(s).\n", len + 2);
+ for (i = 0; i < len + 2; i++) {
+ event.message |= (msg[i] << bits);
+ bits += 8;
+ if (bits == 32) { /* full message - send it */
+ Pm_Write(midi_out, &event, 1);
+ bits = 0;
+ event.message = 0;
+ }
+ }
+ if (bits > 0) { /* last message is partially full */
+ Pm_Write(midi_out, &event, 1);
+ }
+ }
+
+ /* receive the message and compare to msg[] */
+ data = 0;
+ shift = 0;
+ i = 0;
+ start_time = Pt_Time();
+ if (VERBOSE) {
+ printf("start_time %d\n", start_time);
+ }
+ error_position = -1;
+ /* allow up to 2 seconds for transmission */
+ while (data != MIDI_EOX && start_time + 2000 > Pt_Time()) {
+ count = Pm_Read(midi_in, &event, 1);
+ if (count == 0) {
+ Sleep(1); /* be nice: give some CPU time to the system */
+ continue; /* continue polling for input */
+ }
+ if (VERBOSE) {
+ printf("read %08x ", event.message);
+ fflush(stdout);
+ }
+ /* compare 4 bytes of data until you reach an eox */
+ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
+ data = (event.message >> shift) & 0xFF;
+ if (data != msg[i] && error_position < 0) {
+ error_position = i;
+ expected = msg[i];
+ actual = data;
+ }
+ i++;
+ }
+ }
+ if (error_position >= 0) {
+ printf("Error at time %d byte %d: sent %x recd %x.\n", Pt_Time(),
+ error_position, expected, actual);
+ break;
+ } else if (i != len + 2) {
+ printf("Error at time %d: byte %d not received.\n", Pt_Time(), i);
+ break;
+ } else {
+ int seconds = (Pt_Time() - begin_time) / 1000;
+ if (seconds == 0) seconds = 1;
+ printf("Correctly received %d byte sysex message.\n", i);
+ total_bytes += i;
+ printf("Cummulative bytes/sec: %d, %d%% done.\n",
+ (int) (total_bytes / seconds),
+ (int) (100 * total_bytes / 100000));
+ }
+ }
+cleanup:
+ Pm_Close(midi_out);
+ Pm_Close(midi_in);
+ return;
+}
+
+
+/* send_multiple test -- send many sysex messages */
+/**/
+void send_multiple_test()
+{
+ int outp;
+ int length;
+ int num_msgs;
+ PmStream *midi_out;
+ unsigned char msg[1024];
+ int i;
+ PtTimestamp start_time;
+ PtTimestamp stop_time;
+
+ Pt_Start(1, 0, 0);
+
+ printf("This is for performance testing. You should be sending to this\n");
+ printf("program running the receive multiple test. Do NOT send to\n");
+ printf("a synthesizer or you risk reprogramming it\n");
+ outp = get_number("Type output device number: ");
+ while ((latency = get_number(
+ "Latency in milliseconds (0 to send data immediatedly,\n"
+ " >0 to send timestamped messages): ")) < 0);
+ Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
+ while ((length = get_number("Message length (7 - 1024): ")) < 7 ||
+ length > 1024) ;
+ while ((num_msgs = get_number("Number of messages: ")) < 1);
+ /* latency, length, and num_msgs should now all be valid */
+ /* compose the message except for sequence number in first 5 bytes */
+ msg[0] = (char) MIDI_SYSEX;
+ for (i = 6; i < length - 1; i++) {
+ msg[i] = i % 128; /* this is just filler */
+ }
+ msg[length - 1] = (char) MIDI_EOX;
+
+ start_time = Pt_Time();
+ /* send the messages */
+ for (i = num_msgs; i > 0; i--) {
+ /* insert sequence number into first 5 data bytes */
+ /* sequence counts down to zero */
+ int j;
+ int count = i;
+ /* 7 bits of message count i goes into each data byte */
+ for (j = 1; j <= 5; j++) {
+ msg[j] = count & 127;
+ count >>= 7;
+ }
+ /* send the message */
+ Pm_WriteSysEx(midi_out, 0, msg);
+ }
+ stop_time = Pt_Time();
+ Pm_Close(midi_out);
+ return;
+}
+
+#define MAX_MSG_LEN 1024
+static unsigned char receive_msg[MAX_MSG_LEN];
+static int receive_msg_index;
+static int receive_msg_length;
+static int receive_msg_count;
+static int receive_msg_error;
+static int receive_msg_messages;
+static PmStream *receive_msg_midi_in;
+static int receive_poll_running;
+
+/* receive_poll -- callback function to check for midi input */
+/**/
+void receive_poll(PtTimestamp timestamp, void *userData)
+{
+ PmError count;
+ PmEvent event;
+ int shift;
+ int data = 0;
+ int i;
+
+ if (!receive_poll_running) return; /* wait until midi device is opened */
+ shift = 0;
+ while (data != MIDI_EOX) {
+ count = Pm_Read(receive_msg_midi_in, &event, 1);
+ if (count == 0) return;
+
+ /* compare 4 bytes of data until you reach an eox */
+ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
+ receive_msg[receive_msg_index++] = data =
+ (event.message >> shift) & 0xFF;
+ if (receive_msg_index >= MAX_MSG_LEN) {
+ printf("error: incoming sysex too long\n");
+ goto error;
+ }
+ }
+ }
+ /* check the message */
+ if (receive_msg_length == 0) {
+ receive_msg_length = receive_msg_index;
+ }
+ if (receive_msg_length != receive_msg_index) {
+ printf("error: incoming sysex wrong length\n");
+ goto error;
+ }
+ if (receive_msg[0] != MIDI_SYSEX) {
+ printf("error: incoming sysex missing status byte\n");
+ goto error;
+ }
+ /* get and check the count */
+ count = 0;
+ for (i = 0; i < 5; i++) {
+ count += receive_msg[i + 1] << (7 * i);
+ }
+ if (receive_msg_count == -1) {
+ receive_msg_count = count;
+ receive_msg_messages = count;
+ }
+ if (receive_msg_count != count) {
+ printf("error: incoming sysex has wrong count\n");
+ goto error;
+ }
+ for (i = 6; i < receive_msg_index - 1; i++) {
+ if (receive_msg[i] != i % 128) {
+ printf("error: incoming sysex has bad data\n");
+ goto error;
+ }
+ }
+ if (receive_msg[receive_msg_length - 1] != MIDI_EOX) goto error;
+ receive_msg_index = 0; /* get ready for next message */
+ receive_msg_count--;
+ return;
+ error:
+ receive_msg_error = 1;
+ return;
+}
+
+
+/* receive_multiple_test -- send/rcv from 2 to 1000 bytes of random midi data */
+/**/
+void receive_multiple_test()
+{
+ PmError err;
+ int inp;
+
+ printf("This test expects to receive data sent by the send_multiple test\n");
+ printf("The test will check that correct data is received.\n");
+
+ /* Important: start PortTime first -- if it is not started first, it will
+ be started by PortMidi, and then our attempt to open again will fail */
+ receive_poll_running = false;
+ if ((err = Pt_Start(1, receive_poll, 0))) {
+ printf("PortTime error code: %d\n", err);
+ goto cleanup;
+ }
+ inp = get_number("Type input device number: ");
+ Pm_OpenInput(&receive_msg_midi_in, inp, NULL, 512, NULL, NULL);
+ receive_msg_index = 0;
+ receive_msg_length = 0;
+ receive_msg_count = -1;
+ receive_msg_error = 0;
+ receive_poll_running = true;
+ while ((!receive_msg_error) && (receive_msg_count != 0)) {
+#ifdef WIN32
+ Sleep(1000);
+#else
+ sleep(1); /* block and wait */
+#endif
+ }
+ if (receive_msg_error) {
+ printf("Receive_multiple test encountered an error\n");
+ } else {
+ printf("Receive_multiple test successfully received %d sysex messages\n",
+ receive_msg_messages);
+ }
+cleanup:
+ receive_poll_running = false;
+ Pm_Close(receive_msg_midi_in);
+ Pt_Stop();
+ return;
+}
+
+
+#define is_real_time_msg(msg) ((0xF0 & Pm_MessageStatus(msg)) == 0xF8)
+
+
+void receive_sysex()
+{
+ char line[80];
+ FILE *f;
+ PmStream *midi;
+ int shift = 0;
+ int data = 0;
+ int bytes_on_line = 0;
+ PmEvent msg;
+
+ /* determine which output device to use */
+ int i = get_number("Type input device number: ");
+
+ /* open input device */
+ Pm_OpenInput(&midi, i, NULL, 512, NULL, NULL);
+ printf("Midi Input opened, type file for sysex data: ");
+
+ /* open file */
+ if (!fgets(line, STRING_MAX, stdin)) return; /* no more stdin? */
+ /* remove the newline character */
+ if (strlen(line) > 0) line[strlen(line) - 1] = 0;
+ f = fopen(line, "w");
+ if (!f) {
+ printf("Could not open %s\n", line);
+ Pm_Close(midi);
+ return;
+ }
+
+ printf("Ready to receive a sysex message\n");
+
+ /* read data and write to file */
+ while (data != MIDI_EOX) {
+ PmError count;
+ count = Pm_Read(midi, &msg, 1);
+ /* CAUTION: this causes busy waiting. It would be better to
+ be in a polling loop to avoid being compute bound. PortMidi
+ does not support a blocking read since this is so seldom
+ useful.
+ */
+ if (count == 0) continue;
+ /* ignore real-time messages */
+ if (is_real_time_msg(Pm_MessageStatus(msg.message))) continue;
+
+ /* write 4 bytes of data until you reach an eox */
+ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
+ data = (msg.message >> shift) & 0xFF;
+ /* if this is a status byte that's not MIDI_EOX, the sysex
+ message is incomplete and there is no more sysex data */
+ if (data & 0x80 && data != MIDI_EOX) break;
+ fprintf(f, "%2x ", data);
+ if (++bytes_on_line >= 16) {
+ fprintf(f, "\n");
+ bytes_on_line = 0;
+ }
+ }
+ }
+ fclose(f);
+ Pm_Close(midi);
+}
+
+
+void send_sysex()
+{
+ char line[80];
+ FILE *f;
+ PmStream *midi;
+ int data;
+ int shift = 0;
+ PmEvent msg;
+
+ /* determine which output device to use */
+ int i = get_number("Type output device number: ");
+ while ((latency = get_number(
+ "Latency in milliseconds (0 to send data immediatedly,\n"
+ " >0 to send timestamped messages): ")) < 0);
+
+ msg.timestamp = 0; /* no need for timestamp */
+
+ /* open output device */
+ Pm_OpenOutput(&midi, i, NULL, 0, NULL, NULL, latency);
+ printf("Midi Output opened, type file with sysex data: ");
+
+ /* open file */
+ if (!fgets(line, STRING_MAX, stdin)) return; /* no more stdin? */
+ /* remove the newline character */
+ if (strlen(line) > 0) line[strlen(line) - 1] = 0;
+ f = fopen(line, "r");
+ if (!f) {
+ printf("Could not open %s\n", line);
+ Pm_Close(midi);
+ return;
+ }
+
+ /* read file and send data */
+ msg.message = 0;
+ while (1) {
+ /* get next byte from file */
+
+ if (fscanf(f, "%x", &data) == 1) {
+ /* printf("read %x, ", data); */
+ /* OR byte into message at proper offset */
+ msg.message |= (data << shift);
+ shift += 8;
+ }
+ /* send the message if it's full (shift == 32) or if we are at end */
+ if (shift == 32 || data == MIDI_EOX) {
+ /* this will send sysex data 4 bytes at a time -- it would
+ be much more efficient to send multiple PmEvents at once
+ but this method is simpler. See Pm_WriteSysEx for a more
+ efficient code example.
+ */
+ Pm_Write(midi, &msg, 1);
+ msg.message = 0;
+ shift = 0;
+ }
+ if (data == MIDI_EOX) { /* end of message */
+ fclose(f);
+ Pm_Close(midi);
+ return;
+ }
+ }
+}
+
+
+int main()
+{
+ int i;
+
+ /* list device information */
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (info->input) printf(" (input)");
+ if (info->output) printf(" (output)");
+ printf("\n");
+ }
+ while (1) {
+ char cmd;
+ printf("Type r to receive sysex, s to send,"
+ " l for loopback test, m to send multiple,"
+ " n to receive multiple, q to quit: ");
+ cmd = getchar();
+ while (getchar() != '\n') ;
+ switch (cmd) {
+ case 'r':
+ receive_sysex();
+ break;
+ case 's':
+ send_sysex();
+ break;
+ case 'l':
+ loopback_test();
+ break;
+ case 'm':
+ send_multiple_test();
+ break;
+ case 'n':
+ receive_multiple_test();
+ break;
+ case 'q':
+ exit(0);
+ default:
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/portmidi/pm_test/testio.c b/portmidi/pm_test/testio.c
new file mode 100755
index 0000000..2711286
--- /dev/null
+++ b/portmidi/pm_test/testio.c
@@ -0,0 +1,594 @@
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define INPUT_BUFFER_SIZE 100
+#define OUTPUT_BUFFER_SIZE 0
+#define TIME_PROC ((int32_t (*)(void *)) Pt_Time)
+#define TIME_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+#define WAIT_FOR_ENTER while (getchar() != '\n') ;
+
+int32_t latency = 0;
+int verbose = FALSE;
+PmSysDepInfo *sysdepinfo = NULL;
+
+/* crash the program to test whether midi ports are closed */
+/**/
+void doSomethingReallyStupid() {
+ int * tmp = NULL;
+ *tmp = 5;
+}
+
+
+/* exit the program without any explicit cleanup */
+/**/
+void doSomethingStupid() {
+ assert(0);
+}
+
+
+/* read a number from console */
+/**/
+int get_number(const char *prompt)
+{
+ int n = 0, i;
+ fputs(prompt, stdout);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ WAIT_FOR_ENTER
+ }
+ return i;
+}
+
+
+static void set_sysdepinfo(char m_or_p, const char *name)
+{
+ if (!sysdepinfo) {
+ // allocate some space we will alias with open-ended PmDriverInfo:
+ // there is space for 4 parameters:
+ static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8];
+ sysdepinfo = (PmSysDepInfo *) dimem;
+ // build the driver info structure:
+ sysdepinfo->structVersion = PM_SYSDEPINFO_VERS;
+ sysdepinfo->length = 0;
+ }
+ if (sysdepinfo->length > 1) {
+ printf("Error: sysdepinfo was allocated to hold 2 parameters\n");
+ exit(1);
+ }
+ int i = sysdepinfo->length++;
+ enum PmSysDepPropertyKey k = pmKeyNone;
+ if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer;
+ else if (m_or_p == 'p') k = pmKeyAlsaPortName;
+ else if (m_or_p == 'c') k = pmKeyAlsaClientName;
+ sysdepinfo->properties[i].key = k;
+ sysdepinfo->properties[i].value = name;
+}
+
+
+/*
+ * the somethingStupid parameter can be set to simulate a program crash.
+ * We want PortMidi to close Midi ports automatically in the event of a
+ * crash because Windows does not (and this may cause an OS crash)
+ */
+void main_test_input(unsigned int somethingStupid) {
+ PmStream * midi;
+ PmError status, length;
+ PmEvent buffer[1];
+ int num = 10;
+ int i = get_number("Type input number: ");
+ /* It is recommended to start timer before Midi; otherwise, PortMidi may
+ start the timer with its (default) parameters
+ */
+ TIME_START;
+
+ /* open input device */
+ Pm_OpenInput(&midi,
+ i,
+ sysdepinfo,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+
+ printf("Midi Input opened. Reading %d Midi messages...\n", num);
+ Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
+ /* empty the buffer after setting filter, just in case anything
+ got through */
+ while (Pm_Poll(midi)) {
+ Pm_Read(midi, buffer, 1);
+ }
+ /* now start paying attention to messages */
+ i = 0; /* count messages as they arrive */
+ while (i < num) {
+ status = Pm_Poll(midi);
+ if (status == TRUE) {
+ length = Pm_Read(midi, buffer, 1);
+ if (length > 0) {
+ printf("Got message %d @ time %ld: timestamp %ld, "
+ "%2lx %2lx %2lx\n", i, (long) Pt_Time(),
+ (long) buffer[0].timestamp,
+ (long) Pm_MessageStatus(buffer[0].message),
+ (long) Pm_MessageData1(buffer[0].message),
+ (long) Pm_MessageData2(buffer[0].message));
+ i++;
+ } else {
+ assert(0);
+ }
+ }
+ /* simulate crash if somethingStupid is 1 or 2 */
+ if ((i > (num/2)) && (somethingStupid == 1)) {
+ doSomethingStupid();
+ } else if ((i > (num/2)) && (somethingStupid == 2)) {
+ doSomethingReallyStupid();
+ }
+ }
+
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close...");
+
+ Pm_Close(midi);
+ printf("done closing...");
+}
+
+
+
+void main_test_output(int isochronous_test)
+{
+ PmStream * midi;
+ int32_t off_time;
+ int chord[] = { 60, 67, 76, 83, 90 };
+ #define chord_size 5
+ PmEvent buffer[chord_size];
+ PmTimestamp timestamp;
+
+ /* determine which output device to use */
+ int i = get_number("Type output number: ");
+
+ /* It is recommended to start timer before PortMidi */
+ TIME_START;
+
+ /* open output device -- since PortMidi avoids opening a timer
+ when latency is zero, we will pass in a NULL timer pointer
+ for that case. If PortMidi tries to access the time_proc,
+ we will crash, so this test will tell us something. */
+ Pm_OpenOutput(&midi,
+ i,
+ sysdepinfo,
+ OUTPUT_BUFFER_SIZE,
+ (latency == 0 ? NULL : TIME_PROC),
+ (latency == 0 ? NULL : TIME_INFO),
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", (long) latency);
+
+ /* output note on/off w/latency offset; hold until user prompts */
+ printf("ready to send program 1 change... (type ENTER):");
+ WAIT_FOR_ENTER
+ /* if we were writing midi for immediate output, we could always use
+ timestamps of zero, but since we may be writing with latency, we
+ will explicitly set the timestamp to "now" by getting the time.
+ The source of timestamps should always correspond to the TIME_PROC
+ and TIME_INFO parameters used in Pm_OpenOutput(). */
+ buffer[0].timestamp = Pt_Time();
+ /* Send a program change to increase the chances we will hear notes */
+ /* Program 0 is usually a piano, but you can change it here: */
+#define PROGRAM 0
+ buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
+ Pm_Write(midi, buffer, 1);
+
+ if (isochronous_test) { // play 4 notes per sec for 20s
+ int count;
+ PmTimestamp start;
+ if (latency < 100) {
+ printf("Warning: latency < 100, but this test sends messages"
+ " at times that are jittered by up to 100ms, so you"
+ " may hear uneven timing\n");
+ }
+ printf("Starting in 1s..."); fflush(stdout);
+ Pt_Sleep(1000);
+ start = Pt_Time();
+ for (count = 0; count < 80; count++) {
+ PmTimestamp next_time;
+ buffer[0].timestamp = start + count * 250;
+ buffer[0].message = Pm_Message(0x90, 69, 100);
+ buffer[1].timestamp = start + count * 250 + 200;
+ buffer[1].message = Pm_Message(0x90, 69, 0);
+ Pm_Write(midi, buffer, 2);
+ next_time = start + (count + 1) * 250;
+ // sleep for a random time up to 100ms to add jitter to
+ // the times at which we send messages. PortMidi timing
+ // should remove the jitter if latency > 100
+ while (Pt_Time() < next_time) {
+ Pt_Sleep(rand() % 100);
+ }
+ }
+ printf("Done sending 80 notes at 4 notes per second.\n");
+ } else {
+ PmError err = 0;
+ printf("ready to note-on... (type ENTER):");
+ WAIT_FOR_ENTER
+ buffer[0].timestamp = Pt_Time();
+ buffer[0].message = Pm_Message(0x90, 60, 100);
+ if ((err = Pm_Write(midi, buffer, 1))) {
+ printf("Pm_Write returns error: %d (%s)\n",
+ err, Pm_GetErrorText(err));
+ if (err == pmHostError) {
+ char errmsg[128];
+ Pm_GetHostErrorText(errmsg, 127);
+ printf(" Host error: %s\n", errmsg);
+ }
+ }
+ printf("ready to note-off... (type ENTER):");
+ WAIT_FOR_ENTER
+ buffer[0].timestamp = Pt_Time();
+ buffer[0].message = Pm_Message(0x90, 60, 0);
+ if ((err = Pm_Write(midi, buffer, 1))) {
+ printf("Pm_Write returns error: %d (%s)\n",
+ err, Pm_GetErrorText(err));
+ if (err == pmHostError) {
+ char errmsg[128];
+ Pm_GetHostErrorText(errmsg, 127);
+ printf(" Host error: %s\n", errmsg);
+ }
+ }
+
+ /* output short note on/off w/latency offset; hold until user prompts */
+ printf("ready to note-on (short form)... (type ENTER):");
+ WAIT_FOR_ENTER
+ Pm_WriteShort(midi, Pt_Time(),
+ Pm_Message(0x90, 60, 100));
+ printf("ready to note-off (short form)... (type ENTER):");
+ WAIT_FOR_ENTER
+ Pm_WriteShort(midi, Pt_Time(),
+ Pm_Message(0x90, 60, 0));
+
+ /* output several note on/offs to test timing.
+ Should be 1s between notes */
+ if (latency == 0) {
+ printf("chord should not arpeggiate, latency == 0\n");
+ } else {
+ printf("chord should arpeggiate (latency = %ld > 0\n",
+ (long) latency);
+ }
+ printf("ready to chord-on/chord-off... (type ENTER):");
+ WAIT_FOR_ENTER
+ timestamp = Pt_Time();
+ printf("starting timestamp %ld\n", (long) timestamp);
+ for (i = 0; i < chord_size; i++) {
+ buffer[i].timestamp = timestamp + 1000 * i;
+ buffer[i].message = Pm_Message(0x90, chord[i], 100);
+ }
+ Pm_Write(midi, buffer, chord_size);
+
+ off_time = timestamp + 1000 + chord_size * 1000;
+ while (Pt_Time() < off_time)
+ /* There was a report that Pm_Write with zero length sent last
+ * message again, so call Pm_Write here to see if note repeats
+ */
+ Pm_Write(midi, buffer, 0);
+ Pt_Sleep(20); /* wait */
+
+ for (i = 0; i < chord_size; i++) {
+ buffer[i].timestamp = timestamp + 1000 * i;
+ buffer[i].message = Pm_Message(0x90, chord[i], 0);
+ }
+ Pm_Write(midi, buffer, chord_size);
+ }
+
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close and terminate... (type ENTER):");
+ WAIT_FOR_ENTER
+
+ Pm_Close(midi);
+ Pm_Terminate();
+ printf("done closing and terminating...\n");
+}
+
+
+void main_test_both()
+{
+ int i = 0;
+ int in, out;
+ PmStream * midi, * midiOut;
+ PmEvent buffer[1];
+ PmError status, length;
+ int num = 11;
+
+ in = get_number("Type input number: ");
+ out = get_number("Type output number: ");
+
+ /* In is recommended to start timer before PortMidi */
+ TIME_START;
+
+ Pm_OpenOutput(&midiOut,
+ out,
+ sysdepinfo,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", (long) latency);
+ /* open input device */
+ Pm_OpenInput(&midi,
+ in,
+ sysdepinfo,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+ printf("Midi Input opened. Reading %d Midi messages...\n", num);
+ Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+ /* empty the buffer after setting filter, just in case anything
+ got through */
+ while (Pm_Poll(midi)) {
+ Pm_Read(midi, buffer, 1);
+ }
+ i = 0;
+ while (i < num) {
+ status = Pm_Poll(midi);
+ if (status == TRUE) {
+ length = Pm_Read(midi,buffer,1);
+ if (length > 0) {
+ Pm_Write(midiOut, buffer, 1);
+ printf("Got message %d @ time %ld: timestamp %ld, "
+ "%2lx %2lx %2lx\n", i, (long) Pt_Time(),
+ (long) buffer[0].timestamp,
+ (long) Pm_MessageStatus(buffer[0].message),
+ (long) Pm_MessageData1(buffer[0].message),
+ (long) Pm_MessageData2(buffer[0].message));
+ i++;
+ } else {
+ assert(0);
+ }
+ }
+ }
+ /* allow time for last message to go out */
+ Pt_Sleep(100 + latency);
+
+ /* close midi devices */
+ Pm_Close(midi);
+ Pm_Close(midiOut);
+ Pm_Terminate();
+}
+
+
+/* main_test_stream exercises windows winmm API's stream mode */
+/* The winmm stream mode is used for latency>0, and sends
+ timestamped messages. The timestamps are relative (delta)
+ times, whereas PortMidi times are absolute. Since peculiar
+ things happen when messages are not always sent in advance,
+ this function allows us to exercise the system and test it.
+ */
+void main_test_stream() {
+ PmStream * midi;
+ PmEvent buffer[16];
+
+ /* determine which output device to use */
+ int i = get_number("Type output number: ");
+
+ latency = 500; /* ignore LATENCY for this test and
+ fix the latency at 500ms */
+
+ /* It is recommended to start timer before PortMidi */
+ TIME_START;
+
+ /* open output device */
+ Pm_OpenOutput(&midi,
+ i,
+ sysdepinfo,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", (long) latency);
+
+ /* output note on/off w/latency offset; hold until user prompts */
+ printf("ready to send output... (type ENTER):");
+ WAIT_FOR_ENTER
+
+ /* if we were writing midi for immediate output, we could always use
+ timestamps of zero, but since we may be writing with latency, we
+ will explicitly set the timestamp to "now" by getting the time.
+ The source of timestamps should always correspond to the TIME_PROC
+ and TIME_INFO parameters used in Pm_OpenOutput(). */
+ buffer[0].timestamp = Pt_Time();
+ buffer[0].message = Pm_Message(0xC0, 0, 0);
+ buffer[1].timestamp = buffer[0].timestamp;
+ buffer[1].message = Pm_Message(0x90, 60, 100);
+ buffer[2].timestamp = buffer[0].timestamp + 1000;
+ buffer[2].message = Pm_Message(0x90, 62, 100);
+ buffer[3].timestamp = buffer[0].timestamp + 2000;
+ buffer[3].message = Pm_Message(0x90, 64, 100);
+ buffer[4].timestamp = buffer[0].timestamp + 3000;
+ buffer[4].message = Pm_Message(0x90, 66, 100);
+ buffer[5].timestamp = buffer[0].timestamp + 4000;
+ buffer[5].message = Pm_Message(0x90, 60, 0);
+ buffer[6].timestamp = buffer[0].timestamp + 4000;
+ buffer[6].message = Pm_Message(0x90, 62, 0);
+ buffer[7].timestamp = buffer[0].timestamp + 4000;
+ buffer[7].message = Pm_Message(0x90, 64, 0);
+ buffer[8].timestamp = buffer[0].timestamp + 4000;
+ buffer[8].message = Pm_Message(0x90, 66, 0);
+
+ Pm_Write(midi, buffer, 9);
+#ifdef SEND8
+ /* Now, we're ready for the real test.
+ Play 4 notes at now, now+500, now+1000, and now+1500
+ Then wait until now+2000.
+ Play 4 more notes as before.
+ We should hear 8 evenly spaced notes. */
+ now = Pt_Time();
+ for (i = 0; i < 4; i++) {
+ buffer[i * 2].timestamp = now + (i * 500);
+ buffer[i * 2].message = Pm_Message(0x90, 60, 100);
+ buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
+ buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
+ }
+ Pm_Write(midi, buffer, 8);
+
+ while (Pt_Time() < now + 2500)
+ Pt_Sleep(10);
+ /* now we are 500 ms behind schedule, but since the latency
+ is 500, the delay should not be audible */
+ now += 2000;
+ for (i = 0; i < 4; i++) {
+ buffer[i * 2].timestamp = now + (i * 500);
+ buffer[i * 2].message = Pm_Message(0x90, 60, 100);
+ buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
+ buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
+ }
+ Pm_Write(midi, buffer, 8);
+#endif
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close and terminate... (type ENTER):");
+ WAIT_FOR_ENTER
+
+ Pm_Close(midi);
+ Pm_Terminate();
+ printf("done closing and terminating...\n");
+}
+
+
+void show_usage()
+{
+ printf("Usage: test [-h] [-l latency-in-ms] [-c clientname] "
+ "[-p portname] [-v]\n"
+ " -h for this help message (only)\n"
+ " -l for latency\n"
+ " -c name designates a client name (linux only),\n"
+ " -p name designates a port name (linux only),\n"
+ " -v for verbose (enables more output)\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int default_in;
+ int default_out;
+ int i = 0, n = 0;
+ int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0;
+ int isochronous_test = 0;
+ int stream_test = 0;
+ int latency_valid = FALSE;
+
+ show_usage();
+ if (sizeof(void *) == 8)
+ printf("Apparently this is a 64-bit machine.\n");
+ else if (sizeof(void *) == 4)
+ printf ("Apparently this is a 32-bit machine.\n");
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0) {
+ exit(0);
+ } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ const char *port_name = argv[i];
+ set_sysdepinfo('p', port_name);
+ printf("Port name will be %s\n", port_name);
+ } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ set_sysdepinfo('c', argv[i]);
+ printf("Client name will be %s\n", argv[i]);
+ } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ latency = atoi(argv[i]);
+ printf("Latency will be %ld\n", (long) latency);
+ latency_valid = TRUE;
+ } else if (strcmp(argv[i], "-v") == 0) {
+ printf("Verbose is now TRUE\n");
+ verbose = TRUE; /* not currently used for anything */
+ } else {
+ show_usage();
+ exit(0);
+ }
+ }
+
+ while (!latency_valid) {
+ int lat; // declared int to match "%d"
+ printf("Latency in ms: ");
+ if (scanf("%d", &lat) == 1) {
+ latency = (int32_t) lat; // coerce from "%d" to known size
+ latency_valid = TRUE;
+ }
+ }
+
+ /* determine what type of test to run */
+ printf("begin portMidi test...\n");
+ printf("enter your choice...\n 1: test input\n"
+ " 2: test input (fail w/assert)\n"
+ " 3: test input (fail w/NULL assign)\n"
+ " 4: test output\n 5: test both\n"
+ " 6: stream test (for WinMM)\n"
+ " 7. isochronous out\n");
+ while (n != 1) {
+ n = scanf("%d", &i);
+ WAIT_FOR_ENTER
+ switch(i) {
+ case 1:
+ test_input = 1;
+ break;
+ case 2:
+ test_input = 1;
+ somethingStupid = 1;
+ break;
+ case 3:
+ test_input = 1;
+ somethingStupid = 2;
+ break;
+ case 4:
+ test_output = 1;
+ break;
+ case 5:
+ test_both = 1;
+ break;
+ case 6:
+ stream_test = 1;
+ break;
+ case 7:
+ test_output = 1;
+ isochronous_test = 1;
+ break;
+ default:
+ printf("got %d (invalid input)\n", n);
+ break;
+ }
+ }
+
+ /* list device information */
+ default_in = Pm_GetDefaultInputDeviceID();
+ default_out = Pm_GetDefaultOutputDeviceID();
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ char *deflt;
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (((test_input | test_both) & info->input) |
+ ((test_output | test_both | stream_test) & info->output)) {
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (info->input) {
+ deflt = (i == default_in ? "default " : "");
+ printf(" (%sinput)", deflt);
+ }
+ if (info->output) {
+ deflt = (i == default_out ? "default " : "");
+ printf(" (%soutput)", deflt);
+ }
+ printf("\n");
+ }
+ }
+
+ /* run test */
+ if (stream_test) {
+ main_test_stream();
+ } else if (test_input) {
+ main_test_input(somethingStupid);
+ } else if (test_output) {
+ main_test_output(isochronous_test);
+ } else if (test_both) {
+ main_test_both();
+ }
+
+ printf("finished portMidi test...type ENTER to quit...");
+ WAIT_FOR_ENTER
+ return 0;
+}
diff --git a/portmidi/pm_test/txdata.syx b/portmidi/pm_test/txdata.syx
new file mode 100755
index 0000000..1e06e5a
--- /dev/null
+++ b/portmidi/pm_test/txdata.syx
@@ -0,0 +1,257 @@
+20 0 1d 4 c 6 0 34 1 4d 4 d 1f 7 3 6
+ c 5e 4 4d d b 18 5 3 6 0 3d 1 4a 16 18
+1f 8 3 6 d 0 1 63 4 13 3a 23 0 0 0 2
+ c 2 4 0 63 32 0 0 0 32 0 47 72 61 6e 64
+50 69 61 6e 6f 63 63 63 32 32 32 0 0 0 0 0
+10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 9 9 f c 27 2 35 37 10 1f 4 3 4
+ d 19 4 56 5 16 1f f 8 d c 0 43 60 4 e
+1f c 3 7 e 0 43 63 5 10 3c 14 8 2 1b 56
+ 5 2 4 0 63 32 0 0 0 32 0 4c 6f 54 69 6e
+65 38 31 5a 20 63 63 63 32 32 32 0 7f 0 1 0
+18 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f e f e 9 0 3 43 2d e 1f f 5 7
+ f 16 43 5a 0 0 1f 12 6 8 d 0 3 63 4 0
+1f 12 6 8 f 0 2 63 4 6 34 14 0 1 2 4e
+18 2 4 0 63 32 0 32 0 32 0 44 79 6e 6f 6d
+69 74 65 45 50 63 63 63 32 32 32 0 70 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f b 1 b 8 18 40 5f a e 1f 1f 0 a
+ f 0 40 5f 4 0 1f 1f 0 a f 0 40 63 5 6
+1f 1f 0 a f 0 40 5f 0 8 1f 20 0 3 0 5a
+18 4 4 0 63 32 32 0 0 32 0 50 65 72 63 4f
+72 67 61 6e 20 63 63 63 32 32 32 0 0 0 0 0
+ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f b 7 f 9 0 4 49 13 13 1f 8 7 5
+ e 0 2 58 0 c 1f 6 4 6 f 23 3 46 10 a
+1f 7 8 c d 0 2 63 8 b 2 1c 0 0 0 52
+18 4 4 0 63 32 0 32 0 32 0 54 68 69 6e 20
+43 6c 61 76 20 63 63 63 32 32 32 0 70 0 20 0
+10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f c 0 6 1 a 4 50 20 e 1f c 0 6
+ 1 a 4 50 1f 8 1f b 9 5 e 0 2 63 5 e
+1f b 9 5 e 0 3 63 4 8 4 1a 0 0 0 52
+1d 2 4 0 63 32 0 32 0 32 0 42 72 69 74 65
+43 65 6c 73 74 63 63 63 32 32 32 0 20 0 26 0
+ 1 0 8 4 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 f 1f 4 8 f 0 3a 51 4 b e 1f 0 8
+ f 0 22 4b 4 3 f 1a b 8 d 0 3b 36 9 3
+12 1f 0 8 f 0 22 5d 4 b 3a 1e 19 5 0 52
+18 4 4 0 63 32 0 0 0 32 0 54 72 75 6d 70
+65 74 38 31 5a 63 63 63 32 32 32 0 0 0 50 0
+51 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 c 5 0 8 0 0 2 4a 4 b f 1f 0 8
+ f 0 2 3f 4 3 1f f 0 8 0 23 3 44 b 3
+10 1f 0 9 f 0 2 5e 4 c 3a 1f 19 7 0 52
+18 4 4 0 63 32 0 0 0 32 0 46 6c 75 67 65
+6c 68 6f 72 6e 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 10 1f 0 8 f 0 42 4a 0 3 11 1f 0 8
+ f a 43 51 0 3 11 9 0 8 d 0 42 2b 16 6
+10 1f 0 9 f 0 42 63 4 b 3a 1e 9 9 0 5a
+24 4 4 0 63 32 31 0 0 32 0 52 61 73 70 41
+6c 74 6f 20 20 63 63 63 32 32 32 0 10 0 20 0
+54 0 20 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 10 9 2 6 d 0 41 3e 4 15 c b 2 3
+ e 0 41 4f 4 12 c e 2 8 d 0 42 4b a 1c
+ d b 1 9 e 0 3 63 a 14 0 23 f 2 1b 5e
+18 4 5 0 63 28 50 32 0 32 0 48 61 72 6d 6f
+6e 69 63 61 20 63 63 63 32 32 32 0 50 10 50 0
+50 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1c 2 0 4 e 63 0 4e 4 3 d 5 0 6
+ e 63 1 56 a 8 12 7 0 6 9 63 2 47 1b e
+ a a 0 5 f 0 1 63 4 b 32 1a 8 d 0 52
+ c 4 4 0 63 32 0 0 0 32 0 44 6f 75 62 6c
+65 42 61 73 73 63 63 63 32 32 32 0 10 0 0 0
+ 3 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 b 4 0 4 f 14 2 49 9 6 a 7 0 4
+ f 14 2 51 a 0 8 1f 0 5 f 0 1 63 9 6
+ a 1f 0 5 f 0 1 63 a 0 3c 1f 6 9 0 52
+ 5 4 4 0 63 32 0 0 0 32 0 48 69 53 74 72
+69 6e 67 20 31 63 63 63 32 32 32 0 2 0 30 0
+32 0 10 5 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 10 13 f 4 a 0 3 3b 14 14 1f e 8 7
+ 9 0 2 42 5 e 18 13 d 9 c 0 2 3c 13 8
+1f 11 7 4 f 0 42 63 4 10 3a 1b 0 0 0 52
+1d 4 4 0 63 32 0 0 0 32 0 48 61 72 70 20
+20 20 20 20 20 63 63 63 32 32 32 8 0 0 21 0
+ 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 6 6 4 f 0 40 48 5 0 c 8 7 5
+ f 5 0 52 4 0 f 7 3 7 e 8 3 63 4 6
+ f 8 4 5 f 0 3 63 4 6 7c 1f 0 6 0 4a
+11 2 4 0 63 32 0 0 0 32 0 46 61 6e 66 61
+72 54 70 74 73 63 63 63 32 32 32 6 1 0 38 0
+ 8 0 48 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 d b 0 1 c 0 2 2c 3d 3 d 7 0 1
+ c 0 2 1f 3c 3 d 1f 0 5 f 0 2 63 5 6
+ d 1f 0 5 f 0 2 63 4 0 3c 63 0 2f 0 53
+11 4 4 0 63 32 0 0 0 32 0 42 72 65 61 74
+68 4f 72 67 6e 63 63 63 32 32 32 4 30 5 50 0
+11 0 18 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 9 0 6 0 27 2 51 19 b 1c 6 0 8
+ 0 37 2 47 a 3 1f a 0 9 0 3d 2 4d a e
+1f 12 8 8 f 0 3 61 4 b 28 1f 0 3 0 52
+ c 3 4 0 63 32 1 32 0 32 0 4e 79 6c 6f 6e
+47 75 69 74 20 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f e e f f 0 3 48 2d 6 1f f 4 f
+ f 25 3 5b 0 0 1f 12 6 c e 1c 3 55 0 10
+1f 13 7 8 e 6 4 62 4 e 3b 14 0 0 0 42
+18 2 4 0 63 32 0 32 0 32 0 47 75 69 74 61
+72 20 23 31 20 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 19 8 a 3 0 3 63 10 18 1f c 5 b
+ 5 0 3 52 0 b 1f 19 6 b 5 0 3 63 a 16
+1f f 11 9 7 0 4 63 4 3 3a 14 0 0 0 42
+18 2 4 0 63 32 0 32 0 32 0 46 75 6e 6b 79
+20 50 69 63 6b 63 63 63 32 32 32 0 30 0 0 0
+ 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 1 0 8 4 0 3 3d a 1e 1f 1 0 8
+ 0 0 0 43 0 10 1f 9 6 8 c 1b 7 46 1c 1e
+1f 9 0 9 9 0 1 63 4 3 3a 1c 0 0 0 52
+ c 4 5 0 63 4b 0 0 0 32 0 45 6c 65 63 42
+61 73 73 20 31 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f f f e 9 0 3 46 1d 16 1f f 5 e
+ e d 3 63 0 b 1f 13 6 5 d 1c 3 63 0 0
+1f 13 6 8 f 0 4 63 4 6 3b 1f 0 0 0 42
+ c 4 4 0 63 32 0 32 0 32 0 53 79 6e 46 75
+6e 6b 42 61 73 63 63 63 32 32 32 d 6c 0 0 0
+70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 10 7 8 3 0 3 4f 4 3 1f 9 0 8
+ 0 0 1 4a 0 b 1f 11 0 8 0 0 1 47 4 8
+1f 9 0 8 0 0 0 63 0 b 39 19 0 7 0 52
+ c 2 4 0 63 32 0 32 0 32 0 4c 61 74 65 6c
+79 42 61 73 73 63 63 63 32 32 32 2 0 0 0 0
+40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 13 12 0 9 d 22 0 51 0 b 1f 14 0 5
+ 8 24 40 5c 0 3 1f 11 0 6 c 2c 0 53 9 0
+10 1f 0 b f 0 0 5c a e 3a 22 11 e 1e 5e
+18 7 4 0 63 32 0 32 0 32 0 53 79 6e 63 20
+4c 65 61 64 20 63 63 63 32 32 32 0 70 0 40 0
+ 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 13 1e 0 9 e 0 0 63 3f b 1f 14 0 5
+ e 24 1 51 4 3 1f 14 0 f 1 0 41 4d 8 3
+ f 1f 0 b f 0 2 63 4 b 3b 20 11 12 33 56
+18 4 4 0 63 37 e 0 0 32 0 4a 61 7a 7a 20
+46 6c 75 74 65 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 15 13 d 3 d 1e 2 50 18 e 15 14 9 4
+ c 1e 2 56 11 8 1b 1f f 7 f 0 1 63 4 6
+1a 1f e 6 f 0 2 63 4 0 7c b 0 8 0 62
+18 4 4 0 63 32 0 0 0 32 0 4a 61 76 61 20
+4a 69 76 65 20 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 0 0 4 f 0 40 63 3c 0 b 8 7 7
+ f 5 0 63 4 6 f 5 3 7 f 8 0 3b 5 6
+ e 8 4 5 f 0 3 63 3 0 7e 1d 6 f 0 4a
+11 0 4 0 63 32 0 0 0 32 0 42 61 61 64 42
+72 65 61 74 68 63 63 63 32 32 32 6 30 0 38 0
+ 1 0 46 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 0 0 4 f 0 40 47 2f 0 e 8 7 7
+ f 5 0 4c 0 6 13 1c d c 6 8 0 63 5 6
+14 11 d b 0 0 3 63 4 0 7a 10 0 51 0 68
+17 0 4 0 63 32 0 0 0 32 0 56 6f 63 61 6c
+4e 75 74 73 20 63 63 63 32 32 32 6 30 0 30 0
+ 1 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 1f 0 5 f 0 0 41 32 3 1f 14 10 5
+ 5 1 2 63 7 3 1f b 12 8 f 0 1 63 c 3
+1f 1f f 8 f 0 1 63 4 3 39 23 0 0 0 62
+18 7 4 0 63 32 0 0 0 32 0 57 61 74 65 72
+47 6c 61 73 73 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 16 2 0 4 6 9 1 4f 8 0 19 e 1 4
+ 0 20 1 43 19 0 1f 12 10 6 7 0 0 54 3d 3
+16 d 6 6 2 1e 3 61 8 e 3a 20 1 14 0 42
+ c 2 4 2 63 63 63 0 0 32 0 46 75 7a 7a 79
+20 4b 6f 74 6f 63 63 63 32 32 32 0 0 0 0 b
+50 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1c 8 0 3 e 0 1 55 12 3 1c 7 0 1
+ e 2e 1 58 27 b e 4 0 2 a 0 2 63 4 a
+ d 9 0 2 c 1 2 63 10 b 4 54 0 47 0 53
+18 7 4 0 63 32 0 0 0 32 0 42 72 74 68 62
+65 6c 6c 73 20 63 63 63 32 32 32 0 4 0 40 0
+40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1a 4 1 1 b 16 0 47 5 3 15 e 0 1
+ d 0 0 4c 5 16 1c 6 4 2 7 0 0 63 4 16
+18 18 3 1 e 0 0 5e 4 10 24 7 0 4 0 62
+24 4 4 0 63 32 0 0 0 32 0 54 75 62 65 20
+42 65 6c 6c 73 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 1f 13 3 0 0 0 5f 3d 6 1f 12 13 2
+ 0 0 1 52 5 2 1f 14 13 3 0 0 1 56 28 5
+1e b 13 f 9 0 0 63 6 3 3b 63 0 63 0 73
+23 7 4 0 63 32 0 0 0 32 0 4e 6f 69 73 65
+20 53 68 6f 74 63 63 63 32 32 32 8 0 0 0 8
+ 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 16 0 3 7 0 1 50 0 3 1f 18 3 3
+ 3 22 0 63 0 14 1d 7 6 3 6 0 1 3c 8 3
+1f 5 7 3 0 0 1 63 4 1b 39 23 0 8 0 42
+18 4 4 0 63 32 0 0 0 32 0 48 61 6e 64 20
+44 72 75 6d 20 63 63 63 32 32 32 0 1 0 3 0
+ 1 0 1 3 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 7d f7 \ No newline at end of file
diff --git a/portmidi/pm_test/virttest.c b/portmidi/pm_test/virttest.c
new file mode 100644
index 0000000..1aeb09b
--- /dev/null
+++ b/portmidi/pm_test/virttest.c
@@ -0,0 +1,339 @@
+/* virttest.c -- test for creating/deleting virtual ports */
+/*
+ * Roger B. Dannenberg
+ * Oct 2021
+
+This test is performed by running 2 instances of the program. The
+first instance makes input and output ports named portmidi and waits
+for a message. The second tries to do the same, but will fail because
+portmidi already exists. It then opens portmidi (both input and
+output). In greater detail:
+
+FIRST INSTANCE SECOND INSTANCE
+-------------- ---------------
+
+initialize PortMidi initialize PortMidi
+create portmidi in
+create portmidi out
+wait for input
+ create portmidi in -> fails
+ open portmidi in/out
+ send to portmidi
+recv from portmidi
+send to portmidi
+wait 1s recv from portmidi
+ close portmidi in and out
+ terminate PortMidi
+list all devices:
+ - check for correct number
+ - check for good description of portmidi in port (open)
+ - check for good description of portmidi out port (open)
+close portmidi in
+list all devices:
+ - check for correct number
+ - check for good description of portmidi in port (closed)
+ - check for good description of portmidi out port (open)
+close portmidi out
+list all devices:
+ - check for correct number
+ - check for good description of portmidi in port (closed)
+ - check for good description of portmidi out port (closed)
+delete portmidi in
+ - check for correct number
+ - check for NULL description of portmidi in port
+ - check for good description of portmidi out port (closed)
+delete portmidi out
+ - check for correct number
+ - check for NULL description of portmidi in port
+ - check for NULL description of portmidi out port
+terminate portmidi
+REPEAT 3 TIMES wait 2 seconds to give head start to other instance
+ REPEAT 3 TIMES
+ */
+
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define OUTPUT_BUFFER_SIZE 0
+#define INPUT_BUFFER_SIZE 10
+#define DEVICE_INFO NULL
+#define DRIVER_INFO NULL
+#define TIME_PROC ((PmTimeProcPtr) Pt_Time)
+#define TIME_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+
+static void prompt_and_exit(void)
+{
+ printf("type ENTER...");
+ while (getchar() != '\n') ;
+ /* this will clean up open ports: */
+ exit(-1);
+}
+
+
+static PmError printerror(PmError err, const char *msg)
+{
+ if (err == pmHostError) {
+ /* it seems pointless to allocate memory and copy the string,
+ * so I will do the work of Pm_GetHostErrorText directly
+ */
+ char errmsg[80];
+ Pm_GetHostErrorText(errmsg, 80);
+ printf("%s\n %s\n", msg, errmsg);
+ } else if (err < 0) {
+ printf("%s\n %s\n", msg, Pm_GetErrorText(err));
+ }
+ return err;
+}
+
+
+static PmError checkerror(PmError err)
+{
+ if (err < 0) {
+ printerror(err, "PortMidi call failed...");
+ prompt_and_exit();
+ }
+ return err;
+}
+
+
+void wait_until(PmTimestamp when)
+{
+ PtTimestamp now = Pt_Time();
+ if (when > now) {
+ Pt_Sleep(when - now);
+ }
+}
+
+
+void show_usage()
+{
+ printf("Usage: virttest\n"
+ " run two instances to test virtual port create/delete\n");
+}
+
+
+void check_info(int id, char stat, int input, int virtual)
+{
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(id);
+ if (stat == 'd') {
+ if (info) {
+ printf("Expected device %d to be deleted.\n", id);
+ prompt_and_exit();
+ }
+ return;
+ }
+ if (!info) {
+ printf("Expected device %d to not be deleted.\n", id);
+ prompt_and_exit();
+ }
+ if (strcmp("portmidi", info->name) != 0) {
+ printf("Device %d name is %s, not \"portmidi\".\n", id, info->name);
+ prompt_and_exit();
+ }
+ if (info->input != input || (!info->output) != input) {
+ printf("Device %d input/output fields are wrong.\n", id);
+ prompt_and_exit();
+ }
+ if ((!info->opened && stat == 'o') || (info->opened && stat == 'c')) {
+ printf("Device %d opened==%d, status should be %c.\n", id,
+ info->opened, stat);
+ prompt_and_exit();
+ }
+ if (info->is_virtual != virtual) {
+ printf("Expected device %d to be virtual.\n", id);
+ prompt_and_exit();
+ }
+}
+
+
+/* stat is 'o' for open, 'c' for closed, 'd' for deleted device */
+void check_ports(int cnt, int in_id, char in_stat,
+ int out_id, char out_stat, int virtual)
+{
+ if (cnt != Pm_CountDevices()) {
+ printf("Device count changed from %d to %d.\n", cnt, Pm_CountDevices());
+ prompt_and_exit();
+ }
+ check_info(in_id, in_stat, TRUE, virtual);
+ check_info(out_id, out_stat, FALSE, virtual);
+}
+
+
+void devices_list()
+{
+ int i;
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (info) {
+ printf("%d: %s %s %s %s\n", i, info->name,
+ (info->input ? "input" : "output"),
+ (info->is_virtual ? "virtual" : "real_device"),
+ (info->opened ? "opened" : "closed"));
+ }
+ }
+}
+
+
+void test2()
+{
+ PmStream *out = NULL;
+ PmStream *in = NULL;
+ int out_id;
+ int in_id;
+ PmEvent buffer[1];
+ PmTimestamp timestamp;
+ int pitch = 60;
+ int device_count = 0;
+ int i;
+
+ printf("This must be virttest instance #2\n");
+
+ /* find and open portmidi in and out */
+ device_count = Pm_CountDevices();
+ for (i = 0; i < device_count; i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (info && strcmp(info->name, "portmidi") == 0) {
+ if (info->input) {
+ checkerror(Pm_OpenInput(&in, i, DRIVER_INFO,
+ INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO));
+ in_id = i;
+ } else {
+ checkerror(Pm_OpenOutput(&out, i, DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE, NULL, NULL, 0));
+ out_id = i;
+ }
+ }
+ }
+ if (!in) {
+ printf("Did not open portmidi as input (virtual output).\n");
+ prompt_and_exit();
+ }
+ if (!out) {
+ printf("Did not open portmidi as output (virtual input).\n");
+ prompt_and_exit();
+ }
+ printf("Input device %d and output device %d are open.\n", in_id, out_id);
+
+ /* send a message */
+ buffer[0].timestamp = 0;
+ buffer[0].message = Pm_Message(0x90, pitch, 100);
+ checkerror(Pm_Write(out, buffer, 1));
+
+ /* wait for reply */
+ printf("Sent message, waiting for reply...\n");
+ while (Pm_Read(in, buffer, 1) < 1) Pt_Sleep(10);
+
+ printf("********** GOT THE MESSAGE, SHUTTING DOWN ************\n");
+
+ /* close in */
+ checkerror(Pm_Close(in));
+ check_ports(device_count, in_id, 'c', out_id, 'o', FALSE);
+ printf("Closed input %d\n", in_id);
+
+ /* close out */
+ checkerror(Pm_Close(out));
+ check_ports(device_count, in_id, 'c', out_id, 'c', FALSE);
+ printf("Closed output %d\n", out_id);
+
+ Pt_Sleep(1000);
+ /* wrap it up */
+ Pm_Terminate();
+ printf("Got reply and terminated...\n");
+ Pt_Sleep(2000); /* 2 seconds because other is waiting 1s. */
+ /* 1 more second to make sure other shuts down before test repeats. */
+}
+
+extern int pm_check_errors;
+
+void test()
+{
+ PmStream *out;
+ PmStream *in;
+ int out_id;
+ int in_id;
+ PmEvent buffer[1];
+ PmTimestamp timestamp;
+ int device_count = 0;
+
+ TIME_START;
+
+ printf("******** INITIALIZING PORTMIDI ***********\n");
+ timestamp = Pt_Time();
+ Pm_Initialize();
+ printf("Pm_Initialize took %dms\n", Pt_Time() - timestamp);
+ devices_list();
+
+ pm_check_errors = FALSE; /* otherwise, PM_CHECK_ERRORS, if defined, */
+ /* can cause this program to report an error and exit on pmNameConflict. */
+ in_id = Pm_CreateVirtualInput("portmidi", NULL, DEVICE_INFO);
+ pm_check_errors = TRUE; /* there should be no other errors */
+ if (in_id < 0) {
+ printerror(in_id, "Pm_CreateVirtualInput failed...");
+ test2();
+ return;
+ }
+ printf("Created portmidi virtual input; this is virttest instance #1\n");
+ out_id = checkerror(Pm_CreateVirtualOutput("portmidi", NULL, DRIVER_INFO));
+ device_count = Pm_CountDevices();
+
+ checkerror(Pm_OpenInput(&in, in_id, NULL, 0, NULL, NULL));
+ checkerror(Pm_OpenOutput(&out, out_id, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
+ TIME_PROC, TIME_INFO, 0));
+ printf("Created/Opened input %d and output %d\n", in_id, out_id);
+ Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
+ /* empty the buffer after setting filter, just in case anything
+ got through */
+ while (Pm_Read(in, buffer, 1)) ;
+
+ /* wait for input */
+ printf("Waiting for input...\n");
+ while (Pm_Read(in, buffer, 1) < 1) Pt_Sleep(10);
+
+ /* send two replies (only one would be fine) */
+ checkerror(Pm_Write(out, buffer, 1));
+ printf("Received input, writing output...\n");
+
+ /* wait 1s so receiver can get the message before we shut down */
+ Pt_Sleep(1000);
+ printf("****** Closing everything and shutting down...\n");
+
+ /* expect 2 open ports */
+ check_ports(device_count, in_id, 'o', out_id, 'o', TRUE);
+ /* close in */
+ checkerror(Pm_Close(in));
+ check_ports(device_count, in_id, 'c', out_id, 'o', TRUE);
+
+ /* close out */
+ checkerror(Pm_Close(out));
+ check_ports(device_count, in_id, 'c', out_id, 'c', TRUE);
+
+ /* delete in */
+ checkerror(Pm_DeleteVirtualDevice(in_id));
+ check_ports(device_count, in_id, 'd', out_id, 'c', TRUE);
+
+ /* delete out */
+ checkerror(Pm_DeleteVirtualDevice(out_id));
+ check_ports(device_count, in_id, 'd', out_id, 'd', TRUE);
+
+ /* we are done */
+ Pm_Terminate();
+}
+
+
+int main(int argc, char *argv[])
+{
+ int i;
+ show_usage();
+ for (i = 0; i < 3; i++) {
+ test();
+ }
+ printf("finished virttest (SUCCESS). Type ENTER to quit...");
+ while (getchar() != '\n') ;
+ return 0;
+}
diff --git a/portmidi/pm_win/README_WIN.txt b/portmidi/pm_win/README_WIN.txt
new file mode 100755
index 0000000..8bfb467
--- /dev/null
+++ b/portmidi/pm_win/README_WIN.txt
@@ -0,0 +1,174 @@
+File: PortMidi Win32 Readme
+Author: Belinda Thom, June 16 2002
+Revised by: Roger Dannenberg, June 2002, May 2004, June 2007,
+ Umpei Kurokawa, June 2007
+ Roger Dannenberg Sep 2009, May 2022
+
+Contents:
+ Using Portmidi
+ To Install Portmidi
+ To Compile Portmidi
+ About Cmake
+ Using other versions of Visual C++
+ To Create Your Own Portmidi Client Application
+
+
+
+=============================================================================
+USING PORTMIDI:
+=============================================================================
+
+I recommend building a static library and linking with your
+application. PortMidi is not large. See ../README.md for
+basic compiling instructions.
+
+The Windows version has a couple of extra switches: You can define
+DEBUG and MMDEBUG for a few extra messages (see the code).
+
+If PM_CHECK_ERRORS is defined, PortMidi reports and exits on any
+error. This requires terminal output to see, and aborts your
+application, so it's only intended for quick command line programs
+where you do not care to check return values and handle errors
+more robustly.
+
+PortMidi is designed to run without a console and should work perfectly
+well within a graphical user interface application.
+
+Read the portmidi.h file for PortMidi API details on using the PortMidi API.
+See <...>\pm_test\testio.c and other files in pm_test for usage examples.
+
+There are many other programs in pm_test, including a MIDI monitor.
+
+
+============================================================================
+DESIGN NOTES
+============================================================================
+
+Orderly cleanup after errors are encountered is based on a fixed order of
+steps and state changes to reflect each step. Here's the order:
+
+To open input:
+ initialize return value to NULL
+ - allocate the PmInternal strucure (representation of PortMidiStream)
+ return value is (non-null) PmInternal structure
+ - allocate midi buffer
+ set buffer field of PmInternal structure
+ - call system-dependent open code
+ - allocate midiwinmm_type for winmm dependent data
+ set descriptor field of PmInternal structure
+ - open device
+ set handle field of midiwinmm_type structure
+ - allocate buffers
+ - start device
+ - return
+ - return
+
+SYSEX HANDLING
+
+There are three cases: simple output, stream output, input
+Each must deal with:
+ 1. Buffer Initialization (creating buffers)
+ 2. Buffer Allocation (finding a free buffer)
+ 3. Buffer Fill (putting bytes in the buffer)
+ 4. Buffer Preparation (midiOutPrepare, etc.)
+ 5. Buffer Send (to Midi device)
+ 6. Buffer Receive (in callback)
+ 7. Buffer Empty (removing bytes from buffer)
+ 8. Buffer Free (returning to the buffer pool)
+ 9. Buffer Finalization (returning to heap)
+
+Here's how simple output handles sysex:
+ 1. Buffer Initialization (creating buffers)
+ allocated when code tries to write first byte to a buffer
+ the test is "if (!m->sysex_buffers[0]) { ... }"
+ this field is initialized to NULL when device is opened
+ the size is SYSEX_BYTES_PER_BUFFER
+ allocate_sysex_buffers() does the initialization
+ note that the actual size of the allocation includes
+ additional space for a MIDIEVENT (3 longs) which are
+ not used in this case
+ 2. Buffer Allocation (finding a free buffer)
+ see get_free_sysex_buffer()
+ cycle through m->sysex_buffers[] using m->next_sysex_buffer
+ to determine where to look next
+ if nothing is found, wait by blocking on m->sysex_buffer_signal
+ this is signaled by the callback every time a message is
+ received
+ 3. Buffer Fill (putting bytes in the buffer)
+ essentially a state machine approach
+ hdr->dwBytesRecorded is a position in message pointed to by m->hdr
+ keep appending bytes until dwBytesRecorded >= SYSEX_BYTES_PER_BUFFER
+ then send the message, reseting the state to initial values
+ 4. Buffer Preparation (midiOutPrepare, etc.)
+ just before sending in winmm_end_sysex()
+ 5. Buffer Send (to Midi device)
+ message is padded with zero at end (since extra space was allocated
+ this is ok) -- the zero works around a bug in (an old version of)
+ MIDI YOKE drivers
+ dwBufferLength gets dwBytesRecorded, and dwBytesRecorded gets 0
+ uses midiOutLongMsg()
+ 6. Buffer Receive (in callback)
+ 7. Buffer Empty (removing bytes from buffer)
+ not applicable for output
+ 8. Buffer Free (returning to the buffer pool)
+ unprepare message to indicate that it is free
+ SetEvent on m->buffer_signal in case client is waiting
+ 9. Buffer Finalization (returning to heap)
+ when device is closed, winmm_out_delete frees all sysex buffers
+
+Here's how stream output handles sysex:
+ 1. Buffer Initialization (creating buffers)
+ same code as simple output (see above)
+ 2. Buffer Allocation (finding a free buffer)
+ same code as simple output (see above)
+ 3. Buffer Fill (putting bytes in the buffer)
+ essentially a state machine approach
+ m->dwBytesRecorded is a position in message
+ keep appending bytes until buffer is full (one byte to spare)
+ 4. Buffer Preparation (midiOutPrepare, etc.)
+ done before sending message
+ dwBytesRecorded and dwBufferLength are set in winmm_end_sysex
+ 5. Buffer Send (to Midi device)
+ uses midiStreamOutMsg()
+ 6. Buffer Receive (in callback)
+ 7. Buffer Empty (removing bytes from buffer)
+ not applicable for output
+ 8. Buffer Free (returning to the buffer pool)
+ unprepare message to indicate that it is free
+ SetEvent on m->buffer_signal in case client is waiting
+ 9. Buffer Finalization (returning to heap)
+ when device is closed, winmm_out_delete frees all sysex buffers
+
+
+Here's how input handles sysex:
+ 1. Buffer Initialization (creating buffers)
+ two buffers are allocated in winmm_in_open
+ 2. Buffer Allocation (finding a free buffer)
+ same code as simple output (see above)
+ 3. Buffer Fill (putting bytes in the buffer)
+ not applicable for input
+ 4. Buffer Preparation (midiOutPrepare, etc.)
+ done before sending message -- in winmm_in_open and in callback
+ 5. Buffer Send (to Midi device)
+ uses midiInAddbuffer in allocate_sysex_input_buffer (called from
+ winmm_in_open) and callback
+ 6. Buffer Receive (in callback)
+ 7. Buffer Empty (removing bytes from buffer)
+ done without pause in loop in callback
+ 8. Buffer Free (returning to the buffer pool)
+ done by midiInAddBuffer in callback, no pointer to buffers
+ is retained except by device
+ 9. Buffer Finalization (returning to heap)
+ when device is closed, empty buffers are delivered to callback,
+ which frees them
+
+IMPORTANT: In addition to the above, PortMidi now has
+"shortcuts" to optimize the transfer of sysex data. To enable
+the optimization for sysex output, the system-dependent code
+sets fields in the pmInternal structure: fill_base, fill_offset_ptr,
+and fill_length. When fill_base is non-null, the system-independent
+part of PortMidi is allowed to directly copy sysex bytes to
+"fill_base[*fill_offset_ptr++]" until *fill_offset_ptr reaches
+fill_length. See the code for details.
+
+
diff --git a/portmidi/pm_win/debugging_dlls.txt b/portmidi/pm_win/debugging_dlls.txt
new file mode 100755
index 0000000..82b81a5
--- /dev/null
+++ b/portmidi/pm_win/debugging_dlls.txt
@@ -0,0 +1,145 @@
+========================================================================================================================
+Methods for Debugging DLLs
+========================================================================================================================
+If you have the source for both the DLL and the calling program, open the project for the calling executable file and
+debug the DLL from there. If you load a DLL dynamically, you must specify it in the Additional DLLs category of the
+Debug tab in the Project Settings dialog box.
+
+If you have the source for the DLL only, open the project that builds the DLL. Use the Debug tab in the Project
+Settings dialog box to specify the executable file that calls the DLL.
+
+You can also debug a DLL without a project. For example, maybe you just picked up a DLL and source code but you
+don’t have an associated project or workspace. You can use the Open command on the File menu to select the .DLL
+file you want to debug. The debug information should be in either the .DLL or the related .PDB file. After
+Visual C++ opens the file, on the Build menu click Start Debug and Go to begin debugging.
+
+To debug a DLL using the project for the executable file
+
+From the Project menu, click Settings.
+The Project Settings dialog box appears.
+
+Choose the Debug tab.
+
+
+In the Category drop-down list box, select General.
+
+
+In the Program Arguments text box, type any command-line arguments required by the executable file.
+
+
+In the Category drop-down list box, select Additional DLLs.
+
+
+In the Local Name column, type the names of DLLs to debug.
+If you are debugging remotely, the Remote Name column appears. In this column, type the complete path for the
+remote module to map to the local module name.
+
+In the Preload column, select the check box if you want to load the module before debugging begins.
+
+
+Click OK to store the information in your project.
+
+
+From the Build menu, click Start Debug and Go to start the debugger.
+You can set breakpoints in the DLL or the calling program. You can open a source file for the DLL and set breakpoints
+in that file, even though it is not a part of the executable file’s project.
+
+To debug a DLL using the project for the DLL
+
+From the Project menu, click Settings.
+The Project Settings dialog box appears.
+
+Choose the Debug tab.
+
+
+In the Category drop-down list box, select General.
+
+
+In the Executable For Debug Session text box, type the name of the executable file that calls the DLL.
+
+
+In the Category list box, select Additional DLLs.
+
+
+In the Local Module Name column, type the name of the DLLs you want to debug.
+
+
+Click OK to store the information in your project.
+
+
+Set breakpoints as required in your DLL source files or on function symbols in the DLL.
+
+
+From the Build menu, click Start Debug and Go to start the debugger.
+To debug a DLL created with an external project
+
+From the Project menu, click Settings.
+The Project Settings dialog box appears.
+
+Choose the Debug tab.
+
+
+In the Category drop-down list box, select General.
+
+
+In the Executable For Debug Session text box, type the name of the DLL that your external makefile builds.
+
+
+Click OK to store the information in your project.
+
+
+Build a debug version of the DLL with symbolic debugging information, if you don’t already have one.
+
+
+Follow one of the two procedures immediately preceding this one to debug the DLL.
+
+========================================================================================================================
+Why Don’t My DLL Breakpoints Work?
+========================================================================================================================
+Some reasons why your breakpoints don’t work as expected are listed here, along with solutions or work-arounds for each.
+If you follow the instructions in one topic and are still having breakpoint problems, look at some of the other topics.
+Often breakpoint problems result from a combination of conditions.
+
+You can't set a breakpoint in a source file when the corresponding symbolic information isn't loaded into memory by
+the debugger.
+You cannot set a breakpoint in any source file when the corresponding symbolic information will not be loaded into memory
+by the debugger.
+Symptoms include messages such as "the breakpoint cannot be set" or a simple, noninformational beep.
+
+When setting breakpoints before the code to be debugged has been started, the debugger uses a breakpoint list to keep
+track of how and where to set breakpoints. When you actually begin the debugging session, the debugger loads the symbolic
+information for all the code to be debugged and then walks through its breakpoint list, attempting to set the
+breakpoints.
+
+However, if one or more of the code modules have not been designated to the debugger, there will be no symbolic
+information for the debugger to use when walking through its breakpoint list. Situations where this is likely to
+occur include:
+
+Attempts to set breakpoints in a DLL before the call to LoadLibrary.
+
+Setting a breakpoint in an ActiveX server before the container has started the server.
+
+Other similar cases.
+
+To prevent this behavior in Visual C++, specify all additional DLLs and COM servers in the Additional DLLs field
+in the Debug/Options dialog box to notify the debugger that you want it to load symbolic debug information for
+additional .DLL files. When this has been done, breakpoints set in code that has not yet been loaded into memory
+will be "virtual" breakpoints. When the code is actually loaded into memory by the loader, these become physical
+breakpoints. Make sure that these additional debugging processes are not already running when you start your
+debugging session. The debugging process and these additional processes must be sychronized at the same beginning
+point to work correctly, hitting all breakpoints.
+
+Breakpoints are missed when more than one copy of a DLL is on your hard disk.
+Having more than one copy of a DLL on your hard drive, especially if it is in your Windows directory, can cause
+debugger confusion. The debugger will load the symbolic information for the DLL specified to it at run time (with the
+Additional DLLs field in the Debug/Options dialog box), while Windows has actually loaded a different copy of the
+DLL itself into memory. Because there is no way to force the debugger to load a specific DLL, it is a good idea to
+keep only one version of a DLL at a time in your path, current directory, and Windows directory.
+
+You can’t set "Break When Expression Has Changed" breakpoints on a variable local to a DLL.
+Setting a "Break When Expression Has Changed" breakpoint on a variable local to a DLL function before the call
+to LoadLibrary causes the breakpoint to be virtual (there are no physical addresses for the DLL in memory yet).
+Virtual breakpoints involving expressions pose a special problem. The DLL must be specified to the debugger at
+startup (causing its symbolic information to be loaded). In addition, the DLL's executable code must also be loaded
+into memory before this kind of breakpoint can be set. This means that the calling application's code must be
+executed to the point after its call to LoadLibrary before the debugger will allow this type of breakpoint to be set.
diff --git a/portmidi/pm_win/pmwin.c b/portmidi/pm_win/pmwin.c
new file mode 100755
index 0000000..5cb73b0
--- /dev/null
+++ b/portmidi/pm_win/pmwin.c
@@ -0,0 +1,98 @@
+/* pmwin.c -- PortMidi os-dependent code */
+
+/* This file only needs to implement:
+ pm_init(), which calls various routines to register the
+ available midi devices,
+ Pm_GetDefaultInputDeviceID(), and
+ Pm_GetDefaultOutputDeviceID().
+ This file must
+ be separate from the main portmidi.c file because it is system
+ dependent, and it is separate from, say, pmwinmm.c, because it
+ might need to register devices for winmm, directx, and others.
+
+ */
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "pmwinmm.h"
+#ifdef DEBUG
+#include "stdio.h"
+#endif
+#include <windows.h>
+
+/* pm_exit is called when the program exits.
+ It calls pm_term to make sure PortMidi is properly closed.
+ If DEBUG is on, we prompt for input to avoid losing error messages.
+ */
+static void pm_exit(void) {
+ pm_term();
+}
+
+
+static BOOL WINAPI ctrl_c_handler(DWORD fdwCtrlType)
+{
+ exit(1); /* invokes pm_exit() */
+ ExitProcess(1); /* probably never called */
+ return TRUE;
+}
+
+/* pm_init is the windows-dependent initialization.*/
+void pm_init(void)
+{
+ atexit(pm_exit);
+ SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
+#ifdef DEBUG
+ printf("registered pm_exit with atexit()\n");
+#endif
+ pm_winmm_init();
+ /* initialize other APIs (DirectX?) here */
+}
+
+
+void pm_term(void) {
+ pm_winmm_term();
+}
+
+
+static PmDeviceID pm_get_default_device_id(int is_input, char *key) {
+#define PATTERN_MAX 256
+ /* Find first input or device -- this is the default. */
+ PmDeviceID id = pmNoDevice;
+ int i;
+ Pm_Initialize(); /* make sure descriptors exist! */
+ for (i = 0; i < pm_descriptor_len; i++) {
+ if (pm_descriptors[i].pub.input == is_input) {
+ id = i;
+ break;
+ }
+ }
+ return id;
+}
+
+
+PmDeviceID Pm_GetDefaultInputDeviceID() {
+ return pm_get_default_device_id(TRUE,
+ "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E");
+}
+
+
+PmDeviceID Pm_GetDefaultOutputDeviceID() {
+ return pm_get_default_device_id(FALSE,
+ "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E");
+}
+
+
+#include "stdio.h"
+
+void *pm_alloc(size_t s) {
+ return malloc(s);
+}
+
+
+void pm_free(void *ptr) {
+ free(ptr);
+}
+
+
diff --git a/portmidi/pm_win/pmwinmm.c b/portmidi/pm_win/pmwinmm.c
new file mode 100755
index 0000000..6f4b6f3
--- /dev/null
+++ b/portmidi/pm_win/pmwinmm.c
@@ -0,0 +1,1196 @@
+/* pmwinmm.c -- system specific definitions */
+
+#ifndef _WIN32_WINNT
+ /* without this define, InitializeCriticalSectionAndSpinCount is
+ * undefined. This version level means "Windows 2000 and higher"
+ */
+ #define _WIN32_WINNT 0x0500
+#endif
+
+#define UNICODE 1
+#include <wchar.h>
+#include "windows.h"
+#include "mmsystem.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "pmwinmm.h"
+#include <string.h>
+#include "porttime.h"
+#ifndef UNICODE
+#error Expected UNICODE to be defined
+#endif
+
+
+/* asserts used to verify portMidi code logic is sound; later may want
+ something more graceful */
+#include <assert.h>
+#ifdef MMDEBUG
+/* this printf stuff really important for debugging client app w/host errors.
+ probably want to do something else besides read/write from/to console
+ for portability, however */
+#define STRING_MAX 80
+#include "stdio.h"
+#endif
+
+#define streql(x, y) (strcmp(x, y) == 0)
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+/* callback routines */
+static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn,
+ UINT wMsg, DWORD_PTR dwInstance,
+ DWORD_PTR dwParam1, DWORD_PTR dwParam2);
+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
+ DWORD_PTR dwInstance,
+ DWORD_PTR dwParam1,
+ DWORD_PTR dwParam2);
+
+extern pm_fns_node pm_winmm_in_dictionary;
+extern pm_fns_node pm_winmm_out_dictionary;
+
+static void winmm_out_delete(PmInternal *midi); /* forward reference */
+
+/*
+A note about buffers: WinMM seems to hold onto buffers longer than
+one would expect, e.g. when I tried using 2 small buffers to send
+long sysex messages, at some point WinMM held both buffers. This problem
+was fixed by making buffers bigger. Therefore, it seems that there should
+be enough buffer space to hold a whole sysex message.
+
+The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)
+will be used to estimate the largest sysex message (= buffer_len * 4 bytes).
+Call that the max_sysex_len = buffer_len * 4.
+
+For simple midi output (latency == 0), allocate 3 buffers, each with half
+the size of max_sysex_len, but each at least 256 bytes.
+
+For stream output, there will already be enough space in very short
+buffers, so use them, but make sure there are at least 16.
+
+For input, use many small buffers rather than 2 large ones so that when
+there are short sysex messages arriving frequently (as in control surfaces)
+there will be more free buffers to fill. Use max_sysex_len / 64 buffers,
+but at least 16, of size 64 bytes each.
+
+The following constants help to represent these design parameters:
+*/
+#define NUM_SIMPLE_SYSEX_BUFFERS 3
+#define MIN_SIMPLE_SYSEX_LEN 256
+
+#define MIN_STREAM_BUFFERS 16
+#define STREAM_BUFFER_LEN 24
+
+#define INPUT_SYSEX_LEN 64
+#define MIN_INPUT_BUFFERS 16
+
+/* if we run out of space for output (assume this is due to a sysex msg,
+ expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN
+ */
+#define NUM_EXPANSION_BUFFERS 128
+#define EXPANSION_BUFFER_LEN 1024
+
+/* A sysex buffer has 3 DWORDS as a header plus the actual message size */
+#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)
+/* A MIDIHDR with a sysex message is the buffer length plus the header size */
+#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
+
+/*
+==============================================================================
+win32 mmedia system specific structure passed to midi callbacks
+==============================================================================
+*/
+
+/* global winmm device info */
+MIDIINCAPS *midi_in_caps = NULL;
+MIDIINCAPS midi_in_mapper_caps;
+UINT midi_num_inputs = 0;
+MIDIOUTCAPS *midi_out_caps = NULL;
+MIDIOUTCAPS midi_out_mapper_caps;
+UINT midi_num_outputs = 0;
+
+/* per device info */
+typedef struct winmm_info_struct {
+ union {
+ HMIDISTRM stream; /* windows handle for stream */
+ HMIDIOUT out; /* windows handle for out calls */
+ HMIDIIN in; /* windows handle for in calls */
+ } handle;
+
+ /* midi output messages are sent in these buffers, which are allocated
+ * in a round-robin fashion, using next_buffer as an index
+ */
+ LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */
+ int max_buffers; /* length of buffers array */
+ int buffers_expanded; /* buffers array expanded for extra msgs? */
+ int num_buffers; /* how many buffers allocated in buffers array */
+ int next_buffer; /* index of next buffer to send */
+ HANDLE buffer_signal; /* used to wait for buffer to become free */
+ unsigned long last_time; /* last output time */
+ int first_message; /* flag: treat first message differently */
+ int sysex_mode; /* middle of sending sysex */
+ unsigned long sysex_word; /* accumulate data when receiving sysex */
+ unsigned int sysex_byte_count; /* count how many received */
+ LPMIDIHDR hdr; /* the message accumulating sysex to send */
+ unsigned long sync_time; /* when did we last determine delta? */
+ long delta; /* difference between stream time and
+ real time */
+ CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */
+} winmm_info_node, *winmm_info_type;
+
+
+/*
+=============================================================================
+general MIDI device queries
+=============================================================================
+*/
+
+/* add a device after converting device (product) name to UTF-8 */
+static void pm_add_device_w(char *api, WCHAR *device_name, int is_input,
+ int is_virtual, void *descriptor, pm_fns_type dictionary)
+{
+ char utf8name[4 * MAXPNAMELEN];
+ WideCharToMultiByte(CP_UTF8, 0, device_name, -1,
+ utf8name, 4 * MAXPNAMELEN - 1, NULL, NULL);
+ /* ignore errors here -- if pm_descriptor_max is exceeded,
+ some devices will not be accessible. */
+ pm_add_device(api, utf8name, is_input, is_virtual, descriptor, dictionary);
+}
+
+
+static void pm_winmm_general_inputs()
+{
+ UINT i;
+ WORD wRtn;
+ midi_num_inputs = midiInGetNumDevs();
+ midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) *
+ midi_num_inputs);
+ if (midi_in_caps == NULL) {
+ /* if you can't open a particular system-level midi interface
+ * (such as winmm), we just consider that system or API to be
+ * unavailable and move on without reporting an error.
+ */
+ return;
+ }
+
+ for (i = 0; i < midi_num_inputs; i++) {
+ wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i],
+ sizeof(MIDIINCAPS));
+ if (wRtn == MMSYSERR_NOERROR) {
+ pm_add_device_w("MMSystem", midi_in_caps[i].szPname, TRUE, FALSE,
+ (void *) (intptr_t) i, &pm_winmm_in_dictionary);
+ }
+ }
+}
+
+
+static void pm_winmm_mapper_input()
+{
+ WORD wRtn;
+ /* Note: if MIDIMAPPER opened as input (documentation implies you
+ can, but current system fails to retrieve input mapper
+ capabilities) then you still should retrieve some form of
+ setup info. */
+ wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,
+ (LPMIDIINCAPS) & midi_in_mapper_caps,
+ sizeof(MIDIINCAPS));
+ if (wRtn == MMSYSERR_NOERROR) {
+ pm_add_device_w("MMSystem", midi_in_mapper_caps.szPname, TRUE, FALSE,
+ (void *) (intptr_t) MIDIMAPPER,
+ &pm_winmm_in_dictionary);
+ }
+}
+
+
+static void pm_winmm_general_outputs()
+{
+ UINT i;
+ DWORD wRtn;
+ midi_num_outputs = midiOutGetNumDevs();
+ midi_out_caps = pm_alloc(sizeof(MIDIOUTCAPS) * midi_num_outputs);
+
+ if (midi_out_caps == NULL) {
+ /* no error is reported -- see pm_winmm_general_inputs */
+ return ;
+ }
+
+ for (i = 0; i < midi_num_outputs; i++) {
+ wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i],
+ sizeof(MIDIOUTCAPS));
+ if (wRtn == MMSYSERR_NOERROR) {
+ pm_add_device_w("MMSystem", midi_out_caps[i].szPname, FALSE, FALSE,
+ (void *) (intptr_t) i, &pm_winmm_out_dictionary);
+ }
+ }
+}
+
+
+static void pm_winmm_mapper_output()
+{
+ WORD wRtn;
+ /* Note: if MIDIMAPPER opened as output (pseudo MIDI device
+ maps device independent messages into device dependant ones,
+ via NT midimapper program) you still should get some setup info */
+ wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS)
+ & midi_out_mapper_caps, sizeof(MIDIOUTCAPS));
+ if (wRtn == MMSYSERR_NOERROR) {
+ pm_add_device_w("MMSystem", midi_out_mapper_caps.szPname, FALSE, FALSE,
+ (void *) (intptr_t) MIDIMAPPER,
+ &pm_winmm_out_dictionary);
+ }
+}
+
+
+/*
+============================================================================
+host error handling
+============================================================================
+*/
+
+static unsigned int winmm_check_host_error(PmInternal *midi)
+{
+ return FALSE;
+}
+
+
+/*
+=============================================================================
+buffer handling
+=============================================================================
+*/
+static MIDIHDR *allocate_buffer(long data_size)
+{
+ LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
+ MIDIEVENT *evt;
+ if (!hdr) return NULL;
+ evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
+ hdr->lpData = (LPSTR) evt;
+ hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);
+ hdr->dwBytesRecorded = 0;
+ hdr->dwFlags = 0;
+ hdr->dwUser = hdr->dwBufferLength;
+ return hdr;
+}
+
+
+static PmError allocate_buffers(winmm_info_type info, long data_size,
+ long count)
+{
+ int i;
+ /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
+ info->num_buffers = 0; /* in case no memory can be allocated */
+ info->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
+ if (!info->buffers) return pmInsufficientMemory;
+ info->max_buffers = count;
+ for (i = 0; i < count; i++) {
+ LPMIDIHDR hdr = allocate_buffer(data_size);
+ if (!hdr) { /* free everything allocated so far and return */
+ for (i = i - 1; i >= 0; i--) pm_free(info->buffers[i]);
+ pm_free(info->buffers);
+ info->max_buffers = 0;
+ return pmInsufficientMemory;
+ }
+ info->buffers[i] = hdr; /* this may be NULL if allocation fails */
+ }
+ info->num_buffers = count;
+ return pmNoError;
+}
+
+
+static LPMIDIHDR get_free_output_buffer(PmInternal *midi)
+{
+ LPMIDIHDR r = NULL;
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ while (TRUE) {
+ int i;
+ for (i = 0; i < info->num_buffers; i++) {
+ /* cycle through buffers, modulo info->num_buffers */
+ info->next_buffer++;
+ if (info->next_buffer >= info->num_buffers) info->next_buffer = 0;
+ r = info->buffers[info->next_buffer];
+ if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;
+ }
+ /* after scanning every buffer and not finding anything, block */
+ if (WaitForSingleObject(info->buffer_signal, 1000) == WAIT_TIMEOUT) {
+#ifdef MMDEBUG
+ printf("PortMidi warning: get_free_output_buffer() "
+ "wait timed out after 1000ms\n");
+#endif
+ /* if we're trying to send a sysex message, maybe the
+ * message is too big and we need more message buffers.
+ * Expand the buffer pool by 128KB using 1024-byte buffers.
+ */
+ /* first, expand the buffers array if necessary */
+ if (!info->buffers_expanded) {
+ LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(
+ (info->num_buffers + NUM_EXPANSION_BUFFERS) *
+ sizeof(LPMIDIHDR));
+ /* if no memory, we could return a no-memory error, but user
+ * probably will be unprepared to deal with it. Maybe the
+ * MIDI driver is temporarily hung so we should just wait.
+ * I don't know the right answer, but waiting is easier.
+ */
+ if (!new_buffers) continue;
+ /* copy buffers to new_buffers and replace buffers */
+ memcpy(new_buffers, info->buffers,
+ info->num_buffers * sizeof(LPMIDIHDR));
+ pm_free(info->buffers);
+ info->buffers = new_buffers;
+ info->max_buffers = info->num_buffers + NUM_EXPANSION_BUFFERS;
+ info->buffers_expanded = TRUE;
+ }
+ /* next, add one buffer and return it */
+ if (info->num_buffers < info->max_buffers) {
+ r = allocate_buffer(EXPANSION_BUFFER_LEN);
+ /* again, if there's no memory, we may not really be
+ * dead -- maybe the system is temporarily hung and
+ * we can just wait longer for a message buffer */
+ if (!r) continue;
+ info->buffers[info->num_buffers++] = r;
+ goto found_buffer; /* break out of 2 loops */
+ }
+ /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,
+ * and we have no free buffers to send. We'll just keep
+ * polling to see if any buffers show up.
+ */
+ }
+ }
+found_buffer:
+ r->dwBytesRecorded = 0;
+ /* actual buffer length is saved in dwUser field */
+ r->dwBufferLength = (DWORD) r->dwUser;
+ return r;
+}
+
+/*
+============================================================================
+begin midi input implementation
+============================================================================
+*/
+
+
+static unsigned int allocate_input_buffer(HMIDIIN h, long buffer_len)
+{
+ LPMIDIHDR hdr = allocate_buffer(buffer_len);
+ if (!hdr) return pmInsufficientMemory;
+ /* note: pm_hosterror is normally a boolean, but here, we store Win
+ * error code. The caller must test the value for nonzero, set
+ * pm_hosterror_text, and then set pm_hosterror to TRUE */
+ pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));
+ if (pm_hosterror) {
+ pm_free(hdr);
+ return pm_hosterror;
+ }
+ pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));
+ return pm_hosterror;
+}
+
+
+static winmm_info_type winmm_info_create()
+{
+ winmm_info_type info = (winmm_info_type) pm_alloc(sizeof(winmm_info_node));
+ info->handle.in = NULL;
+ info->handle.out = NULL;
+ info->buffers = NULL; /* not used for input */
+ info->num_buffers = 0; /* not used for input */
+ info->max_buffers = 0; /* not used for input */
+ info->buffers_expanded = FALSE; /* not used for input */
+ info->next_buffer = 0; /* not used for input */
+ info->buffer_signal = 0; /* not used for input */
+ info->last_time = 0;
+ info->first_message = TRUE; /* not used for input */
+ info->sysex_mode = FALSE;
+ info->sysex_word = 0;
+ info->sysex_byte_count = 0;
+ info->hdr = NULL; /* not used for input */
+ info->sync_time = 0;
+ info->delta = 0;
+ return info;
+}
+
+
+static void report_hosterror(LPWCH error_msg)
+{
+ WideCharToMultiByte(CP_UTF8, 0, error_msg, -1, pm_hosterror_text,
+ sizeof(pm_hosterror_text), NULL, NULL);
+ if (pm_hosterror == MMSYSERR_NOMEM) {
+ /* add explanation to Window's confusing error message */
+ /* if there's room: */
+ if (PM_HOST_ERROR_MSG_LEN - strlen(pm_hosterror_text) > 60) {
+ strcat_s(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
+ " Probably this MIDI device is open "
+ "in another application.");
+ }
+ }
+ pm_hosterror = TRUE;
+}
+
+
+static void report_hosterror_in()
+{
+ WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];
+ int err = midiInGetErrorText(pm_hosterror, error_msg,
+ PM_HOST_ERROR_MSG_LEN);
+ assert(err == MMSYSERR_NOERROR);
+ report_hosterror(error_msg);
+}
+
+
+static void report_hosterror_out()
+{
+ WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];
+ int err = midiOutGetErrorText(pm_hosterror, error_msg,
+ PM_HOST_ERROR_MSG_LEN);
+ assert(err == MMSYSERR_NOERROR);
+ report_hosterror(error_msg);
+}
+
+
+static PmError winmm_in_open(PmInternal *midi, void *driverInfo)
+{
+ DWORD dwDevice;
+ int i = midi->device_id;
+ int max_sysex_len = midi->buffer_len * 4;
+ int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN;
+ winmm_info_type info;
+
+ dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;
+
+ /* create system dependent device data */
+ info = winmm_info_create();
+ midi->api_info = info;
+ if (!info) goto no_memory;
+ /* 4000 is based on Windows documentation -- that's the value used
+ in the memory manager. It's small enough that it should not
+ hurt performance even if it's not optimal.
+ */
+ InitializeCriticalSectionAndSpinCount(&info->lock, 4000);
+ /* open device */
+ pm_hosterror = midiInOpen(
+ &(info->handle.in), /* input device handle */
+ dwDevice, /* device ID */
+ (DWORD_PTR) winmm_in_callback, /* callback address */
+ (DWORD_PTR) midi, /* callback instance data */
+ CALLBACK_FUNCTION); /* callback is a procedure */
+ if (pm_hosterror) goto free_descriptor;
+
+ if (num_input_buffers < MIN_INPUT_BUFFERS)
+ num_input_buffers = MIN_INPUT_BUFFERS;
+ for (i = 0; i < num_input_buffers; i++) {
+ if (allocate_input_buffer(info->handle.in, INPUT_SYSEX_LEN)) {
+ /* either pm_hosterror was set, or the proper return code
+ is pmInsufficientMemory */
+ goto close_device;
+ }
+ }
+ /* start device */
+ pm_hosterror = midiInStart(info->handle.in);
+ if (!pm_hosterror) {
+ return pmNoError;
+ }
+
+ /* undo steps leading up to the detected error */
+
+ /* ignore return code (we already have an error to report) */
+ midiInReset(info->handle.in);
+close_device:
+ midiInClose(info->handle.in); /* ignore return code */
+free_descriptor:
+ midi->api_info = NULL;
+ pm_free(info);
+no_memory:
+ if (pm_hosterror) {
+ report_hosterror_in();
+ return pmHostError;
+ }
+ /* if !pm_hosterror, then the error must be pmInsufficientMemory */
+ return pmInsufficientMemory;
+ /* note: if we return an error code, the device will be
+ closed and memory will be freed. It's up to the caller
+ to free the parameter midi */
+}
+
+
+/* winmm_in_close -- close an open midi input device */
+/*
+ * assume midi is non-null (checked by caller)
+ */
+static PmError winmm_in_close(PmInternal *midi)
+{
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ if (!info) return pmBadPtr;
+ /* device to close */
+ if ((pm_hosterror = midiInStop(info->handle.in))) {
+ midiInReset(info->handle.in); /* try to reset and close port */
+ midiInClose(info->handle.in);
+ } else if ((pm_hosterror = midiInReset(info->handle.in))) {
+ midiInClose(info->handle.in); /* best effort to close midi port */
+ } else {
+ pm_hosterror = midiInClose(info->handle.in);
+ }
+ midi->api_info = NULL;
+ DeleteCriticalSection(&info->lock);
+ pm_free(info); /* delete */
+ if (pm_hosterror) {
+ report_hosterror_in();
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+
+/* Callback function executed via midiInput SW interrupt (via midiInOpen). */
+static void FAR PASCAL winmm_in_callback(
+ HMIDIIN hMidiIn, /* midiInput device Handle */
+ UINT wMsg, /* midi msg */
+ DWORD_PTR dwInstance, /* application data */
+ DWORD_PTR dwParam1, /* MIDI data */
+ DWORD_PTR dwParam2) /* device timestamp (wrt most recent midiInStart) */
+{
+ PmInternal *midi = (PmInternal *) dwInstance;
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+
+ /* NOTE: we do not just EnterCriticalSection() here because an
+ * MIM_CLOSE message arrives when the port is closed, but then
+ * the info->lock has been destroyed.
+ */
+
+ switch (wMsg) {
+ case MIM_DATA: {
+ /* if this callback is reentered with data, we're in trouble.
+ * It's hard to imagine that Microsoft would allow callbacks
+ * to be reentrant -- isn't the model that this is like a
+ * hardware interrupt? -- but I've seen reentrant behavior
+ * using a debugger, so it happens.
+ */
+ EnterCriticalSection(&info->lock);
+
+ /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of
+ message LOB;
+ dwParam2 is time message received by input device driver, specified
+ in [ms] from when midiInStart called.
+ each message is expanded to include the status byte */
+
+ if ((dwParam1 & 0x80) == 0) {
+ /* not a status byte -- ignore it. This happened running the
+ sysex.c test under Win2K with MidiMan USB 1x1 interface,
+ but I can't reproduce it. -RBD
+ */
+ /* printf("non-status byte found\n"); */
+ } else { /* data to process */
+ PmEvent event;
+ if (midi->time_proc)
+ dwParam2 = (*midi->time_proc)(midi->time_info);
+ event.timestamp = (PmTimestamp)dwParam2;
+ event.message = (PmMessage)dwParam1;
+ pm_read_short(midi, &event);
+ }
+ LeaveCriticalSection(&info->lock);
+ break;
+ }
+ case MIM_LONGDATA: {
+ MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
+ unsigned char *data = (unsigned char *) lpMidiHdr->lpData;
+ unsigned int processed = 0;
+ int remaining = lpMidiHdr->dwBytesRecorded;
+
+ EnterCriticalSection(&info->lock);
+ /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n",
+ lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */
+ if (midi->time_proc)
+ dwParam2 = (*midi->time_proc)(midi->time_info);
+ /* can there be more than one message in one buffer? */
+ /* assume yes and iterate through them */
+ pm_read_bytes(midi, data + processed, remaining, (PmTimestamp)dwParam2);
+
+ /* when a device is closed, the pending MIM_LONGDATA buffers are
+ returned to this callback with dwBytesRecorded == 0. In this
+ case, we do not want to send them back to the interface (if
+ we do, the interface will not close, and Windows OS may hang). */
+ if (lpMidiHdr->dwBytesRecorded > 0) {
+ MMRESULT rslt;
+ lpMidiHdr->dwBytesRecorded = 0;
+ lpMidiHdr->dwFlags = 0;
+
+ /* note: no error checking -- can this actually fail? */
+ rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));
+ assert(rslt == MMSYSERR_NOERROR);
+ /* note: I don't think this can fail except possibly for
+ * MMSYSERR_NOMEM, but the pain of reporting this
+ * unlikely but probably catastrophic error does not seem
+ * worth it.
+ */
+ rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));
+ assert(rslt == MMSYSERR_NOERROR);
+ LeaveCriticalSection(&info->lock);
+ } else {
+ midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR));
+ LeaveCriticalSection(&info->lock);
+ pm_free(lpMidiHdr);
+ }
+ break;
+ }
+ case MIM_OPEN:
+ break;
+ case MIM_CLOSE:
+ break;
+ case MIM_ERROR:
+ /* printf("MIM_ERROR\n"); */
+ break;
+ case MIM_LONGERROR:
+ /* printf("MIM_LONGERROR\n"); */
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+===========================================================================
+begin midi output implementation
+===========================================================================
+*/
+
+/* begin helper routines used by midiOutStream interface */
+
+/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */
+static int add_to_buffer(winmm_info_type m, LPMIDIHDR hdr,
+ unsigned long delta, unsigned long msg)
+{
+ unsigned long *ptr = (unsigned long *)
+ (hdr->lpData + hdr->dwBytesRecorded);
+ *ptr++ = delta; /* dwDeltaTime */
+ *ptr++ = 0; /* dwStream */
+ *ptr++ = msg; /* dwEvent */
+ hdr->dwBytesRecorded += 3 * sizeof(long);
+ /* if the addition of three more words (a message) would extend beyond
+ the buffer length, then return TRUE (full)
+ */
+ return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength;
+}
+
+
+static PmTimestamp pm_time_get(winmm_info_type info)
+{
+ MMTIME mmtime;
+ MMRESULT wRtn;
+ mmtime.wType = TIME_TICKS;
+ mmtime.u.ticks = 0;
+ wRtn = midiStreamPosition(info->handle.stream, &mmtime, sizeof(mmtime));
+ assert(wRtn == MMSYSERR_NOERROR);
+ return mmtime.u.ticks;
+}
+
+
+/* end helper routines used by midiOutStream interface */
+
+
+static PmError winmm_out_open(PmInternal *midi, void *driverInfo)
+{
+ DWORD dwDevice;
+ int i = midi->device_id;
+ winmm_info_type info;
+ MIDIPROPTEMPO propdata;
+ MIDIPROPTIMEDIV divdata;
+ int max_sysex_len = midi->buffer_len * 4;
+ int output_buffer_len;
+ int num_buffers;
+ dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;
+
+ /* create system dependent device data */
+ info = winmm_info_create();
+ midi->api_info = info;
+ if (!info) goto no_memory;
+ /* create a signal */
+ info->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
+ /* this should only fail when there are very serious problems */
+ assert(info->buffer_signal);
+ /* open device */
+ if (midi->latency == 0) {
+ /* use simple midi out calls */
+ pm_hosterror = midiOutOpen(
+ (LPHMIDIOUT) & info->handle.out, /* device Handle */
+ dwDevice, /* device ID */
+ /* note: same callback fn as for StreamOpen: */
+ (DWORD_PTR) winmm_streamout_callback, /* callback fn */
+ (DWORD_PTR) midi, /* callback instance data */
+ CALLBACK_FUNCTION); /* callback type */
+ } else {
+ /* use stream-based midi output (schedulable in future) */
+ pm_hosterror = midiStreamOpen(
+ &info->handle.stream, /* device Handle */
+ (LPUINT) & dwDevice, /* device ID pointer */
+ 1, /* reserved, must be 1 */
+ (DWORD_PTR) winmm_streamout_callback,
+ (DWORD_PTR) midi, /* callback instance data */
+ CALLBACK_FUNCTION);
+ }
+ if (pm_hosterror != MMSYSERR_NOERROR) {
+ goto free_descriptor;
+ }
+
+ if (midi->latency == 0) {
+ num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;
+ output_buffer_len = max_sysex_len / num_buffers;
+ if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)
+ output_buffer_len = MIN_SIMPLE_SYSEX_LEN;
+ } else {
+ num_buffers = max(midi->buffer_len, midi->latency / 2);
+ if (num_buffers < MIN_STREAM_BUFFERS)
+ num_buffers = MIN_STREAM_BUFFERS;
+ output_buffer_len = STREAM_BUFFER_LEN;
+
+ propdata.cbStruct = sizeof(MIDIPROPTEMPO);
+ propdata.dwTempo = 480000; /* microseconds per quarter */
+ pm_hosterror = midiStreamProperty(info->handle.stream,
+ (LPBYTE) & propdata,
+ MIDIPROP_SET | MIDIPROP_TEMPO);
+ if (pm_hosterror) goto close_device;
+
+ divdata.cbStruct = sizeof(MIDIPROPTEMPO);
+ divdata.dwTimeDiv = 480; /* divisions per quarter */
+ pm_hosterror = midiStreamProperty(info->handle.stream,
+ (LPBYTE) & divdata,
+ MIDIPROP_SET | MIDIPROP_TIMEDIV);
+ if (pm_hosterror) goto close_device;
+ }
+ /* allocate buffers */
+ if (allocate_buffers(info, output_buffer_len, num_buffers))
+ goto free_buffers;
+ /* start device */
+ if (midi->latency != 0) {
+ pm_hosterror = midiStreamRestart(info->handle.stream);
+ if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;
+ }
+ return pmNoError;
+
+free_buffers:
+ /* buffers are freed below by winmm_out_delete */
+close_device:
+ midiOutClose(info->handle.out);
+free_descriptor:
+ midi->api_info = NULL;
+ winmm_out_delete(midi); /* frees buffers and m */
+no_memory:
+ if (pm_hosterror) {
+ report_hosterror_out();
+ return pmHostError;
+ }
+ return pmInsufficientMemory;
+}
+
+
+/* winmm_out_delete -- carefully free data associated with midi */
+/**/
+static void winmm_out_delete(PmInternal *midi)
+{
+ int i;
+ /* delete system dependent device data */
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ if (info) {
+ if (info->buffer_signal) {
+ /* don't report errors -- better not to stop cleanup */
+ CloseHandle(info->buffer_signal);
+ }
+ /* if using stream output, free buffers */
+ for (i = 0; i < info->num_buffers; i++) {
+ if (info->buffers[i]) pm_free(info->buffers[i]);
+ }
+ info->num_buffers = 0;
+ pm_free(info->buffers);
+ info->max_buffers = 0;
+ }
+ midi->api_info = NULL;
+ pm_free(info); /* delete */
+}
+
+
+/* see comments for winmm_in_close */
+static PmError winmm_out_close(PmInternal *midi)
+{
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ if (info->handle.out) {
+ /* device to close */
+ if (midi->latency == 0) {
+ pm_hosterror = midiOutClose(info->handle.out);
+ } else {
+ pm_hosterror = midiStreamClose(info->handle.stream);
+ }
+ /* regardless of outcome, free memory */
+ winmm_out_delete(midi);
+ }
+ if (pm_hosterror) {
+ report_hosterror_out();
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+
+static PmError winmm_out_abort(PmInternal *midi)
+{
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+
+ /* only stop output streams */
+ if (midi->latency > 0) {
+ pm_hosterror = midiStreamStop(info->handle.stream);
+ if (pm_hosterror) {
+ report_hosterror_out();
+ return pmHostError;
+ }
+ }
+ return pmNoError;
+}
+
+
+static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp)
+{
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ assert(info);
+ if (info->hdr) {
+ pm_hosterror = midiOutPrepareHeader(info->handle.out, info->hdr,
+ sizeof(MIDIHDR));
+ if (pm_hosterror) {
+ /* do not send message */
+ } else if (midi->latency == 0) {
+ /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded
+ * should be zero. This is set in get_free_sysex_buffer().
+ * The msg length goes in dwBufferLength in spite of what
+ * Microsoft documentation says (or doesn't say). */
+ info->hdr->dwBufferLength = info->hdr->dwBytesRecorded;
+ info->hdr->dwBytesRecorded = 0;
+ pm_hosterror = midiOutLongMsg(info->handle.out, info->hdr,
+ sizeof(MIDIHDR));
+ } else {
+ pm_hosterror = midiStreamOut(info->handle.stream, info->hdr,
+ sizeof(MIDIHDR));
+ }
+ midi->fill_base = NULL;
+ info->hdr = NULL;
+ if (pm_hosterror) {
+ report_hosterror_out();
+ return pmHostError;
+ }
+ }
+ return pmNoError;
+}
+
+
+static PmError winmm_write_short(PmInternal *midi, PmEvent *event)
+{
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ PmError rslt = pmNoError;
+ assert(info);
+
+ if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
+ pm_hosterror = midiOutShortMsg(info->handle.out, event->message);
+ if (pm_hosterror) {
+ if (info->hdr) { /* device disconnect may delete hdr */
+ info->hdr->dwFlags = 0; /* release the buffer */
+ }
+ report_hosterror_out();
+ return pmHostError;
+ }
+ } else { /* use midiStream interface -- pass data through buffers */
+ unsigned long when = event->timestamp;
+ unsigned long delta;
+ int full;
+ if (when == 0) when = midi->now;
+ /* when is in real_time; translate to intended stream time */
+ when = when + info->delta + midi->latency;
+ /* make sure we don't go backward in time */
+ if (when < info->last_time) when = info->last_time;
+ delta = when - info->last_time;
+ info->last_time = when;
+ /* before we insert any data, we must have a buffer */
+ if (info->hdr == NULL) {
+ /* stream interface: buffers allocated when stream is opened */
+ info->hdr = get_free_output_buffer(midi);
+ }
+ full = add_to_buffer(info, info->hdr, delta, event->message);
+ /* note: winmm_write_flush sets pm_hosterror etc. on host error */
+ if (full) rslt = winmm_write_flush(midi, when);
+ }
+ return rslt;
+}
+
+#define winmm_begin_sysex winmm_write_flush
+#ifndef winmm_begin_sysex
+static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+ winmm_info_type m = (winmm_info_type) midi->api_info;
+ PmError rslt = pmNoError;
+
+ if (midi->latency == 0) {
+ /* do nothing -- it's handled in winmm_write_byte */
+ } else {
+ /* sysex expects an empty sysex buffer, so send whatever is here */
+ rslt = winmm_write_flush(midi);
+ }
+ return rslt;
+}
+#endif
+
+static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+ /* could check for callback_error here, but I haven't checked
+ * what happens if we exit early and don't finish the sysex msg
+ * and clean up
+ */
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ PmError rslt = pmNoError;
+ LPMIDIHDR hdr = info->hdr;
+ if (!hdr) return rslt; /* something bad happened earlier,
+ do not report an error because it would have been
+ reported (at least) once already */
+ /* a(n old) version of MIDI YOKE requires a zero byte after
+ * the sysex message, but do not increment dwBytesRecorded: */
+ hdr->lpData[hdr->dwBytesRecorded] = 0;
+ if (midi->latency == 0) {
+#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX
+ /* DEBUG CODE: */
+ { int i; int len = info->hdr->dwBufferLength;
+ printf("OutLongMsg %d ", len);
+ for (i = 0; i < len; i++) {
+ printf("%2x ", (unsigned char) (info->hdr->lpData[i]));
+ }
+ }
+#endif
+ } else {
+ /* Using stream interface. There are accumulated bytes in info->hdr
+ to send using midiStreamOut
+ */
+ /* add bytes recorded to MIDIEVENT length, but don't
+ count the MIDIEVENT data (3 longs) */
+ MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData);
+ evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
+ /* round up BytesRecorded to multiple of 4 */
+ hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
+ }
+ rslt = winmm_write_flush(midi, timestamp);
+ return rslt;
+}
+
+
+static PmError winmm_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp)
+{
+ /* write a sysex byte */
+ PmError rslt = pmNoError;
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ LPMIDIHDR hdr = info->hdr;
+ unsigned char *msg_buffer;
+ assert(info);
+ if (!hdr) {
+ info->hdr = hdr = get_free_output_buffer(midi);
+ assert(hdr);
+ midi->fill_base = (unsigned char *) info->hdr->lpData;
+ midi->fill_offset_ptr = (uint32_t *) &(hdr->dwBytesRecorded);
+ /* when buffer fills, Pm_WriteSysEx will revert to calling
+ * pmwin_write_byte, which expect to have space, so leave
+ * one byte free for pmwin_write_byte. Leave another byte
+ * of space for zero after message to make early version of
+ * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */
+ midi->fill_length = hdr->dwBufferLength - 2;
+ if (midi->latency != 0) {
+ unsigned long when = (unsigned long) timestamp;
+ unsigned long delta;
+ unsigned long *ptr;
+ if (when == 0) when = midi->now;
+ /* when is in real_time; translate to intended stream time */
+ when = when + info->delta + midi->latency;
+ /* make sure we don't go backward in time */
+ if (when < info->last_time) when = info->last_time;
+ delta = when - info->last_time;
+ info->last_time = when;
+
+ ptr = (unsigned long *) hdr->lpData;
+ *ptr++ = delta;
+ *ptr++ = 0;
+ *ptr = MEVT_F_LONG;
+ hdr->dwBytesRecorded = 3 * sizeof(long);
+ /* data will be added at an offset of dwBytesRecorded ... */
+ }
+ }
+ /* add the data byte */
+ msg_buffer = (unsigned char *) (hdr->lpData);
+ msg_buffer[hdr->dwBytesRecorded++] = byte;
+
+ /* see if buffer is full, leave one byte extra for pad */
+ if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {
+ /* write what we've got and continue */
+ rslt = winmm_end_sysex(midi, timestamp);
+ }
+ return rslt;
+}
+
+
+static PmTimestamp winmm_synchronize(PmInternal *midi)
+{
+ winmm_info_type info;
+ unsigned long pm_stream_time_2;
+ unsigned long real_time;
+ unsigned long pm_stream_time;
+
+ /* only synchronize if we are using stream interface */
+ if (midi->latency == 0) return 0;
+
+ /* figure out the time */
+ info = (winmm_info_type) midi->api_info;
+ pm_stream_time_2 = pm_time_get(info);
+
+ do {
+ /* read real_time between two reads of stream time */
+ pm_stream_time = pm_stream_time_2;
+ real_time = (*midi->time_proc)(midi->time_info);
+ pm_stream_time_2 = pm_time_get(info);
+ /* repeat if more than 1ms elapsed */
+ } while (pm_stream_time_2 > pm_stream_time + 1);
+ info->delta = pm_stream_time - real_time;
+ info->sync_time = real_time;
+ return real_time;
+}
+
+
+/* winmm_streamout_callback -- unprepare (free) buffer header */
+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
+ DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+{
+ PmInternal *midi = (PmInternal *) dwInstance;
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
+ int err;
+
+ /* Even if an error is pending, I think we should unprepare msgs and
+ signal their arrival
+ */
+ /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n",
+ hdr, wMsg, MOM_DONE); */
+ if (wMsg == MOM_DONE) {
+ MMRESULT ret = midiOutUnprepareHeader(info->handle.out, hdr,
+ sizeof(MIDIHDR));
+ assert(ret == MMSYSERR_NOERROR);
+ } else if (wMsg == MOM_CLOSE) {
+ /* The streaming API gets a callback when the device is closed.
+ * The non-streaming API gets a callback when the device is
+ * removed or closed. It is misleading to set is_removed when
+ * the device is closed normally, but in that case, midi itself
+ * will be freed immediately, so there should be no way to
+ * observe is_removed == TRUE. On the other hand, if the device
+ * is removed, setting is_removed will cause PortMidi to return
+ * the pmDeviceRemoved error on attempts to output to the device.
+ * In the case of normal closing, due to midiOutClose(),
+ * the call below is reentrant (!), but for some reason this does
+ * not cause an error or infinite recursion, so we are not taking
+ * any precautions to flag midi as "in the process of closing."
+ */
+ midi->is_removed = TRUE;
+ midiOutClose(info->handle.out);
+ }
+ /* signal client in case it is blocked waiting for buffer */
+ err = SetEvent(info->buffer_signal);
+ assert(err); /* false -> error */
+}
+
+
+/*
+===========================================================================
+begin exported functions
+===========================================================================
+*/
+
+#define winmm_in_abort pm_fail_fn
+pm_fns_node pm_winmm_in_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ winmm_synchronize,
+ winmm_in_open,
+ winmm_in_abort,
+ winmm_in_close,
+ success_poll,
+ winmm_check_host_error
+ };
+
+pm_fns_node pm_winmm_out_dictionary = {
+ winmm_write_short,
+ winmm_begin_sysex,
+ winmm_end_sysex,
+ winmm_write_byte,
+ /* short realtime message: */ winmm_write_short,
+ winmm_write_flush,
+ winmm_synchronize,
+ winmm_out_open,
+ winmm_out_abort,
+ winmm_out_close,
+ none_poll,
+ winmm_check_host_error
+ };
+
+
+/* initialize winmm interface. Note that if there is something wrong
+ with winmm (e.g. it is not supported or installed), it is not an
+ error. We should simply return without having added any devices to
+ the table. Hence, no error code is returned. Furthermore, this init
+ code is called along with every other supported interface, so the
+ user would have a very hard time figuring out what hardware and API
+ generated the error. Finally, it would add complexity to pmwin.c to
+ remember where the error code came from in order to convert to text.
+ */
+void pm_winmm_init( void )
+{
+ pm_winmm_mapper_input();
+ pm_winmm_mapper_output();
+ pm_winmm_general_inputs();
+ pm_winmm_general_outputs();
+}
+
+
+/* no error codes are returned, even if errors are encountered, because
+ there is probably nothing the user could do (e.g. it would be an error
+ to retry.
+ */
+void pm_winmm_term( void )
+{
+ int i;
+#ifdef MMDEBUG
+ int doneAny = 0;
+ printf("pm_winmm_term called\n");
+#endif
+ for (i = 0; i < pm_descriptor_len; i++) {
+ PmInternal *midi = pm_descriptors[i].pm_internal;
+ if (midi) {
+ winmm_info_type info = (winmm_info_type) midi->api_info;
+ if (info->handle.out) {
+ /* close next open device*/
+#ifdef MMDEBUG
+ if (doneAny == 0) {
+ printf("begin closing open devices...\n");
+ doneAny = 1;
+ }
+#endif
+ /* close all open ports */
+ (*midi->dictionary->close)(midi);
+ }
+ }
+ }
+ if (midi_in_caps) {
+ pm_free(midi_in_caps);
+ midi_in_caps = NULL;
+ }
+ if (midi_out_caps) {
+ pm_free(midi_out_caps);
+ midi_out_caps = NULL;
+ }
+#ifdef MMDEBUG
+ if (doneAny) {
+ printf("warning: devices were left open. They have been closed.\n");
+ }
+ printf("pm_winmm_term exiting\n");
+#endif
+ pm_descriptor_len = 0;
+}
diff --git a/portmidi/pm_win/pmwinmm.h b/portmidi/pm_win/pmwinmm.h
new file mode 100755
index 0000000..4a353e2
--- /dev/null
+++ b/portmidi/pm_win/pmwinmm.h
@@ -0,0 +1,5 @@
+/* pmwinmm.h -- system-specific definitions for windows multimedia API */
+
+void pm_winmm_init( void );
+void pm_winmm_term( void );
+
diff --git a/portmidi/pm_win/static.cmake b/portmidi/pm_win/static.cmake
new file mode 100644
index 0000000..7fdad18
--- /dev/null
+++ b/portmidi/pm_win/static.cmake
@@ -0,0 +1,24 @@
+# static.cmake -- change flags to link with static runtime libraries
+#
+# Even when BUILD_SHARED_LIBS is OFF, CMake specifies linking wtih
+# multithread DLL, so you give inconsistent linking instructions
+# resulting in warning messages from MS Visual Studio. If you want
+# a static binary, I've found this approach works to eliminate
+# warnings and make everything static:
+#
+# Changes /MD (multithread DLL) to /MT (multithread static)
+
+if(MSVC)
+ foreach(flag_var
+ CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
+ CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO
+ CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
+ CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO)
+ if(${flag_var} MATCHES "/MD")
+ string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+ endif(${flag_var} MATCHES "/MD")
+ endforeach(flag_var)
+
+ message(STATUS
+ "Note: overriding CMAKE_*_FLAGS_* to use Visual C static multithread library")
+endif(MSVC)
diff --git a/portmidi/portmusic_logo.png b/portmidi/portmusic_logo.png
new file mode 100644
index 0000000..17a063a
--- /dev/null
+++ b/portmidi/portmusic_logo.png
Binary files differ
diff --git a/portmidi/porttime/porttime.c b/portmidi/porttime/porttime.c
new file mode 100755
index 0000000..71b06f4
--- /dev/null
+++ b/portmidi/porttime/porttime.c
@@ -0,0 +1,3 @@
+/* porttime.c -- portable API for millisecond timer */
+
+/* There is no machine-independent implementation code to put here */
diff --git a/portmidi/porttime/porttime.h b/portmidi/porttime/porttime.h
new file mode 100755
index 0000000..0a61c5c
--- /dev/null
+++ b/portmidi/porttime/porttime.h
@@ -0,0 +1,103 @@
+/** @file porttime.h portable interface to millisecond timer. */
+
+/* CHANGE LOG FOR PORTTIME
+ 10-Jun-03 Mark Nelson & RBD
+ boost priority of timer thread in ptlinux.c implementation
+ */
+
+#ifndef PORTMIDI_PORTTIME_H
+#define PORTMIDI_PORTTIME_H
+
+/* Should there be a way to choose the source of time here? */
+
+#ifdef WIN32
+#ifndef INT32_DEFINED
+// rather than having users install a special .h file for windows,
+// just put the required definitions inline here. portmidi.h uses
+// these too, so the definitions are (unfortunately) duplicated there
+typedef int int32_t;
+typedef unsigned int uint32_t;
+#define INT32_DEFINED
+#endif
+#else
+#include <stdint.h> // needed for int32_t
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef PMEXPORT
+#ifdef _WINDLL
+#define PMEXPORT __declspec(dllexport)
+#else
+#define PMEXPORT
+#endif
+#endif
+
+/** @defgroup grp_porttime PortTime: Millisecond Timer
+ @{
+*/
+
+typedef enum {
+ ptNoError = 0, /* success */
+ ptHostError = -10000, /* a system-specific error occurred */
+ ptAlreadyStarted, /* cannot start timer because it is already started */
+ ptAlreadyStopped, /* cannot stop timer because it is already stopped */
+ ptInsufficientMemory /* memory could not be allocated */
+} PtError; /**< @brief @enum PtError PortTime error code; a common return type.
+ * No error is indicated by zero; errors are indicated by < 0.
+ */
+
+/** real time or time offset in milliseconds. */
+typedef int32_t PtTimestamp;
+
+/** a function that gets a current time */
+typedef void (PtCallback)(PtTimestamp timestamp, void *userData);
+
+/** start a real-time clock service.
+
+ @param resolution the timer resolution in ms. The time will advance every
+ \p resolution ms.
+
+ @param callback a function pointer to be called every resolution ms.
+
+ @param userData is passed to \p callback as a parameter.
+
+ @return #ptNoError on success. See #PtError for other values.
+*/
+PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
+
+/** stop the timer.
+
+ @return #ptNoError on success. See #PtError for other values.
+*/
+PMEXPORT PtError Pt_Stop(void);
+
+/** test if the timer is running.
+
+ @return TRUE or FALSE
+*/
+PMEXPORT int Pt_Started(void);
+
+/** get the current time in ms.
+
+ @return the current time
+*/
+PMEXPORT PtTimestamp Pt_Time(void);
+
+/** pauses the current thread, allowing other threads to run.
+
+ @param duration the length of the pause in ms. The true duration
+ of the pause may be rounded to the nearest or next clock tick
+ as determined by resolution in #Pt_Start().
+*/
+PMEXPORT void Pt_Sleep(int32_t duration);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PORTMIDI_PORTTIME_H
diff --git a/portmidi/porttime/pthaiku.cpp b/portmidi/porttime/pthaiku.cpp
new file mode 100644
index 0000000..9d8de14
--- /dev/null
+++ b/portmidi/porttime/pthaiku.cpp
@@ -0,0 +1,88 @@
+// pthaiku.cpp - portable timer implementation for Haiku
+
+#include "porttime.h"
+#include <Looper.h>
+#include <MessageRunner.h>
+#include <OS.h>
+
+namespace {
+ const uint32 timerMessage = 'PTTM';
+
+ struct TimerLooper : BLooper {
+ TimerLooper() : BLooper() {
+ }
+
+
+ virtual void MessageReceived(BMessage *message)
+ {
+ PtCallback *callback;
+ void *userData;
+ if (message->what == timerMessage && message->FindPointer("callback", (void**)&callback) == B_OK && message->FindPointer("userData", &userData) == B_OK) {
+ (*callback)(Pt_Time(), userData);
+ }
+ BLooper::MessageReceived(message);
+ }
+ };
+
+ bool time_started_flag = false;
+ bigtime_t time_offset;
+ TimerLooper *timerLooper;
+ BMessageRunner *timerRunner;
+}
+
+extern "C" {
+ PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+ {
+ if (time_started_flag) return ptAlreadyStarted;
+ time_offset = system_time();
+ if (callback) {
+ timerLooper = new TimerLooper;
+ timerLooper->Run();
+ BMessenger target(timerLooper);
+ BMessage message(timerMessage);
+ message.AddPointer("callback", (void*)callback);
+ message.AddPointer("userData", userData);
+ bigtime_t interval = resolution * 1000;
+ timerRunner = new BMessageRunner(target, &message, interval);
+ if(timerRunner->InitCheck() != B_OK) {
+ delete timerRunner;
+ timerRunner = NULL;
+ timerLooper->PostMessage(B_QUIT_REQUESTED);
+ timerLooper = NULL;
+ return ptHostError;
+ }
+ }
+ time_started_flag = true;
+ return ptNoError;
+ }
+
+
+ PtError Pt_Stop()
+ {
+ if (!time_started_flag) return ptAlreadyStopped;
+ time_started_flag = false;
+ delete timerRunner;
+ timerRunner = NULL;
+ timerLooper->PostMessage(B_QUIT_REQUESTED);
+ timerLooper = NULL;
+ return ptNoError;
+ }
+
+
+ int Pt_Started()
+ {
+ return time_started_flag;
+ }
+
+
+ PtTimestamp Pt_Time()
+ {
+ return (system_time() - time_offset) / 1000;
+ }
+
+
+ void Pt_Sleep(int32_t duration)
+ {
+ snooze(duration * 1000);
+ }
+}
diff --git a/portmidi/porttime/ptlinux.c b/portmidi/porttime/ptlinux.c
new file mode 100755
index 0000000..c4af5c1
--- /dev/null
+++ b/portmidi/porttime/ptlinux.c
@@ -0,0 +1,139 @@
+/* ptlinux.c -- portable timer implementation for linux */
+
+
+/* IMPLEMENTATION NOTES (by Mark Nelson):
+
+Unlike Windows, Linux has no system call to request a periodic callback,
+so if Pt_Start() receives a callback parameter, it must create a thread
+that wakes up periodically and calls the provided callback function.
+If running as superuser, use setpriority() to renice thread to -20.
+One could also set the timer thread to a real-time priority (SCHED_FIFO
+and SCHED_RR), but this is dangerous for This is necessary because
+if the callback hangs it'll never return. A more serious reason
+is that the current scheduler implementation busy-waits instead
+of sleeping when realtime threads request a sleep of <=2ms (as a way
+to get around the 10ms granularity), which means the thread would never
+let anyone else on the CPU.
+
+CHANGE LOG
+
+18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer
+ thread. Simplified implementation notes.
+
+*/
+/* stdlib, stdio, unistd, and sys/types were added because they appeared
+ * in a Gentoo patch, but I'm not sure why they are needed. -RBD
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include "porttime.h"
+#include "time.h"
+#include "sys/resource.h"
+#include "pthread.h"
+
+#define TRUE 1
+#define FALSE 0
+
+#ifndef CLOCK_MONOTONIC_RAW
+#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
+#endif
+
+static int time_started_flag = FALSE;
+static struct timespec time_offset = {0, 0};
+static pthread_t pt_thread_pid;
+static int pt_thread_created = FALSE;
+
+/* note that this is static data -- we only need one copy */
+typedef struct {
+ int id;
+ int resolution;
+ PtCallback *callback;
+ void *userData;
+} pt_callback_parameters;
+
+static int pt_callback_proc_id = 0;
+
+static void *Pt_CallbackProc(void *p)
+{
+ pt_callback_parameters *parameters = (pt_callback_parameters *) p;
+ int mytime = 1;
+ /* to kill a process, just increment the pt_callback_proc_id */
+ /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
+ parameters->id); */
+ if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);
+ while (pt_callback_proc_id == parameters->id) {
+ /* wait for a multiple of resolution ms */
+ struct timeval timeout;
+ int delay = mytime++ * parameters->resolution - Pt_Time();
+ if (delay < 0) delay = 0;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = delay * 1000;
+ select(0, NULL, NULL, NULL, &timeout);
+ (*(parameters->callback))(Pt_Time(), parameters->userData);
+ }
+ /* printf("Pt_CallbackProc exiting\n"); */
+ // free(parameters);
+ return NULL;
+}
+
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+ if (time_started_flag) return ptNoError;
+ /* need this set before process runs: */
+ clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset);
+ if (callback) {
+ int res;
+ pt_callback_parameters *parms = (pt_callback_parameters *)
+ malloc(sizeof(pt_callback_parameters));
+ if (!parms) return ptInsufficientMemory;
+ parms->id = pt_callback_proc_id;
+ parms->resolution = resolution;
+ parms->callback = callback;
+ parms->userData = userData;
+ res = pthread_create(&pt_thread_pid, NULL,
+ Pt_CallbackProc, parms);
+ if (res != 0) return ptHostError;
+ pt_thread_created = TRUE;
+ }
+ time_started_flag = TRUE;
+ return ptNoError;
+}
+
+
+PtError Pt_Stop()
+{
+ /* printf("Pt_Stop called\n"); */
+ pt_callback_proc_id++;
+ if (pt_thread_created) {
+ pthread_join(pt_thread_pid, NULL);
+ pt_thread_created = FALSE;
+ }
+ time_started_flag = FALSE;
+ return ptNoError;
+}
+
+
+int Pt_Started()
+{
+ return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time()
+{
+ long seconds, ms;
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+ seconds = now.tv_sec - time_offset.tv_sec;
+ ms = (now.tv_nsec - time_offset.tv_nsec) / 1000000; /* round down */
+ return seconds * 1000 + ms;
+}
+
+
+void Pt_Sleep(int32_t duration)
+{
+ usleep(duration * 1000);
+}
diff --git a/portmidi/porttime/ptmacosx_cf.c b/portmidi/porttime/ptmacosx_cf.c
new file mode 100755
index 0000000..a174c86
--- /dev/null
+++ b/portmidi/porttime/ptmacosx_cf.c
@@ -0,0 +1,140 @@
+/* ptmacosx.c -- portable timer implementation for mac os x */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <pthread.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#import <mach/mach.h>
+#import <mach/mach_error.h>
+#import <mach/mach_time.h>
+#import <mach/clock.h>
+
+#include "porttime.h"
+
+#define THREAD_IMPORTANCE 30
+#define LONG_TIME 1000000000.0
+
+static int time_started_flag = FALSE;
+static CFAbsoluteTime startTime = 0.0;
+static CFRunLoopRef timerRunLoop;
+
+typedef struct {
+ int resolution;
+ PtCallback *callback;
+ void *userData;
+} PtThreadParams;
+
+
+void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info)
+{
+ PtThreadParams *params = (PtThreadParams*)info;
+ (*params->callback)(Pt_Time(), params->userData);
+}
+
+static void* Pt_Thread(void *p)
+{
+ CFTimeInterval timerInterval;
+ CFRunLoopTimerContext timerContext;
+ CFRunLoopTimerRef timer;
+ PtThreadParams *params = (PtThreadParams*)p;
+ //CFTimeInterval timeout;
+
+ /* raise the thread's priority */
+ kern_return_t error;
+ thread_extended_policy_data_t extendedPolicy;
+ thread_precedence_policy_data_t precedencePolicy;
+
+ extendedPolicy.timeshare = 0;
+ error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
+ (thread_policy_t)&extendedPolicy,
+ THREAD_EXTENDED_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread timeshare policy", error);
+ }
+
+ precedencePolicy.importance = THREAD_IMPORTANCE;
+ error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,
+ (thread_policy_t)&precedencePolicy,
+ THREAD_PRECEDENCE_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread precedence policy", error);
+ }
+
+ /* set up the timer context */
+ timerContext.version = 0;
+ timerContext.info = params;
+ timerContext.retain = NULL;
+ timerContext.release = NULL;
+ timerContext.copyDescription = NULL;
+
+ /* create a new timer */
+ timerInterval = (double)params->resolution / 1000.0;
+ timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval,
+ 0, 0, Pt_CFTimerCallback, &timerContext);
+
+ timerRunLoop = CFRunLoopGetCurrent();
+ CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode"));
+
+ /* run until we're told to stop by Pt_Stop() */
+ CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false);
+
+ CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode"));
+ CFRelease(timer);
+ free(params);
+
+ return NULL;
+}
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+ PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams));
+ pthread_t pthread_id;
+
+ printf("Pt_Start() called\n");
+
+ // /* make sure we're not already playing */
+ if (time_started_flag) return ptAlreadyStarted;
+ startTime = CFAbsoluteTimeGetCurrent();
+
+ if (callback) {
+
+ params->resolution = resolution;
+ params->callback = callback;
+ params->userData = userData;
+
+ pthread_create(&pthread_id, NULL, Pt_Thread, params);
+ }
+
+ time_started_flag = TRUE;
+ return ptNoError;
+}
+
+
+PtError Pt_Stop()
+{
+ printf("Pt_Stop called\n");
+
+ CFRunLoopStop(timerRunLoop);
+ time_started_flag = FALSE;
+ return ptNoError;
+}
+
+
+int Pt_Started()
+{
+ return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time()
+{
+ CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
+ return (PtTimestamp) ((now - startTime) * 1000.0);
+}
+
+
+void Pt_Sleep(int32_t duration)
+{
+ usleep(duration * 1000);
+}
diff --git a/portmidi/porttime/ptmacosx_mach.c b/portmidi/porttime/ptmacosx_mach.c
new file mode 100755
index 0000000..1390af8
--- /dev/null
+++ b/portmidi/porttime/ptmacosx_mach.c
@@ -0,0 +1,204 @@
+/* ptmacosx.c -- portable timer implementation for mac os x */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <CoreAudio/HostTime.h>
+
+#import <mach/mach.h>
+#import <mach/mach_error.h>
+#import <mach/mach_time.h>
+#import <mach/clock.h>
+#include <unistd.h>
+#include <AvailabilityMacros.h>
+
+#include "porttime.h"
+#include "sys/time.h"
+#include "pthread.h"
+
+#ifndef NSEC_PER_MSEC
+#define NSEC_PER_MSEC 1000000
+#endif
+#define THREAD_IMPORTANCE 63
+
+// QOS headers are available as of macOS 10.10
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
+#include "sys/qos.h"
+#define HAVE_APPLE_QOS 1
+#else
+#undef HAVE_APPLE_QOS
+#endif
+
+static int time_started_flag = FALSE;
+static UInt64 start_time;
+static pthread_t pt_thread_pid;
+
+/* note that this is static data -- we only need one copy */
+typedef struct {
+ int id;
+ int resolution;
+ PtCallback *callback;
+ void *userData;
+} pt_callback_parameters;
+
+static int pt_callback_proc_id = 0;
+
+static void *Pt_CallbackProc(void *p)
+{
+ pt_callback_parameters *parameters = (pt_callback_parameters *) p;
+ int mytime = 1;
+
+ kern_return_t error;
+ thread_extended_policy_data_t extendedPolicy;
+ thread_precedence_policy_data_t precedencePolicy;
+
+ extendedPolicy.timeshare = 0;
+ error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
+ (thread_policy_t)&extendedPolicy,
+ THREAD_EXTENDED_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread timeshare policy", error);
+ }
+
+ precedencePolicy.importance = THREAD_IMPORTANCE;
+ error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,
+ (thread_policy_t)&precedencePolicy,
+ THREAD_PRECEDENCE_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread precedence policy", error);
+ }
+
+ // Most important, set real-time constraints.
+
+ // Define the guaranteed and max fraction of time for the audio thread.
+ // These "duty cycle" values can range from 0 to 1. A value of 0.5
+ // means the scheduler would give half the time to the thread.
+ // These values have empirically been found to yield good behavior.
+ // Good means that audio performance is high and other threads won't starve.
+ const double kGuaranteedAudioDutyCycle = 0.75;
+ const double kMaxAudioDutyCycle = 0.85;
+
+ // Define constants determining how much time the audio thread can
+ // use in a given time quantum. All times are in milliseconds.
+
+ // About 128 frames @44.1KHz
+ const double kTimeQuantum = 2.9;
+
+ // Time guaranteed each quantum.
+ const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;
+
+ // Maximum time each quantum.
+ const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum;
+
+ // Get the conversion factor from milliseconds to absolute time
+ // which is what the time-constraints call needs.
+ mach_timebase_info_data_t tb_info;
+ mach_timebase_info(&tb_info);
+ double ms_to_abs_time =
+ ((double)tb_info.denom / (double)tb_info.numer) * 1000000;
+
+ thread_time_constraint_policy_data_t time_constraints;
+ time_constraints.period = (uint32_t)(kTimeQuantum * ms_to_abs_time);
+ time_constraints.computation = (uint32_t)(kAudioTimeNeeded * ms_to_abs_time);
+ time_constraints.constraint = (uint32_t)(kMaxTimeAllowed * ms_to_abs_time);
+ time_constraints.preemptible = 0;
+
+ error = thread_policy_set(mach_thread_self(),
+ THREAD_TIME_CONSTRAINT_POLICY,
+ (thread_policy_t)&time_constraints,
+ THREAD_TIME_CONSTRAINT_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread precedence policy", error);
+ }
+
+ /* to kill a process, just increment the pt_callback_proc_id */
+ /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
+ parameters->id); */
+ while (pt_callback_proc_id == parameters->id) {
+ /* wait for a multiple of resolution ms */
+ UInt64 wait_time;
+ int delay = mytime++ * parameters->resolution - Pt_Time();
+ PtTimestamp timestamp;
+ if (delay < 0) delay = 0;
+ wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);
+ wait_time += AudioGetCurrentHostTime();
+ mach_wait_until(wait_time);
+ timestamp = Pt_Time();
+ (*(parameters->callback))(timestamp, parameters->userData);
+ }
+ free(parameters);
+ return NULL;
+}
+
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+ if (time_started_flag) return ptAlreadyStarted;
+ start_time = AudioGetCurrentHostTime();
+
+ if (callback) {
+ int res;
+ pt_callback_parameters *parms;
+
+ parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));
+ if (!parms) return ptInsufficientMemory;
+ parms->id = pt_callback_proc_id;
+ parms->resolution = resolution;
+ parms->callback = callback;
+ parms->userData = userData;
+
+#ifdef HAVE_APPLE_QOS
+ pthread_attr_t qosAttribute;
+ pthread_attr_init(&qosAttribute);
+ pthread_attr_set_qos_class_np(&qosAttribute,
+ QOS_CLASS_USER_INTERACTIVE, 0);
+
+ res = pthread_create(&pt_thread_pid, &qosAttribute, Pt_CallbackProc,
+ parms);
+#else
+ res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);
+#endif
+
+ struct sched_param sp;
+ memset(&sp, 0, sizeof(struct sched_param));
+ sp.sched_priority = sched_get_priority_max(SCHED_RR);
+ if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == -1) {
+ return ptHostError;
+ }
+
+ if (res != 0) return ptHostError;
+ }
+
+ time_started_flag = TRUE;
+ return ptNoError;
+}
+
+
+PtError Pt_Stop(void)
+{
+ /* printf("Pt_Stop called\n"); */
+ pt_callback_proc_id++;
+ pthread_join(pt_thread_pid, NULL);
+ time_started_flag = FALSE;
+ return ptNoError;
+}
+
+
+int Pt_Started(void)
+{
+ return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time(void)
+{
+ UInt64 clock_time, nsec_time;
+ clock_time = AudioGetCurrentHostTime() - start_time;
+ nsec_time = AudioConvertHostTimeToNanos(clock_time);
+ return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);
+}
+
+
+void Pt_Sleep(int32_t duration)
+{
+ usleep(duration * 1000);
+}
diff --git a/portmidi/porttime/ptwinmm.c b/portmidi/porttime/ptwinmm.c
new file mode 100755
index 0000000..5204659
--- /dev/null
+++ b/portmidi/porttime/ptwinmm.c
@@ -0,0 +1,70 @@
+/* ptwinmm.c -- portable timer implementation for win32 */
+
+
+#include "porttime.h"
+#include "windows.h"
+#include "time.h"
+
+
+TIMECAPS caps;
+
+static long time_offset = 0;
+static int time_started_flag = FALSE;
+static long time_resolution;
+static MMRESULT timer_id;
+static PtCallback *time_callback;
+
+void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser,
+ DWORD_PTR dw1, DWORD_PTR dw2)
+{
+ (*time_callback)(Pt_Time(), (void *) dwUser);
+}
+
+
+PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+ if (time_started_flag) return ptAlreadyStarted;
+ timeBeginPeriod(resolution);
+ time_resolution = resolution;
+ time_offset = timeGetTime();
+ time_started_flag = TRUE;
+ time_callback = callback;
+ if (callback) {
+ timer_id = timeSetEvent(resolution, 1, winmm_time_callback,
+ (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
+ if (!timer_id) return ptHostError;
+ }
+ return ptNoError;
+}
+
+
+PMEXPORT PtError Pt_Stop()
+{
+ if (!time_started_flag) return ptAlreadyStopped;
+ if (time_callback && timer_id) {
+ timeKillEvent(timer_id);
+ time_callback = NULL;
+ timer_id = 0;
+ }
+ time_started_flag = FALSE;
+ timeEndPeriod(time_resolution);
+ return ptNoError;
+}
+
+
+PMEXPORT int Pt_Started()
+{
+ return time_started_flag;
+}
+
+
+PMEXPORT PtTimestamp Pt_Time()
+{
+ return timeGetTime() - time_offset;
+}
+
+
+PMEXPORT void Pt_Sleep(int32_t duration)
+{
+ Sleep(duration);
+}