summaryrefslogtreecommitdiff
path: root/portmidi/pm_common
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi/pm_common')
-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
6 files changed, 3271 insertions, 0 deletions
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 */