diff options
Diffstat (limited to 'portmidi/pm_common')
| -rw-r--r-- | portmidi/pm_common/CMakeLists.txt | 167 | ||||
| -rwxr-xr-x | portmidi/pm_common/pminternal.h | 190 | ||||
| -rwxr-xr-x | portmidi/pm_common/pmutil.c | 284 | ||||
| -rwxr-xr-x | portmidi/pm_common/pmutil.h | 184 | ||||
| -rwxr-xr-x | portmidi/pm_common/portmidi.c | 1472 | ||||
| -rwxr-xr-x | portmidi/pm_common/portmidi.h | 974 |
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 */ |
