diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-07 19:30:56 +0200 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-07 19:30:56 +0200 |
| commit | 40a899bd6ee536eae093337bf2d0dcc8db4e46f1 (patch) | |
| tree | 485ace3e6fd28b91f394efd277732651e10824d8 /portmidi/pm_common | |
| parent | 6fc4bddfdf8e056469f316c1a0fe488efbb4253a (diff) | |
| download | ttdaw-40a899bd6ee536eae093337bf2d0dcc8db4e46f1.tar.gz | |
Moved example code examples folder
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, 0 insertions, 3271 deletions
diff --git a/portmidi/pm_common/CMakeLists.txt b/portmidi/pm_common/CMakeLists.txt deleted file mode 100644 index 1ad54ad..0000000 --- a/portmidi/pm_common/CMakeLists.txt +++ /dev/null @@ -1,167 +0,0 @@ -# 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 deleted file mode 100755 index 8b3d8f5..0000000 --- a/portmidi/pm_common/pminternal.h +++ /dev/null @@ -1,190 +0,0 @@ -/** @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 deleted file mode 100755 index a70fe2f..0000000 --- a/portmidi/pm_common/pmutil.c +++ /dev/null @@ -1,284 +0,0 @@ -/* 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 deleted file mode 100755 index 46c618e..0000000 --- a/portmidi/pm_common/pmutil.h +++ /dev/null @@ -1,184 +0,0 @@ -/** @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 deleted file mode 100755 index e78ee73..0000000 --- a/portmidi/pm_common/portmidi.c +++ /dev/null @@ -1,1472 +0,0 @@ -/* 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 deleted file mode 100755 index 8696a73..0000000 --- a/portmidi/pm_common/portmidi.h +++ /dev/null @@ -1,974 +0,0 @@ -#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 */ |
