PortMidi 0.2.0.0 → 0.2.0.1
raw patch · 26 files changed
+7455/−6149 lines, 26 filesnew-uploaderPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +5/−0
- PortMidi.cabal +59/−33
- README.md +39/−0
- README.txt +0/−31
- portmidi/pm_common/pminternal.h +191/−174
- portmidi/pm_common/pmutil.c +283/−289
- portmidi/pm_common/pmutil.h +184/−127
- portmidi/pm_common/portmidi.c +1477/−1084
- portmidi/pm_common/portmidi.h +1009/−612
- portmidi/pm_linux/pmlinux.c +68/−60
- portmidi/pm_linux/pmlinux.h +0/−5
- portmidi/pm_linux/pmlinuxalsa.c +913/−789
- portmidi/pm_linux/pmlinuxnull.c +31/−0
- portmidi/pm_linux/pmlinuxnull.h +6/−0
- portmidi/pm_mac/pmmac.c +44/−42
- portmidi/pm_mac/pmmac.h +0/−4
- portmidi/pm_mac/pmmacosxcm.c +1184/−923
- portmidi/pm_mac/pmmacosxcm.h +3/−3
- portmidi/pm_win/pmwin.c +98/−114
- portmidi/pm_win/pmwinmm.c +1196/−1449
- portmidi/pm_win/pmwinmm.h +5/−5
- portmidi/porttime/porttime.h +106/−72
- portmidi/porttime/ptlinux.c +139/−133
- portmidi/porttime/ptmacosx_cf.c +140/−0
- portmidi/porttime/ptmacosx_mach.c +205/−130
- portmidi/porttime/ptwinmm.c +70/−70
CHANGELOG.md view
@@ -6,6 +6,11 @@ [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +## 0.2.0.1+### Changed+- Link to the shared PortMidi library per default.+ No user visible changes, but difference in how to build the package.+ ## 0.2.0.0 ### Changed - Error reporting (Breaking change).
PortMidi.cabal view
@@ -1,67 +1,93 @@+Cabal-Version: 2.2 name: PortMidi-version: 0.2.0.0-Cabal-Version: >= 1.6+version: 0.2.0.1 build-type: Simple-license: BSD3+license: BSD-3-Clause license-file: LICENSE maintainer: Paul H. Liu <paul@thev.net>- , Olivier Sohn <olivier.sohn@gmail.com>-homepage: http://github.com/ninegua/PortMidi+ , Henning Thielemann <haskell@henning-thielemann.de>+homepage: http://github.com/PortMidi/PortMidi category: Sound stability: experimental synopsis: A binding for PortMedia/PortMidi description: A Haskell binding for PortMedia/PortMidi-extra-source-files:- README.txt+extra-doc-files:+ README.md CHANGELOG.md+extra-source-files: portmidi/pm_common/portmidi.h portmidi/pm_common/pmutil.h portmidi/pm_common/pminternal.h- portmidi/pm_linux/pmlinux.h portmidi/pm_linux/pmlinuxalsa.h- portmidi/pm_mac/pmmac.h+ portmidi/pm_linux/pmlinuxnull.h portmidi/pm_mac/pmmacosxcm.h portmidi/pm_win/pmwinmm.h portmidi/porttime/porttime.h++Flag vendoredC+ Description: Use bundled C code instead of shared PortMidi library+ Manual: True+ Default: False++Flag pkgConfig+ Description: Use PkgConfig (portmidi.pc) instead of Extra-Libraries+ Manual: True+ Default: False+ Library exposed-modules: Sound.PortMidi other-modules: Sound.PortMidi.DeviceInfo- build-depends: base >= 3 && < 5- extensions: CPP, ForeignFunctionInterface- if os(linux) || os(freebsd)- include-dirs: portmidi/pm_common portmidi/pm_linux portmidi/porttime- cc-options: -DPMALSA- c-sources:+ build-depends: base >= 4.8 && < 5+ default-language: Haskell98+ default-extensions: CPP, ForeignFunctionInterface+ if flag(vendoredC)+ if os(linux) || os(freebsd)+ include-dirs: portmidi/pm_common portmidi/pm_linux portmidi/porttime+ cc-options: -DPMALSA+ c-sources: portmidi/pm_common/pmutil.c portmidi/pm_common/portmidi.c portmidi/pm_linux/pmlinux.c portmidi/pm_linux/pmlinuxalsa.c+ portmidi/pm_linux/pmlinuxnull.c portmidi/porttime/porttime.c portmidi/porttime/ptlinux.c- extra-libraries: asound- else- if os(darwin)- include-dirs: portmidi/pm_common portmidi/pm_mac portmidi/porttime- cc-options: -msse2- c-sources:+ extra-libraries: asound+ else+ if os(darwin)+ include-dirs: portmidi/pm_common portmidi/pm_mac portmidi/porttime+ cc-options: -msse2+ c-sources: portmidi/pm_common/pmutil.c portmidi/pm_common/portmidi.c portmidi/pm_mac/pmmac.c portmidi/pm_mac/pmmacosxcm.c portmidi/porttime/porttime.c+ portmidi/porttime/ptmacosx_cf.c portmidi/porttime/ptmacosx_mach.c- frameworks: CoreMIDI CoreFoundation CoreAudio+ frameworks: CoreMIDI CoreFoundation CoreAudio+ else+ if os(mingw32)+ include-dirs: portmidi/pm_common portmidi/pm_win portmidi/porttime+ c-sources:+ portmidi/pm_common/pmutil.c+ portmidi/pm_common/portmidi.c+ portmidi/pm_win/pmwin.c+ portmidi/pm_win/pmwinmm.c+ portmidi/porttime/porttime.c+ portmidi/porttime/ptwinmm.c+ extra-libraries: winmm+ else+ if flag(pkgConfig)+ pkgconfig-depends: portmidi else- if os(mingw32)- include-dirs: portmidi/pm_common portmidi/pm_win portmidi/porttime- c-sources:- portmidi/pm_common/pmutil.c- portmidi/pm_common/portmidi.c- portmidi/pm_win/pmwin.c- portmidi/pm_win/pmwinmm.c- portmidi/porttime/porttime.c- portmidi/porttime/ptwinmm.c- extra-libraries: winmm+ extra-libraries: portmidi+ source-repository head type: git- location: https://github.com/ninegua/PortMidi+ location: https://github.com/PortMidi/PortMidi-haskell++source-repository this+ tag: 0.2.0.1+ type: git+ location: https://github.com/PortMidi/PortMidi-haskell
+ README.md view
@@ -0,0 +1,39 @@+This is a Haskell module for PortMidi audio library, which supports+real-time MIDI input and output.+++ChangeLog+=========++Please see [CHANGELOG.md](CHANGELOG.md).+++Installation+============++The usual cabal installation steps apply, either:++ runhaskell Setup.hs configure+ runhaskell Setup.hs build+ runhaskell Setup.hs install++or simply:++ cabal install++This will install a PortMidi package that contains a `Sound.PortMidi` module.+++Bug reports / Feature requests+==============================++Bug reports and feature requests can be created at+[GitHub](https://github.com/PortMidi/portmidi-haskell/issues).+++Examples+========++* https://hackage.haskell.org/package/Euterpea+* https://hackage.haskell.org/package/portmidi-utility+* https://hackage.haskell.org/package/PortMidi-simple
− README.txt
@@ -1,31 +0,0 @@-This is a Haskell module for PortMidi audio library, which supports-real-time MIDI input and output.--=========-ChangeLog-=========--Please see CHANGELOG.md.--============-Installation-============--The usual cabal installation steps apply, either:-- runhaskell Setup.hs configure- runhaskell Setup.hs build- runhaskell Setup.hs install--or simply:-- cabal install--This will install a PortMidi package that contains a Sound.PortMidi module.--==============================-Bug reports / Feature requests-==============================--Bug reports and feature requests can be created here:-https://github.com/ninegua/PortMidi/issues.
portmidi/pm_common/pminternal.h view
@@ -1,174 +1,191 @@-/* pminternal.h -- header for interface 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. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -/* these are defined in system-specific file */ -void *pm_alloc(size_t s); -void pm_free(void *ptr); - -/* if an error occurs while opening or closing a midi stream, set these: */ -extern int pm_hosterror; -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_abort_fn)(struct pm_internal_struct *midi); -/* pm_close_fn should clean up all memory and close the device if any - part of the close fails. */ -typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi); -typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi); -typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg, - unsigned int len); -typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi); - -typedef struct { - pm_write_short_fn write_short; /* output short MIDI msg */ - pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */ - pm_end_sysex_fn end_sysex; /* marks end of sysex message */ - pm_write_byte_fn write_byte; /* accumulate one more sysex byte */ - pm_write_realtime_fn write_realtime; /* send real-time message within sysex */ - pm_write_flush_fn write_flush; /* send any accumulated but unsent data */ - pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */ - pm_open_fn open; /* open MIDI device */ - pm_abort_fn abort; /* abort */ - pm_close_fn close; /* close device */ - pm_poll_fn poll; /* read pending midi events into portmidi buffer */ - pm_has_host_error_fn has_host_error; /* true when device has had host - error message */ - pm_host_error_fn host_error; /* provide text readable host error message - for device (clears and resets) */ -} pm_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 autmatic - device closing (see PmDeviceInfo struct) */ - void *descriptor; /* ID number passed to win32 multimedia API open */ - void *internalDescriptor; /* points to PmInternal device, allows automatic - device closing */ - pm_fns_type dictionary; -} descriptor_node, *descriptor_type; - -extern int pm_descriptor_max; -extern descriptor_type descriptors; -extern int pm_descriptor_index; - -typedef unsigned long (*time_get_proc_type)(void *time_info); - -typedef struct pm_internal_struct { - int device_id; /* which device is open (index to descriptors) */ - short write_flag; /* MIDI_IN, or MIDI_OUT */ - - PmTimeProcPtr time_proc; /* where to get the time */ - void *time_info; /* pass this to get_time() */ - long buffer_len; /* how big is the buffer or queue? */ - PmQueue *queue; - - long 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 sysex_message; /* buffer for 4 bytes of sysex data */ - int sysex_message_count; /* how many bytes in sysex_message so far */ - - long filters; /* flags that filter incoming message classes */ - int channel_mask; /* flter 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 *descriptor; /* 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 */ - unsigned long *fill_offset_ptr; /* offset of next sysex byte */ - int fill_length; /* how many sysex bytes to write */ -} PmInternal; - - -/* 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_device(char *interf, char *name, int input, void *descriptor, - pm_fns_type dictionary); -unsigned int 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 - +/** @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.+ */++/* add INTERNAL to Doxygen ENABLED_SECTIONS to include this: */+/** @cond INTERNAL */++#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(const char *interf, pm_create_fn create_fn,+ pm_delete_fn delete_fn);+PmError pm_add_device(const char *interf, const char *name, int is_input,+ int is_virtual, void *descriptor, pm_fns_type dictionary);+void pm_undo_add_device(int id);+uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len,+ PmTimestamp timestamp);+void pm_read_short(PmInternal *midi, PmEvent *event);++#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 */
portmidi/pm_common/pmutil.c view
@@ -1,289 +1,283 @@-/* 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 - -/* code is based on 4-byte words -- it should work on a 64-bit machine - as long as a "long" has 4 bytes. This code could be generalized to - be independent of the size of "long" */ - -typedef long int32; - -typedef struct { - long head; - long tail; - long len; - long msg_size; /* number of int32 in a message including extra word */ - long overflow; - long peek_overflow; - int32 *buffer; - int32 *peek; - int peek_flag; -} PmQueueRep; - - -PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg) -{ - int int32s_per_msg = ((bytes_per_msg + sizeof(int32) - 1) & - ~(sizeof(int32) - 1)) / sizeof(int32); - 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 *) pm_alloc(queue->len * sizeof(int32)); - bzero(queue->buffer, queue->len * sizeof(int32)); - if (!queue->buffer) { - pm_free(queue); - return NULL; - } else { /* allocate the "peek" buffer */ - queue->peek = (int32 *) pm_alloc(int32s_per_msg * sizeof(int32)); - 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)); - 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; -} - - -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; -} - - -PmError Pm_Dequeue(PmQueue *q, void *msg) -{ - long head; - PmQueueRep *queue = (PmQueueRep *) q; - int i; - int32 *msg_as_int32 = (int32 *) 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)); - 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) * (queue->msg_size - 1)); - /* fix up zeros */ - i = queue->buffer[head]; - while (i < queue->msg_size) { - int32 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) * queue->msg_size); - - /* update head */ - head += queue->msg_size; - if (head == queue->len) head = 0; - queue->head = head; - return pmGotData; /* success */ -} - - - -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; -} - - -PmError Pm_Enqueue(PmQueue *q, void *msg) -{ - PmQueueRep *queue = (PmQueueRep *) q; - long tail; - int i; - int32 *src = (int32 *) msg; - int32 *ptr; - int32 *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 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; -} - - -int Pm_QueueEmpty(PmQueue *q) -{ - PmQueueRep *queue = (PmQueueRep *) q; - return (!queue) || /* null pointer -> return "empty" */ - (queue->buffer[queue->head] == 0 && !queue->peek_flag); -} - - -int Pm_QueueFull(PmQueue *q) -{ - int 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; -} - - -void *Pm_QueuePeek(PmQueue *q) -{ - PmError rslt; - long 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; -} - +/* 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));+ 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;+}+
portmidi/pm_common/pmutil.h view
@@ -1,127 +1,184 @@-/* pmutil.h -- some helpful utilities for building midi - applications that use PortMidi - */ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -typedef void PmQueue; - -/* - A single-reader, single-writer queue is created by - Pm_QueueCreate(), which takes the number of messages and - the message size as parameters. The queue only accepts - fixed sized messages. Returns NULL if memory cannot be allocated. - - 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. - - Pm_QueueDestroy() destroys the queue and frees its storage. - */ - -PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg); -PmError Pm_QueueDestroy(PmQueue *queue); - -/* - Pm_Dequeue() removes one item from the queue, copying it into msg. - Returns 1 if successful, and 0 if the queue is empty. - Returns pmBufferOverflow if what would have been the next thing - in the queue was dropped due to overflow. (So when overflow occurs, - the receiver can receive a queue full of messages before getting the - overflow report. This protocol ensures that the reader will be - notified when data is lost due to overflow. - */ -PmError Pm_Dequeue(PmQueue *queue, void *msg); - - -/* - Pm_Enqueue() inserts one item into the queue, copying it from msg. - Returns pmNoError if successful and pmBufferOverflow if the queue was - already full. If pmBufferOverflow is returned, the overflow flag is set. - */ -PmError Pm_Enqueue(PmQueue *queue, void *msg); - - -/* - Pm_QueueFull() returns non-zero if the queue is full - Pm_QueueEmpty() returns non-zero if the queue is empty - - Either condition may change immediately because a parallel - enqueue or dequeue operation could be in progress. Furthermore, - Pm_QueueEmpty() is optimistic: it may say false, when due to - out-of-order writes, the full message has not arrived. Therefore, - Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns - false. On the other hand, Pm_QueueFull() is pessimistic: if it - returns false, then Pm_Enqueue() is guaranteed to succeed. - - Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. - Pm_QueueEmpty() returns FALSE if queue is NULL. - */ -int Pm_QueueFull(PmQueue *queue); -int Pm_QueueEmpty(PmQueue *queue); - - -/* - Pm_QueuePeek() returns a pointer to the item at the head of the queue, - or NULL if the queue is empty. The item is not removed from the queue. - Pm_QueuePeek() will not indicate when an overflow occurs. If you want - to get and check pmBufferOverflow messages, use the return value of - Pm_QueuePeek() *only* as an indication that you should call - Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would - return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally - clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume - enqueuing messages. A subsequent call to Pm_QueuePeek() - will return a pointer to the first message *after* the overflow. - Using this as an indication to call Pm_Dequeue(), the first call - to Pm_Dequeue() will return pmBufferOverflow. The second call will - return success, copying the same message pointed to by the previous - Pm_QueuePeek(). - - When to use Pm_QueuePeek(): (1) when you need to look at the message - 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. - */ -void *Pm_QueuePeek(PmQueue *queue); - -/* - Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow - condition to the reader (dequeuer). E.g. when transfering data from - the OS to an application, if the OS indicates a buffer overrun, - Pm_SetOverflow() can be used to insure that the reader receives a - pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue - is NULL, returns pmBufferOverflow if buffer is already in an overflow - state, returns pmNoError if successfully set overflow state. - */ -PmError Pm_SetOverflow(PmQueue *queue); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ +/** @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 bytes_per_msg the fixed message size++ @return the allocated and initialized queue, or NULL if memory+ cannot be allocated. Allocation uses pm_alloc().++ 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 */
portmidi/pm_common/portmidi.c view
@@ -1,1084 +1,1477 @@-#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) - -static int pm_initialized = FALSE; -int pm_hosterror; -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..."); - fgets(line, STRING_MAX, stdin); - /* this will clean up open ports: */ - exit(-1); -} - - -static PmError pm_errmsg(PmError err) -{ - if (err == pmHostError) { - /* it seems pointless to allocate memory and copy the string, - * so I will do the work of Pm_GetHostErrorText directly - */ - 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 - -/* -==================================================================== -system implementation of portmidi interface -==================================================================== -*/ - -int pm_descriptor_max = 0; -int pm_descriptor_index = 0; -descriptor_type descriptors = NULL; - -/* pm_add_device -- describe interface/device pair to library - * - * This is called at intialization time, once for each - * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1) - * The strings are retained but NOT COPIED, so do not destroy them! - * - * returns pmInvalidDeviceId if device memory is exceeded - * otherwise returns pmNoError - */ -PmError pm_add_device(char *interf, char *name, int input, - void *descriptor, pm_fns_type dictionary) { - if (pm_descriptor_index >= pm_descriptor_max) { - // expand descriptors - descriptor_type new_descriptors = (descriptor_type) - pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32)); - if (!new_descriptors) return pmInsufficientMemory; - if (descriptors) { - memcpy(new_descriptors, descriptors, - sizeof(descriptor_node) * pm_descriptor_max); - free(descriptors); - } - pm_descriptor_max += 32; - descriptors = new_descriptors; - } - descriptors[pm_descriptor_index].pub.interf = interf; - descriptors[pm_descriptor_index].pub.name = name; - descriptors[pm_descriptor_index].pub.input = input; - descriptors[pm_descriptor_index].pub.output = !input; - - /* default state: nothing to close (for automatic device closing) */ - descriptors[pm_descriptor_index].pub.opened = FALSE; - - /* ID number passed to win32 multimedia API open */ - descriptors[pm_descriptor_index].descriptor = descriptor; - - /* points to PmInternal, allows automatic device closing */ - descriptors[pm_descriptor_index].internalDescriptor = NULL; - - descriptors[pm_descriptor_index].dictionary = dictionary; - - pm_descriptor_index++; - - return pmNoError; -} - - -/* -==================================================================== -portmidi implementation -==================================================================== -*/ - -int Pm_CountDevices( void ) { - Pm_Initialize(); - /* no error checking -- Pm_Initialize() does not fail */ - return pm_descriptor_index; -} - - -const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) { - Pm_Initialize(); /* no error check needed */ - if (id >= 0 && id < pm_descriptor_index) { - return &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 void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) { - strcpy(msg, ""); -} -static unsigned int none_has_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_has_host_error, - none_get_host_error -}; - - -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; - default: - msg = "PortMidi: `Illegal error number'"; - break; - } - return msg; -} - - -/* This can be called whenever you get a pmHostError return value. - * The error will always be in the global pm_hosterror_text. - */ -void Pm_GetHostErrorText(char * msg, unsigned int len) { - assert(msg); - assert(len > 0); - if (pm_hosterror) { - 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 */ - } -} - - -int Pm_HasHostError(PortMidiStream * stream) { - if (pm_hosterror) return TRUE; - if (stream) { - PmInternal * midi = (PmInternal *) stream; - pm_hosterror = (*midi->dictionary->has_host_error)(midi); - if (pm_hosterror) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - /* now error message is global */ - return TRUE; - } - } - return FALSE; -} - - -PmError Pm_Initialize( void ) { - if (!pm_initialized) { - pm_hosterror = FALSE; - pm_hosterror_text[0] = 0; /* the null string */ - pm_init(); - pm_initialized = TRUE; - } - return pmNoError; -} - - -PmError Pm_Terminate( void ) { - if (pm_initialized) { - pm_term(); - // if there are no devices, descriptors might still be NULL - if (descriptors != NULL) { - free(descriptors); - descriptors = NULL; - } - pm_descriptor_index = 0; - pm_descriptor_max = 0; - pm_initialized = FALSE; - } - return pmNoError; -} - - -/* Pm_Read -- read up to length longs from source into buffer */ -/* - * returns number of longs actually read, or error code - */ -int Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) { - PmInternal *midi = (PmInternal *) stream; - int n = 0; - PmError err = pmNoError; - pm_hosterror = FALSE; - /* arg checking */ - if(midi == NULL) - err = pmBadPtr; - else if(!descriptors[midi->device_id].pub.opened) - err = pmBadPtr; - else if(!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->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } - return pm_errmsg(err); - } - - while (n < length) { - PmError 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; -} - -PmError Pm_Poll( PortMidiStream *stream ) -{ - PmInternal *midi = (PmInternal *) stream; - PmError err; - - pm_hosterror = FALSE; - /* arg checking */ - if(midi == NULL) - err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.opened) - err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.input) - err = pmBadPtr; - else - err = (*(midi->dictionary->poll))(midi); - - if (err != pmNoError) { - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } - return pm_errmsg(err); - } - - return !Pm_QueueEmpty(midi->queue); -} - - -/* 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; - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } - 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 */ - -PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) -{ - PmInternal *midi = (PmInternal *) stream; - PmError err = pmNoError; - int i; - int bits; - - pm_hosterror = FALSE; - /* arg checking */ - if(midi == NULL) - err = pmBadPtr; - else if(!descriptors[midi->device_id].pub.opened) - err = pmBadPtr; - else if(!descriptors[midi->device_id].pub.output) - err = pmBadPtr; - else - err = pmNoError; - - 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++) { - unsigned long 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 >> 18; 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->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } -error_exit: - return pm_errmsg(err); -} - - -PmError Pm_WriteShort(PortMidiStream *stream, long when, long msg) -{ - PmEvent event; - - event.timestamp = when; - event.message = msg; - return Pm_Write(stream, &event, 1); -} - - -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 (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; - /* 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) { - PmError 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) { - PmError err; - 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 */ - PmError err = Pm_Write(stream, buffer, bufx); - if (err) return err; - } - return pmNoError; -} - - - -PmError Pm_OpenInput(PortMidiStream** stream, - PmDeviceID inputDevice, - void *inputDriverInfo, - long bufferSize, - PmTimeProcPtr time_proc, - void *time_info) -{ - PmInternal *midi; - PmError err = pmNoError; - pm_hosterror = FALSE; - *stream = NULL; - - /* arg checking */ - if (inputDevice < 0 || inputDevice >= pm_descriptor_index) - err = pmInvalidDeviceId; - else if (!descriptors[inputDevice].pub.input) - err = pmBadPtr; - else if(descriptors[inputDevice].pub.opened) - err = pmBadPtr; - - if (err != pmNoError) - goto error_return; - - /* create portMidi internal data */ - midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); - *stream = midi; - if (!midi) { - err = pmInsufficientMemory; - goto error_return; - } - midi->device_id = inputDevice; - midi->write_flag = FALSE; - midi->time_proc = time_proc; - midi->time_info = time_info; - /* windows adds timestamps in the driver and these are more accurate than - using a time_proc, so do not automatically provide a time proc. Non-win - implementations may want to provide a default time_proc in their - system-specific midi_out_open() method. - */ - if (bufferSize <= 0) bufferSize = 256; /* default buffer size */ - midi->queue = Pm_QueueCreate(bufferSize, sizeof(PmEvent)); - if (!midi->queue) { - /* free portMidi data */ - *stream = NULL; - pm_free(midi); - err = pmInsufficientMemory; - goto error_return; - } - midi->buffer_len = bufferSize; /* portMidi input storage */ - midi->latency = 0; /* not used */ - midi->sysex_in_progress = FALSE; - midi->sysex_message = 0; - midi->sysex_message_count = 0; - midi->filters = PM_FILT_ACTIVE; - midi->channel_mask = 0xFFFF; - midi->sync_time = 0; - midi->first_message = TRUE; - midi->dictionary = descriptors[inputDevice].dictionary; - midi->fill_base = NULL; - midi->fill_offset_ptr = NULL; - midi->fill_length = 0; - descriptors[inputDevice].internalDescriptor = midi; - /* open system dependent input device */ - err = (*midi->dictionary->open)(midi, inputDriverInfo); - if (err) { - *stream = NULL; - descriptors[inputDevice].internalDescriptor = NULL; - /* free portMidi data */ - Pm_QueueDestroy(midi->queue); - pm_free(midi); - } else { - /* portMidi input open successful */ - 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); -} - - -PmError Pm_OpenOutput(PortMidiStream** stream, - PmDeviceID outputDevice, - void *outputDriverInfo, - long bufferSize, - PmTimeProcPtr time_proc, - void *time_info, - long latency ) -{ - PmInternal *midi; - PmError err = pmNoError; - pm_hosterror = FALSE; - *stream = NULL; - - /* arg checking */ - if (outputDevice < 0 || outputDevice >= pm_descriptor_index) - err = pmInvalidDeviceId; - else if (!descriptors[outputDevice].pub.output) - err = pmInvalidDeviceId; - else if (descriptors[outputDevice].pub.opened) - err = pmInvalidDeviceId; - if (err != pmNoError) - goto error_return; - - /* create portMidi internal data */ - midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); - *stream = midi; - if (!midi) { - err = pmInsufficientMemory; - goto error_return; - } - midi->device_id = outputDevice; - midi->write_flag = TRUE; - midi->time_proc = time_proc; - /* if latency > 0, we need a time reference. If none is provided, - use PortTime library */ - if (time_proc == NULL && latency != 0) { - if (!Pt_Started()) - Pt_Start(1, 0, 0); - /* time_get does not take a parameter, so coerce */ - midi->time_proc = (PmTimeProcPtr) Pt_Time; - } - midi->time_info = time_info; - midi->buffer_len = bufferSize; - midi->queue = NULL; /* unused by output */ - /* if latency zero, output immediate (timestamps ignored) */ - /* if latency < 0, use 0 but don't return an error */ - if (latency < 0) latency = 0; - midi->latency = latency; - midi->sysex_in_progress = FALSE; - midi->sysex_message = 0; /* unused by output */ - midi->sysex_message_count = 0; /* unused by output */ - midi->filters = 0; /* not used for output */ - midi->channel_mask = 0xFFFF; - midi->sync_time = 0; - midi->first_message = TRUE; - midi->dictionary = descriptors[outputDevice].dictionary; - midi->fill_base = NULL; - midi->fill_offset_ptr = NULL; - midi->fill_length = 0; - descriptors[outputDevice].internalDescriptor = midi; - /* open system dependent output device */ - err = (*midi->dictionary->open)(midi, outputDriverInfo); - if (err) { - *stream = NULL; - descriptors[outputDevice].internalDescriptor = NULL; - /* free portMidi data */ - pm_free(midi); - } else { - /* portMidi input open successful */ - 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); -} - - -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); -} - - -PmError Pm_SetFilter(PortMidiStream *stream, long filters) { - PmInternal *midi = (PmInternal *) stream; - PmError err = pmNoError; - - /* arg checking */ - if (midi == NULL) - err = pmBadPtr; - else if (!descriptors[midi->device_id].pub.opened) - err = pmBadPtr; - else - midi->filters = filters; - return pm_errmsg(err); -} - - -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_index) - err = pmBadPtr; - /* and the device should be in the opened state */ - else if (!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 */ - descriptors[midi->device_id].internalDescriptor = NULL; - 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_Abort( PortMidiStream* stream ) { - PmInternal *midi = (PmInternal *) stream; - PmError err; - /* arg checking */ - if (midi == NULL) - err = pmBadPtr; - if (!descriptors[midi->device_id].pub.output) - err = pmBadPtr; - if (!descriptors[midi->device_id].pub.opened) - err = pmBadPtr; - else - err = (*midi->dictionary->abort)(midi); - - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } - 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->sysex_message_count == 0) return; /* nothing to flush */ - - event.message = midi->sysex_message; - event.timestamp = timestamp; - /* copied from pm_read_short, avoids filtering */ - if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { - midi->sysex_in_progress = FALSE; - } - midi->sysex_message_count = 0; - midi->sysex_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) eash 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->sysex_message |= - (status << (8 * midi->sysex_message_count++)); - if (midi->sysex_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 -- read one (partial) sysex msg from MIDI data */ -/* - * returns how many bytes processed - */ -unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, - int len, PmTimestamp timestamp) -{ - unsigned int i = 0; /* index into data */ - PmEvent event; - event.timestamp = timestamp; - assert(midi); - /* note that since buffers may not have multiples of 4 bytes, - * pm_read_bytes may be called in the middle of an outgoing - * 4-byte PortMidi message. sysex_in_progress indicates that - * a sysex has been sent but no eox. - */ - if (len == 0) return 0; /* sanity check */ - if (!midi->sysex_in_progress) { - while (i < len) { /* process all data */ - unsigned char byte = data[i++]; - if (byte == MIDI_SYSEX && - !pm_realtime_filtered(byte, midi->filters)) { - midi->sysex_in_progress = TRUE; - i--; /* back up so code below will get SYSEX byte */ - break; /* continue looping below to process msg */ - } else if (byte == MIDI_EOX) { - midi->sysex_in_progress = FALSE; - return i; /* done with one message */ - } else if (byte & MIDI_STATUS_MASK) { - /* We're getting MIDI but no sysex in progress. - * Either the SYSEX status byte was dropped or - * the message was filtered. Drop the data, but - * send any embedded realtime bytes. - */ - /* assume that this is a real-time message: - * it is an error to pass non-real-time messages - * to pm_read_bytes - */ - event.message = byte; - pm_read_short(midi, &event); - } - } /* all bytes in the buffer are processed */ - } - /* Now, i<len implies sysex_in_progress. If sysex_in_progress - * becomes false in the loop, there must have been an overflow - * and we can just drop all remaining bytes - */ - while (i < len && midi->sysex_in_progress) { - if (midi->sysex_message_count == 0 && i <= len - 4 && - ((event.message = (((long) data[i]) | - (((long) data[i+1]) << 8) | - (((long) data[i+2]) << 16) | - (((long) data[i+3]) << 24))) & - 0x80808080) == 0) { /* all data, no status */ - if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { - midi->sysex_in_progress = FALSE; - } - i += 4; - } else { - while (i < len) { - /* send one byte at a time */ - unsigned char byte = data[i++]; - if (is_real_time(byte) && - pm_realtime_filtered(byte, midi->filters)) { - continue; /* real-time data is filtered, so omit */ - } - midi->sysex_message |= - (byte << (8 * midi->sysex_message_count++)); - if (byte == MIDI_EOX) { - midi->sysex_in_progress = FALSE; - pm_flush_sysex(midi, event.timestamp); - return i; - } else if (midi->sysex_message_count == 4) { - pm_flush_sysex(midi, event.timestamp); - /* after handling at least one non-data byte - * and reaching a 4-byte message boundary, - * resume trying to send 4 at a time in outer loop - */ - break; - } - } - } - } - return i; -} - - +/* 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(const char *interf, pm_create_fn create_fn,+ pm_delete_fn delete_fn)+{+ if (pm_interf_list_len >= MAX_INTERF) {+ return pmInsufficientMemory;+ }+ pm_interf_list[pm_interf_list_len].interf = interf;+ pm_interf_list[pm_interf_list_len].create_fn = create_fn;+ pm_interf_list[pm_interf_list_len].delete_fn = delete_fn;+ pm_interf_list_len++;+ return pmNoError;+}+++PmError pm_create_virtual(PmInternal *midi, int is_input, const char *interf,+ const char *name, void *device_info)+{+ int i;+ if (pm_interf_list_len == 0) {+ return pmNotImplemented;+ }+ if (!interf) {+ /* default interface is the first one */+ interf = pm_interf_list[0].interf;+ }+ for (i = 0; i < pm_interf_list_len; i++) {+ if (strcmp(pm_interf_list[i].interf,+ interf) == 0) {+ int id = (*pm_interf_list[i].create_fn)(is_input, name,+ device_info);+ pm_descriptors[id].pub.is_virtual = TRUE;+ return id;+ }+ }+ return pmInterfaceNotSupported;+}+++/* pm_add_device -- describe interface/device pair to library + *+ * This is called at intialization time, once for each + * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1).+ * 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(const char *interf, const char *name, int is_input, + int is_virtual, void *descriptor, pm_fns_type dictionary) {+ /* printf("pm_add_device: %s %s %d %p %p\n",+ interf, name, is_input, descriptor, dictionary); */+ int device_id;+ PmDeviceInfo *d;+ /* if virtual, search for duplicate name or unused ID; otherwise,+ * just add a new device at the next integer available:+ */+ for (device_id = (is_virtual ? 0 : pm_descriptor_len);+ device_id < pm_descriptor_len; device_id++) {+ d = &pm_descriptors[device_id].pub;+ d->structVersion = 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 */+ const 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 &&+ 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; /* initialized below */+ 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);+ if (err) {+ goto error_return; /* will return with *stream == NULL */+ }+ *stream = midi; /* no error, so midi is valid */++ /* 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 {+ /* If Sysex is in progress, we cancel it because we encountered+ a status byte:+ */+ 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) {+ event.message = byte;+ 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;+}
portmidi/pm_common/portmidi.h view
@@ -1,612 +1,1009 @@-#ifndef PORT_MIDI_H -#define PORT_MIDI_H -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -/* - * PortMidi Portable Real-Time MIDI Library - * PortMidi API Header File - * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/ - * - * Copyright (c) 1999-2000 Ross Bencina and Phil Burk - * Copyright (c) 2001-2006 Roger B. Dannenberg - * - * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/ - * - * 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) - * - * IMPORTANT INFORMATION ABOUT A WIN32 BUG: - * - * Windows apparently has a serious midi bug -- if you do not close ports, Windows - * may crash. PortMidi tries to protect against this by using a DLL to clean up. - * - * If client exits for example with: - * i) assert - * ii) Ctrl^c, - * then DLL clean-up routine called. However, when client does something - * really bad (e.g. assigns value to NULL pointer) then DLL CLEANUP ROUTINE - * NEVER RUNS! In this state, if you wait around long enough, you will - * probably get the blue screen of death. Can also go into Pview and there will - * exist zombie process that you can't kill. - * - * You can enable the DLL cleanup routine by defining USE_DLL_FOR_CLEANUP. - * Do not define this preprocessor symbol if you do not want to use this - * feature. - * - * NOTES ON HOST ERROR REPORTING: - * - * PortMidi errors (of type PmError) are generic, system-independent errors. - * When an error does not map to one of the more specific PmErrors, the - * catch-all code pmHostError is returned. This means that PortMidi has - * retained a more specific system-dependent error code. The caller can - * get more information by calling Pm_HasHostError() to test if there is - * a pending host error, and Pm_GetHostErrorText() to get a text string - * describing the error. Host errors are reported on a per-device basis - * because only after you open a device does PortMidi have a place to - * record the host error code. I.e. only - * those routines that receive a (PortMidiStream *) argument check and - * report errors. One exception to this is that Pm_OpenInput() and - * Pm_OpenOutput() can report errors even though when an error occurs, - * there is no PortMidiStream* to hold the error. Fortunately, both - * of these functions return any error immediately, so we do not really - * need per-device error memory. Instead, any host error code is stored - * in a global, pmHostError is returned, and the user can call - * Pm_GetHostErrorText() to get the error message (and the invalid stream - * parameter will be ignored.) The functions - * pm_init and pm_term do not fail or raise - * errors. The job of pm_init is to locate all available devices so that - * the caller can get information via PmDeviceInfo(). If an error occurs, - * the device is simply not listed as available. - * - * Host errors come in two flavors: - * a) host error - * b) host error during callback - * These can occur w/midi input or output devices. (b) can only happen - * asynchronously (during callback routines), whereas (a) only occurs while - * synchronously running PortMidi and any resulting system dependent calls. - * Both (a) and (b) are reported by the next read or write call. You can - * also query for asynchronous errors (b) at any time by calling - * Pm_HasHostError(). - * - * 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. - * USE_DLL_FOR_CLEANUP is described above. (Windows only.) - * - */ - -#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, - pmNoData = 0, /* A "no error" return that also indicates no data avail. */ - pmGotData = 1, /* A "no error" return that also indicates 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, - pmBadData, /* illegal midi data, e.g. missing EOX */ - pmInternalError, - pmBufferMaxSize /* buffer is already as large as it can be */ - /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */ -} PmError; - -/* - Pm_Initialize() is the library initialisation function - call this before - using the library. -*/ - -PmError Pm_Initialize( void ); - -/* - Pm_Terminate() is the library termination function - call this after - using the library. -*/ - -PmError Pm_Terminate( void ); - -/* A single PortMidiStream is a descriptor for an open MIDI device. -*/ -typedef void PortMidiStream; -#define PmStream PortMidiStream - -/* - Test whether stream has a pending host error. Normally, the client finds - out about errors through returned error codes, but some errors can occur - asynchronously where the client does not - explicitly call a function, and therefore cannot receive an error code. - The client can test for a pending error using Pm_HasHostError(). If true, - the error can be accessed and cleared by calling Pm_GetErrorText(). - Errors are also cleared by calling other functions that can return - errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The - client does not need to call Pm_HasHostError(). Any pending error will be - reported the next time the client performs an explicit function call on - the stream, e.g. an input or output operation. Until the error is cleared, - no new error codes will be obtained, even for a different stream. -*/ -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 -*/ -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. -*/ -void Pm_GetHostErrorText(char * msg, unsigned int len); - -#define HDRLENGTH 50 -#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less - than this number of characters */ - -/* - Device enumeration mechanism. - - Device ids range from 0 to Pm_CountDevices()-1. - -*/ -typedef int PmDeviceID; -#define pmNoDevice -1 -typedef struct { - int structVersion; - const char *interf; /* underlying MIDI API, e.g. MMSystem or DirectX */ - const char *name; /* device name, e.g. USB MidiSport 1x1 */ - int input; /* true iff input is available */ - int output; /* true iff output is available */ - int opened; /* used by generic PortMidi code to do error checking on arguments */ - -} PmDeviceInfo; - - -int Pm_CountDevices( void ); -/* - Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID() - - Return the default device ID or pmNoDevice if there are no devices. - The result can be passed to Pm_OpenMidi(). - - On the PC, the user can specify a default device by - setting an environment variable. For example, to use device #1. - - set PM_RECOMMENDED_OUTPUT_DEVICE=1 - - The user should first determine the available device ID by using - the supplied application "testin" or "testout". - - In general, the registry is a better place for this kind of info, - and with USB devices that can come and go, using integers is not - very reliable for device identification. Under Windows, if - PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is - *NOT* found in the environment, then the default device is obtained - by looking for a string in the registry under: - HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device - and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device - for a string. The number of the first device with a substring that - matches the string exactly is returned. For example, if the string - in the registry is "USB", and device 1 is named - "In USB MidiSport 1x1", then that will be the default - input because it contains the string "USB". - - In addition to the name, PmDeviceInfo has the member "interf", which - is the interface name. (The "interface" is the underlying software - system or API used by PortMidi to access devices. Examples are - MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.) - At present, the only Win32 interface is "MMSystem", the only Linux - interface is "ALSA", and the only Max OS X interface is "CoreMIDI". - To specify both the interface and the device name in the registry, - separate the two with a comma and a space, e.g.: - MMSystem, In USB MidiSport 1x1 - In this case, the string before the comma must be a substring of - the "interf" string, and the string after the space must be a - substring of the "name" name string in order to match the device. - - Note: in the current release, the default is simply the first device - (the input or output device with the lowest PmDeviceID). -*/ -PmDeviceID Pm_GetDefaultInputDeviceID( void ); -PmDeviceID Pm_GetDefaultOutputDeviceID( void ); - -/* - PmTimestamp is used to represent a millisecond clock with arbitrary - start time. The type is used for all MIDI timestampes and clocks. -*/ -typedef long PmTimestamp; -typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); - -/* TRUE if t1 before t2 */ -#define PmBefore(t1,t2) ((t1-t2) < 0) - -/* - Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure - referring to the device specified by id. - If id is out of range the function returns NULL. - - 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(). -*/ -const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); - -/* - Pm_OpenInput() and Pm_OpenOutput() open devices. - - stream is the address of a PortMidiStream pointer which will receive - a pointer to the newly opened stream. - - inputDevice is the id of the device used for input (see PmDeviceID above). - - inputDriverInfo is a pointer to an optional driver specific data structure - containing additional information for device setup or handle processing. - inputDriverInfo is never required for correct operation. If not used - inputDriverInfo should be NULL. - - outputDevice is the id of the device used for output (see PmDeviceID above.) - - outputDriverInfo is a pointer to an optional driver specific data structure - containing additional information for device setup or handle processing. - outputDriverInfo is never required for correct operation. If not used - outputDriverInfo should be NULL. - - For input, the buffersize specifies the number of input events to be - buffered waiting to be read using Pm_Read(). For output, buffersize - specifies the number of output events to be buffered waiting for output. - (In some cases -- see below -- PortMidi does not buffer output at all - and merely passes data to a lower-level API, in which case buffersize - is ignored.) - - latency is the delay in milliseconds applied to timestamps to determine - when the output should actually occur. (If latency is < 0, 0 is assumed.) - If latency is zero, timestamps are ignored and all output is delivered - immediately. If latency is greater than zero, output is delayed until the - message timestamp plus the latency. (NOTE: the time is measured relative - to the time source indicated by time_proc. Timestamps are absolute, - not relative delays or offsets.) In some cases, PortMidi can obtain - better timing than your application by passing timestamps along to the - device driver or hardware. Latency may also help you to synchronize midi - data to audio data by matching midi latency to the audio buffer latency. - - time_proc is a pointer to a procedure that returns time in milliseconds. It - may be NULL, in which case a default millisecond timebase (PortTime) is - used. If the application wants to use PortTime, it should start the timer - (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the - application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput, - it may get a ptAlreadyStarted error from Pt_Start, and the application's - preferred time resolution and callback function will be ignored. - time_proc result values are appended to incoming MIDI data, and time_proc - times are used to schedule outgoing MIDI data (when latency is non-zero). - - time_info is a pointer passed to time_proc. - - Example: If I provide a timestamp of 5000, latency is 1, and time_proc - returns 4990, then the desired output time will be when time_proc returns - timestamp+latency = 5001. This will be 5001-4990 = 11ms from now. - - return value: - Upon success Pm_Open() returns PmNoError and places a pointer to a - valid PortMidiStream in the stream argument. - If a call to Pm_Open() fails a nonzero error code is returned (see - PMError above) and the value of port is invalid. - - Any stream that is successfully opened should eventually be closed - by calling Pm_Close(). - -*/ -PmError Pm_OpenInput( PortMidiStream** stream, - PmDeviceID inputDevice, - void *inputDriverInfo, - long bufferSize, - PmTimeProcPtr time_proc, - void *time_info ); - -PmError Pm_OpenOutput( PortMidiStream** stream, - PmDeviceID outputDevice, - void *outputDriverInfo, - long bufferSize, - PmTimeProcPtr time_proc, - void *time_info, - long latency ); - -/* - Pm_SetFilter() sets filters on an open input stream to drop selected - input types. By default, only active sensing messages are filtered. - To prohibit, say, active sensing and sysex messages, call - Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX); - - Filtering is useful when midi routing or midi thru functionality is being - provided by the user application. - For example, you may want to exclude timing messages (clock, MTC, start/stop/continue), - while allowing note-related messages to pass. - Or you may be using a sequencer or drum-machine for MIDI clock information but want to - exclude any notes it may play. - */ - -/* filter 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) - - -PmError Pm_SetFilter( PortMidiStream* stream, long filters ); - -/* - Pm_SetChannelMask() filters incoming messages based on channel. - The mask is a 16-bit bitfield corresponding to appropriate channels - The Pm_Channel macro can assist in calling this function. - i.e. to set receive only input on channel 1, call with - Pm_SetChannelMask(Pm_Channel(1)); - Multiple channels should be OR'd together, like - Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11)) - - All channels are allowed by default -*/ -#define Pm_Channel(channel) (1<<(channel)) - -PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); - -/* - Pm_Abort() terminates outgoing messages immediately - The caller should immediately close the output port; - this call may result in transmission of a partial midi message. - There is no abort for Midi input because the user can simply - ignore messages in the buffer and close an input device at - any time. - */ -PmError Pm_Abort( PortMidiStream* stream ); - -/* - Pm_Close() closes a midi stream, flushing any pending buffers. - (PortMidi attempts to close open streams when the application - exits -- this is particularly difficult under Windows.) -*/ -PmError Pm_Close( PortMidiStream* stream ); - -/* - Pm_Message() encodes a short Midi message into a long word. If data1 - and/or data2 are not present, use zero. - - Pm_MessageStatus(), Pm_MessageData1(), and - Pm_MessageData2() extract fields from a long-encoded midi message. -*/ -#define Pm_Message(status, data1, data2) \ - ((((data2) << 16) & 0xFF0000) | \ - (((data1) << 8) & 0xFF00) | \ - ((status) & 0xFF)) -#define Pm_MessageStatus(msg) ((msg) & 0xFF) -#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF) -#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF) - -/* 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. - - 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 long PmMessage; -typedef struct { - PmMessage message; - PmTimestamp timestamp; -} PmEvent; - -/* - Pm_Read() retrieves midi data into a buffer, and returns the number - of events read. Result is a non-negative number unless an error occurs, - in which case a PmError value will be returned. - - Buffer Overflow - - 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 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. - -*/ -int Pm_Read( PortMidiStream *stream, PmEvent *buffer, long length ); - -/* - Pm_Poll() tests whether input is available, - returning TRUE, FALSE, or an error value. -*/ -PmError Pm_Poll( PortMidiStream *stream); - -/* - Pm_Write() writes midi data from a buffer. This may contain: - - short messages - or - - sysex messages that are converted into a sequence of PmEvent - structures, e.g. sending data from a file or forwarding them - from midi input. - - Use Pm_WriteSysEx() to write a sysex message stored as a contiguous - array of bytes. - - Sysex data may contain embedded real-time messages. -*/ -PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length ); - -/* - Pm_WriteShort() writes a timestamped non-system-exclusive midi message. - Messages are delivered in order as received, and timestamps must be - non-decreasing. (But timestamps are ignored if the stream was opened - with latency = 0.) -*/ -PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, long msg); - -/* - Pm_WriteSysEx() writes a timestamped system-exclusive midi message. -*/ -PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg); - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* PORT_MIDI_H */ +#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, /**< error was returned from the system level below+ PortMidi. See ##Pm_GetHostErrorText() */+ 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, /**< buffer overflow (see #Pm_Read) */+ 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().+*/+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;+#define PM_DEVICEINFO_VERS 200++/** Version number of PmDeviceInfo, stored in #PmDeviceInfo::structVersion + field */+#define PM_DEVICEINFO_VERS 200++/** MIDI system-dependent device or driver info is passed in this+ structure, which is owned by the caller.+*/+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. */+};+#define PM_SYSDEPINFO_VERS 210++/** 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; /**< this structure version */+ int length; /**< number of properties in this structure */+ struct {+ enum PmSysDepPropertyKey key;+ const void *value;+ } properties[]; /**< array of key/value pairs */+} PmSysDepInfo;++/** Version number of PmSysDepInfo, stored in #PmSysDepInfo::structVersion+ field */+#define PM_SYSDEPINFO_VERS 210++++/** 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 \p 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;++/** @brief function pointer to retrieve the time in milliseconds.+ This is the time used in all PortMidi timestamps. The use of+ a function pointer allows the user to derive time from an audio+ sample count to synchronize MIDI to audio, or from a remote+ machine through clock synchronization protocols to synchronize+ MIDI across multiple machine. The time is independent of the+ internal system timestamps (e.g., MacOS CoreMIDI uses its own+ clock and timestamp representation, and PortMidi translates+ between different clocks, which need not be synchronized.) */+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 outputSysDepInfo 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; /**< up to 4 bytes of MIDI data */+ PmTimestamp timestamp; /**< PortMidi time of message */+} PmEvent;++/** @} */++/** \defgroup grp_io Reading and Writing Midi Messages+ @{+*/+/** Retrieve midi data into a buffer. ++ @param stream the open input stream.++ @param buffer input data is stored here++ @param length the length of buffer (number of #PmEvent, not bytes)++ @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 #pmNoError, #pmBadPtr (if \p stream is not valid and opened),+ #pmDeviceRemoved (if the MIDI device no longer exists),+ #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,+ nested SYSEX messages, or #pmHostError (error returned from API's+ MIDI write operation, see #Pm_GetHostErrorText). ++ \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.++ @return #pmNoError, #pmBadPtr (if \p stream is not valid and opened),+ #pmDeviceRemoved (if the MIDI device no longer exists),+ #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,+ nested SYSEX messages, or #pmHostError (error returned from API's+ MIDI write operation, see #Pm_GetHostErrorText). ++ 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.++ @return #pmNoError, #pmBadPtr (if \p stream is not valid and opened),+ #pmDeviceRemoved (if the MIDI device no longer exists),+ #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,+ nested SYSEX messages, or #pmHostError (error returned from API's+ MIDI write operation, see #Pm_GetHostErrorText). ++ \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 */
portmidi/pm_linux/pmlinux.c view
@@ -1,60 +1,68 @@-/* pmlinux.c -- PortMidi os-dependent code */ - -/* This file only needs to implement pm_init(), which calls various - routines to register the available midi devices. This file must - be separate from the main portmidi.c file because it is system - dependent, and it is separate from, pmlinuxalsa.c, because it - might need to register non-alsa devices as well. - - NOTE: if you add non-ALSA support, you need to fix :alsa_poll() - in pmlinuxalsa.c, which assumes all input devices are ALSA. - */ - -#include "stdlib.h" -#include "portmidi.h" -#ifdef PMALSA - #include "pmlinuxalsa.h" -#endif - -#ifdef PMNULL - #include "pmlinuxnull.h" -#endif - -PmError pm_init() -{ - /* Note: it is not an error for PMALSA to fail to initialize. - * It may be a design error that the client cannot query what subsystems - * are working properly other than by looking at the list of available - * devices. - */ - #ifdef PMALSA - pm_linuxalsa_init(); - #endif - #ifdef PMNULL - pm_linuxnull_init(); - #endif - return pmNoError; -} - -void pm_term(void) -{ - #ifdef PMALSA - pm_linuxalsa_term(); - #endif -} - -PmDeviceID pm_default_input_device_id = -1; -PmDeviceID pm_default_output_device_id = -1; - -PmDeviceID Pm_GetDefaultInputDeviceID() { - return pm_default_input_device_id; -} - -PmDeviceID Pm_GetDefaultOutputDeviceID() { - return pm_default_output_device_id; -} - -void *pm_alloc(size_t s) { return malloc(s); } - -void pm_free(void *ptr) { free(ptr); } - +/* pmlinux.c -- PortMidi os-dependent code */++/* This file only needs to implement pm_init(), which calls various+ routines to register the available midi devices. This file must+ be separate from the main portmidi.c file because it is system+ dependent, and it is separate from, pmlinuxalsa.c, because it+ might need to register non-alsa devices as well.++ NOTE: if you add non-ALSA support, you need to fix :alsa_poll()+ in pmlinuxalsa.c, which assumes all input devices are ALSA.+ */++#include "stdlib.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"++#ifdef PMALSA+ #include "pmlinuxalsa.h"+#endif++#ifdef PMNULL+ #include "pmlinuxnull.h"+#endif++#if !(defined(PMALSA) || defined(PMNULL))+#error One of PMALSA or PMNULL must be defined+#endif++void pm_init(void)+{+ /* Note: it is not an error for PMALSA to fail to initialize. + * It may be a design error that the client cannot query what subsystems+ * are working properly other than by looking at the list of available+ * devices.+ */+#ifdef PMALSA+ pm_linuxalsa_init();+#endif+#ifdef PMNULL+ pm_linuxnull_init();+#endif+}++void pm_term(void)+{+ #ifdef PMALSA+ pm_linuxalsa_term();+ #endif+ #ifdef PMNULL+ pm_linuxnull_term();+ #endif+}++PmDeviceID Pm_GetDefaultInputDeviceID(void) { + Pm_Initialize();+ return pm_default_input_device_id; +}++PmDeviceID Pm_GetDefaultOutputDeviceID(void) { + Pm_Initialize();+ return pm_default_output_device_id; +}++void *pm_alloc(size_t s) { return malloc(s); }++void pm_free(void *ptr) { free(ptr); }+
− portmidi/pm_linux/pmlinux.h
@@ -1,5 +0,0 @@-/* pmlinux.h */ - -extern PmDeviceID pm_default_input_device_id; -extern PmDeviceID pm_default_output_device_id; -
portmidi/pm_linux/pmlinuxalsa.c view
@@ -1,789 +1,913 @@-/* - * pmlinuxalsa.c -- system specific definitions - * - * written by: - * Roger Dannenberg (port to Alsa 0.9.x) - * Clemens Ladisch (provided code examples and invaluable consulting) - * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation) - */ - -#include "stdlib.h" -#include "portmidi.h" -#include "pmutil.h" -#include "pminternal.h" -#include "pmlinuxalsa.h" -#include "string.h" -#include "porttime.h" -#include "pmlinux.h" - -#include <alsa/asoundlib.h> - -/* I used many print statements to debug this code. I left them in the - * source, and you can turn them on by changing false to true below: - */ -#define VERBOSE_ON 0 -#define VERBOSE if (VERBOSE_ON) - -#define MIDI_SYSEX 0xf0 -#define MIDI_EOX 0xf7 - -#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9 -#error needs ALSA 0.9.0 or later -#endif - -/* to store client/port in the device descriptor */ -#define MAKE_DESCRIPTOR(client, port) ((void*)(size_t)(((client) << 8) | (port))) -#define GET_DESCRIPTOR_CLIENT(info) ((((size_t)(info)) >> 8) & 0xff) -#define GET_DESCRIPTOR_PORT(info) (((size_t)(info)) & 0xff) - -#define BYTE unsigned char -#define UINT unsigned long - -extern pm_fns_node pm_linuxalsa_in_dictionary; -extern pm_fns_node pm_linuxalsa_out_dictionary; - -static snd_seq_t *seq = NULL; // all input comes here, - // output queue allocated on seq -static int queue, queue_used; /* one for all ports, reference counted */ - -typedef struct alsa_descriptor_struct { - int client; - int port; - int this_port; - int in_sysex; - snd_midi_event_t *parser; - int error; /* host error code */ -} alsa_descriptor_node, *alsa_descriptor_type; - - -/* get_alsa_error_text -- copy error text to potentially short string */ -/**/ -static void get_alsa_error_text(char *msg, int len, int err) -{ - int errlen = strlen(snd_strerror(err)); - if (errlen < len) { - strcpy(msg, snd_strerror(err)); - } else if (len > 20) { - sprintf(msg, "Alsa error %d", err); - } else if (len > 4) { - strcpy(msg, "Alsa"); - } else { - msg[0] = 0; - } -} - - -/* queue is shared by both input and output, reference counted */ -static PmError alsa_use_queue(void) -{ - if (queue_used == 0) { - snd_seq_queue_tempo_t *tempo; - - queue = snd_seq_alloc_queue(seq); - if (queue < 0) { - pm_hosterror = queue; - return pmHostError; - } - snd_seq_queue_tempo_alloca(&tempo); - snd_seq_queue_tempo_set_tempo(tempo, 480000); - snd_seq_queue_tempo_set_ppq(tempo, 480); - pm_hosterror = snd_seq_set_queue_tempo(seq, queue, tempo); - if (pm_hosterror < 0) - return pmHostError; - - snd_seq_start_queue(seq, queue, NULL); - snd_seq_drain_output(seq); - } - ++queue_used; - return pmNoError; -} - - -static void alsa_unuse_queue(void) -{ - if (--queue_used == 0) { - snd_seq_stop_queue(seq, queue, NULL); - snd_seq_drain_output(seq); - snd_seq_free_queue(seq, queue); - VERBOSE printf("queue freed\n"); - } -} - - -/* midi_message_length -- how many bytes in a message? */ -static int midi_message_length(PmMessage message) -{ - message &= 0xff; - if (message < 0x80) { - return 0; - } else if (message < 0xf0) { - static const int length[] = {3, 3, 3, 3, 2, 2, 3}; - return length[(message - 0x80) >> 4]; - } else { - static const int length[] = { - -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1}; - return length[message - 0xf0]; - } -} - - -static PmError alsa_out_open(PmInternal *midi, void *driverInfo) -{ - void *client_port = descriptors[midi->device_id].descriptor; - alsa_descriptor_type desc = (alsa_descriptor_type) - pm_alloc(sizeof(alsa_descriptor_node)); - snd_seq_port_info_t *info; - int err; - - if (!desc) return pmInsufficientMemory; - - snd_seq_port_info_alloca(&info); - snd_seq_port_info_set_port(info, midi->device_id); - snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | - SND_SEQ_PORT_CAP_READ); - snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | - SND_SEQ_PORT_TYPE_APPLICATION); - snd_seq_port_info_set_port_specified(info, 1); - err = snd_seq_create_port(seq, info); - if (err < 0) goto free_desc; - - /* fill in fields of desc, which is passed to pm_write routines */ - midi->descriptor = desc; - desc->client = GET_DESCRIPTOR_CLIENT(client_port); - desc->port = GET_DESCRIPTOR_PORT(client_port); - desc->this_port = midi->device_id; - desc->in_sysex = 0; - - desc->error = 0; - - err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &desc->parser); - if (err < 0) goto free_this_port; - - if (midi->latency > 0) { /* must delay output using a queue */ - err = alsa_use_queue(); - if (err < 0) goto free_parser; - - err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port); - if (err < 0) goto unuse_queue; /* clean up and return on error */ - } else { - err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port); - if (err < 0) goto free_parser; /* clean up and return on error */ - } - return pmNoError; - - unuse_queue: - alsa_unuse_queue(); - free_parser: - snd_midi_event_free(desc->parser); - free_this_port: - snd_seq_delete_port(seq, desc->this_port); - free_desc: - pm_free(desc); - pm_hosterror = err; - if (err < 0) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); - } - return pmHostError; -} - - -static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, - PmTimestamp timestamp) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - snd_seq_event_t ev; - int err; - - snd_seq_ev_clear(&ev); - if (snd_midi_event_encode_byte(desc->parser, byte, &ev) == 1) { - snd_seq_ev_set_dest(&ev, desc->client, desc->port); - snd_seq_ev_set_source(&ev, desc->this_port); - if (midi->latency > 0) { - /* compute relative time of event = timestamp - now + latency */ - PmTimestamp now = (midi->time_proc ? - midi->time_proc(midi->time_info) : - Pt_Time(NULL)); - int when = timestamp; - /* if timestamp is zero, send immediately */ - /* otherwise compute time delay and use delay if positive */ - if (when == 0) when = now; - when = (when - now) + midi->latency; - if (when < 0) when = 0; - VERBOSE printf("timestamp %d now %d latency %ld, ", - (int) timestamp, (int) now, midi->latency); - VERBOSE printf("scheduling event after %d\n", when); - /* message is sent in relative ticks, where 1 tick = 1 ms */ - snd_seq_ev_schedule_tick(&ev, queue, 1, when); - /* NOTE: for cases where the user does not supply a time function, - we could optimize the code by not starting Pt_Time and using - the alsa tick time instead. I didn't do this because it would - entail changing the queue management to start the queue tick - count when PortMidi is initialized and keep it running until - PortMidi is terminated. (This should be simple, but it's not - how the code works now.) -RBD */ - } else { /* send event out without queueing */ - VERBOSE printf("direct\n"); - /* ev.queue = SND_SEQ_QUEUE_DIRECT; - ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */ - snd_seq_ev_set_direct(&ev); - } - VERBOSE printf("sending event\n"); - err = snd_seq_event_output(seq, &ev); - if (err < 0) { - desc->error = err; - return pmHostError; - } - } - return pmNoError; -} - - -static PmError alsa_out_close(PmInternal *midi) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - if (!desc) return pmBadPtr; - - if (pm_hosterror = snd_seq_disconnect_to(seq, desc->this_port, - desc->client, desc->port)) { - // if there's an error, try to delete the port anyway, but don't - // change the pm_hosterror value so we retain the first error - snd_seq_delete_port(seq, desc->this_port); - } else { // if there's no error, delete the port and retain any error - pm_hosterror = snd_seq_delete_port(seq, desc->this_port); - } - if (midi->latency > 0) alsa_unuse_queue(); - snd_midi_event_free(desc->parser); - midi->descriptor = NULL; /* destroy the pointer to signify "closed" */ - pm_free(desc); - if (pm_hosterror) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, - pm_hosterror); - return pmHostError; - } - return pmNoError; -} - - -static PmError alsa_in_open(PmInternal *midi, void *driverInfo) -{ - void *client_port = descriptors[midi->device_id].descriptor; - alsa_descriptor_type desc = (alsa_descriptor_type) - pm_alloc(sizeof(alsa_descriptor_node)); - snd_seq_port_info_t *info; - snd_seq_port_subscribe_t *sub; - snd_seq_addr_t addr; - int err; - - if (!desc) return pmInsufficientMemory; - - err = alsa_use_queue(); - if (err < 0) goto free_desc; - - snd_seq_port_info_alloca(&info); - snd_seq_port_info_set_port(info, midi->device_id); - snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | - SND_SEQ_PORT_CAP_READ); - snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | - SND_SEQ_PORT_TYPE_APPLICATION); - snd_seq_port_info_set_port_specified(info, 1); - /* bugfix: without the following, it won't generate timestamp - PaulLiu */ - snd_seq_port_info_set_timestamping(info, 1); - snd_seq_port_info_set_timestamp_real(info, 0); - snd_seq_port_info_set_timestamp_queue(info, queue); - err = snd_seq_create_port(seq, info); - if (err < 0) goto free_queue; - - /* fill in fields of desc, which is passed to pm_write routines */ - midi->descriptor = desc; - desc->client = GET_DESCRIPTOR_CLIENT(client_port); - desc->port = GET_DESCRIPTOR_PORT(client_port); - desc->this_port = midi->device_id; - desc->in_sysex = 0; - - desc->error = 0; - - VERBOSE printf("snd_seq_connect_from: %d %d %d\n", - desc->this_port, desc->client, desc->port); - snd_seq_port_subscribe_alloca(&sub); - addr.client = snd_seq_client_id(seq); - addr.port = desc->this_port; - snd_seq_port_subscribe_set_dest(sub, &addr); - addr.client = desc->client; - addr.port = desc->port; - snd_seq_port_subscribe_set_sender(sub, &addr); - snd_seq_port_subscribe_set_time_update(sub, 1); - /* this doesn't seem to work: messages come in with real timestamps */ - snd_seq_port_subscribe_set_time_real(sub, 0); - err = snd_seq_subscribe_port(seq, sub); - /* err = - snd_seq_connect_from(seq, desc->this_port, desc->client, desc->port); */ - if (err < 0) goto free_this_port; /* clean up and return on error */ - return pmNoError; - - free_this_port: - snd_seq_delete_port(seq, desc->this_port); - free_queue: - alsa_unuse_queue(); - free_desc: - pm_free(desc); - pm_hosterror = err; - if (err < 0) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); - } - return pmHostError; -} - -static PmError alsa_in_close(PmInternal *midi) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - if (!desc) return pmBadPtr; - if (pm_hosterror = snd_seq_disconnect_from(seq, desc->this_port, - desc->client, desc->port)) { - snd_seq_delete_port(seq, desc->this_port); /* try to close port */ - } else { - pm_hosterror = snd_seq_delete_port(seq, desc->this_port); - } - alsa_unuse_queue(); - pm_free(desc); - if (pm_hosterror) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, - pm_hosterror); - return pmHostError; - } - return pmNoError; -} - - -static PmError alsa_abort(PmInternal *midi) -{ - /* NOTE: ALSA documentation is vague. This is supposed to - * remove any pending output messages. If you can test and - * confirm this code is correct, please update this comment. -RBD - */ - /* Unfortunately, I can't even compile it -- my ALSA version - * does not implement snd_seq_remove_events_t, so this does - * not compile. I'll try again, but it looks like I'll need to - * upgrade my entire Linux OS -RBD - */ - /* - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - snd_seq_remove_events_t info; - snd_seq_addr_t addr; - addr.client = desc->client; - addr.port = desc->port; - snd_seq_remove_events_set_dest(&info, &addr); - snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST); - pm_hosterror = snd_seq_remove_events(seq, &info); - if (pm_hosterror) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, - pm_hosterror); - return pmHostError; - } - */ - printf("WARNING: alsa_abort not implemented\n"); - return pmNoError; -} - - -#ifdef GARBAGE -This is old code here temporarily for reference -static PmError alsa_write(PmInternal *midi, PmEvent *buffer, long length) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - int i, bytes; - unsigned char byte; - long msg; - - desc->error = 0; - for (; length > 0; length--, buffer++) { - VERBOSE printf("message 0x%x\n", buffer->message); - if (Pm_MessageStatus(buffer->message) == MIDI_SYSEX) - desc->in_sysex = TRUE; - if (desc->in_sysex) { - msg = buffer->message; - for (i = 0; i < 4; i++) { - byte = msg; /* extract next byte to send */ - alsa_write_byte(midi, byte, buffer->timestamp); - if (byte == MIDI_EOX) { - desc->in_sysex = FALSE; - break; - } - if (desc->error < 0) break; - msg >>= 8; /* shift next byte into position */ - } - } else { - bytes = midi_message_length(buffer->message); - msg = buffer->message; - for (i = 0; i < bytes; i++) { - byte = msg; /* extract next byte to send */ - VERBOSE printf("sending 0x%x\n", byte); - alsa_write_byte(midi, byte, buffer->timestamp); - if (desc->error < 0) break; - msg >>= 8; /* shift next byte into position */ - } - } - } - if (desc->error < 0) return pmHostError; - - VERBOSE printf("snd_seq_drain_output: 0x%x\n", seq); - desc->error = snd_seq_drain_output(seq); - if (desc->error < 0) return pmHostError; - - desc->error = pmNoError; - return pmNoError; -} -#endif - - -static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - VERBOSE printf("snd_seq_drain_output: 0x%p\n", seq); - desc->error = snd_seq_drain_output(seq); - if (desc->error < 0) return pmHostError; - - desc->error = pmNoError; - return pmNoError; -} - - -static PmError alsa_write_short(PmInternal *midi, PmEvent *event) -{ - int bytes = midi_message_length(event->message); - long msg = event->message; - int i; - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - for (i = 0; i < bytes; i++) { - unsigned char byte = msg; - VERBOSE printf("sending 0x%x\n", byte); - alsa_write_byte(midi, byte, event->timestamp); - if (desc->error < 0) break; - msg >>= 8; /* shift next byte into position */ - } - if (desc->error < 0) return pmHostError; - desc->error = pmNoError; - return pmNoError; -} - - -/* alsa_sysex -- implements begin_sysex and end_sysex */ -PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) { - return pmNoError; -} - - -static PmTimestamp alsa_synchronize(PmInternal *midi) -{ - return 0; /* linux implementation does not use this synchronize function */ - /* Apparently, Alsa data is relative to the time you send it, and there - is no reference. If this is true, this is a serious shortcoming of - Alsa. If not true, then PortMidi has a serious shortcoming -- it - should be scheduling relative to Alsa's time reference. */ -} - - -static void handle_event(snd_seq_event_t *ev) -{ - int device_id = ev->dest.port; - PmInternal *midi = descriptors[device_id].internalDescriptor; - PmEvent pm_ev; - PmTimeProcPtr time_proc = midi->time_proc; - PmTimestamp timestamp; - - /* time stamp should be in ticks, using our queue where 1 tick = 1ms */ - assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK); - - /* if no time_proc, just return "native" ticks (ms) */ - if (time_proc == NULL) { - timestamp = ev->time.tick; - } else { /* translate time to time_proc basis */ - snd_seq_queue_status_t *queue_status; - snd_seq_queue_status_alloca(&queue_status); - snd_seq_get_queue_status(seq, queue, queue_status); - /* return (now - alsa_now) + alsa_timestamp */ - timestamp = (*time_proc)(midi->time_info) + ev->time.tick - - snd_seq_queue_status_get_tick_time(queue_status); - } - pm_ev.timestamp = timestamp; - switch (ev->type) { - case SND_SEQ_EVENT_NOTEON: - pm_ev.message = Pm_Message(0x90 | ev->data.note.channel, - ev->data.note.note & 0x7f, - ev->data.note.velocity & 0x7f); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_NOTEOFF: - pm_ev.message = Pm_Message(0x80 | ev->data.note.channel, - ev->data.note.note & 0x7f, - ev->data.note.velocity & 0x7f); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_KEYPRESS: - pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel, - ev->data.note.note & 0x7f, - ev->data.note.velocity & 0x7f); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_CONTROLLER: - pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, - ev->data.control.param & 0x7f, - ev->data.control.value & 0x7f); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_PGMCHANGE: - pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel, - ev->data.control.value & 0x7f, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_CHANPRESS: - pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel, - ev->data.control.value & 0x7f, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_PITCHBEND: - pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel, - (ev->data.control.value + 0x2000) & 0x7f, - ((ev->data.control.value + 0x2000) >> 7) & 0x7f); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_CONTROL14: - if (ev->data.control.param < 0x20) { - pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, - ev->data.control.param, - (ev->data.control.value >> 7) & 0x7f); - pm_read_short(midi, &pm_ev); - pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, - ev->data.control.param + 0x20, - ev->data.control.value & 0x7f); - pm_read_short(midi, &pm_ev); - } else { - pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, - ev->data.control.param & 0x7f, - ev->data.control.value & 0x7f); - - pm_read_short(midi, &pm_ev); - } - break; - case SND_SEQ_EVENT_SONGPOS: - pm_ev.message = Pm_Message(0xf2, - ev->data.control.value & 0x7f, - (ev->data.control.value >> 7) & 0x7f); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_SONGSEL: - pm_ev.message = Pm_Message(0xf3, - ev->data.control.value & 0x7f, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_QFRAME: - pm_ev.message = Pm_Message(0xf1, - ev->data.control.value & 0x7f, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_START: - pm_ev.message = Pm_Message(0xfa, 0, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_CONTINUE: - pm_ev.message = Pm_Message(0xfb, 0, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_STOP: - pm_ev.message = Pm_Message(0xfc, 0, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_CLOCK: - pm_ev.message = Pm_Message(0xf8, 0, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_TUNE_REQUEST: - pm_ev.message = Pm_Message(0xf6, 0, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_RESET: - pm_ev.message = Pm_Message(0xff, 0, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_SENSING: - pm_ev.message = Pm_Message(0xfe, 0, 0); - pm_read_short(midi, &pm_ev); - break; - case SND_SEQ_EVENT_SYSEX: { - const BYTE *ptr = (const BYTE *) ev->data.ext.ptr; - /* assume there is one sysex byte to process */ - pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp); - break; - } - /* TODO: must handle port close messsage - PaulLiu */ - //case SND_SEQ_EVENT_UNSUBSCRIBE: { - //} - default: - printf("not handled type %x\n", ev->type); - } -} - - -static PmError alsa_poll(PmInternal *midi) -{ - snd_seq_event_t *ev; - /* expensive check for input data, gets data from device: */ - while (snd_seq_event_input_pending(seq, TRUE) > 0) { - /* cheap check on local input buffer */ - while (snd_seq_event_input_pending(seq, FALSE) > 0) { - /* check for and ignore errors, e.g. input overflow */ - /* note: if there's overflow, this should be reported - * all the way through to client. Since input from all - * devices is merged, we need to find all input devices - * and set all to the overflow state. - * NOTE: this assumes every input is ALSA based. - */ - int rslt = snd_seq_event_input(seq, &ev); - if (rslt >= 0) { - handle_event(ev); - } else if (rslt == -ENOSPC) { - int i; - for (i = 0; i < pm_descriptor_index; i++) { - if (descriptors[i].pub.input) { - PmInternal *midi = (PmInternal *) - descriptors[i].internalDescriptor; - /* careful, device may not be open! */ - if (midi) Pm_SetOverflow(midi->queue); - } - } - } - } - } - return pmNoError; -} - - -static unsigned int alsa_has_host_error(PmInternal *midi) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - return desc->error; -} - - -static void alsa_get_host_error(PmInternal *midi, char *msg, unsigned int len) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - int err = (pm_hosterror || desc->error); - get_alsa_error_text(msg, len, err); -} - - -pm_fns_node pm_linuxalsa_in_dictionary = { - none_write_short, - none_sysex, - none_sysex, - none_write_byte, - none_write_short, - none_write_flush, - alsa_synchronize, - alsa_in_open, - alsa_abort, - alsa_in_close, - alsa_poll, - alsa_has_host_error, - alsa_get_host_error -}; - -pm_fns_node pm_linuxalsa_out_dictionary = { - alsa_write_short, - alsa_sysex, - alsa_sysex, - alsa_write_byte, - alsa_write_short, /* short realtime message */ - alsa_write_flush, - alsa_synchronize, - alsa_out_open, - alsa_abort, - alsa_out_close, - none_poll, - alsa_has_host_error, - alsa_get_host_error -}; - - -/* pm_strdup -- copy a string to the heap. Use this rather than strdup so - * that we call pm_alloc, not malloc. This allows portmidi to avoid - * malloc which might cause priority inversion. Probably ALSA is going - * to call malloc anyway, so this extra work here may be pointless. - */ -char *pm_strdup(const char *s) -{ - int len = strlen(s); - char *dup = (char *) pm_alloc(len + 1); - strcpy(dup, s); - return dup; -} - - -PmError pm_linuxalsa_init( void ) -{ - int err; - snd_seq_client_info_t *cinfo; - snd_seq_port_info_t *pinfo; - unsigned int caps; - - /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this - * would cause messages to be dropped if the ALSA buffer fills up. - * The correct behavior is for writes to block until there is - * room to send all the data. The client should normally allocate - * a large enough buffer to avoid blocking on output. - * Now that blocking is enabled, the seq_event_input() will block - * if there is no input data. This is not what we want, so must - * call seq_event_input_pending() to avoid blocking. - */ - err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); - if (err < 0) return err; - - snd_seq_client_info_alloca(&cinfo); - snd_seq_port_info_alloca(&pinfo); - - snd_seq_client_info_set_client(cinfo, -1); - while (snd_seq_query_next_client(seq, cinfo) == 0) { - snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); - snd_seq_port_info_set_port(pinfo, -1); - while (snd_seq_query_next_port(seq, pinfo) == 0) { - if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM) - continue; /* ignore Timer and Announce ports on client 0 */ - caps = snd_seq_port_info_get_capability(pinfo); - if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE))) - continue; /* ignore if you cannot read or write port */ - if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) { - if (pm_default_output_device_id == -1) - pm_default_output_device_id = pm_descriptor_index; - pm_add_device("ALSA", - pm_strdup(snd_seq_port_info_get_name(pinfo)), - FALSE, - MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), - snd_seq_port_info_get_port(pinfo)), - &pm_linuxalsa_out_dictionary); - } - if (caps & SND_SEQ_PORT_CAP_SUBS_READ) { - if (pm_default_input_device_id == -1) - pm_default_input_device_id = pm_descriptor_index; - pm_add_device("ALSA", - pm_strdup(snd_seq_port_info_get_name(pinfo)), - TRUE, - MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), - snd_seq_port_info_get_port(pinfo)), - &pm_linuxalsa_in_dictionary); - } - } - } - return pmNoError; -} - - -void pm_linuxalsa_term(void) -{ - if (seq) { - snd_seq_close(seq); - pm_free(descriptors); - descriptors = NULL; - pm_descriptor_index = 0; - pm_descriptor_max = 0; - } -} +/*+ * pmlinuxalsa.c -- system specific definitions+ * + * written by:+ * Roger Dannenberg (port to Alsa 0.9.x)+ * Clemens Ladisch (provided code examples and invaluable consulting)+ * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation)+ */ ++/* omit this code if PMALSA is not defined -- this mechanism allows+ * selection of different MIDI interfaces (at compile time).+ */+#ifdef PMALSA++#include "stdlib.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "pmlinuxalsa.h"+#include "string.h"+#include "porttime.h"++#include <alsa/asoundlib.h>++/* I used many print statements to debug this code. I left them in the+ * source, and you can turn them on by changing false to true below:+ */+#define VERBOSE_ON 0+#define VERBOSE if (VERBOSE_ON)++#define MIDI_SYSEX 0xf0+#define MIDI_EOX 0xf7++#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9+#error needs ALSA 0.9.0 or later+#endif++/* to store client/port in the device descriptor */+#define MAKE_DESCRIPTOR(client, port) ((void*)(long)(((client) << 8) | (port)))+#define GET_DESCRIPTOR_CLIENT(info) ((((long)(info)) >> 8) & 0xff)+#define GET_DESCRIPTOR_PORT(info) (((long)(info)) & 0xff)++#define BYTE unsigned char++extern pm_fns_node pm_linuxalsa_in_dictionary;+extern pm_fns_node pm_linuxalsa_out_dictionary;++static snd_seq_t *seq = NULL; /* all input comes here, + output queue allocated on seq */+static int queue, queue_used; /* one for all ports, reference counted */++#define PORT_IS_CLOSED -999999++typedef struct alsa_info_struct {+ int is_virtual;+ int client;+ int port;+ int this_port;+ int in_sysex;+ snd_midi_event_t *parser;+} alsa_info_node, *alsa_info_type;+++/* get_alsa_error_text -- copy error text to potentially short string */+/**/+static void get_alsa_error_text(char *msg, int len, int err)+{+ int errlen = strlen(snd_strerror(err));+ if (errlen > 0 && errlen < len) {+ strcpy(msg, snd_strerror(err));+ } else if (len > 20) {+ sprintf(msg, "Alsa error %d", err);+ } else {+ msg[0] = 0;+ }+}+++static PmError check_hosterror(int err)+{+ if (err < 0) {+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);+ pm_hosterror = TRUE;+ return pmHostError;+ }+ return pmNoError;+}+++/* queue is shared by both input and output, reference counted */+static PmError alsa_use_queue(void)+{+ int err = 0;+ if (queue_used == 0) {+ snd_seq_queue_tempo_t *tempo;++ queue = snd_seq_alloc_queue(seq);+ if (queue < 0) {+ return check_hosterror(queue);+ }+ snd_seq_queue_tempo_alloca(&tempo);+ snd_seq_queue_tempo_set_tempo(tempo, 480000);+ snd_seq_queue_tempo_set_ppq(tempo, 480);+ err = snd_seq_set_queue_tempo(seq, queue, tempo);+ if (err < 0) {+ return check_hosterror(err);+ }+ snd_seq_start_queue(seq, queue, NULL);+ snd_seq_drain_output(seq);+ }+ ++queue_used;+ return pmNoError;+}+++static void alsa_unuse_queue(void)+{+ if (--queue_used == 0) {+ snd_seq_stop_queue(seq, queue, NULL);+ snd_seq_drain_output(seq);+ snd_seq_free_queue(seq, queue);+ VERBOSE printf("queue freed\n");+ }+}+++/* midi_message_length -- how many bytes in a message? */+static int midi_message_length(PmMessage message)+{+ message &= 0xff;+ if (message < 0x80) {+ return 0;+ } else if (message < 0xf0) {+ static const int length[] = {3, 3, 3, 3, 2, 2, 3};+ return length[(message - 0x80) >> 4];+ } else {+ static const int length[] = {+ -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1};+ return length[message - 0xf0];+ }+}+++static alsa_info_type alsa_info_create(int client_port, long id, int is_virtual)+{+ alsa_info_type info = (alsa_info_type) pm_alloc(sizeof(alsa_info_node));+ info->is_virtual = is_virtual;+ info->this_port = id;+ info->client = GET_DESCRIPTOR_CLIENT(client_port);+ info->port = GET_DESCRIPTOR_PORT(client_port);+ info->in_sysex = 0;+ return info;+} +++/* search system dependent extra parameters for string */+static const char *get_sysdep_name(enum PmSysDepPropertyKey key,+ PmSysDepInfo *info)+{+ /* the version where all current properties were introduced is 210 */+ if (info && info->structVersion >= 210) {+ int i;+ for (i = 0; i < info->length; i++) { /* search for key */+ if (info->properties[i].key == key) {+ return info->properties[i].value;+ }+ }+ }+ return NULL;+}+++static void maybe_set_client_name(PmSysDepInfo *driverInfo)+{+ /* make sure seq is created and we have info */+ if (!seq || !driverInfo) {+ return;+ }+ + const char *client_name = get_sysdep_name(pmKeyAlsaClientName,+ (PmSysDepInfo *) driverInfo);+ if (client_name) {+ snd_seq_set_client_name(seq, client_name);+ VERBOSE printf("maybe_set_client_name set client to %s\n", client_name);+ }+} +++static PmError alsa_out_open(PmInternal *midi, void *driverInfo) +{+ int id = midi->device_id;+ void *client_port = pm_descriptors[id].descriptor;+ alsa_info_type ainfo = alsa_info_create((long) client_port, id,+ pm_descriptors[id].pub.is_virtual);+ snd_seq_port_info_t *pinfo;+ int err = 0;+ int using_the_queue = 0;++ if (!ainfo) return pmInsufficientMemory;+ midi->api_info = ainfo;++ snd_seq_port_info_alloca(&pinfo);+ if (!ainfo->is_virtual) {+ snd_seq_port_info_set_port(pinfo, id);+ snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ);+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION);+ const char *port_name = get_sysdep_name(pmKeyAlsaPortName,+ (PmSysDepInfo *) driverInfo);+ if (port_name) {+ snd_seq_port_info_set_name(pinfo, port_name);+ }+ snd_seq_port_info_set_port_specified(pinfo, 1);++ err = snd_seq_create_port(seq, pinfo);+ if (err < 0) goto free_ainfo;++ }+ + err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &ainfo->parser);+ if (err < 0) goto free_this_port;++ if (midi->latency > 0) { /* must delay output using a queue */+ err = alsa_use_queue();+ if (err < 0) goto free_parser;+ using_the_queue++;+ }++ if (!ainfo->is_virtual) {+ err = snd_seq_connect_to(seq, ainfo->this_port, ainfo->client,+ ainfo->port);+ if (err < 0) goto unuse_queue; /* clean up and return on error */+ }++ maybe_set_client_name(driverInfo);++ return pmNoError;++ unuse_queue:+ if (using_the_queue > 0) /* only for latency>0 case */+ alsa_unuse_queue();+ free_parser:+ snd_midi_event_free(ainfo->parser);+ free_this_port:+ snd_seq_delete_port(seq, ainfo->this_port);+ free_ainfo:+ pm_free(ainfo);+ return check_hosterror(err);+}+ ++static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp)+{+ alsa_info_type info = (alsa_info_type) midi->api_info;+ snd_seq_event_t ev;+ int err = 0;++ snd_seq_ev_clear(&ev);+ if (snd_midi_event_encode_byte(info->parser, byte, &ev) == 1) {+ if (info->is_virtual) {+ snd_seq_ev_set_subs(&ev);+ } else {+ snd_seq_ev_set_dest(&ev, info->client, info->port);+ }+ snd_seq_ev_set_source(&ev, info->this_port);+ if (midi->latency > 0) {+ /* compute relative time of event = timestamp - now + latency */+ PmTimestamp now = (midi->time_proc ? + midi->time_proc(midi->time_info) : + Pt_Time());+ int when = timestamp;+ /* if timestamp is zero, send immediately */+ /* otherwise compute time delay and use delay if positive */+ if (when == 0) when = now;+ when = (when - now) + midi->latency;+ if (when < 0) when = 0;+ VERBOSE printf("timestamp %d now %d latency %d, ", + (int) timestamp, (int) now, midi->latency);+ VERBOSE printf("scheduling event after %d\n", when);+ /* message is sent in relative ticks, where 1 tick = 1 ms */+ snd_seq_ev_schedule_tick(&ev, queue, 1, when);+ /* NOTE: for cases where the user does not supply a time function,+ we could optimize the code by not starting Pt_Time and using+ the alsa tick time instead. I didn't do this because it would+ entail changing the queue management to start the queue tick+ count when PortMidi is initialized and keep it running until+ PortMidi is terminated. (This should be simple, but it's not+ how the code works now.) -RBD */+ } else { /* send event out without queueing */+ VERBOSE printf("direct\n");+ /* ev.queue = SND_SEQ_QUEUE_DIRECT;+ ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */+ snd_seq_ev_set_direct(&ev);+ }+ VERBOSE printf("sending event, timestamp %d (%d+%dns) (%s, %s)\n",+ ev.time.tick, ev.time.time.tv_sec, ev.time.time.tv_nsec,+ (ev.flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),+ (ev.flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));+ err = snd_seq_event_output(seq, &ev);+ }+ return check_hosterror(err);+}+++static PmError alsa_out_close(PmInternal *midi)+{+ alsa_info_type info = (alsa_info_type) midi->api_info;+ int err = 0;+ int error2 = 0;+ if (!info) return pmBadPtr;++ if (info->this_port != PORT_IS_CLOSED) {+ if (!info->is_virtual) {+ err = snd_seq_disconnect_to(seq, info->this_port,+ info->client, info->port);+ /* even if there was an error, we still try to delete the port */+ error2 = snd_seq_delete_port(seq, info->this_port);++ if (!err) { /* retain original error if there was one */+ err = error2; /* otherwise, use port delete status */+ }+ }+ }+ if (midi->latency > 0) alsa_unuse_queue();+ snd_midi_event_free(info->parser);+ midi->api_info = NULL; /* destroy the pointer to signify "closed" */+ pm_free(info);+ return check_hosterror(err);+}+++static PmError alsa_create_virtual(int is_input, const char *name,+ void *device_info)+{+ snd_seq_port_info_t *pinfo;+ int err;+ int client, port;+ + /* we need the id to set the port. */+ PmDeviceID id = pm_add_device("ALSA", name, is_input, TRUE, NULL,+ (is_input ? &pm_linuxalsa_in_dictionary :+ &pm_linuxalsa_out_dictionary));+ if (id < 0) { /* error -- out of memory? */+ return id;+ }+ snd_seq_port_info_alloca(&pinfo);+ snd_seq_port_info_set_capability(pinfo,+ (is_input ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE :+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ));+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |+ SND_SEQ_PORT_TYPE_APPLICATION);+ snd_seq_port_info_set_name(pinfo, name);+ snd_seq_port_info_set_port(pinfo, id);+ snd_seq_port_info_set_port_specified(pinfo, 1);+ /* next 3 lines needed to generate timestamp - PaulLiu */+ snd_seq_port_info_set_timestamping(pinfo, 1);+ snd_seq_port_info_set_timestamp_real(pinfo, 0);+ snd_seq_port_info_set_timestamp_queue(pinfo, queue);++ err = snd_seq_create_port(seq, pinfo);+ if (err < 0) {+ pm_undo_add_device(id);+ return check_hosterror(err);+ }++ client = snd_seq_port_info_get_client(pinfo);+ port = snd_seq_port_info_get_port(pinfo);+ pm_descriptors[id].descriptor = MAKE_DESCRIPTOR(client, port);+ return id;+}+++ static PmError alsa_delete_virtual(PmDeviceID id)+ {+ int err = snd_seq_delete_port(seq, id);+ return check_hosterror(err);+ }+ ++static PmError alsa_in_open(PmInternal *midi, void *driverInfo)+{+ int id = midi->device_id;+ void *client_port = pm_descriptors[id].descriptor;+ alsa_info_type ainfo = alsa_info_create((long) client_port, id,+ pm_descriptors[id].pub.is_virtual);+ snd_seq_port_info_t *pinfo;+ snd_seq_port_subscribe_t *sub;+ snd_seq_addr_t addr;+ int err = 0;+ int is_virtual = pm_descriptors[id].pub.is_virtual;+ + if (!ainfo) return pmInsufficientMemory;+ midi->api_info = ainfo;++ err = alsa_use_queue();+ if (err < 0) goto free_ainfo;++ snd_seq_port_info_alloca(&pinfo);+ if (is_virtual) {+ ainfo->is_virtual = TRUE;+ if (snd_seq_get_port_info(seq, ainfo->port, pinfo)) {+ pinfo = NULL;+ goto free_ainfo;+ }+ } else {+ /* create a port for this alsa client (seq) where the port+ number matches the portmidi device ID of the input device */+ snd_seq_port_info_set_port(pinfo, id);+ snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_WRITE);++ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION);+ snd_seq_port_info_set_port_specified(pinfo, 1);+ + const char *port_name = get_sysdep_name(pmKeyAlsaPortName,+ (PmSysDepInfo *) driverInfo);+ if (port_name) {+ snd_seq_port_info_set_name(pinfo, port_name);+ }+ + err = snd_seq_create_port(seq, pinfo);+ if (err < 0) goto free_queue;++ /* forward messages from input to this alsa client, so this+ * alsa client is the destination, and the destination port is the+ * port we just created using the device ID as port number+ */+ snd_seq_port_subscribe_alloca(&sub);+ addr.client = snd_seq_client_id(seq);+ addr.port = ainfo->this_port;+ snd_seq_port_subscribe_set_dest(sub, &addr);++ /* forward from the sender which is the device named by + client and port */+ addr.client = ainfo->client;+ addr.port = ainfo->port;+ snd_seq_port_subscribe_set_sender(sub, &addr);+ snd_seq_port_subscribe_set_time_update(sub, 1);+ /* this doesn't seem to work: messages come in with real timestamps */+ snd_seq_port_subscribe_set_time_real(sub, 0);+ err = snd_seq_subscribe_port(seq, sub);+ if (err < 0) goto free_this_port; /* clean up and return on error */+ }++ maybe_set_client_name(driverInfo);++ return pmNoError;+ free_this_port:+ snd_seq_delete_port(seq, ainfo->this_port);+ free_queue:+ alsa_unuse_queue();+ free_ainfo:+ pm_free(ainfo);+ return check_hosterror(err);+}++static PmError alsa_in_close(PmInternal *midi)+{+ int err = 0;+ alsa_info_type info = (alsa_info_type) midi->api_info;+ if (!info) return pmBadPtr;+ /* virtual ports stay open because the represent devices */+ if (!info->is_virtual && info->this_port != PORT_IS_CLOSED) {+ err = snd_seq_delete_port(seq, info->this_port);+ }+ alsa_unuse_queue();+ midi->api_info = NULL;+ pm_free(info);+ return check_hosterror(err);+}+ ++static PmError alsa_abort(PmInternal *midi)+{+ /* NOTE: ALSA documentation is vague. This is supposed to + * remove any pending output messages. If you can test and + * confirm this code is correct, please update this comment. -RBD+ */+ /* Unfortunately, I can't even compile it -- my ALSA version + * does not implement snd_seq_remove_events_t, so this does+ * not compile. I'll try again, but it looks like I'll need to+ * upgrade my entire Linux OS -RBD+ */+ /*+ info_type info = (info_type) midi->api_info;+ snd_seq_remove_events_t info;+ snd_seq_addr_t addr;+ addr.client = info->client;+ addr.port = info->port;+ snd_seq_remove_events_set_dest(&info, &addr);+ snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);+ pm_hosterror = snd_seq_remove_events(seq, &info);+ if (pm_hosterror) {+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, + pm_hosterror);+ return pmHostError;+ }+ */+ printf("WARNING: alsa_abort not implemented\n");+ return pmNoError;+}+++static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)+{+ int err;+ alsa_info_type info = (alsa_info_type) midi->api_info;+ if (!info) return pmBadPtr;+ VERBOSE printf("snd_seq_drain_output: %p\n", seq);+ err = snd_seq_drain_output(seq);+ return check_hosterror(err);+}+++static PmError alsa_write_short(PmInternal *midi, PmEvent *event)+{+ int bytes = midi_message_length(event->message);+ PmMessage msg = event->message;+ int i;+ alsa_info_type info = (alsa_info_type) midi->api_info;+ if (!info) return pmBadPtr;+ for (i = 0; i < bytes; i++) {+ unsigned char byte = msg;+ VERBOSE printf("sending 0x%x\n", byte);+ alsa_write_byte(midi, byte, event->timestamp);+ if (pm_hosterror) break;+ msg >>= 8; /* shift next byte into position */+ }+ if (pm_hosterror) return pmHostError;+ return pmNoError;+}+++/* alsa_sysex -- implements begin_sysex and end_sysex */+PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) {+ return pmNoError;+}+++static PmTimestamp alsa_synchronize(PmInternal *midi)+{+ return 0; /* linux implementation does not use this synchronize function */+ /* Apparently, Alsa data is relative to the time you send it, and there+ is no reference. If this is true, this is a serious shortcoming of+ Alsa. If not true, then PortMidi has a serious shortcoming -- it + should be scheduling relative to Alsa's time reference. */+}+++static void handle_event(snd_seq_event_t *ev)+{+ int device_id = ev->dest.port;++ assert(device_id >= 0 && device_id < pm_descriptor_len);+ assert(pm_descriptors[device_id].pub.name);++ PmInternal *midi = pm_descriptors[device_id].pm_internal;+ /* There is a race condition when closing a device and+ continuing to poll other open devices. The closed device may+ have outstanding events from before the close operation.+ */+ if (!midi) {+ return;+ }+ PmEvent pm_ev;++ /* Copilot says: "snd_seq_event_input() will not return events for a+ * MIDI output device." However, this does not seem to be true, e.g.,+ * for MIDIFLEX 4 on Arch Linux, so we want to check and ignore the+ * call if this is an output device.+ */+ if (!midi->is_input) {+ return;+ }++ if (!midi->time_proc) { /* extra sanity check */+ return;+ }++ PmTimestamp timestamp = midi->time_proc(midi->time_info);++ /* time stamp should be in ticks, using our queue where 1 tick = 1ms */+ /* assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);+ Currently, event timestamp is ignored. See long note below. + */+ VERBOSE {+ /* translate time to time_proc basis */+ snd_seq_queue_status_t *queue_status;+ snd_seq_queue_status_alloca(&queue_status);+ snd_seq_get_queue_status(seq, queue, queue_status);+ printf("handle_event: alsa_now %d, "+ "event timestamp %d (%d+%dns) (%s, %s)\n",+ snd_seq_queue_status_get_tick_time(queue_status),+ ev->time.tick, ev->time.time.tv_sec, ev->time.time.tv_nsec,+ (ev->flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),+ (ev->flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));+ /* OLD: portmidi timestamp is (now - alsa_now) + alsa_timestamp */+ /* timestamp = (*time_proc)(midi->time_info) + ev->time.tick -+ snd_seq_queue_status_get_tick_time(queue_status); */+ }+ /* CURRENT: portmidi timestamp is "now". In a test, timestamps from+ * hardware (MIDI over USB) were timestamped with the current ALSA+ * time (snd_seq_queue_status_get_tick_time) and flags indicating + * absolute ticks, but timestamps from another application's virtual+ * port, sent direct with 0 absolute ticks, were received with a + * large value that is apparently the time since the start time of+ * the other application. Without any reference to our local time,+ * this seems useless. PortMidi is supposed to return the local+ * PortMidi time of the arrival of the message, so the best we can+ * do is set the timestamp to our local clock. This seems to be a+ * design flaw in ALSA -- I pointed this out a decade ago, but if+ * there is a workaround, I'd still like to know. Maybe there is a+ * way to use absolute real time and maybe that's sharable across+ * applications by referencing the system time?+ */+ pm_ev.timestamp = timestamp;+ switch (ev->type) {+ case SND_SEQ_EVENT_NOTEON:+ pm_ev.message = Pm_Message(0x90 | ev->data.note.channel,+ ev->data.note.note & 0x7f,+ ev->data.note.velocity & 0x7f);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_NOTEOFF:+ pm_ev.message = Pm_Message(0x80 | ev->data.note.channel,+ ev->data.note.note & 0x7f,+ ev->data.note.velocity & 0x7f);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_KEYPRESS:+ pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel,+ ev->data.note.note & 0x7f,+ ev->data.note.velocity & 0x7f);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_CONTROLLER:+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,+ ev->data.control.param & 0x7f,+ ev->data.control.value & 0x7f);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_PGMCHANGE:+ pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel,+ ev->data.control.value & 0x7f, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_CHANPRESS:+ pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel,+ ev->data.control.value & 0x7f, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_PITCHBEND:+ pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel,+ (ev->data.control.value + 0x2000) & 0x7f,+ ((ev->data.control.value + 0x2000) >> 7) & 0x7f);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_CONTROL14:+ if (ev->data.control.param < 0x20) {+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,+ ev->data.control.param,+ (ev->data.control.value >> 7) & 0x7f);+ pm_read_short(midi, &pm_ev);+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,+ ev->data.control.param + 0x20,+ ev->data.control.value & 0x7f);+ pm_read_short(midi, &pm_ev);+ } else {+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,+ ev->data.control.param & 0x7f,+ ev->data.control.value & 0x7f);++ pm_read_short(midi, &pm_ev);+ }+ break;+ case SND_SEQ_EVENT_SONGPOS:+ pm_ev.message = Pm_Message(0xf2,+ ev->data.control.value & 0x7f,+ (ev->data.control.value >> 7) & 0x7f);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_SONGSEL:+ pm_ev.message = Pm_Message(0xf3,+ ev->data.control.value & 0x7f, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_QFRAME:+ pm_ev.message = Pm_Message(0xf1,+ ev->data.control.value & 0x7f, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_START:+ pm_ev.message = Pm_Message(0xfa, 0, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_CONTINUE:+ pm_ev.message = Pm_Message(0xfb, 0, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_STOP:+ pm_ev.message = Pm_Message(0xfc, 0, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_CLOCK:+ pm_ev.message = Pm_Message(0xf8, 0, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_TUNE_REQUEST:+ pm_ev.message = Pm_Message(0xf6, 0, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_RESET:+ pm_ev.message = Pm_Message(0xff, 0, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_SENSING:+ pm_ev.message = Pm_Message(0xfe, 0, 0);+ pm_read_short(midi, &pm_ev);+ break;+ case SND_SEQ_EVENT_SYSEX: {+ const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;+ /* assume there is one sysex byte to process */+ pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);+ break;+ }+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: {+ /* this happens if you have an input port open and the + * device or application with virtual ports closes. We+ * mark the port as closed to avoid closing a 2nd time+ * when Pm_Close() is called.+ */+ alsa_info_type info = (alsa_info_type) midi->api_info;+ /* printf("SND_SEQ_EVENT_UNSUBSCRIBE message\n"); */+ info->this_port = PORT_IS_CLOSED;+ break;+ }+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:+ break; /* someone connected to a virtual output port, not reported */+ default:+ printf("portmidi handle_event: not handled type %x\n", ev->type);+ break;+ }+}+++static PmError alsa_poll(PmInternal *midi)+{+ if (!midi) {+ return pmBadPtr;+ }+ snd_seq_event_t *ev;+ /* expensive check for input data, gets data from device: */+ while (snd_seq_event_input_pending(seq, TRUE) > 0) {+ /* cheap check on local input buffer */+ while (snd_seq_event_input_pending(seq, FALSE) > 0) {+ /* check for and ignore errors, e.g. input overflow */+ /* note: if there's overflow, this should be reported+ * all the way through to client. Since input from all+ * devices is merged, we need to find all input devices+ * and set all to the overflow state.+ * NOTE: this assumes every input is ALSA based.+ */+ int rslt = snd_seq_event_input(seq, &ev);+ if (rslt >= 0) {+ handle_event(ev);+ } else if (rslt == -ENOSPC) {+ int i;+ for (i = 0; i < pm_descriptor_len; i++) {+ if (pm_descriptors[i].pub.input) {+ PmInternal *midi_i = pm_descriptors[i].pm_internal;+ /* careful, device may not be open! */+ if (midi_i) Pm_SetOverflow(midi_i->queue);+ }+ }+ }+ }+ }+ return pmNoError;+}+++static unsigned int alsa_check_host_error(PmInternal *midi)+{+ return FALSE;+}+++pm_fns_node pm_linuxalsa_in_dictionary = {+ none_write_short,+ none_sysex,+ none_sysex,+ none_write_byte,+ none_write_short,+ none_write_flush,+ alsa_synchronize,+ alsa_in_open,+ alsa_abort,+ alsa_in_close,+ alsa_poll,+ alsa_check_host_error+};++pm_fns_node pm_linuxalsa_out_dictionary = {+ alsa_write_short,+ alsa_sysex,+ alsa_sysex,+ alsa_write_byte,+ alsa_write_short, /* short realtime message */+ alsa_write_flush,+ alsa_synchronize,+ alsa_out_open, + alsa_abort, + alsa_out_close,+ none_poll,+ alsa_check_host_error+};+++/* pm_strdup -- copy a string to the heap. Use this rather than strdup so + * that we call pm_alloc, not malloc. This allows portmidi to avoid + * malloc which might cause priority inversion. Probably ALSA is going+ * to call malloc anyway, so this extra work here may be pointless.+ */+char *pm_strdup(const char *s)+{+ int len = strlen(s);+ char *dup = (char *) pm_alloc(len + 1);+ strcpy(dup, s);+ return dup;+}+++PmError pm_linuxalsa_init(void)+{+ int err;+ snd_seq_client_info_t *cinfo;+ snd_seq_port_info_t *pinfo;+ unsigned int caps;++ /* Register interface ALSA with create_virtual fn */+ pm_add_interf("ALSA", &alsa_create_virtual, &alsa_delete_virtual);++ /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this + * would cause messages to be dropped if the ALSA buffer fills up.+ * The correct behavior is for writes to block until there is + * room to send all the data. The client should normally allocate+ * a large enough buffer to avoid blocking on output. + * Now that blocking is enabled, the seq_event_input() will block+ * if there is no input data. This is not what we want, so must+ * call seq_event_input_pending() to avoid blocking.+ */+ err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);+ if (err < 0) goto error_return;+ + snd_seq_client_info_alloca(&cinfo);+ snd_seq_port_info_alloca(&pinfo);++ snd_seq_client_info_set_client(cinfo, -1);+ while (snd_seq_query_next_client(seq, cinfo) == 0) {+ snd_seq_port_info_set_client(pinfo,+ snd_seq_client_info_get_client(cinfo));+ snd_seq_port_info_set_port(pinfo, -1);+ while (snd_seq_query_next_port(seq, pinfo) == 0) {+ if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)+ continue; /* ignore Timer and Announce ports on client 0 */+ caps = snd_seq_port_info_get_capability(pinfo);+ if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ |+ SND_SEQ_PORT_CAP_SUBS_WRITE)))+ continue; /* ignore if you cannot read or write port */+ if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) {+ if (pm_default_output_device_id == -1) + pm_default_output_device_id = pm_descriptor_len;+ pm_add_device("ALSA",+ pm_strdup(snd_seq_port_info_get_name(pinfo)),+ FALSE, FALSE,+ MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),+ snd_seq_port_info_get_port(pinfo)),+ &pm_linuxalsa_out_dictionary);+ }+ if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {+ if (pm_default_input_device_id == -1) + pm_default_input_device_id = pm_descriptor_len;+ pm_add_device("ALSA",+ pm_strdup(snd_seq_port_info_get_name(pinfo)),+ TRUE, FALSE,+ MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),+ snd_seq_port_info_get_port(pinfo)),+ &pm_linuxalsa_in_dictionary);+ }+ }+ }+ return pmNoError;+ error_return:+ pm_linuxalsa_term(); /* clean up */+ return check_hosterror(err);+}+++void pm_linuxalsa_term(void)+{+ if (seq) {+ snd_seq_close(seq);+ pm_free(pm_descriptors);+ pm_descriptors = NULL;+ pm_descriptor_len = 0;+ pm_descriptor_max = 0;+ }+}++#endif
+ portmidi/pm_linux/pmlinuxnull.c view
@@ -0,0 +1,31 @@+/*+ * pmlinuxnull.c -- system specific definitions+ * + * written by:+ * Roger Dannenberg+ * + * If there is no ALSA, you can define PMNULL and build PortMidi. It will+ * not report any devices, so you will not be able to open any, but if+ * you wanted to disable MIDI from some application, this could be used.+ * Mainly, this code shows the possibility of supporting multiple+ * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux.+ * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA.+ */++#ifdef PMNULL++#include "portmidi.h"+#include "pmlinuxnull.h"+++PmError pm_linuxnull_init(void)+{+ return pmNoError;+}+++void pm_linuxnull_term(void)+{+}++#endif
+ portmidi/pm_linux/pmlinuxnull.h view
@@ -0,0 +1,6 @@+/* pmlinuxnull.h -- system-specific definitions */++PmError pm_linuxnull_init(void);+void pm_linuxnull_term(void);++
portmidi/pm_mac/pmmac.c view
@@ -1,42 +1,44 @@-/* pmmac.c -- PortMidi os-dependent code */ - -/* This file only needs to implement: -pm_init(), which calls various routines to register the -available midi devices, -Pm_GetDefaultInputDeviceID(), and -Pm_GetDefaultOutputDeviceID(). -It is seperate from pmmacosxcm because we might want to register -non-CoreMIDI devices. -*/ - -#include "stdlib.h" -#include "portmidi.h" -#include "pmmacosxcm.h" - -PmError pm_init() -{ - return pm_macosxcm_init(); -} - -void pm_term(void) -{ - pm_macosxcm_term(); -} - -PmDeviceID pm_default_input_device_id = -1; -PmDeviceID pm_default_output_device_id = -1; - -PmDeviceID Pm_GetDefaultInputDeviceID() -{ - return pm_default_input_device_id; -} - -PmDeviceID Pm_GetDefaultOutputDeviceID() { - return pm_default_output_device_id; -} - -void *pm_alloc(size_t s) { return malloc(s); } - -void pm_free(void *ptr) { free(ptr); } - - +/* pmmac.c -- PortMidi os-dependent code */++/* This file only needs to implement:+pm_init(), which calls various routines to register the +available midi devices,+Pm_GetDefaultInputDeviceID(), and+Pm_GetDefaultOutputDeviceID().+It is seperate from pmmacosxcm because we might want to register+non-CoreMIDI devices.+*/++#include "stdlib.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "pmmacosxcm.h"++void pm_init(void)+{+ pm_macosxcm_init();+}+++void pm_term(void)+{+ pm_macosxcm_term();+}++PmDeviceID Pm_GetDefaultInputDeviceID(void) +{+ Pm_Initialize();+ return pm_default_input_device_id;+}++PmDeviceID Pm_GetDefaultOutputDeviceID(void) {+ Pm_Initialize();+ return pm_default_output_device_id;+}++void *pm_alloc(size_t s) { return malloc(s); }++void pm_free(void *ptr) { free(ptr); }++
− portmidi/pm_mac/pmmac.h
@@ -1,4 +0,0 @@-/* pmmac.h */ - -extern PmDeviceID pm_default_input_device_id; -extern PmDeviceID pm_default_output_device_id;
portmidi/pm_mac/pmmacosxcm.c view
@@ -9,927 +9,1188 @@ */ /* Notes:- since the input and output streams are represented by MIDIEndpointRef- values and almost no other state, we store the MIDIEndpointRef on- descriptors[midi->device_id].descriptor. The only other state we need- is for errors: we need to know if there is an error and if so, what is- the error text. We use a structure with two kinds of- host error: "error" and "callback_error". That way, asynchronous callbacks- do not interfere with other error information.- - OS X does not seem to have an error-code-to-text function, so we will- just use text messages instead of error codes.- */--#include <stdlib.h>--//#define CM_DEBUG 1--#include "portmidi.h"-#include "pmutil.h"-#include "pminternal.h"-#include "porttime.h"-#include "pmmac.h"-#include "pmmacosxcm.h"--#include <stdio.h>-#include <string.h>--#include <CoreServices/CoreServices.h>-#include <CoreMIDI/MIDIServices.h>-#include <CoreAudio/HostTime.h>--#define PACKET_BUFFER_SIZE 1024--/* this is very strange: if I put in a reasonable - number here, e.g. 128, which would allow sysex data- to be sent 128 bytes at a time, then I lose sysex- data in my loopback test. With a buffer size of 4,- we put at most 4 bytes in a packet (but maybe many- packets in a packetList), and everything works fine.- */-#define SYSEX_BUFFER_SIZE 4--#define VERBOSE_ON 1-#define VERBOSE if (VERBOSE_ON)--#define MIDI_SYSEX 0xf0-#define MIDI_EOX 0xf7-#define MIDI_STATUS_MASK 0x80--static MIDIClientRef client = NULL; /* Client handle to the MIDI server */-static MIDIPortRef portIn = NULL; /* Input port handle */-static MIDIPortRef portOut = NULL; /* Output port handle */--extern pm_fns_node pm_macosx_in_dictionary;-extern pm_fns_node pm_macosx_out_dictionary;--typedef struct midi_macosxcm_struct {- unsigned long sync_time; /* when did we last determine delta? */- UInt64 delta; /* difference between stream time and real time in ns */- UInt64 last_time; /* last output time */- int first_message; /* tells midi_write to sychronize timestamps */- int sysex_mode; /* middle of sending sysex */- unsigned long sysex_word; /* accumulate data when receiving sysex */- unsigned int sysex_byte_count; /* count how many received */- char error[PM_HOST_ERROR_MSG_LEN];- char callback_error[PM_HOST_ERROR_MSG_LEN];- Byte packetBuffer[PACKET_BUFFER_SIZE];- MIDIPacketList *packetList; /* a pointer to packetBuffer */- MIDIPacket *packet;- Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */- MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */- /* allow for running status (is running status possible here? -rbd): -cpr */- unsigned char last_command; - long last_msg_length;-} midi_macosxcm_node, *midi_macosxcm_type;--/* private function declarations */-MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);-PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);--char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);---static int-midi_length(long 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];-}--static PmTimestamp midi_synchronize(PmInternal *midi)-{- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- UInt64 pm_stream_time_2 = - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());- PmTimestamp real_time;- UInt64 pm_stream_time;- /* if latency is zero and this is an output, there is no - time reference and midi_synchronize should never be called */- assert(midi->time_proc);- assert(!(midi->write_flag && midi->latency == 0));- do {- /* read real_time between two reads of stream time */- pm_stream_time = pm_stream_time_2;- real_time = (*midi->time_proc)(midi->time_info);- pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());- /* repeat if more than 0.5 ms has elapsed */- } while (pm_stream_time_2 > pm_stream_time + 500000);- m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);- m->sync_time = real_time;- return real_time;-}---static void-process_packet(MIDIPacket *packet, PmEvent *event, - PmInternal *midi, midi_macosxcm_type m)-{- /* handle a packet of MIDI messages from CoreMIDI */- /* there may be multiple short messages in one packet (!) */- unsigned int remaining_length = packet->length;- unsigned char *cur_packet_data = packet->data;- while (remaining_length > 0) {- if (cur_packet_data[0] == MIDI_SYSEX ||- /* are we in the middle of a sysex message? */- (m->last_command == 0 &&- !(cur_packet_data[0] & MIDI_STATUS_MASK))) {- m->last_command = 0; /* no running status */- unsigned int amt = pm_read_bytes(midi, cur_packet_data, - remaining_length, - event->timestamp);- remaining_length -= amt;- cur_packet_data += amt;- } else if (cur_packet_data[0] == MIDI_EOX) {- /* this should never happen, because pm_read_bytes should- * get and read all EOX bytes*/- midi->sysex_in_progress = FALSE;- m->last_command = 0;- } else if (cur_packet_data[0] & MIDI_STATUS_MASK) {- /* compute the length of the next (short) msg in packet */- unsigned int cur_message_length = midi_length(cur_packet_data[0]);- if (cur_message_length > remaining_length) {-#ifdef DEBUG- printf("PortMidi debug msg: not enough data");-#endif- /* since there's no more data, we're done */- return;- }- m->last_msg_length = cur_message_length;- m->last_command = cur_packet_data[0];- switch (cur_message_length) {- case 1:- event->message = Pm_Message(cur_packet_data[0], 0, 0);- break; - case 2:- event->message = Pm_Message(cur_packet_data[0], - cur_packet_data[1], 0);- break;- case 3:- event->message = Pm_Message(cur_packet_data[0],- cur_packet_data[1], - cur_packet_data[2]);- break;- default:- /* PortMIDI internal error; should never happen */- assert(cur_message_length == 1);- return; /* give up on packet if continued after assert */- }- pm_read_short(midi, event);- remaining_length -= m->last_msg_length;- cur_packet_data += m->last_msg_length;- } else if (m->last_msg_length > remaining_length + 1) {- /* we have running status, but not enough data */-#ifdef DEBUG- printf("PortMidi debug msg: not enough data in CoreMIDI packet");-#endif- /* since there's no more data, we're done */- return;- } else { /* output message using running status */- switch (m->last_msg_length) {- case 1:- event->message = Pm_Message(m->last_command, 0, 0);- break;- case 2:- event->message = Pm_Message(m->last_command, - cur_packet_data[0], 0);- break;- case 3:- event->message = Pm_Message(m->last_command, - cur_packet_data[0], - cur_packet_data[1]);- break;- default:- /* last_msg_length is invalid -- internal PortMIDI error */- assert(m->last_msg_length == 1);- }- pm_read_short(midi, event);- remaining_length -= (m->last_msg_length - 1);- cur_packet_data += (m->last_msg_length - 1);- }- }-}----/* called when MIDI packets are received */-static void-readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)-{- PmInternal *midi;- midi_macosxcm_type m;- PmEvent event;- MIDIPacket *packet;- unsigned int packetIndex;- unsigned long now;- unsigned int status;- -#ifdef CM_DEBUG- printf("readProc: numPackets %d: ", newPackets->numPackets);-#endif-- /* Retrieve the context for this connection */- midi = (PmInternal *) connRefCon;- m = (midi_macosxcm_type) midi->descriptor;- assert(m);- - /* synchronize time references every 100ms */- now = (*midi->time_proc)(midi->time_info);- if (m->first_message || m->sync_time + 100 /*ms*/ < now) { - /* time to resync */- now = midi_synchronize(midi);- m->first_message = FALSE;- }- - packet = (MIDIPacket *) &newPackets->packet[0];- /* printf("readproc packet status %x length %d\n", packet->data[0], - packet->length); */- for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {- /* Set the timestamp and dispatch this message */- event.timestamp = - (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) / - (UInt64) 1000000;- status = packet->data[0];- /* process packet as sysex data if it begins with MIDI_SYSEX, or- MIDI_EOX or non-status byte with no running status */-#ifdef CM_DEBUG- printf(" %d", packet->length);-#endif- if (status == MIDI_SYSEX || status == MIDI_EOX || - ((!(status & MIDI_STATUS_MASK)) && !m->last_command)) {- /* previously was: !(status & MIDI_STATUS_MASK)) {- * but this could mistake running status for sysex data- */- /* reset running status data -cpr */- m->last_command = 0;- m->last_msg_length = 0;- /* printf("sysex packet length: %d\n", packet->length); */- pm_read_bytes(midi, packet->data, packet->length, event.timestamp);- } else {- process_packet(packet, &event, midi, m);- }- packet = MIDIPacketNext(packet);- }-#ifdef CM_DEBUG- printf("\n");-#endif-}--static PmError-midi_in_open(PmInternal *midi, void *driverInfo)-{- MIDIEndpointRef endpoint;- midi_macosxcm_type m;- OSStatus macHostError;- - /* insure that we have a time_proc for timing */- if (midi->time_proc == NULL) {- if (!Pt_Started()) - Pt_Start(1, 0, 0);- /* time_get does not take a parameter, so coerce */- midi->time_proc = (PmTimeProcPtr) Pt_Time;- }- - endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;- if (endpoint == NULL) {- return pmInvalidDeviceId;- }-- m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */- midi->descriptor = m;- if (!m) {- return pmInsufficientMemory;- }- m->error[0] = 0;- m->callback_error[0] = 0;- m->sync_time = 0;- m->delta = 0;- m->last_time = 0;- m->first_message = TRUE;- m->sysex_mode = FALSE;- m->sysex_word = 0;- m->sysex_byte_count = 0;- m->packetList = NULL;- m->packet = NULL;- m->last_command = 0;- m->last_msg_length = 0;-- macHostError = MIDIPortConnectSource(portIn, endpoint, midi);- if (macHostError != noErr) {- pm_hosterror = macHostError;- sprintf(pm_hosterror_text, - "Host error %ld: MIDIPortConnectSource() in midi_in_open()",- macHostError);- midi->descriptor = NULL;- pm_free(m);- return pmHostError;- }- - return pmNoError;-}--static PmError-midi_in_close(PmInternal *midi)-{- MIDIEndpointRef endpoint;- OSStatus macHostError;- PmError err = pmNoError;- - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- - if (!m) return pmBadPtr;-- endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;- if (endpoint == NULL) {- pm_hosterror = pmBadPtr;- }- - /* shut off the incoming messages before freeing data structures */- macHostError = MIDIPortDisconnectSource(portIn, endpoint);- if (macHostError != noErr) {- pm_hosterror = macHostError;- sprintf(pm_hosterror_text, - "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()",- macHostError);- err = pmHostError;- }- - midi->descriptor = NULL;- pm_free(midi->descriptor);- - return err;-}---static PmError-midi_out_open(PmInternal *midi, void *driverInfo)-{- midi_macosxcm_type m;-- m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */- midi->descriptor = m;- if (!m) {- return pmInsufficientMemory;- }- m->error[0] = 0;- m->callback_error[0] = 0;- m->sync_time = 0;- m->delta = 0;- m->last_time = 0;- m->first_message = TRUE;- m->sysex_mode = FALSE;- m->sysex_word = 0;- m->sysex_byte_count = 0;- m->packetList = (MIDIPacketList *) m->packetBuffer;- m->packet = NULL;- m->last_command = 0;- m->last_msg_length = 0;-- return pmNoError;-}---static PmError-midi_out_close(PmInternal *midi)-{- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- if (!m) return pmBadPtr;- - midi->descriptor = NULL;- pm_free(midi->descriptor);- - return pmNoError;-}--static PmError-midi_abort(PmInternal *midi)-{- return pmNoError;-}---static PmError-midi_write_flush(PmInternal *midi, PmTimestamp timestamp)-{- OSStatus macHostError;- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- MIDIEndpointRef endpoint = - (MIDIEndpointRef) descriptors[midi->device_id].descriptor;- assert(m);- assert(endpoint);- if (m->packet != NULL) {- /* out of space, send the buffer and start refilling it */- macHostError = MIDISend(portOut, endpoint, m->packetList);- m->packet = NULL; /* indicate no data in packetList now */- if (macHostError != noErr) goto send_packet_error;- }- return pmNoError;- -send_packet_error:- pm_hosterror = macHostError;- sprintf(pm_hosterror_text, - "Host error %ld: MIDISend() in midi_write()",- macHostError);- return pmHostError;--}---static PmError-send_packet(PmInternal *midi, Byte *message, unsigned int messageLength, - MIDITimeStamp timestamp)-{- PmError err;- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- assert(m);- - /* printf("add %d to packet %lx len %d\n", message[0], m->packet, messageLength); */- m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), - m->packet, timestamp, messageLength, - message);- if (m->packet == NULL) {- /* out of space, send the buffer and start refilling it */- /* make midi->packet non-null to fool midi_write_flush into sending */- m->packet = (MIDIPacket *) 4; - if ((err = midi_write_flush(midi, timestamp)) != pmNoError) return err;- m->packet = MIDIPacketListInit(m->packetList);- assert(m->packet); /* if this fails, it's a programming error */- m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),- m->packet, timestamp, messageLength, - message);- assert(m->packet); /* can't run out of space on first message */ - }- return pmNoError;-} ---static PmError-midi_write_short(PmInternal *midi, PmEvent *event)-{- long when = event->timestamp;- long what = event->message;- MIDITimeStamp timestamp;- UInt64 when_ns;- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- Byte message[4];- unsigned int messageLength;-- if (m->packet == NULL) {- m->packet = MIDIPacketListInit(m->packetList);- /* this can never fail, right? failure would indicate something - unrecoverable */- assert(m->packet);- }- - /* compute timestamp */- if (when == 0) when = midi->now;- /* if latency == 0, midi->now is not valid. We will just set it to zero */- if (midi->latency == 0) when = 0;- when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;- /* make sure we don't go backward in time */- if (when_ns < m->last_time) when_ns = m->last_time;- m->last_time = when_ns;- timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);-- message[0] = Pm_MessageStatus(what);- message[1] = Pm_MessageData1(what);- message[2] = Pm_MessageData2(what);- messageLength = midi_length(what);- - /* Add this message to the packet list */- return send_packet(midi, message, messageLength, timestamp);-}---static PmError -midi_begin_sysex(PmInternal *midi, PmTimestamp when)-{- UInt64 when_ns;- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- assert(m);- m->sysex_byte_count = 0;- - /* compute timestamp */- if (when == 0) when = midi->now;- /* if latency == 0, midi->now is not valid. We will just set it to zero */- if (midi->latency == 0) when = 0;- when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;- m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);-- if (m->packet == NULL) {- m->packet = MIDIPacketListInit(m->packetList);- /* this can never fail, right? failure would indicate something - unrecoverable */- assert(m->packet);- }- return pmNoError;-}---static PmError-midi_end_sysex(PmInternal *midi, PmTimestamp when)-{- PmError err;- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- assert(m);- - /* make sure we don't go backward in time */- if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time;- - /* now send what's in the buffer */- err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,- m->sysex_timestamp);- m->sysex_byte_count = 0;- if (err != pmNoError) {- m->packet = NULL; /* flush everything in the packet list */- return err;- }- return pmNoError;-}---static PmError-midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)-{- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- assert(m);- if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) {- PmError err = midi_end_sysex(midi, timestamp);- if (err != pmNoError) return err;- }- m->sysex_buffer[m->sysex_byte_count++] = byte;- return pmNoError;-}---static PmError-midi_write_realtime(PmInternal *midi, PmEvent *event)-{- /* to send a realtime message during a sysex message, first- flush all pending sysex bytes into packet list */- PmError err = midi_end_sysex(midi, 0);- if (err != pmNoError) return err;- /* then we can just do a normal midi_write_short */- return midi_write_short(midi, event);-}--static unsigned int midi_has_host_error(PmInternal *midi)-{- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- return (m->callback_error[0] != 0) || (m->error[0] != 0);-}---static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len)-{- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;- msg[0] = 0; /* initialize to empty string */- if (m) { /* make sure there is an open device to examine */- if (m->error[0]) {- strncpy(msg, m->error, len);- m->error[0] = 0; /* clear the error */- } else if (m->callback_error[0]) {- strncpy(msg, m->callback_error, len);- m->callback_error[0] = 0; /* clear the error */- }- msg[len - 1] = 0; /* make sure string is terminated */- }-}---MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)-{- UInt64 nanos;- if (timestamp <= 0) {- return (MIDITimeStamp)0;- } else {- nanos = (UInt64)timestamp * (UInt64)1000000;- return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);- }-}--PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)-{- UInt64 nanos;- nanos = AudioConvertHostTimeToNanos(timestamp);- return (PmTimestamp)(nanos / (UInt64)1000000);-}---//-// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html-//////////////////////////////////////-// Obtain the name of an endpoint without regard for whether it has connections.-// The result should be released by the caller.-CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)-{- CFMutableStringRef result = CFStringCreateMutable(NULL, 0);- CFStringRef str;- - // begin with the endpoint's name- str = NULL;- MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);- if (str != NULL) {- CFStringAppend(result, str);- CFRelease(str);- }- - MIDIEntityRef entity = NULL;- MIDIEndpointGetEntity(endpoint, &entity);- if (entity == NULL)- // probably virtual- return result;- - if (CFStringGetLength(result) == 0) {- // endpoint name has zero length -- try the entity- str = NULL;- MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);- if (str != NULL) {- CFStringAppend(result, str);- CFRelease(str);- }- }- // now consider the device's name- MIDIDeviceRef device = NULL;- MIDIEntityGetDevice(entity, &device);- if (device == NULL)- return result;- - str = NULL;- MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);- if (CFStringGetLength(result) == 0) {- CFRelease(result);- return str;- }- if (str != NULL) {- // if an external device has only one entity, throw away- // the endpoint name and just use the device name- if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {- CFRelease(result);- return str;- } else {- if (CFStringGetLength(str) == 0) {- CFRelease(str);- return result;- }- // does the entity name already start with the device name?- // (some drivers do this though they shouldn't)- // if so, do not prepend- if (CFStringCompareWithOptions( result, /* endpoint name */- str /* device name */,- CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) {- // prepend the device name to the entity name- if (CFStringGetLength(result) > 0)- CFStringInsert(result, 0, CFSTR(" "));- CFStringInsert(result, 0, str);- }- CFRelease(str);- }- }- return result;-}---// Obtain the name of an endpoint, following connections.-// The result should be released by the caller.-static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)-{- CFMutableStringRef result = CFStringCreateMutable(NULL, 0);- CFStringRef str;- OSStatus err;- int i;- - // Does the endpoint have connections?- CFDataRef connections = NULL;- int nConnected = 0;- bool anyStrings = false;- err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);- if (connections != NULL) {- // It has connections, follow them- // Concatenate the names of all connected devices- nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID);- if (nConnected) {- const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));- for (i = 0; i < nConnected; ++i, ++pid) {- MIDIUniqueID id = EndianS32_BtoN(*pid);- MIDIObjectRef connObject;- MIDIObjectType connObjectType;- err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);- if (err == noErr) {- if (connObjectType == kMIDIObjectType_ExternalSource ||- connObjectType == kMIDIObjectType_ExternalDestination) {- // Connected to an external device's endpoint (10.3 and later).- str = EndpointName((MIDIEndpointRef)(connObject), true);- } else {- // Connected to an external device (10.2) (or something else, catch-all)- str = NULL;- MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);- }- if (str != NULL) {- if (anyStrings)- CFStringAppend(result, CFSTR(", "));- else anyStrings = true;- CFStringAppend(result, str);- CFRelease(str);- }- }- }- }- CFRelease(connections);- }- if (anyStrings)- return result;- - // Here, either the endpoint had no connections, or we failed to obtain names for any of them.- return EndpointName(endpoint, false);-}---char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)-{-#ifdef OLDCODE- MIDIEntityRef entity;- MIDIDeviceRef device;-- CFStringRef endpointName = NULL;- CFStringRef deviceName = NULL;-#endif- CFStringRef fullName = NULL;- CFStringEncoding defaultEncoding;- char* newName;-- /* get the default string encoding */- defaultEncoding = CFStringGetSystemEncoding();-- fullName = ConnectedEndpointName(endpoint);- -#ifdef OLDCODE- /* get the entity and device info */- MIDIEndpointGetEntity(endpoint, &entity);- MIDIEntityGetDevice(entity, &device);-- /* create the nicely formated name */- MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName);- MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName);- if (deviceName != NULL) {- fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"),- deviceName, endpointName);- } else {- fullName = endpointName;- }-#endif - /* copy the string into our buffer */- newName = (char *) malloc(CFStringGetLength(fullName) + 1);- CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,- defaultEncoding);-- /* clean up */-#ifdef OLDCODE- if (endpointName) CFRelease(endpointName);- if (deviceName) CFRelease(deviceName);-#endif- if (fullName) CFRelease(fullName);-- return newName;-}-- --pm_fns_node pm_macosx_in_dictionary = {- none_write_short,- none_sysex,- none_sysex,- none_write_byte,- none_write_short,- none_write_flush,- none_synchronize,- midi_in_open,- midi_abort,- midi_in_close,- success_poll,- midi_has_host_error,- midi_get_host_error,-};--pm_fns_node pm_macosx_out_dictionary = {- midi_write_short,- midi_begin_sysex,- midi_end_sysex,- midi_write_byte,- midi_write_realtime,- midi_write_flush,- midi_synchronize,- midi_out_open,- midi_abort,- midi_out_close,- success_poll,- midi_has_host_error,- midi_get_host_error,-};---PmError pm_macosxcm_init(void)-{- ItemCount numInputs, numOutputs, numDevices;- MIDIEndpointRef endpoint;- int i;- OSStatus macHostError;- char *error_text;-- /* Determine the number of MIDI devices on the system */- numDevices = MIDIGetNumberOfDevices();- numInputs = MIDIGetNumberOfSources();- numOutputs = MIDIGetNumberOfDestinations();-- /* Return prematurely if no devices exist on the system- Note that this is not an error. There may be no devices.- Pm_CountDevices() will return zero, which is correct and- useful information- */- if (numDevices <= 0) {- return pmNoError;- }--- /* Initialize the client handle */- macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client);- if (macHostError != noErr) {- error_text = "MIDIClientCreate() in pm_macosxcm_init()";- goto error_return;- }-- /* Create the input port */- macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,- NULL, &portIn);- if (macHostError != noErr) {- error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";- goto error_return;- }- - /* Create the output port */- macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);- if (macHostError != noErr) {- error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";- goto error_return;- }-- /* Iterate over the MIDI input devices */- for (i = 0; i < numInputs; i++) {- endpoint = MIDIGetSource(i);- if (endpoint == NULL) {- continue;- }-- /* set the first input we see to the default */- if (pm_default_input_device_id == -1)- pm_default_input_device_id = pm_descriptor_index;- - /* Register this device with PortMidi */- pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),- TRUE, (void*)endpoint, &pm_macosx_in_dictionary);- }-- /* Iterate over the MIDI output devices */- for (i = 0; i < numOutputs; i++) {- endpoint = MIDIGetDestination(i);- if (endpoint == NULL) {- continue;- }-- /* set the first output we see to the default */- if (pm_default_output_device_id == -1)- pm_default_output_device_id = pm_descriptor_index;-- /* Register this device with PortMidi */- pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),- FALSE, (void*)endpoint, &pm_macosx_out_dictionary);- }- return pmNoError;- -error_return:- pm_hosterror = macHostError;- sprintf(pm_hosterror_text, "Host error %ld: %s\n", macHostError, error_text);- pm_macosxcm_term(); /* clear out any opened ports */- return pmHostError;-}--void pm_macosxcm_term(void)-{- if (client != NULL) MIDIClientDispose(client);- if (portIn != NULL) MIDIPortDispose(portIn);- if (portOut != NULL) MIDIPortDispose(portOut);++ Since the input and output streams are represented by+ MIDIEndpointRef values and almost no other state, we store the+ MIDIEndpointRef on pm_descriptors[midi->device_id].descriptor.+ + OS X does not seem to have an error-code-to-text function, so we+ will just use text messages instead of error codes.++ Virtual device input synchronization: Once we create a virtual+ device, it is always "on" and receiving messages, but it must drop+ messages unless the device has been opened with Pm_OpenInput. To+ open, the main thread should create all the data structures, then+ call OSMemoryBarrier so that writes are observed, then set+ is_opened = TRUE. To close without locks, we need to get the+ callback to set is_opened to FALSE before we free data structures;+ otherwise, there's a race condition where closing could delete+ structures in use by the virtual_read_callback function. We send+ 8 MIDI resets (FF) in a single packet to our own port to signal+ the virtual_read_callback to close it. Then, we wait for the+ callback to recognize the "close" packet and reset is_opened.+ + Device scanning is done when you first open an application.+ PortMIDI does not actively update the devices. Instead, you must+ Pm_Terminate() and Pm_Initialize(), basically starting over. But+ CoreMIDI does not have a way to shut down(!), and even+ MIDIClientDispose() somehow retains state (and docs say do not+ call it even if it worked). The solution, apparently, is to+ call CFRunLoopRunInMode(), which somehow updates CoreMIDI+ state.+ + But when do we call CFRunLoopRunInMode()? I tried calling it+ in midi_in_poll() which is called when you call Pm_Read() since+ that is called often. I observed that this caused the program+ to block for as long as 50ms and fairly often for 2 or 3ms.+ What was Apple thinking? Is it really OK to design systems that+ can only function with a tricky multi-threaded, non-blocking+ priority-based solution, and then not provide a proof of concept+ or documentation? Or is Apple's design really flawed? If anyone+ at Apple reads this, please let me know -- I'm curious.+ + But I digress... Here's the PortMidi approach: Since+ CFRunLoopRunInMode() is potentially a non-realtime operation,+ we only call it in Pm_Initialize(), where other calls to look+ up devices and device names are quite slow to begin with. Again,+ PortMidi does not actively scan for new or deleted devices, so+ if devices change, you won't see it until the next Pm_Terminate+ and Pm_Initialize.+ + Calling CFRunLoopRunInMode() once is probably not enough. There+ might be better way, but it seems to work to just call it 100+ times and insert 20 1ms delays (in case some inter-process+ communication or synchronization is going on).+ This adds 20ms to the wall time of Pm_Initialize(), but it+ typically runs 30ms to much more (~4s), so this has little impact.+ */++#include <stdlib.h>++/* turn on lots of debugging print statements */+#define CM_DEBUG if (0)+/* #define CM_DEBUG if (1) */++#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "porttime.h"+#include "pmmacosxcm.h"++#include <stdio.h>+#include <string.h>++#include <CoreServices/CoreServices.h>+#include <CoreMIDI/MIDIServices.h>+#include <CoreAudio/HostTime.h>+#include <unistd.h>+#include <libkern/OSAtomic.h>++#define PACKET_BUFFER_SIZE 1024+/* maximum overall data rate (OS X limits MIDI rate in case there+ * is a cycle among IAC ports.+ */++#define MAX_BYTES_PER_S 5400++/* Apple reports that packets are dropped when the MIDI bytes/sec+ exceeds 15000. This is computed by "tracking the number of MIDI+ bytes scheduled into 1-second buckets over the last six seconds and+ averaging these counts." This was confirmed in measurements+ (2021) with pm_test/fast.c and pm_test/fastrcv.c Now, in 2022, with+ macOS 12, pm_test/fast{rcv}.c show problems begin at 6000 bytes/sec.+ Previously, we set MAX_BYTES_PER_S to 14000. This is reduced to+ 5400 based on testing (which shows 5700 is too high) to fix the+ packet loss problem that showed up with macOS 12.+ + Experiments show this restriction applies to IAC bus MIDI, but not+ to hardware interfaces. (I measured 0.5 Mbps each way over USB to a+ Teensy 3.2 microcontroller implementing a USB MIDI loopback. Maybe+ it would get 1 Mbps one-way, which would make the CoreMIDI+ restriction 18x slower than USB. Maybe other USB MIDI+ implementations are faster -- USB top speed for other protocols is+ certainly higher than 1 Mbps!)++ This is apparently based on timestamps, not on real time, so we+ have to avoid constructing packets that schedule high speed output+ regardless of when writes occur. The solution is to alter+ timestamps to limit data rates. This adds a slight time+ distortion, e.g. an 11 note chord with all notes on the same+ timestamp will be altered so that the last message is delayed by+ 11 messages x 3 bytes/message / 5400 bytes/second = 6.1 ms.+ Note that this is about 2x MIDI speed, but at least 18x slower + than USB MIDI.+ + Altering timestamps creates another problem, which is that a sender+ that exceeds the maximum rate can queue up an unbounded number of+ messages. With non-USB MIDI devices, you could be writing 5x faster+ to CoreMIDI than the hardware interface can send, causing an+ unbounded backlog, not to mention that the output stream will be a+ steady byte stream (e.g., one 3-byte MIDI message every 0.55 ms),+ losing any original timing or rhythm. PortMidi does not guarantee+ delivery if, over the long run, you write faster than the hardware+ can send.+ + The LIMIT_RATE symbol, if defined (which is the default), enables+ code to modify timestamps for output to an IAC device as follows:++ Before a packet is formed, the message timestamp is set to the+ maximum of the PortMidi timestamp (converted to CoreMIDI time)+ and min_next_time. After each send, min_next_time is updated to+ the packet time + packet length * delay_per_byte, which limits+ the scheduled bytes-per-second. Also, after each packet list+ flush, min_next_time is updated to the maximum of min_next_time+ and the real time, which prevents many bytes to be scheduled in+ the past. (We could more directly just say packets are never+ scheduled in the past, but we prefer to get the current time -- a+ system call -- only when we perform the more expensive operation+ of flushing packets, so that's when we update min_next_time to+ the current real time. If we are sending a lot, we have to flush+ a lot, so the time will be updated frequently when it matters.)++ This possible adjustment to timestamps can distort accurate+ timestamps by up to 0.556 us per 3-byte MIDI message.+ + Nothing blocks the sender from queueing up an arbitrary number of+ messages. Timestamps should be used for accurate timing by sending+ timestamped messages a little ahead of real time, not for+ scheduling an entire MIDI sequence at once!+ */+#define LIMIT_RATE 1++#define SYSEX_BUFFER_SIZE 128+/* What is the maximum PortMidi device number for an IAC device? A+ * cleaner design would be to not use the endpoint as our device+ * representation. Instead, we could have a private extensible struct+ * to keep all device information, including whether the device is+ * implemented with the AppleMIDIIACDriver, which we need because we+ * have to limit the data rate to this particular driver to avoid+ * dropping messages. Rather than rewrite a lot of code, I am just+ * allocating 64 bytes to flag which devices are IAC ones. If an IAC+ * device number is greater than 63, PortMidi will fail to limit+ * writes to it, but will not complain and will not access memory+ * outside the 64-element array of char.+ */+#define MAX_IAC_NUM 63++#define VERBOSE_ON 1+#define VERBOSE if (VERBOSE_ON)++#define MIDI_SYSEX 0xf0+#define MIDI_EOX 0xf7+#define MIDI_CLOCK 0xf8+#define MIDI_STATUS_MASK 0x80++/* "Ref"s are pointers on 32-bit machines and ints on 64 bit machines+ NULL_REF is our representation of either 0 or NULL+*/+#ifdef __LP64__+#define NULL_REF 0+#else+#define NULL_REF NULL+#endif++static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */+static MIDIPortRef portIn = NULL_REF; /* Input port handle */+static MIDIPortRef portOut = NULL_REF; /* Output port handle */+static char isIAC[MAX_IAC_NUM + 1]; /* is device an IAC device */++extern pm_fns_node pm_macosx_in_dictionary;+extern pm_fns_node pm_macosx_out_dictionary;++typedef struct coremidi_info_struct {+ int is_virtual; /* virtual device (TRUE) or actual device (FALSE)? */+ UInt64 delta; /* difference between stream time and real time in ns */+ int sysex_mode; /* middle of sending sysex */+ uint32_t sysex_word; /* accumulate data when receiving sysex */+ uint32_t sysex_byte_count; /* count how many received */+ char error[PM_HOST_ERROR_MSG_LEN];+ char callback_error[PM_HOST_ERROR_MSG_LEN];+ Byte packetBuffer[PACKET_BUFFER_SIZE];+ MIDIPacketList *packetList; /* a pointer to packetBuffer */+ MIDIPacket *packet;+ Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */+ MIDITimeStamp sysex_timestamp; /* host timestamp to use with sysex data */+ /* allow for running status (is running status possible here? -rbd): -cpr */+ UInt64 min_next_time; /* when can the next send take place? (host time) */+ int isIACdevice;+ Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */+ UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */+} coremidi_info_node, *coremidi_info_type;++/* private function declarations */+MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); /* returns host time */+PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); /* returns ms */++char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag);++static PmError check_hosterror(OSStatus err, const char *msg)+{+ if (err != noErr) {+ snprintf(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, "Host error %ld: %s", (long) err, msg);+ pm_hosterror = TRUE;+ return pmHostError;+ }+ return pmNoError;+}+++static PmTimestamp midi_synchronize(PmInternal *midi)+{+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ UInt64 pm_stream_time_2 = /* current time in ns */+ AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());+ PmTimestamp real_time; /* in ms */+ UInt64 pm_stream_time; /* in ns */+ /* if latency is zero and this is an output, there is no + time reference and midi_synchronize should never be called */+ assert(midi->time_proc);+ assert(midi->is_input || midi->latency != 0);+ do {+ /* read real_time between two reads of stream time */+ pm_stream_time = pm_stream_time_2;+ real_time = (*midi->time_proc)(midi->time_info);+ pm_stream_time_2 = AudioConvertHostTimeToNanos(+ AudioGetCurrentHostTime());+ /* repeat if more than 0.5 ms has elapsed */+ } while (pm_stream_time_2 > pm_stream_time + 500000);+ info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);+ midi->sync_time = real_time;+ return real_time;+}+++/* called when MIDI packets are received */+static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi)+{+ PmTimestamp timestamp;+ MIDIPacket *packet;+ unsigned int packetIndex;+ uint32_t now;+ /* Retrieve the context for this connection */+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ assert(info);+ + CM_DEBUG printf("read_callback: numPackets %d: ", newPackets->numPackets);+ + /* synchronize time references every 100ms */+ now = (*midi->time_proc)(midi->time_info);+ if (midi->first_message || midi->sync_time + 100 /*ms*/ < now) {+ /* time to resync */+ now = midi_synchronize(midi);+ midi->first_message = FALSE;+ }+ + packet = (MIDIPacket *) &newPackets->packet[0];+ /* hardware devices get untimed messages and apply timestamps. We+ * want to preserve them because they should be more accurate than+ * applying the current time here. virtual devices just pass on the+ * packet->timeStamp, which could be anything. PortMidi says the + * PortMidi timestamp is the time the message is received. We do not+ * know if we are receiving from a device driver or a virtual device.+ * PortMidi sends to virtual devices get a current timestamp, so we+ * can treat them as the receive time. If the timestamp is zero,+ * suggested by CoreMIDI as the value to use for immediate delivery,+ * then we plug in `now` which is obtained above. If another+ * application sends bogus non-zero timestamps, we will convert them+ * to this port's reference time and pass them as event.timestamp.+ * Receiver beware.+ */+ CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) "+ "status %x length %d\n",+ AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),+ AudioGetCurrentHostTime(),+ packet->data[0], packet->length);+ for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {+ /* Set the timestamp and dispatch this message */+ CM_DEBUG printf(" packet->timeStamp %lld ns %lld host\n",+ packet->timeStamp,+ AudioConvertHostTimeToNanos(packet->timeStamp));+ if (packet->timeStamp == 0) {+ timestamp = now;+ } else {+ timestamp = (PmTimestamp) /* explicit conversion */ (+ (AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) /+ (UInt64) 1000000);+ }+ pm_read_bytes(midi, packet->data, packet->length, timestamp);+ packet = MIDIPacketNext(packet);+ }+}++/* callback for real devices - redirects to read_callback */+static void device_read_callback(const MIDIPacketList *newPackets, + void *refCon, void *connRefCon)+{+ read_callback(newPackets, (PmInternal *) connRefCon);+}+++/* callback for virtual devices - redirects to read_callback */+static void virtual_read_callback(const MIDIPacketList *newPackets, + void *refCon, void *connRefCon)+{+ /* this refCon is the device ID -- if there is a valid ID and + the pm_descriptors table has a non-null pointer to a PmInternal,+ then then device is open and should receive this data */+ PmDeviceID id = (PmDeviceID) (size_t) refCon;+ if (id >= 0 && id < pm_descriptor_len) {+ if (pm_descriptors[id].pub.opened) {+ /* check for close request (7 reset status bytes): */+ if (newPackets->numPackets == 1 &&+ newPackets->packet[0].length == 8 &&+ /* CoreMIDI declares packets with 4-byte alignment, so we+ * should be safe to test for 8 0xFF's as 2 32-bit values: */+ *(SInt32 *) &newPackets->packet[0].data[0] == -1 &&+ *(SInt32 *) &newPackets->packet[0].data[4] == -1) {+ CM_DEBUG printf("got close request packet\n");+ pm_descriptors[id].pub.opened = FALSE;+ return;+ } else {+ read_callback(newPackets, pm_descriptors[id].pm_internal);+ }+ }+ }+}+++/* allocate and initialize our internal coremidi connection info */+static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input)+{+ coremidi_info_type info = (coremidi_info_type)+ pm_alloc(sizeof(coremidi_info_node));+ if (!info) {+ return NULL;+ }+ info->is_virtual = is_virtual;+ info->delta = 0;+ info->sysex_mode = FALSE;+ info->sysex_word = 0;+ info->sysex_byte_count = 0;+ info->packet = NULL;+ info->min_next_time = 0;+ info->isIACdevice = FALSE;+ info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency();+ info->host_ticks_per_byte =+ (UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S));+ info->packetList = (is_input ? NULL :+ (MIDIPacketList *) info->packetBuffer);+ return info;+}+++static PmError midi_in_open(PmInternal *midi, void *driverInfo)+{+ MIDIEndpointRef endpoint;+ coremidi_info_type info;+ OSStatus macHostError;+ int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;+ + /* if this is an external device, descriptor is a MIDIEndpointRef.+ * if this is a virtual device for this application, descriptor is NULL.+ */+ if (!is_virtual) {+ endpoint = (MIDIEndpointRef) (intptr_t)+ pm_descriptors[midi->device_id].descriptor;+ if (endpoint == NULL_REF) {+ return pmInvalidDeviceId;+ }+ }++ info = create_macosxcm_info(is_virtual, TRUE);+ midi->api_info = info;+ if (!info) {+ return pmInsufficientMemory;+ }+ if (!is_virtual) {+ macHostError = MIDIPortConnectSource(portIn, endpoint, midi);+ if (macHostError != noErr) {+ midi->api_info = NULL;+ pm_free(info);+ return check_hosterror(macHostError,+ "MIDIPortConnectSource() in midi_in_open()");+ }+ }+ return pmNoError;+}++static PmError midi_in_close(PmInternal *midi)+{+ MIDIEndpointRef endpoint;+ OSStatus macHostError;+ PmError err = pmNoError;+ + coremidi_info_type info = (coremidi_info_type) midi->api_info;+ + if (!info) return pmBadPtr;++ endpoint = (MIDIEndpointRef) (intptr_t)+ pm_descriptors[midi->device_id].descriptor;+ if (endpoint == NULL_REF) {+ return pmBadPtr;+ }+ + if (!info->is_virtual) {+ /* shut off the incoming messages before freeing data structures */+ macHostError = MIDIPortDisconnectSource(portIn, endpoint);+ /* If the source closes, you get paramErr == -50 here. It seems+ * possible to monitor changes like sources closing by getting+ * notifications ALL changes, but the CoreMIDI documentation is+ * really terrible overall, and it seems easier to just ignore+ * this host error.+ */+ if (macHostError != noErr && macHostError != -50) {+ pm_hosterror = TRUE;+ err = check_hosterror(macHostError, + "MIDIPortDisconnectSource() in midi_in_close()");+ }+ } else {+ /* make "close virtual port" message */+ SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF;+ /* memory requirements: packet count (4), timestamp (8), length (2),+ * data (8). Total: 22, but we allocate plenty more:+ */+ Byte packetBuffer[64];+ MIDIPacketList *plist = (MIDIPacketList *) packetBuffer;+ MIDIPacket *packet = MIDIPacketListInit(plist);+ MIDIPacketListAdd(plist, 64, packet, 0, 8,+ (const Byte *) &close_port_bytes);+ macHostError = MIDISend(portOut, endpoint, plist);+ if (macHostError != noErr) {+ err = check_hosterror(macHostError, "MIDISend() (PortMidi close "+ "port packet) in midi_in_close()");+ } + /* when packet is delivered, callback thread will clear opened;+ * we must wait for that before removing the input queues etc.+ * Maybe this could use signals of some kind, but if signals use+ * locks, locks can cause priority inversion problems, so we will+ * just sleep as needed. On the MIDI timescale, inserting a 0.5ms+ * latency should be OK, as the application has no business+ * opening/closing devices during time-critical moments.+ *+ * We expect the MIDI thread to close the device quickly (<0.5ms),+ * but we wait up to 50ms in case something terrible happens like+ * getting paged out in the middle of deliving packets to this+ * virtual device. If there is still no response, we time out and+ * force the close without the MIDI thread (even this will probably+ * succeed - the problem would be: this thread proceeds to delete+ * the input queues, and the freed memory is reallocated and+ * overwritten so that queues are no longer usable. Meanwhile,+ * the MIDI thread has already begun to deliver packets, so the+ * check for opened == TRUE passed, but MIDI thread does not insert+ * into queue until queue is freed, reallocated and overwritten.+ */+ for (int i = 0; i < 100; i++) { /* up to 50ms delay */+ if (!pm_descriptors[midi->device_id].pub.opened) {+ break;+ }+ usleep(500); /* 0.5ms */+ }+ pm_descriptors[midi->device_id].pub.opened = FALSE; /* force it */+ }+ midi->api_info = NULL;+ pm_free(info);+ return err;+}+++static PmError midi_out_open(PmInternal *midi, void *driverInfo)+{+ coremidi_info_type info;+ int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;++ info = create_macosxcm_info(is_virtual, FALSE);+ if (midi->device_id <= MAX_IAC_NUM) {+ info->isIACdevice = isIAC[midi->device_id];+ CM_DEBUG printf("midi_out_open isIACdevice %d\n", info->isIACdevice);+ }+ midi->api_info = info;+ if (!info) {+ return pmInsufficientMemory;+ }+ return pmNoError;+}+++static PmError midi_out_close(PmInternal *midi)+{+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ if (!info) return pmBadPtr;+ midi->api_info = NULL;+ pm_free(info);+ return pmNoError;+}+++/* MIDIDestinationCreate apparently cannot create a virtual device+ * without a callback and a "refCon" parameter, but when we create+ * a virtual device, we do not want a PortMidi stream yet -- that+ * should wait for the user to open the stream. So, for the refCon,+ * use the PortMidi device ID. The callback will check if the+ * device is opened within PortMidi, and if so, use the pm_descriptors+ * table to locate the corresponding PmStream.+ */+static PmError midi_create_virtual(int is_input, const char *name,+ void *device_info)+{+ OSStatus macHostError;+ MIDIEndpointRef endpoint;+ CFStringRef nameRef;+ PmDeviceID id = pm_add_device("CoreMIDI", name, is_input, TRUE, NULL,+ (is_input ? &pm_macosx_in_dictionary :+ &pm_macosx_out_dictionary));+ if (id < 0) { /* error -- out of memory or name conflict? */+ return id;+ }++ nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);+ if (is_input) {+ macHostError = MIDIDestinationCreate(client, nameRef, + virtual_read_callback, (void *) (intptr_t) id, &endpoint);+ } else {+ macHostError = MIDISourceCreate(client, nameRef, &endpoint);+ }+ CFRelease(nameRef);++ if (macHostError != noErr) {+ /* undo the device we just allocated */+ pm_undo_add_device(id);+ return check_hosterror(macHostError, (is_input ?+ "MIDIDestinationCreateWithProtocol() in midi_create_virtual()" :+ "MIDISourceCreateWithProtocol() in midi_create_virtual()"));+ }++ /* Do we have a manufacturer name? If not, set to "PortMidi" */+ const char *mfr_name = "PortMidi";+ PmSysDepInfo *info = (PmSysDepInfo *) device_info;+ /* the version where pmKeyCoreMidiManufacturer was introduced is 210 */+ if (info && info->structVersion >= 210) {+ int i;+ for (i = 0; i < info->length; i++) { /* search for key */+ if (info->properties[i].key == pmKeyCoreMidiManufacturer) {+ mfr_name = info->properties[i].value;+ break;+ } /* no other keys are recognized; they are ignored */+ }+ }+ nameRef = CFStringCreateWithCString(NULL, mfr_name, kCFStringEncodingUTF8);+ MIDIObjectSetStringProperty(endpoint, kMIDIPropertyManufacturer, nameRef);+ CFRelease(nameRef);++ pm_descriptors[id].descriptor = (void *) (intptr_t) endpoint;+ return id;+}+++static PmError midi_delete_virtual(PmDeviceID id)+{+ MIDIEndpointRef endpoint;+ OSStatus macHostError;+ + endpoint = (MIDIEndpointRef) (long) pm_descriptors[id].descriptor;+ if (endpoint == NULL_REF) {+ return pmBadPtr;+ }+ macHostError = MIDIEndpointDispose(endpoint);+ return check_hosterror(macHostError,+ "MIDIEndpointDispose() in midi_in_close()");+}+++static PmError midi_abort(PmInternal *midi)+{+ OSStatus macHostError;+ MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)+ pm_descriptors[midi->device_id].descriptor;+ macHostError = MIDIFlushOutput(endpoint);+ return check_hosterror(macHostError,+ "MIDIFlushOutput() in midi_abort()");+}+++static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp)+{+ OSStatus macHostError = noErr;+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)+ pm_descriptors[midi->device_id].descriptor;+ assert(info);+ assert(endpoint);+ if (info->packet != NULL) {+ /* out of space, send the buffer and start refilling it */+ /* update min_next_time each flush to support rate limit */+ UInt64 host_now = AudioGetCurrentHostTime();+ if (host_now > info->min_next_time) + info->min_next_time = host_now;+ if (info->is_virtual) {+ macHostError = MIDIReceived(endpoint, info->packetList);+ } else {+ macHostError = MIDISend(portOut, endpoint, info->packetList);+ }+ info->packet = NULL; /* indicate no data in packetList now */+ }+ return check_hosterror(macHostError, (info->is_virtual ?+ "MIDIReceived() in midi_write()" :+ "MIDISend() in midi_write()"));+}+++static PmError send_packet(PmInternal *midi, Byte *message,+ unsigned int messageLength, MIDITimeStamp timestamp)+{+ PmError err;+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ assert(info);+ + CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns "+ "(host %lld)\n",+ message[0], info->packet, messageLength, timestamp,+ AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),+ AudioGetCurrentHostTime());+ info->packet = MIDIPacketListAdd(info->packetList,+ sizeof(info->packetBuffer), info->packet,+ timestamp, messageLength, message);+ if (info->packet == NULL) {+ /* out of space, send the buffer and start refilling it */+ /* make midi->packet non-null to fool midi_write_flush into sending */+ info->packet = (MIDIPacket *) 4;+ /* timestamp is 0 because midi_write_flush ignores timestamp since+ * timestamps are already in packets. The timestamp parameter is here+ * because other API's need it. midi_write_flush can be called + * from system-independent code that must be cross-API.+ */+ if ((err = midi_write_flush(midi, 0)) != pmNoError) return err;+ info->packet = MIDIPacketListInit(info->packetList);+ assert(info->packet); /* if this fails, it's a programming error */+ info->packet = MIDIPacketListAdd(info->packetList,+ sizeof(info->packetBuffer), info->packet,+ timestamp, messageLength, message);+ assert(info->packet); /* can't run out of space on first message */+ }+ return pmNoError;+} +++static PmError midi_write_short(PmInternal *midi, PmEvent *event)+{+ PmTimestamp when = event->timestamp;+ PmMessage what = event->message;+ MIDITimeStamp timestamp;+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ Byte message[4];+ unsigned int messageLength;++ if (info->packet == NULL) {+ info->packet = MIDIPacketListInit(info->packetList);+ /* this can never fail, right? failure would indicate something + unrecoverable */+ assert(info->packet);+ }+ + /* PortMidi specifies that incoming timestamps are the receive+ * time. Devices attach their receive times, but virtual devices+ * do not. Instead, they pass along whatever timestamp was sent to+ * them. We do not know if we are connected to real or virtual+ * device. To avoid wild timestamps on the receiving end, we+ * consider 2 cases: PortMidi timestamp is zero or latency is+ * zero. Both mean send immediately, so we attach the current time+ * which will go out immediately and arrive with a sensible+ * timestamp (not zero and not zero mapped to the client's local+ * time). Otherwise, we assume the timestamp is reasonable. It+ * might be slighly in the past, but we pass it along after+ * translation to MIDITimeStamp units. + *+ * Compute timestamp: use current time if timestamp is zero or+ * latency is zero. Both mean no timing and send immediately.+ */+ if (when == 0 || midi->latency == 0) {+ timestamp = AudioGetCurrentHostTime();+ } else { /* translate PortMidi time + latency to CoreMIDI time */+ timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) ++ info->delta;+ timestamp = AudioConvertNanosToHostTime(timestamp);+ }++ message[0] = Pm_MessageStatus(what);+ message[1] = Pm_MessageData1(what);+ message[2] = Pm_MessageData2(what);+ messageLength = pm_midi_length((int32_t) what);++#ifdef LIMIT_RATE+ /* Make sure we go forward in time. */+ if (timestamp < info->min_next_time) {+ timestamp = info->min_next_time;+ }+ /* Note that if application is way behind and slowly catching up, then+ * timestamps could be increasing faster than real time, and since + * timestamps are used to estimate data rate, our estimate could be+ * low, causing CoreMIDI to drop packets. This seems very unlikely.+ */+ if (info->isIACdevice || info->is_virtual) {+ info->min_next_time = timestamp + messageLength * + info->host_ticks_per_byte;+ }+#endif+ /* Add this message to the packet list */+ return send_packet(midi, message, messageLength, timestamp);+}+++static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when)+{+ UInt64 when_ns;+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ assert(info);+ info->sysex_byte_count = 0;+ + /* compute timestamp */+ if (when == 0) when = midi->now;+ /* if latency == 0, midi->now is not valid. We will just set it to zero */+ if (midi->latency == 0) when = 0;+ when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) ++ info->delta;+ info->sysex_timestamp =+ (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);+ UInt64 now; /* only make system time call when writing a virtual port */+ if (info->is_virtual && info->sysex_timestamp <+ (now = AudioGetCurrentHostTime())) {+ info->sysex_timestamp = now;+ }++ if (info->packet == NULL) {+ info->packet = MIDIPacketListInit(info->packetList);+ /* this can never fail, right? failure would indicate something + unrecoverable */+ assert(info->packet);+ }+ return pmNoError;+}+++static PmError midi_end_sysex(PmInternal *midi, PmTimestamp when)+{+ PmError err;+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ assert(info);+ +#ifdef LIMIT_RATE+ /* make sure we go foreward in time */+ if (info->sysex_timestamp < info->min_next_time)+ info->sysex_timestamp = info->min_next_time;++ if (info->isIACdevice) {+ info->min_next_time = info->sysex_timestamp + info->sysex_byte_count *+ info->host_ticks_per_byte;+ }+#endif+ + /* now send what's in the buffer */+ err = send_packet(midi, info->sysex_buffer, info->sysex_byte_count,+ info->sysex_timestamp);+ info->sysex_byte_count = 0;+ if (err != pmNoError) {+ info->packet = NULL; /* flush everything in the packet list */+ }+ return err;+}+++static PmError midi_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp)+{+ coremidi_info_type info = (coremidi_info_type) midi->api_info;+ assert(info);+ if (info->sysex_byte_count >= SYSEX_BUFFER_SIZE) {+ PmError err = midi_end_sysex(midi, timestamp);+ if (err != pmNoError) return err;+ }+ info->sysex_buffer[info->sysex_byte_count++] = byte;+ return pmNoError;+}+++static PmError midi_write_realtime(PmInternal *midi, PmEvent *event)+{+ /* to send a realtime message during a sysex message, first+ flush all pending sysex bytes into packet list */+ PmError err = midi_end_sysex(midi, 0);+ if (err != pmNoError) return err;+ /* then we can just do a normal midi_write_short */+ return midi_write_short(midi, event);+}+++static unsigned int midi_check_host_error(PmInternal *midi)+{+ return FALSE;+}+++MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)+{+ UInt64 nanos;+ if (timestamp <= 0) {+ return (MIDITimeStamp)0;+ } else {+ nanos = (UInt64)timestamp * (UInt64)1000000;+ return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);+ }+}+++PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)+{+ UInt64 nanos;+ nanos = AudioConvertHostTimeToNanos(timestamp);+ return (PmTimestamp)(nanos / (UInt64)1000000);+}+++/* Code taken from http://developer.apple.com/qa/qa2004/qa1374.html+ *+ * Obtain the name of an endpoint without regard for whether it has connections.+ * The result should be released by the caller.+ */+CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal,+ int *iac_flag)+{+ CFMutableStringRef result = CFStringCreateMutable(NULL, 0);+ CFStringRef str;+ *iac_flag = FALSE;+ + /* begin with the endpoint's name */+ str = NULL;+ MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);+ if (str != NULL) {+ CFStringAppend(result, str);+ CFRelease(str);+ }+ MIDIEntityRef entity = NULL_REF;+ MIDIEndpointGetEntity(endpoint, &entity);+ if (entity == NULL_REF) {+ /* probably virtual */+ return result;+ }+ if (!isExternal) { /* detect IAC devices */+ /* extern const CFStringRef kMIDIPropertyDriverOwner; */+ MIDIObjectGetStringProperty(entity, kMIDIPropertyDriverOwner, &str);+ if (str != NULL) {+ char s[32]; /* driver name may truncate, but that's OK */+ CFStringGetCString(str, s, 31, kCFStringEncodingUTF8);+ s[31] = 0; /* make sure it is terminated just to be safe */+ CM_DEBUG printf("driver %s\n", s);+ *iac_flag = (strcmp(s, "com.apple.AppleMIDIIACDriver") == 0);+ }+ }++ if (CFStringGetLength(result) == 0) {+ /* endpoint name has zero length -- try the entity */+ str = NULL;+ MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);+ if (str != NULL) {+ CFStringAppend(result, str);+ CFRelease(str);+ }+ }+ /* now consider the device's name */+ MIDIDeviceRef device = NULL_REF;+ MIDIEntityGetDevice(entity, &device);+ if (device == NULL_REF)+ return result;+ + str = NULL;+ MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);+ if (CFStringGetLength(result) == 0) {+ CFRelease(result);+ return str;+ }+ if (str != NULL) {+ /* if an external device has only one entity, throw away+ the endpoint name and just use the device name+ */+ if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {+ CFRelease(result);+ return str;+ } else {+ if (CFStringGetLength(str) == 0) {+ CFRelease(str);+ return result;+ }+ /* does the entity name already start with the device name?+ (some drivers do this though they shouldn't)+ if so, do not prepend+ */+ if (CFStringCompareWithOptions(result, /* endpoint name */+ str, /* device name */+ CFRangeMake(0, CFStringGetLength(str)), 0) != + kCFCompareEqualTo) {+ /* prepend the device name to the entity name */+ if (CFStringGetLength(result) > 0)+ CFStringInsert(result, 0, CFSTR(" "));+ CFStringInsert(result, 0, str);+ }+ CFRelease(str);+ }+ }+ return result;+}+++/* Obtain the name of an endpoint, following connections.+ The result should be released by the caller.+*/+static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint,+ int *iac_flag)+{+ CFMutableStringRef result = CFStringCreateMutable(NULL, 0);+ CFStringRef str;+ OSStatus err;+ long i;+ + /* Does the endpoint have connections? */+ CFDataRef connections = NULL;+ long nConnected = 0;+ bool anyStrings = false;+ err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID,+ &connections);+ if (connections != NULL) {+ /* It has connections, follow them+ Concatenate the names of all connected devices+ */+ nConnected = CFDataGetLength(connections) / + (int32_t) sizeof(MIDIUniqueID);+ if (nConnected) {+ const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));+ for (i = 0; i < nConnected; ++i, ++pid) {+ MIDIUniqueID id = EndianS32_BtoN(*pid);+ MIDIObjectRef connObject;+ MIDIObjectType connObjectType;+ err = MIDIObjectFindByUniqueID(id, &connObject, + &connObjectType);+ if (err == noErr) {+ if (connObjectType == kMIDIObjectType_ExternalSource ||+ connObjectType == kMIDIObjectType_ExternalDestination) {+ /* Connected to an external device's endpoint (>=10.3) */+ str = EndpointName((MIDIEndpointRef)(connObject), true,+ iac_flag);+ } else {+ /* Connected to an external device (10.2) + (or something else, catch-all)+ */+ str = NULL;+ MIDIObjectGetStringProperty(connObject, + kMIDIPropertyName, &str);+ }+ if (str != NULL) {+ if (anyStrings)+ CFStringAppend(result, CFSTR(", "));+ else anyStrings = true;+ CFStringAppend(result, str);+ CFRelease(str);+ }+ }+ }+ }+ CFRelease(connections);+ }+ if (anyStrings)+ return result; /* caller should release result */++ CFRelease(result);++ /* Here, either the endpoint had no connections, or we failed to+ obtain names for any of them.+ */+ return EndpointName(endpoint, false, iac_flag);+}+++/* cm_get_full_endpoint_name -- returns UTF-8 namem for endpoint.+ * caller is owner and responsible for pm_free'ing the string+ */+char *cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag)+{+ /* Thanks to Dan Wilcox for fixes for Unicode handling+ * and kichikuou at github for fixing buffer size calculation.+ */+ CFStringRef fullName = ConnectedEndpointName(endpoint, iac_flag);+ CFIndex len = CFStringGetLength(fullName) + 1;+ /* (len seems to be length in Unicode characters, although docs add+ * the confusing explanation "in terms of UTF-16 code pairs")+ */+ CFIndex max_byte_len = CFStringGetMaximumSizeForEncoding(+ len, kCFStringEncodingUTF8) + 1;+ char* pmname = (char *) pm_alloc(max_byte_len);++ /* copy the string into our buffer; note that there may be some wasted+ space, but the total waste is not large */+ CFStringGetCString(fullName, pmname, max_byte_len, kCFStringEncodingUTF8);++ /* clean up */+ if (fullName) CFRelease(fullName);+ return pmname;+}+++pm_fns_node pm_macosx_in_dictionary = {+ none_write_short,+ none_sysex,+ none_sysex,+ none_write_byte,+ none_write_short,+ none_write_flush,+ none_synchronize,+ midi_in_open,+ midi_abort,+ midi_in_close,+ success_poll,+ midi_check_host_error+};++pm_fns_node pm_macosx_out_dictionary = {+ midi_write_short,+ midi_begin_sysex,+ midi_end_sysex,+ midi_write_byte,+ midi_write_realtime,+ midi_write_flush,+ midi_synchronize,+ midi_out_open,+ midi_abort,+ midi_out_close,+ success_poll,+ midi_check_host_error+};+++/* We do nothing with callbacks, but generating the callbacks also+ * updates CoreMIDI state. Callback may not be essential, but calling+ * the CFRunLoopRunInMode is necessary.+ */+void cm_notify(const MIDINotification *msg, void *refCon)+{+ /* for debugging, trace change notifications: + const char *descr[] = {+ "undefined (0)",+ "kMIDIMsgSetupChanged",+ "kMIDIMsgObjectAdded",+ "kMIDIMsgObjectRemoved",+ "kMIDIMsgPropertyChanged",+ "kMIDIMsgThruConnectionsChanged",+ "kMIDIMsgSerialPortOwnerChanged",+ "kMIDIMsgIOError"};++ printf("MIDI Notify, messageID %d (%s)\n", (int) msg->messageID,+ descr[(int) msg->messageID]);+ */+ return;+}+++PmError pm_macosxcm_init(void)+{+ ItemCount numInputs, numOutputs, numDevices;+ MIDIEndpointRef endpoint;+ OSStatus macHostError = noErr;+ const char *error_text;++ memset(isIAC, 0, sizeof(isIAC)); /* initialize all FALSE */++ /* Register interface CoreMIDI with create_virtual fn */+ pm_add_interf("CoreMIDI", &midi_create_virtual, &midi_delete_virtual);+ /* no check for error return because this always succeeds */++ /* Determine the number of MIDI devices on the system */+ numDevices = MIDIGetNumberOfDevices();++ /* Return prematurely if no devices exist on the system+ Note that this is not an error. There may be no devices.+ Pm_CountDevices() will return zero, which is correct and+ useful information+ */+ if (numDevices <= 0) {+ return pmNoError;+ }++ /* Initialize the client handle */+ if (client == NULL_REF) {+ macHostError = MIDIClientCreate(CFSTR("PortMidi"), &cm_notify, NULL,+ &client);+ } else { /* see notes above on device scanning */+ for (int i = 0; i < 100; i++) {+ /* look for any changes before scanning for devices: */+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);+ if (i % 5 == 0) Pt_Sleep(1); /* insert 20 delays */+ }+ }+ if (macHostError != noErr) {+ error_text = "MIDIClientCreate() in pm_macosxcm_init()";+ goto error_return;+ }+ numInputs = MIDIGetNumberOfSources();+ numOutputs = MIDIGetNumberOfDestinations();++ /* Create the input port */+ macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), + device_read_callback, NULL, &portIn);+ if (macHostError != noErr) {+ error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";+ goto error_return;+ }++ /* Create the output port */+ macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);+ if (macHostError != noErr) {+ error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";+ goto error_return;+ }++ /* Iterate over the MIDI input devices */+ for (int i = 0; i < numInputs; i++) {+ int iac_flag;+ endpoint = MIDIGetSource(i);+ if (endpoint == NULL_REF) {+ continue;+ }+ /* Register this device with PortMidi */+ char *name = cm_get_full_endpoint_name(endpoint, &iac_flag);+ pm_add_device("CoreMIDI", name, TRUE, FALSE,+ (void *) (intptr_t) endpoint, &pm_macosx_in_dictionary);+ /* pm_add_device copies name, so it is no longer needed */+ pm_free(name);+ }++ /* Iterate over the MIDI output devices */+ for (int i = 0; i < numOutputs; i++) {+ int iac_flag;+ PmDeviceID id;+ endpoint = MIDIGetDestination(i);+ if (endpoint == NULL_REF) {+ continue;+ }+ /* Register this device with PortMidi */+ char *name = cm_get_full_endpoint_name(endpoint, &iac_flag);+ id = pm_add_device("CoreMIDI", name, FALSE, FALSE,+ (void *) (intptr_t) endpoint, &pm_macosx_out_dictionary);+ /* pm_add_device copies name, so it is no longer needed */+ pm_free(name);++ /* if this is an IAC device, tuck that info away for write functions */+ if (iac_flag && id <= MAX_IAC_NUM) {+ isIAC[id] = TRUE;+ }+ }+ return pmNoError;+ +error_return:+ pm_macosxcm_term(); /* clear out any opened ports */+ return check_hosterror(macHostError, error_text);+}++void pm_macosxcm_term(void)+{+ /* docs say do not explicitly dispose of client+ if (client != NULL_REF) MIDIClientDispose(client); */+ if (portIn != NULL_REF) MIDIPortDispose(portIn);+ if (portOut != NULL_REF) MIDIPortDispose(portOut); }
portmidi/pm_mac/pmmacosxcm.h view
@@ -1,4 +1,4 @@-/* system-specific definitions */ - -PmError pm_macosxcm_init(void); +/* system-specific definitions */++PmError pm_macosxcm_init(void); void pm_macosxcm_term(void);
portmidi/pm_win/pmwin.c view
@@ -1,114 +1,98 @@-/* pmwin.c -- PortMidi os-dependent code */ - -/* This file only needs to implement: - pm_init(), which calls various routines to register the - available midi devices, - Pm_GetDefaultInputDeviceID(), and - Pm_GetDefaultOutputDeviceID(). - This file must - be separate from the main portmidi.c file because it is system - dependent, and it is separate from, say, pmwinmm.c, because it - might need to register devices for winmm, directx, and others. - */ - -#include "stdlib.h" -#include "portmidi.h" -#include "pmutil.h" -#include "pminternal.h" -#include "pmwinmm.h" -#ifdef USE_DLL_FOR_CLEANUP -#include "pmdll.h" /* used to close ports on exit */ -#endif -#ifdef DEBUG -#include "stdio.h" -#endif - -/* pm_exit is called when the program exits. - It calls pm_term to make sure PortMidi is properly closed. - If DEBUG is on, we prompt for input to avoid losing error messages. - */ -static void pm_exit(void) { - pm_term(); -#ifdef DEBUG -#define STRING_MAX 80 - { - char line[STRING_MAX]; - printf("Type ENTER...\n"); - /* note, w/o this prompting, client console application can not see one - of its errors before closing. */ - fgets(line, STRING_MAX, stdin); - } -#endif -} - - -/* pm_init is the windows-dependent initialization.*/ -void pm_init(void) -{ -#ifdef USE_DLL_FOR_CLEANUP - /* we were hoping a DLL could offer more robust cleanup after errors, - but the DLL does not seem to run after crashes. Thus, the atexit() - mechanism is just as powerful, and simpler to implement. - */ - pm_set_close_function(pm_exit); -#ifdef DEBUG - printf("registered pm_term with cleanup DLL\n"); -#endif -#else - atexit(pm_exit); -#ifdef DEBUG - printf("registered pm_exit with atexit()\n"); -#endif -#endif - pm_winmm_init(); - /* initialize other APIs (DirectX?) here */ -} - - -void pm_term(void) { - pm_winmm_term(); -} - - -PmDeviceID Pm_GetDefaultInputDeviceID() { - /* This routine should check the environment and the registry - as specified in portmidi.h, but for now, it just returns - the first device of the proper input/output flavor. - */ - int i; - Pm_Initialize(); /* make sure descriptors exist! */ - for (i = 0; i < pm_descriptor_index; i++) { - if (descriptors[i].pub.input) { - return i; - } - } - return pmNoDevice; -} - -PmDeviceID Pm_GetDefaultOutputDeviceID() { - /* This routine should check the environment and the registry - as specified in portmidi.h, but for now, it just returns - the first device of the proper input/output flavor. - */ - int i; - Pm_Initialize(); /* make sure descriptors exist! */ - for (i = 0; i < pm_descriptor_index; i++) { - if (descriptors[i].pub.output) { - return i; - } - } - return pmNoDevice; - return 0; -} - -#include "stdio.h" - -void *pm_alloc(size_t s) { - return malloc(s); -} - - -void pm_free(void *ptr) { - free(ptr); -} - +/* pmwin.c -- PortMidi os-dependent code */++/* This file only needs to implement:+ pm_init(), which calls various routines to register the + available midi devices,+ Pm_GetDefaultInputDeviceID(), and+ Pm_GetDefaultOutputDeviceID().+ This file must+ be separate from the main portmidi.c file because it is system+ dependent, and it is separate from, say, pmwinmm.c, because it+ might need to register devices for winmm, directx, and others.++ */++#include "stdlib.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "pmwinmm.h"+#ifdef DEBUG+#include "stdio.h"+#endif+#include <windows.h>++/* pm_exit is called when the program exits.+ It calls pm_term to make sure PortMidi is properly closed.+ If DEBUG is on, we prompt for input to avoid losing error messages.+ */+static void pm_exit(void) {+ pm_term();+}+++static BOOL WINAPI ctrl_c_handler(DWORD fdwCtrlType)+{+ exit(1); /* invokes pm_exit() */+ ExitProcess(1); /* probably never called */+ return TRUE;+}++/* pm_init is the windows-dependent initialization.*/+void pm_init(void)+{+ atexit(pm_exit);+ SetConsoleCtrlHandler(ctrl_c_handler, TRUE);+#ifdef DEBUG+ printf("registered pm_exit with atexit()\n");+#endif+ pm_winmm_init();+ /* initialize other APIs (DirectX?) here */+}+++void pm_term(void) {+ pm_winmm_term();+}+++static PmDeviceID pm_get_default_device_id(int is_input, char *key) {+#define PATTERN_MAX 256+ /* Find first input or device -- this is the default. */+ PmDeviceID id = pmNoDevice;+ int i;+ Pm_Initialize(); /* make sure descriptors exist! */+ for (i = 0; i < pm_descriptor_len; i++) {+ if (pm_descriptors[i].pub.input == is_input) {+ id = i;+ break;+ }+ }+ return id;+}+++PmDeviceID Pm_GetDefaultInputDeviceID(void) {+ return pm_get_default_device_id(TRUE, + "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E");+}+++PmDeviceID Pm_GetDefaultOutputDeviceID(void) {+ return pm_get_default_device_id(FALSE,+ "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E");+}+++#include "stdio.h" ++void *pm_alloc(size_t s) {+ return malloc(s); +}+++void pm_free(void *ptr) { + free(ptr); +}++
portmidi/pm_win/pmwinmm.c view
@@ -1,1449 +1,1196 @@-/* pmwinmm.c -- system specific definitions */ - -#ifndef _WIN32_WINNT - /* without this define, InitializeCriticalSectionAndSpinCount is - * undefined. This version level means "Windows 2000 and higher" - */ - #define _WIN32_WINNT 0x0500 -#endif - -#include "windows.h" -#include "mmsystem.h" -#include "portmidi.h" -#include "pmutil.h" -#include "pminternal.h" -#include "pmwinmm.h" -#include <string.h> -#include "porttime.h" - -/* asserts used to verify portMidi code logic is sound; later may want - something more graceful */ -#include <assert.h> -#define DEBUG 1 -#ifdef DEBUG -/* this printf stuff really important for debugging client app w/host errors. - probably want to do something else besides read/write from/to console - for portability, however */ -#define STRING_MAX 80 -#include "stdio.h" -#endif - -#define streql(x, y) (strcmp(x, y) == 0) - -#define MIDI_SYSEX 0xf0 -#define MIDI_EOX 0xf7 - -/* callback routines */ -static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn, - WORD wMsg, DWORD_PTR dwInstance, - DWORD_PTR dwParam1, DWORD_PTR dwParam2); -static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, - DWORD_PTR dwInstance, DWORD_PTR dwParam1, - DWORD_PTR dwParam2); -static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, - DWORD_PTR dwInstance, DWORD_PTR dwParam1, - DWORD_PTR dwParam2); - -extern pm_fns_node pm_winmm_in_dictionary; -extern pm_fns_node pm_winmm_out_dictionary; - -static void winmm_out_delete(PmInternal *midi); /* forward reference */ - -/* -A note about buffers: WinMM seems to hold onto buffers longer than -one would expect, e.g. when I tried using 2 small buffers to send -long sysex messages, at some point WinMM held both buffers. This problem -was fixed by making buffers bigger. Therefore, it seems that there should -be enough buffer space to hold a whole sysex message. - -The bufferSize passed into Pm_OpenInput (passed into here as buffer_len) -will be used to estimate the largest sysex message (= buffer_len * 4 bytes). -Call that the max_sysex_len = buffer_len * 4. - -For simple midi output (latency == 0), allocate 3 buffers, each with half -the size of max_sysex_len, but each at least 256 bytes. - -For stream output, there will already be enough space in very short -buffers, so use them, but make sure there are at least 16. - -For input, use many small buffers rather than 2 large ones so that when -there are short sysex messages arriving frequently (as in control surfaces) -there will be more free buffers to fill. Use max_sysex_len / 64 buffers, -but at least 16, of size 64 bytes each. - -The following constants help to represent these design parameters: -*/ -#define NUM_SIMPLE_SYSEX_BUFFERS 3 -#define MIN_SIMPLE_SYSEX_LEN 256 - -#define MIN_STREAM_BUFFERS 16 -#define STREAM_BUFFER_LEN 24 - -#define INPUT_SYSEX_LEN 64 -#define MIN_INPUT_BUFFERS 16 - -/* if we run out of space for output (assume this is due to a sysex msg, - expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN - */ -#define NUM_EXPANSION_BUFFERS 128 -#define EXPANSION_BUFFER_LEN 1024 - -/* A sysex buffer has 3 DWORDS as a header plus the actual message size */ -#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3) -/* A MIDIHDR with a sysex message is the buffer length plus the header size */ -#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) -#ifdef USE_SYSEX_BUFFERS -/* Size of a MIDIHDR with a buffer contaning multiple MIDIEVENT structures */ -#define MIDIHDR_SIZE(x) ((x) + sizeof(MIDIHDR)) -#endif - -/* -============================================================================== -win32 mmedia system specific structure passed to midi callbacks -============================================================================== -*/ - -/* global winmm device info */ -MIDIINCAPS *midi_in_caps = NULL; -MIDIINCAPS midi_in_mapper_caps; -UINT midi_num_inputs = 0; -MIDIOUTCAPS *midi_out_caps = NULL; -MIDIOUTCAPS midi_out_mapper_caps; -UINT midi_num_outputs = 0; - -/* per device info */ -typedef struct midiwinmm_struct { - union { - HMIDISTRM stream; /* windows handle for stream */ - HMIDIOUT out; /* windows handle for out calls */ - HMIDIIN in; /* windows handle for in calls */ - } handle; - - /* midi output messages are sent in these buffers, which are allocated - * in a round-robin fashion, using next_buffer as an index - */ - LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */ - int max_buffers; /* length of buffers array */ - int buffers_expanded; /* buffers array expanded for extra msgs? */ - int num_buffers; /* how many buffers allocated in buffers array */ - int next_buffer; /* index of next buffer to send */ - HANDLE buffer_signal; /* used to wait for buffer to become free */ -#ifdef USE_SYSEX_BUFFERS - /* sysex buffers will be allocated only when - * a sysex message is sent. The size of the buffer is fixed. - */ - LPMIDIHDR sysex_buffers[NUM_SYSEX_BUFFERS]; /* pool of buffers for sysex data */ - int next_sysex_buffer; /* index of next sysexbuffer to send */ -#endif - unsigned long last_time; /* last output time */ - int first_message; /* flag: treat first message differently */ - int sysex_mode; /* middle of sending sysex */ - unsigned long sysex_word; /* accumulate data when receiving sysex */ - unsigned int sysex_byte_count; /* count how many received */ - LPMIDIHDR hdr; /* the message accumulating sysex to send */ - unsigned long sync_time; /* when did we last determine delta? */ - long delta; /* difference between stream time and - real time */ - int error; /* host error from doing port midi call */ - CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */ -} midiwinmm_node, *midiwinmm_type; - - -/* -============================================================================= -general MIDI device queries -============================================================================= -*/ -static void pm_winmm_general_inputs() -{ - UINT i; - WORD wRtn; - midi_num_inputs = midiInGetNumDevs(); - midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) * - midi_num_inputs); - if (midi_in_caps == NULL) { - /* if you can't open a particular system-level midi interface - * (such as winmm), we just consider that system or API to be - * unavailable and move on without reporting an error. - */ - return; - } - - for (i = 0; i < midi_num_inputs; i++) { - wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i], - sizeof(MIDIINCAPS)); - if (wRtn == MMSYSERR_NOERROR) { - /* ignore errors here -- if pm_descriptor_max is exceeded, some - devices will not be accessible. */ - pm_add_device("MMSystem", midi_in_caps[i].szPname, TRUE, - (void *) i, &pm_winmm_in_dictionary); - } - } -} - - -static void pm_winmm_mapper_input() -{ - WORD wRtn; - /* Note: if MIDIMAPPER opened as input (documentation implies you - can, but current system fails to retrieve input mapper - capabilities) then you still should retrieve some formof - setup info. */ - wRtn = midiInGetDevCaps((UINT) MIDIMAPPER, - (LPMIDIINCAPS) & midi_in_mapper_caps, - sizeof(MIDIINCAPS)); - if (wRtn == MMSYSERR_NOERROR) { - pm_add_device("MMSystem", midi_in_mapper_caps.szPname, TRUE, - (void *) MIDIMAPPER, &pm_winmm_in_dictionary); - } -} - - -static void pm_winmm_general_outputs() -{ - UINT i; - DWORD wRtn; - midi_num_outputs = midiOutGetNumDevs(); - midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs ); - - if (midi_out_caps == NULL) { - /* no error is reported -- see pm_winmm_general_inputs */ - return ; - } - - for (i = 0; i < midi_num_outputs; i++) { - wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i], - sizeof(MIDIOUTCAPS)); - if (wRtn == MMSYSERR_NOERROR) { - pm_add_device("MMSystem", midi_out_caps[i].szPname, FALSE, - (void *) i, &pm_winmm_out_dictionary); - } - } -} - - -static void pm_winmm_mapper_output() -{ - WORD wRtn; - /* Note: if MIDIMAPPER opened as output (pseudo MIDI device - maps device independent messages into device dependant ones, - via NT midimapper program) you still should get some setup info */ - wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS) - & midi_out_mapper_caps, sizeof(MIDIOUTCAPS)); - if (wRtn == MMSYSERR_NOERROR) { - pm_add_device("MMSystem", midi_out_mapper_caps.szPname, FALSE, - (void *) MIDIMAPPER, &pm_winmm_out_dictionary); - } -} - - -/* -========================================================================================= -host error handling -========================================================================================= -*/ -static unsigned int winmm_has_host_error(PmInternal * midi) -{ - midiwinmm_type m = (midiwinmm_type)midi->descriptor; - return m->error; -} - - -/* str_copy_len -- like strcat, but won't overrun the destination string */ -/* - * returns length of resulting string - */ -static int str_copy_len(char *dst, char *src, int len) -{ - strncpy(dst, src, len); - /* just in case suffex is greater then len, terminate with zero */ - dst[len - 1] = 0; - return strlen(dst); -} - - -static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) -{ - /* precondition: midi != NULL */ - midiwinmm_node * m = (midiwinmm_node *) midi->descriptor; - char *hdr1 = "Host error: "; - char *hdr2 = "Host callback error: "; - - msg[0] = 0; /* initialize result string to empty */ - - if (descriptors[midi->device_id].pub.input) { - /* input and output use different winmm API calls */ - if (m) { /* make sure there is an open device to examine */ - if (m->error != MMSYSERR_NOERROR) { - int n = str_copy_len(msg, hdr1, len); - /* read and record host error */ - int err = midiInGetErrorText(m->error, msg + n, len - n); - assert(err == MMSYSERR_NOERROR); - m->error = MMSYSERR_NOERROR; - } - } - } else { /* output port */ - if (m) { - if (m->error != MMSYSERR_NOERROR) { - int n = str_copy_len(msg, hdr1, len); - int err = midiOutGetErrorText(m->error, msg + n, len - n); - assert(err == MMSYSERR_NOERROR); - m->error = MMSYSERR_NOERROR; - } - } - } -} - - -/* -============================================================================= -buffer handling -============================================================================= -*/ -static MIDIHDR *allocate_buffer(long data_size) -{ - LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); - MIDIEVENT *evt; - if (!hdr) return NULL; - evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ - hdr->lpData = (LPSTR) evt; - hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); - hdr->dwBytesRecorded = 0; - hdr->dwFlags = 0; - hdr->dwUser = hdr->dwBufferLength; - return hdr; -} - -#ifdef USE_SYSEX_BUFFERS -static MIDIHDR *allocate_sysex_buffer(long data_size) -{ - /* we're actually allocating more than data_size because the buffer - * will include the MIDIEVENT header in addition to the data - */ - LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); - MIDIEVENT *evt; - if (!hdr) return NULL; - evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ - hdr->lpData = (LPSTR) evt; - hdr->dwFlags = 0; - hdr->dwUser = 0; - return hdr; -} -#endif - -static PmError allocate_buffers(midiwinmm_type m, long data_size, long count) -{ - int i; - /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ - m->num_buffers = 0; /* in case no memory can be allocated */ - m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); - if (!m->buffers) return pmInsufficientMemory; - m->max_buffers = count; - for (i = 0; i < count; i++) { - LPMIDIHDR hdr = allocate_buffer(data_size); - if (!hdr) { /* free everything allocated so far and return */ - for (i = i - 1; i >= 0; i--) pm_free(m->buffers[i]); - pm_free(m->buffers); - m->max_buffers = 0; - return pmInsufficientMemory; - } - m->buffers[i] = hdr; /* this may be NULL if allocation fails */ - } - m->num_buffers = count; - return pmNoError; -} - -#ifdef USE_SYSEX_BUFFERS -static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size) -{ - PmError rslt = pmNoError; - /* sysex_buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ - int i; - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { - LPMIDIHDR hdr = allocate_sysex_buffer(data_size); - - if (!hdr) rslt = pmInsufficientMemory; - m->sysex_buffers[i] = hdr; /* this may be NULL if allocation fails */ - hdr->dwFlags = 0; /* mark as free */ - } - return rslt; -} -#endif - -#ifdef USE_SYSEX_BUFFERS -static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi) -{ - LPMIDIHDR r = NULL; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (!m->sysex_buffers[0]) { - if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER)) { - return NULL; - } - } - /* busy wait until we find a free buffer */ - while (TRUE) { - int i; - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { - /* cycle through buffers, modulo NUM_SYSEX_BUFFERS */ - m->next_sysex_buffer++; - if (m->next_sysex_buffer >= NUM_SYSEX_BUFFERS) m->next_sysex_buffer = 0; - r = m->sysex_buffers[m->next_sysex_buffer]; - if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer; - } - /* after scanning every buffer and not finding anything, block */ - if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { -#ifdef DEBUG - printf("PortMidi warning: get_free_sysex_buffer() wait timed out after 1000ms\n"); -#endif - } - } -found_sysex_buffer: - r->dwBytesRecorded = 0; - r->dwBufferLength = 0; /* changed to correct value later */ - return r; -} -#endif - -static LPMIDIHDR get_free_output_buffer(PmInternal *midi) -{ - LPMIDIHDR r = NULL; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - while (TRUE) { - int i; - for (i = 0; i < m->num_buffers; i++) { - /* cycle through buffers, modulo m->num_buffers */ - m->next_buffer++; - if (m->next_buffer >= m->num_buffers) m->next_buffer = 0; - r = m->buffers[m->next_buffer]; - if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer; - } - /* after scanning every buffer and not finding anything, block */ - if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { -#ifdef DEBUG - printf("PortMidi warning: get_free_output_buffer() wait timed out after 1000ms\n"); -#endif - /* if we're trying to send a sysex message, maybe the - * message is too big and we need more message buffers. - * Expand the buffer pool by 128KB using 1024-byte buffers. - */ - /* first, expand the buffers array if necessary */ - if (!m->buffers_expanded) { - LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc( - (m->num_buffers + NUM_EXPANSION_BUFFERS) * - sizeof(LPMIDIHDR)); - /* if no memory, we could return a no-memory error, but user - * probably will be unprepared to deal with it. Maybe the - * MIDI driver is temporarily hung so we should just wait. - * I don't know the right answer, but waiting is easier. - */ - if (!new_buffers) continue; - /* copy buffers to new_buffers and replace buffers */ - memcpy(new_buffers, m->buffers, - m->num_buffers * sizeof(LPMIDIHDR)); - pm_free(m->buffers); - m->buffers = new_buffers; - m->max_buffers = m->num_buffers + NUM_EXPANSION_BUFFERS; - m->buffers_expanded = TRUE; - } - /* next, add one buffer and return it */ - if (m->num_buffers < m->max_buffers) { - r = allocate_buffer(EXPANSION_BUFFER_LEN); - /* again, if there's no memory, we may not really be - * dead -- maybe the system is temporarily hung and - * we can just wait longer for a message buffer */ - if (!r) continue; - m->buffers[m->num_buffers++] = r; - goto found_buffer; /* break out of 2 loops */ - } - /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers, - * and we have no free buffers to send. We'll just keep - * polling to see if any buffers show up. - */ - } - } -found_buffer: - r->dwBytesRecorded = 0; - /* actual buffer length is saved in dwUser field */ - r->dwBufferLength = (DWORD) r->dwUser; - return r; -} - -#ifdef EXPANDING_SYSEX_BUFFERS -note: this is not working code, but might be useful if you want - to grow sysex buffers. -static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size) -{ - LPMIDIHDR big; - int i; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - /* buffer must be smaller than 64k, but be also a multiple of 4 */ - if (new_size > 65520) { - if (old_size >= 65520) - return pmBufferMaxSize; - else - new_size = 65520; - } - /* allocate a bigger message */ - big = allocate_sysex_buffer(new_size); - /* printf("expand to %d bytes\n", new_size);*/ - if (!big) return pmInsufficientMemory; - m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR)); - if (m->error) { - pm_free(big); - return pmHostError; - } - /* make sure we're not going to overwrite any memory */ - assert(old_size <= new_size); - memcpy(big->lpData, m->hdr->lpData, old_size); - /* keep track of how many sysex bytes are in message so far */ - big->dwBytesRecorded = m->hdr->dwBytesRecorded; - big->dwBufferLength = new_size; - /* find which buffer this was, and replace it */ - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { - if (m->sysex_buffers[i] == m->hdr) { - m->sysex_buffers[i] = big; - m->sysex_buffer_size[i] = new_size; - pm_free(m->hdr); - m->hdr = big; - break; - } - } - assert(i != NUM_SYSEX_BUFFERS); - - return pmNoError; -} -#endif - -/* -========================================================================================= -begin midi input implementation -========================================================================================= -*/ - - -static PmError allocate_input_buffer(HMIDIIN h, long buffer_len) -{ - LPMIDIHDR hdr = allocate_buffer(buffer_len); - if (!hdr) return pmInsufficientMemory; - pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR)); - if (pm_hosterror) { - pm_free(hdr); - return pm_hosterror; - } - pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR)); - return pm_hosterror; -} - - -static PmError winmm_in_open(PmInternal *midi, void *driverInfo) -{ - DWORD dwDevice; - int i = midi->device_id; - int max_sysex_len = midi->buffer_len * 4; - int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN; - midiwinmm_type m; - - dwDevice = (DWORD) descriptors[i].descriptor; - - /* create system dependent device data */ - m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ - midi->descriptor = m; - if (!m) goto no_memory; - m->handle.in = NULL; - m->buffers = NULL; /* not used for input */ - m->num_buffers = 0; /* not used for input */ - m->max_buffers = FALSE; /* not used for input */ - m->buffers_expanded = 0; /* not used for input */ - m->next_buffer = 0; /* not used for input */ - m->buffer_signal = 0; /* not used for input */ -#ifdef USE_SYSEX_BUFFERS - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) - m->sysex_buffers[i] = NULL; /* not used for input */ - m->next_sysex_buffer = 0; /* not used for input */ -#endif - m->last_time = 0; - m->first_message = TRUE; /* not used for input */ - m->sysex_mode = FALSE; - m->sysex_word = 0; - m->sysex_byte_count = 0; - m->hdr = NULL; /* not used for input */ - m->sync_time = 0; - m->delta = 0; - m->error = MMSYSERR_NOERROR; - /* 4000 is based on Windows documentation -- that's the value used in the - memory manager. It's small enough that it should not hurt performance even - if it's not optimal. - */ - InitializeCriticalSectionAndSpinCount(&m->lock, 4000); - /* open device */ - pm_hosterror = midiInOpen(&(m->handle.in), /* input device handle */ - dwDevice, /* device ID */ - (DWORD_PTR) winmm_in_callback, /* callback address */ - (DWORD_PTR) midi, /* callback instance data */ - CALLBACK_FUNCTION); /* callback is a procedure */ - if (pm_hosterror) goto free_descriptor; - - if (num_input_buffers < MIN_INPUT_BUFFERS) - num_input_buffers = MIN_INPUT_BUFFERS; - for (i = 0; i < num_input_buffers; i++) { - if (allocate_input_buffer(m->handle.in, INPUT_SYSEX_LEN)) { - /* either pm_hosterror was set, or the proper return code - is pmInsufficientMemory */ - goto close_device; - } - } - /* start device */ - pm_hosterror = midiInStart(m->handle.in); - if (pm_hosterror) goto reset_device; - return pmNoError; - - /* undo steps leading up to the detected error */ -reset_device: - /* ignore return code (we already have an error to report) */ - midiInReset(m->handle.in); -close_device: - midiInClose(m->handle.in); /* ignore return code */ -free_descriptor: - midi->descriptor = NULL; - pm_free(m); -no_memory: - if (pm_hosterror) { - int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - assert(err == MMSYSERR_NOERROR); - return pmHostError; - } - /* if !pm_hosterror, then the error must be pmInsufficientMemory */ - return pmInsufficientMemory; - /* note: if we return an error code, the device will be - closed and memory will be freed. It's up to the caller - to free the parameter midi */ -} - -static PmError winmm_in_poll(PmInternal *midi) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - return m->error; -} - - - -/* winmm_in_close -- close an open midi input device */ -/* - * assume midi is non-null (checked by caller) - */ -static PmError winmm_in_close(PmInternal *midi) -{ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (!m) return pmBadPtr; - /* device to close */ - if (pm_hosterror = midiInStop(m->handle.in)) { - midiInReset(m->handle.in); /* try to reset and close port */ - midiInClose(m->handle.in); - } else if (pm_hosterror = midiInReset(m->handle.in)) { - midiInClose(m->handle.in); /* best effort to close midi port */ - } else { - pm_hosterror = midiInClose(m->handle.in); - } - midi->descriptor = NULL; - DeleteCriticalSection(&m->lock); - pm_free(m); /* delete */ - if (pm_hosterror) { - int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - assert(err == MMSYSERR_NOERROR); - return pmHostError; - } - return pmNoError; -} - - -/* Callback function executed via midiInput SW interrupt (via midiInOpen). */ -static void FAR PASCAL winmm_in_callback( - HMIDIIN hMidiIn, /* midiInput device Handle */ - WORD wMsg, /* midi msg */ - DWORD_PTR dwInstance, /* application data */ - DWORD_PTR dwParam1, /* MIDI data */ - DWORD_PTR dwParam2) /* device timestamp (wrt most recent midiInStart) */ -{ - static int entry = 0; - PmInternal *midi = (PmInternal *) dwInstance; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - - /* NOTE: we do not just EnterCriticalSection() here because an - * MIM_CLOSE message arrives when the port is closed, but then - * the m->lock has been destroyed. - */ - - switch (wMsg) { - case MIM_DATA: { - /* if this callback is reentered with data, we're in trouble. - * It's hard to imagine that Microsoft would allow callbacks - * to be reentrant -- isn't the model that this is like a - * hardware interrupt? -- but I've seen reentrant behavior - * using a debugger, so it happens. - */ - EnterCriticalSection(&m->lock); - - /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of - message LOB; - dwParam2 is time message received by input device driver, specified - in [ms] from when midiInStart called. - each message is expanded to include the status byte */ - - long new_driver_time = dwParam2; - - if ((dwParam1 & 0x80) == 0) { - /* not a status byte -- ignore it. This happened running the - sysex.c test under Win2K with MidiMan USB 1x1 interface, - but I can't reproduce it. -RBD - */ - /* printf("non-status byte found\n"); */ - } else { /* data to process */ - PmEvent event; - if (midi->time_proc) - dwParam2 = (*midi->time_proc)(midi->time_info); - event.timestamp = dwParam2; - event.message = dwParam1; - pm_read_short(midi, &event); - } - LeaveCriticalSection(&m->lock); - break; - } - case MIM_LONGDATA: { - MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1; - unsigned char *data = (unsigned char *) lpMidiHdr->lpData; - unsigned int processed = 0; - int remaining = lpMidiHdr->dwBytesRecorded; - - EnterCriticalSection(&m->lock); - /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", - lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */ - if (midi->time_proc) - dwParam2 = (*midi->time_proc)(midi->time_info); - /* can there be more than one message in one buffer? */ - /* assume yes and iterate through them */ - while (remaining > 0) { - unsigned int amt = pm_read_bytes(midi, data + processed, - remaining, dwParam2); - remaining -= amt; - processed += amt; - } - - /* when a device is closed, the pending MIM_LONGDATA buffers are - returned to this callback with dwBytesRecorded == 0. In this - case, we do not want to send them back to the interface (if - we do, the interface will not close, and Windows OS may hang). */ - if (lpMidiHdr->dwBytesRecorded > 0) { - lpMidiHdr->dwBytesRecorded = 0; - lpMidiHdr->dwFlags = 0; - - /* note: no error checking -- can this actually fail? */ - assert(midiInPrepareHeader(hMidiIn, lpMidiHdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); - /* note: I don't think this can fail except possibly for - * MMSYSERR_NOMEM, but the pain of reporting this - * unlikely but probably catastrophic error does not seem - * worth it. - */ - assert(midiInAddBuffer(hMidiIn, lpMidiHdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); - LeaveCriticalSection(&m->lock); - } else { - midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR)); - LeaveCriticalSection(&m->lock); - pm_free(lpMidiHdr); - } - break; - } - case MIM_OPEN: - break; - case MIM_CLOSE: - break; - case MIM_ERROR: - /* printf("MIM_ERROR\n"); */ - break; - case MIM_LONGERROR: - /* printf("MIM_LONGERROR\n"); */ - break; - default: - break; - } -} - -/* -========================================================================================= -begin midi output implementation -========================================================================================= -*/ - -/* begin helper routines used by midiOutStream interface */ - -/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */ -static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, - unsigned long delta, unsigned long msg) -{ - unsigned long *ptr = (unsigned long *) - (hdr->lpData + hdr->dwBytesRecorded); - *ptr++ = delta; /* dwDeltaTime */ - *ptr++ = 0; /* dwStream */ - *ptr++ = msg; /* dwEvent */ - hdr->dwBytesRecorded += 3 * sizeof(long); - /* if the addition of three more words (a message) would extend beyond - the buffer length, then return TRUE (full) - */ - return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength; -} - - -static PmTimestamp pm_time_get(midiwinmm_type m) -{ - MMTIME mmtime; - MMRESULT wRtn; - mmtime.wType = TIME_TICKS; - mmtime.u.ticks = 0; - wRtn = midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime)); - assert(wRtn == MMSYSERR_NOERROR); - return mmtime.u.ticks; -} - - -/* end helper routines used by midiOutStream interface */ - - -static PmError winmm_out_open(PmInternal *midi, void *driverInfo) -{ - DWORD dwDevice; - int i = midi->device_id; - midiwinmm_type m; - MIDIPROPTEMPO propdata; - MIDIPROPTIMEDIV divdata; - int max_sysex_len = midi->buffer_len * 4; - int output_buffer_len; - int num_buffers; - dwDevice = (DWORD) descriptors[i].descriptor; - - /* create system dependent device data */ - m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ - midi->descriptor = m; - if (!m) goto no_memory; - m->handle.out = NULL; - m->buffers = NULL; - m->num_buffers = 0; - m->max_buffers = 0; - m->buffers_expanded = FALSE; - m->next_buffer = 0; -#ifdef USE_SYSEX_BUFFERS - m->sysex_buffers[0] = NULL; - m->sysex_buffers[1] = NULL; - m->next_sysex_buffer = 0; -#endif - m->last_time = 0; - m->first_message = TRUE; /* we treat first message as special case */ - m->sysex_mode = FALSE; - m->sysex_word = 0; - m->sysex_byte_count = 0; - m->hdr = NULL; - m->sync_time = 0; - m->delta = 0; - m->error = MMSYSERR_NOERROR; - - /* create a signal */ - m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); - - /* this should only fail when there are very serious problems */ - assert(m->buffer_signal); - - /* open device */ - if (midi->latency == 0) { - /* use simple midi out calls */ - pm_hosterror = midiOutOpen((LPHMIDIOUT) & m->handle.out, /* device Handle */ - dwDevice, /* device ID */ - /* note: same callback fn as for StreamOpen: */ - (DWORD_PTR) winmm_streamout_callback, /* callback fn */ - (DWORD_PTR) midi, /* callback instance data */ - CALLBACK_FUNCTION); /* callback type */ - } else { - /* use stream-based midi output (schedulable in future) */ - pm_hosterror = midiStreamOpen(&m->handle.stream, /* device Handle */ - (LPUINT) & dwDevice, /* device ID pointer */ - 1, /* reserved, must be 1 */ - (DWORD_PTR) winmm_streamout_callback, - (DWORD_PTR) midi, /* callback instance data */ - CALLBACK_FUNCTION); - } - if (pm_hosterror != MMSYSERR_NOERROR) { - goto free_descriptor; - } - - if (midi->latency == 0) { - num_buffers = NUM_SIMPLE_SYSEX_BUFFERS; - output_buffer_len = max_sysex_len / num_buffers; - if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN) - output_buffer_len = MIN_SIMPLE_SYSEX_LEN; - } else { - long dur = 0; - num_buffers = max(midi->buffer_len, midi->latency / 2); - if (num_buffers < MIN_STREAM_BUFFERS) - num_buffers = MIN_STREAM_BUFFERS; - output_buffer_len = STREAM_BUFFER_LEN; - - propdata.cbStruct = sizeof(MIDIPROPTEMPO); - propdata.dwTempo = 480000; /* microseconds per quarter */ - pm_hosterror = midiStreamProperty(m->handle.stream, - (LPBYTE) & propdata, - MIDIPROP_SET | MIDIPROP_TEMPO); - if (pm_hosterror) goto close_device; - - divdata.cbStruct = sizeof(MIDIPROPTEMPO); - divdata.dwTimeDiv = 480; /* divisions per quarter */ - pm_hosterror = midiStreamProperty(m->handle.stream, - (LPBYTE) & divdata, - MIDIPROP_SET | MIDIPROP_TIMEDIV); - if (pm_hosterror) goto close_device; - } - /* allocate buffers */ - if (allocate_buffers(m, output_buffer_len, num_buffers)) - goto free_buffers; - /* start device */ - if (midi->latency != 0) { - pm_hosterror = midiStreamRestart(m->handle.stream); - if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers; - } - return pmNoError; - -free_buffers: - /* buffers are freed below by winmm_out_delete */ -close_device: - midiOutClose(m->handle.out); -free_descriptor: - midi->descriptor = NULL; - winmm_out_delete(midi); /* frees buffers and m */ -no_memory: - if (pm_hosterror) { - int err = midiOutGetErrorText(pm_hosterror, (char *) pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - assert(err == MMSYSERR_NOERROR); - return pmHostError; - } - return pmInsufficientMemory; -} - - -/* winmm_out_delete -- carefully free data associated with midi */ -/**/ -static void winmm_out_delete(PmInternal *midi) -{ - int i; - /* delete system dependent device data */ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (m) { - if (m->buffer_signal) { - /* don't report errors -- better not to stop cleanup */ - CloseHandle(m->buffer_signal); - } - /* if using stream output, free buffers */ - for (i = 0; i < m->num_buffers; i++) { - if (m->buffers[i]) pm_free(m->buffers[i]); - } - m->num_buffers = 0; - pm_free(m->buffers); - m->max_buffers = 0; -#ifdef USE_SYSEX_BUFFERS - /* free sysex buffers */ - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { - if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); - } -#endif - } - midi->descriptor = NULL; - pm_free(m); /* delete */ -} - - -/* see comments for winmm_in_close */ -static PmError winmm_out_close(PmInternal *midi) -{ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (m->handle.out) { - /* device to close */ - if (midi->latency == 0) { - pm_hosterror = midiOutClose(m->handle.out); - } else { - pm_hosterror = midiStreamClose(m->handle.stream); - } - /* regardless of outcome, free memory */ - winmm_out_delete(midi); - } - if (pm_hosterror) { - int err = midiOutGetErrorText(pm_hosterror, - (char *) pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - assert(err == MMSYSERR_NOERROR); - return pmHostError; - } - return pmNoError; -} - - -static PmError winmm_out_abort(PmInternal *midi) -{ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - m->error = MMSYSERR_NOERROR; - - /* only stop output streams */ - if (midi->latency > 0) { - m->error = midiStreamStop(m->handle.stream); - } - return m->error ? pmHostError : pmNoError; -} - - -static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp) -{ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - assert(m); - if (m->hdr) { - m->error = midiOutPrepareHeader(m->handle.out, m->hdr, - sizeof(MIDIHDR)); - if (m->error) { - /* do not send message */ - } else if (midi->latency == 0) { - /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded - * should be zero. This is set in get_free_sysex_buffer(). - * The msg length goes in dwBufferLength in spite of what - * Microsoft documentation says (or doesn't say). */ - m->hdr->dwBufferLength = m->hdr->dwBytesRecorded; - m->hdr->dwBytesRecorded = 0; - m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); - } else { - m->error = midiStreamOut(m->handle.stream, m->hdr, - sizeof(MIDIHDR)); - } - midi->fill_base = NULL; - m->hdr = NULL; - if (m->error) { - m->hdr->dwFlags = 0; /* release the buffer */ - return pmHostError; - } - } - return pmNoError; -} - - - -#ifdef GARBAGE -static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte) -{ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - unsigned char *msg_buffer; - - /* at the beginning of sysex, m->hdr is NULL */ - if (!m->hdr) { /* allocate a buffer if none allocated yet */ - m->hdr = get_free_output_buffer(midi); - if (!m->hdr) return pmInsufficientMemory; - m->sysex_byte_count = 0; - } - /* figure out where to write byte */ - msg_buffer = (unsigned char *) (m->hdr->lpData); - assert(m->hdr->lpData == (char *) (m->hdr + 1)); - - /* check for overflow */ - if (m->sysex_byte_count >= m->hdr->dwBufferLength) { - /* allocate a bigger message -- double it every time */ - LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2); - /* printf("expand to %d bytes\n", m->sysex_byte_count * 2); */ - if (!big) return pmInsufficientMemory; - m->error = midiOutPrepareHeader(m->handle.out, big, - sizeof(MIDIHDR)); - if (m->error) { - m->hdr = NULL; - return pmHostError; - } - memcpy(big->lpData, msg_buffer, m->sysex_byte_count); - msg_buffer = (unsigned char *) (big->lpData); - if (m->buffers[0] == m->hdr) { - m->buffers[0] = big; - pm_free(m->hdr); - /* printf("freed m->hdr\n"); */ - } else if (m->buffers[1] == m->hdr) { - m->buffers[1] = big; - pm_free(m->hdr); - /* printf("freed m->hdr\n"); */ - } - m->hdr = big; - } - - /* append byte to message */ - msg_buffer[m->sysex_byte_count++] = byte; - - /* see if we have a complete message */ - if (byte == MIDI_EOX) { - m->hdr->dwBytesRecorded = m->sysex_byte_count; - /* - { int i; int len = m->hdr->dwBytesRecorded; - printf("OutLongMsg %d ", len); - for (i = 0; i < len; i++) { - printf("%2x ", msg_buffer[i]); - } - } - */ - m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); - m->hdr = NULL; /* stop using this message buffer */ - if (m->error) return pmHostError; - } - return pmNoError; -} -#endif - - -static PmError winmm_write_short(PmInternal *midi, PmEvent *event) -{ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - PmError rslt = pmNoError; - assert(m); - - if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ - m->error = midiOutShortMsg(m->handle.out, event->message); - if (m->error) rslt = pmHostError; - } else { /* use midiStream interface -- pass data through buffers */ - unsigned long when = event->timestamp; - unsigned long delta; - int full; - if (when == 0) when = midi->now; - /* when is in real_time; translate to intended stream time */ - when = when + m->delta + midi->latency; - /* make sure we don't go backward in time */ - if (when < m->last_time) when = m->last_time; - delta = when - m->last_time; - m->last_time = when; - /* before we insert any data, we must have a buffer */ - if (m->hdr == NULL) { - /* stream interface: buffers allocated when stream is opened */ - m->hdr = get_free_output_buffer(midi); - } - full = add_to_buffer(m, m->hdr, delta, event->message); - if (full) rslt = winmm_write_flush(midi, when); - } - return rslt; -} - -#define winmm_begin_sysex winmm_write_flush -#ifndef winmm_begin_sysex -static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp) -{ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - PmError rslt = pmNoError; - - if (midi->latency == 0) { - /* do nothing -- it's handled in winmm_write_byte */ - } else { - /* sysex expects an empty sysex buffer, so send whatever is here */ - rslt = winmm_write_flush(midi); - } - return rslt; -} -#endif - -static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) -{ - /* could check for callback_error here, but I haven't checked - * what happens if we exit early and don't finish the sysex msg - * and clean up - */ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - PmError rslt = pmNoError; - LPMIDIHDR hdr = m->hdr; - if (!hdr) return rslt; /* something bad happened earlier, - do not report an error because it would have been - reported (at least) once already */ - /* a(n old) version of MIDI YOKE requires a zero byte after - * the sysex message, but do not increment dwBytesRecorded: */ - hdr->lpData[hdr->dwBytesRecorded] = 0; - if (midi->latency == 0) { -#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX - /* DEBUG CODE: */ - { int i; int len = m->hdr->dwBufferLength; - printf("OutLongMsg %d ", len); - for (i = 0; i < len; i++) { - printf("%2x ", (unsigned char) (m->hdr->lpData[i])); - } - } -#endif - } else { - /* Using stream interface. There are accumulated bytes in m->hdr - to send using midiStreamOut - */ - /* add bytes recorded to MIDIEVENT length, but don't - count the MIDIEVENT data (3 longs) */ - MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData); - evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); - /* round up BytesRecorded to multiple of 4 */ - hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; - } - rslt = winmm_write_flush(midi, timestamp); - return rslt; -} - - -static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, - PmTimestamp timestamp) -{ - /* write a sysex byte */ - PmError rslt = pmNoError; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - LPMIDIHDR hdr = m->hdr; - unsigned char *msg_buffer; - assert(m); - if (!hdr) { - m->hdr = hdr = get_free_output_buffer(midi); - assert(hdr); - midi->fill_base = (unsigned char *) m->hdr->lpData; - midi->fill_offset_ptr = &(hdr->dwBytesRecorded); - /* when buffer fills, Pm_WriteSysEx will revert to calling - * pmwin_write_byte, which expect to have space, so leave - * one byte free for pmwin_write_byte. Leave another byte - * of space for zero after message to make early version of - * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */ - midi->fill_length = hdr->dwBufferLength - 2; - if (midi->latency != 0) { - unsigned long when = (unsigned long) timestamp; - unsigned long delta; - unsigned long *ptr; - if (when == 0) when = midi->now; - /* when is in real_time; translate to intended stream time */ - when = when + m->delta + midi->latency; - /* make sure we don't go backward in time */ - if (when < m->last_time) when = m->last_time; - delta = when - m->last_time; - m->last_time = when; - - ptr = (unsigned long *) hdr->lpData; - *ptr++ = delta; - *ptr++ = 0; - *ptr = MEVT_F_LONG; - hdr->dwBytesRecorded = 3 * sizeof(long); - /* data will be added at an offset of dwBytesRecorded ... */ - } - } - /* add the data byte */ - msg_buffer = (unsigned char *) (hdr->lpData); - msg_buffer[hdr->dwBytesRecorded++] = byte; - - /* see if buffer is full, leave one byte extra for pad */ - if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) { - /* write what we've got and continue */ - rslt = winmm_end_sysex(midi, timestamp); - } - return rslt; -} - -#ifdef EXPANDING_SYSEX_BUFFERS -note: this code is here as an aid in case you want sysex buffers - to expand to hold large messages completely. If so, you - will want to change SYSEX_BYTES_PER_BUFFER above to some - variable that remembers the buffer size. A good place to - put this value would be in the hdr->dwUser field. - - rslt = resize_sysex_buffer(midi, m->sysex_byte_count, - m->sysex_byte_count * 2); - - if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */ -#endif -#ifdef EXPANDING_SYSEX_BUFFERS - int bytesRecorded = hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ - rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); - hdr->dwBytesRecorded = bytesRecorded; - - if (rslt == pmBufferMaxSize) /* if buffer can't be resized */ -#endif - - - -static PmTimestamp winmm_synchronize(PmInternal *midi) -{ - midiwinmm_type m; - unsigned long pm_stream_time_2; - unsigned long real_time; - unsigned long pm_stream_time; - - /* only synchronize if we are using stream interface */ - if (midi->latency == 0) return 0; - - /* figure out the time */ - m = (midiwinmm_type) midi->descriptor; - pm_stream_time_2 = pm_time_get(m); - - do { - /* read real_time between two reads of stream time */ - pm_stream_time = pm_stream_time_2; - real_time = (*midi->time_proc)(midi->time_info); - pm_stream_time_2 = pm_time_get(m); - /* repeat if more than 1ms elapsed */ - } while (pm_stream_time_2 > pm_stream_time + 1); - m->delta = pm_stream_time - real_time; - m->sync_time = real_time; - return real_time; -} - -#ifdef USE_SYSEX_BUFFERS -/* winmm_out_callback -- recycle sysex buffers */ -static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, - DWORD_PTR dwInstance, DWORD_PTR dwParam1, - DWORD_PTR dwParam2) -{ - PmInternal *midi = (PmInternal *) dwInstance; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; - int err = 0; /* set to 0 so that no buffer match will also be an error */ - - /* Future optimization: eliminate UnprepareHeader calls -- they aren't - necessary; however, this code uses the prepared-flag to indicate which - buffers are free, so we need to do something to flag empty buffers if - we leave them prepared - */ - printf("out_callback: hdr %x, wMsg %x, MOM_DONE %x\n", - hdr, wMsg, MOM_DONE); - if (wMsg == MOM_DONE) - assert(midiOutUnprepareHeader(m->handle.out, hdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); - /* notify waiting sender that a buffer is available */ - err = SetEvent(m->buffer_signal); - assert(err); /* false -> error */ -} -#endif - -/* winmm_streamout_callback -- unprepare (free) buffer header */ -static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, - DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) -{ - PmInternal *midi = (PmInternal *) dwInstance; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; - int err; - - /* Even if an error is pending, I think we should unprepare msgs and - signal their arrival - */ - /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", - hdr, wMsg, MOM_DONE); */ - if (wMsg == MOM_DONE) { - assert(midiOutUnprepareHeader(m->handle.out, hdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); - } - /* signal client in case it is blocked waiting for buffer */ - err = SetEvent(m->buffer_signal); - assert(err); /* false -> error */ -} - - -/* -========================================================================================= -begin exported functions -========================================================================================= -*/ - -#define winmm_in_abort pm_fail_fn -pm_fns_node pm_winmm_in_dictionary = { - none_write_short, - none_sysex, - none_sysex, - none_write_byte, - none_write_short, - none_write_flush, - winmm_synchronize, - winmm_in_open, - winmm_in_abort, - winmm_in_close, - winmm_in_poll, - winmm_has_host_error, - winmm_get_host_error - }; - -pm_fns_node pm_winmm_out_dictionary = { - winmm_write_short, - winmm_begin_sysex, - winmm_end_sysex, - winmm_write_byte, - winmm_write_short, /* short realtime message */ - winmm_write_flush, - winmm_synchronize, - winmm_out_open, - winmm_out_abort, - winmm_out_close, - none_poll, - winmm_has_host_error, - winmm_get_host_error - }; - - -/* initialize winmm interface. Note that if there is something wrong - with winmm (e.g. it is not supported or installed), it is not an - error. We should simply return without having added any devices to - the table. Hence, no error code is returned. Furthermore, this init - code is called along with every other supported interface, so the - user would have a very hard time figuring out what hardware and API - generated the error. Finally, it would add complexity to pmwin.c to - remember where the error code came from in order to convert to text. - */ -void pm_winmm_init( void ) -{ - pm_winmm_mapper_input(); - pm_winmm_mapper_output(); - pm_winmm_general_inputs(); - pm_winmm_general_outputs(); -} - - -/* no error codes are returned, even if errors are encountered, because - there is probably nothing the user could do (e.g. it would be an error - to retry. - */ -void pm_winmm_term( void ) -{ - int i; -#ifdef DEBUG - char msg[PM_HOST_ERROR_MSG_LEN]; -#endif - int doneAny = 0; -#ifdef DEBUG - printf("pm_winmm_term called\n"); -#endif - for (i = 0; i < pm_descriptor_index; i++) { - PmInternal * midi = descriptors[i].internalDescriptor; - if (midi) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (m->handle.out) { - /* close next open device*/ -#ifdef DEBUG - if (doneAny == 0) { - printf("begin closing open devices...\n"); - doneAny = 1; - } - /* report any host errors; this EXTEREMELY useful when - trying to debug client app */ - if (winmm_has_host_error(midi)) { - winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN); - printf("%s\n", msg); - } -#endif - /* close all open ports */ - (*midi->dictionary->close)(midi); - } - } - } - if (midi_in_caps) { - pm_free(midi_in_caps); - midi_in_caps = NULL; - } - if (midi_out_caps) { - pm_free(midi_out_caps); - midi_out_caps = NULL; - } -#ifdef DEBUG - if (doneAny) { - printf("warning: devices were left open. They have been closed.\n"); - } - printf("pm_winmm_term exiting\n"); -#endif - pm_descriptor_index = 0; -} +/* pmwinmm.c -- system specific definitions */++#ifndef _WIN32_WINNT+ /* without this define, InitializeCriticalSectionAndSpinCount is + * undefined. This version level means "Windows 2000 and higher" + */+ #define _WIN32_WINNT 0x0500+#endif++#define UNICODE 1+#include <wchar.h>+#include "windows.h"+#include "mmsystem.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "pmwinmm.h"+#include <string.h>+#include "porttime.h"+#ifndef UNICODE+#error Expected UNICODE to be defined+#endif+++/* asserts used to verify portMidi code logic is sound; later may want+ something more graceful */+#include <assert.h>+#ifdef MMDEBUG+/* this printf stuff really important for debugging client app w/host errors.+ probably want to do something else besides read/write from/to console+ for portability, however */+#define STRING_MAX 80+#include "stdio.h"+#endif++#define streql(x, y) (strcmp(x, y) == 0)++#define MIDI_SYSEX 0xf0+#define MIDI_EOX 0xf7++/* callback routines */+static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn,+ UINT wMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2);+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,+ DWORD_PTR dwInstance, + DWORD_PTR dwParam1,+ DWORD_PTR dwParam2);++extern pm_fns_node pm_winmm_in_dictionary;+extern pm_fns_node pm_winmm_out_dictionary;++static void winmm_out_delete(PmInternal *midi); /* forward reference */++/*+A note about buffers: WinMM seems to hold onto buffers longer than+one would expect, e.g. when I tried using 2 small buffers to send+long sysex messages, at some point WinMM held both buffers. This problem+was fixed by making buffers bigger. Therefore, it seems that there should +be enough buffer space to hold a whole sysex message. ++The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)+will be used to estimate the largest sysex message (= buffer_len * 4 bytes).+Call that the max_sysex_len = buffer_len * 4.++For simple midi output (latency == 0), allocate 3 buffers, each with half+the size of max_sysex_len, but each at least 256 bytes.++For stream output, there will already be enough space in very short+buffers, so use them, but make sure there are at least 16.++For input, use many small buffers rather than 2 large ones so that when +there are short sysex messages arriving frequently (as in control surfaces)+there will be more free buffers to fill. Use max_sysex_len / 64 buffers,+but at least 16, of size 64 bytes each.++The following constants help to represent these design parameters:+*/+#define NUM_SIMPLE_SYSEX_BUFFERS 3+#define MIN_SIMPLE_SYSEX_LEN 256++#define MIN_STREAM_BUFFERS 16+#define STREAM_BUFFER_LEN 24++#define INPUT_SYSEX_LEN 64+#define MIN_INPUT_BUFFERS 16++/* if we run out of space for output (assume this is due to a sysex msg,+ expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN+ */+#define NUM_EXPANSION_BUFFERS 128+#define EXPANSION_BUFFER_LEN 1024++/* A sysex buffer has 3 DWORDS as a header plus the actual message size */+#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)+/* A MIDIHDR with a sysex message is the buffer length plus the header size */+#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))++/*+==============================================================================+win32 mmedia system specific structure passed to midi callbacks+==============================================================================+*/++/* global winmm device info */+MIDIINCAPS *midi_in_caps = NULL;+MIDIINCAPS midi_in_mapper_caps;+UINT midi_num_inputs = 0;+MIDIOUTCAPS *midi_out_caps = NULL;+MIDIOUTCAPS midi_out_mapper_caps;+UINT midi_num_outputs = 0;++/* per device info */+typedef struct winmm_info_struct {+ union {+ HMIDISTRM stream; /* windows handle for stream */+ HMIDIOUT out; /* windows handle for out calls */+ HMIDIIN in; /* windows handle for in calls */+ } handle;++ /* midi output messages are sent in these buffers, which are allocated+ * in a round-robin fashion, using next_buffer as an index+ */+ LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */+ int max_buffers; /* length of buffers array */+ int buffers_expanded; /* buffers array expanded for extra msgs? */+ int num_buffers; /* how many buffers allocated in buffers array */+ int next_buffer; /* index of next buffer to send */+ HANDLE buffer_signal; /* used to wait for buffer to become free */+ unsigned long last_time; /* last output time */+ int first_message; /* flag: treat first message differently */+ int sysex_mode; /* middle of sending sysex */+ unsigned long sysex_word; /* accumulate data when receiving sysex */+ unsigned int sysex_byte_count; /* count how many received */+ LPMIDIHDR hdr; /* the message accumulating sysex to send */+ unsigned long sync_time; /* when did we last determine delta? */+ long delta; /* difference between stream time and+ real time */+ CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */+} winmm_info_node, *winmm_info_type;+++/*+=============================================================================+general MIDI device queries+=============================================================================+*/++/* add a device after converting device (product) name to UTF-8 */+static void pm_add_device_w(char *api, WCHAR *device_name, int is_input,+ int is_virtual, void *descriptor, pm_fns_type dictionary)+{+ char utf8name[4 * MAXPNAMELEN];+ WideCharToMultiByte(CP_UTF8, 0, device_name, -1, + utf8name, 4 * MAXPNAMELEN - 1, NULL, NULL);+ /* ignore errors here -- if pm_descriptor_max is exceeded, + some devices will not be accessible. */+ pm_add_device(api, utf8name, is_input, is_virtual, descriptor, dictionary);+}+++static void pm_winmm_general_inputs(void)+{+ UINT i;+ WORD wRtn;+ midi_num_inputs = midiInGetNumDevs();+ midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) * + midi_num_inputs);+ if (midi_in_caps == NULL) {+ /* if you can't open a particular system-level midi interface+ * (such as winmm), we just consider that system or API to be+ * unavailable and move on without reporting an error.+ */+ return;+ }++ for (i = 0; i < midi_num_inputs; i++) {+ wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i],+ sizeof(MIDIINCAPS));+ if (wRtn == MMSYSERR_NOERROR) {+ pm_add_device_w("MMSystem", midi_in_caps[i].szPname, TRUE, FALSE,+ (void *) (intptr_t) i, &pm_winmm_in_dictionary);+ }+ }+}+++static void pm_winmm_mapper_input(void)+{+ WORD wRtn;+ /* Note: if MIDIMAPPER opened as input (documentation implies you+ can, but current system fails to retrieve input mapper+ capabilities) then you still should retrieve some form of+ setup info. */+ wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,+ (LPMIDIINCAPS) & midi_in_mapper_caps, + sizeof(MIDIINCAPS));+ if (wRtn == MMSYSERR_NOERROR) {+ pm_add_device_w("MMSystem", midi_in_mapper_caps.szPname, TRUE, FALSE,+ (void *) (intptr_t) MIDIMAPPER, + &pm_winmm_in_dictionary);+ }+}+++static void pm_winmm_general_outputs(void)+{+ UINT i;+ DWORD wRtn;+ midi_num_outputs = midiOutGetNumDevs();+ midi_out_caps = pm_alloc(sizeof(MIDIOUTCAPS) * midi_num_outputs);++ if (midi_out_caps == NULL) {+ /* no error is reported -- see pm_winmm_general_inputs */+ return ;+ }++ for (i = 0; i < midi_num_outputs; i++) {+ wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i],+ sizeof(MIDIOUTCAPS));+ if (wRtn == MMSYSERR_NOERROR) {+ pm_add_device_w("MMSystem", midi_out_caps[i].szPname, FALSE, FALSE,+ (void *) (intptr_t) i, &pm_winmm_out_dictionary);+ }+ }+}+++static void pm_winmm_mapper_output(void)+{+ WORD wRtn;+ /* Note: if MIDIMAPPER opened as output (pseudo MIDI device+ maps device independent messages into device dependant ones,+ via NT midimapper program) you still should get some setup info */+ wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS)+ & midi_out_mapper_caps, sizeof(MIDIOUTCAPS));+ if (wRtn == MMSYSERR_NOERROR) {+ pm_add_device_w("MMSystem", midi_out_mapper_caps.szPname, FALSE, FALSE,+ (void *) (intptr_t) MIDIMAPPER, + &pm_winmm_out_dictionary);+ }+}+++/*+============================================================================+host error handling+============================================================================+*/++static unsigned int winmm_check_host_error(PmInternal *midi)+{+ return FALSE;+}+++/*+=============================================================================+buffer handling+=============================================================================+*/+static MIDIHDR *allocate_buffer(long data_size)+{+ LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));+ MIDIEVENT *evt;+ if (!hdr) return NULL;+ evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */+ hdr->lpData = (LPSTR) evt;+ hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);+ hdr->dwBytesRecorded = 0;+ hdr->dwFlags = 0;+ hdr->dwUser = hdr->dwBufferLength;+ return hdr;+}+++static PmError allocate_buffers(winmm_info_type info, long data_size, + long count)+{+ int i;+ /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */+ info->num_buffers = 0; /* in case no memory can be allocated */+ info->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);+ if (!info->buffers) return pmInsufficientMemory;+ info->max_buffers = count;+ for (i = 0; i < count; i++) {+ LPMIDIHDR hdr = allocate_buffer(data_size);+ if (!hdr) { /* free everything allocated so far and return */+ for (i = i - 1; i >= 0; i--) pm_free(info->buffers[i]);+ pm_free(info->buffers);+ info->max_buffers = 0;+ return pmInsufficientMemory;+ }+ info->buffers[i] = hdr; /* this may be NULL if allocation fails */+ }+ info->num_buffers = count;+ return pmNoError;+}+++static LPMIDIHDR get_free_output_buffer(PmInternal *midi)+{+ LPMIDIHDR r = NULL;+ winmm_info_type info = (winmm_info_type) midi->api_info;+ while (TRUE) {+ int i;+ for (i = 0; i < info->num_buffers; i++) {+ /* cycle through buffers, modulo info->num_buffers */+ info->next_buffer++;+ if (info->next_buffer >= info->num_buffers) info->next_buffer = 0;+ r = info->buffers[info->next_buffer];+ if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;+ }+ /* after scanning every buffer and not finding anything, block */+ if (WaitForSingleObject(info->buffer_signal, 1000) == WAIT_TIMEOUT) {+#ifdef MMDEBUG+ printf("PortMidi warning: get_free_output_buffer() "+ "wait timed out after 1000ms\n");+#endif+ /* if we're trying to send a sysex message, maybe the + * message is too big and we need more message buffers.+ * Expand the buffer pool by 128KB using 1024-byte buffers.+ */+ /* first, expand the buffers array if necessary */+ if (!info->buffers_expanded) {+ LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(+ (info->num_buffers + NUM_EXPANSION_BUFFERS) *+ sizeof(LPMIDIHDR));+ /* if no memory, we could return a no-memory error, but user+ * probably will be unprepared to deal with it. Maybe the+ * MIDI driver is temporarily hung so we should just wait.+ * I don't know the right answer, but waiting is easier.+ */+ if (!new_buffers) continue;+ /* copy buffers to new_buffers and replace buffers */+ memcpy(new_buffers, info->buffers, + info->num_buffers * sizeof(LPMIDIHDR));+ pm_free(info->buffers);+ info->buffers = new_buffers;+ info->max_buffers = info->num_buffers + NUM_EXPANSION_BUFFERS;+ info->buffers_expanded = TRUE;+ }+ /* next, add one buffer and return it */+ if (info->num_buffers < info->max_buffers) {+ r = allocate_buffer(EXPANSION_BUFFER_LEN);+ /* again, if there's no memory, we may not really be + * dead -- maybe the system is temporarily hung and+ * we can just wait longer for a message buffer */+ if (!r) continue;+ info->buffers[info->num_buffers++] = r;+ goto found_buffer; /* break out of 2 loops */+ }+ /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,+ * and we have no free buffers to send. We'll just keep+ * polling to see if any buffers show up.+ */+ }+ }+found_buffer:+ r->dwBytesRecorded = 0;+ /* actual buffer length is saved in dwUser field */+ r->dwBufferLength = (DWORD) r->dwUser;+ return r;+}++/*+============================================================================+begin midi input implementation+============================================================================+*/+++static unsigned int allocate_input_buffer(HMIDIIN h, long buffer_len)+{+ LPMIDIHDR hdr = allocate_buffer(buffer_len);+ if (!hdr) return pmInsufficientMemory;+ /* note: pm_hosterror is normally a boolean, but here, we store Win+ * error code. The caller must test the value for nonzero, set+ * pm_hosterror_text, and then set pm_hosterror to TRUE */+ pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));+ if (pm_hosterror) {+ pm_free(hdr);+ return pm_hosterror;+ }+ pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));+ return pm_hosterror;+}+++static winmm_info_type winmm_info_create(void)+{+ winmm_info_type info = (winmm_info_type) pm_alloc(sizeof(winmm_info_node));+ info->handle.in = NULL;+ info->handle.out = NULL;+ info->buffers = NULL; /* not used for input */+ info->num_buffers = 0; /* not used for input */+ info->max_buffers = 0; /* not used for input */+ info->buffers_expanded = FALSE; /* not used for input */+ info->next_buffer = 0; /* not used for input */+ info->buffer_signal = 0; /* not used for input */+ info->last_time = 0;+ info->first_message = TRUE; /* not used for input */+ info->sysex_mode = FALSE;+ info->sysex_word = 0;+ info->sysex_byte_count = 0;+ info->hdr = NULL; /* not used for input */+ info->sync_time = 0;+ info->delta = 0;+ return info;+}+++static void report_hosterror(LPWCH error_msg)+{+ WideCharToMultiByte(CP_UTF8, 0, error_msg, -1, pm_hosterror_text,+ sizeof(pm_hosterror_text), NULL, NULL);+ if (pm_hosterror == MMSYSERR_NOMEM) {+ /* add explanation to Window's confusing error message */+ /* if there's room: */+ if (PM_HOST_ERROR_MSG_LEN - strlen(pm_hosterror_text) > 60) {+ strcat_s(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,+ " Probably this MIDI device is open "+ "in another application.");+ }+ }+ pm_hosterror = TRUE;+}+++static void report_hosterror_in(void)+{+ WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];+ int err = midiInGetErrorText(pm_hosterror, error_msg,+ PM_HOST_ERROR_MSG_LEN);+ assert(err == MMSYSERR_NOERROR);+ report_hosterror(error_msg);+}+++static void report_hosterror_out(void)+{+ WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];+ int err = midiOutGetErrorText(pm_hosterror, error_msg,+ PM_HOST_ERROR_MSG_LEN);+ assert(err == MMSYSERR_NOERROR);+ report_hosterror(error_msg);+}+++static PmError winmm_in_open(PmInternal *midi, void *driverInfo)+{+ DWORD dwDevice;+ int i = midi->device_id;+ int max_sysex_len = midi->buffer_len * 4;+ int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN;+ winmm_info_type info;++ dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;++ /* create system dependent device data */+ info = winmm_info_create();+ midi->api_info = info;+ if (!info) goto no_memory;+ /* 4000 is based on Windows documentation -- that's the value used+ in the memory manager. It's small enough that it should not+ hurt performance even if it's not optimal.+ */+ InitializeCriticalSectionAndSpinCount(&info->lock, 4000);+ /* open device */+ pm_hosterror = midiInOpen(+ &(info->handle.in), /* input device handle */+ dwDevice, /* device ID */+ (DWORD_PTR) winmm_in_callback, /* callback address */+ (DWORD_PTR) midi, /* callback instance data */+ CALLBACK_FUNCTION); /* callback is a procedure */+ if (pm_hosterror) goto free_descriptor;++ if (num_input_buffers < MIN_INPUT_BUFFERS)+ num_input_buffers = MIN_INPUT_BUFFERS;+ for (i = 0; i < num_input_buffers; i++) {+ if (allocate_input_buffer(info->handle.in, INPUT_SYSEX_LEN)) {+ /* either pm_hosterror was set, or the proper return code+ is pmInsufficientMemory */+ goto close_device;+ }+ }+ /* start device */+ pm_hosterror = midiInStart(info->handle.in);+ if (!pm_hosterror) {+ return pmNoError;+ }++ /* undo steps leading up to the detected error */++ /* ignore return code (we already have an error to report) */+ midiInReset(info->handle.in);+close_device:+ midiInClose(info->handle.in); /* ignore return code */+free_descriptor:+ midi->api_info = NULL;+ pm_free(info);+no_memory:+ if (pm_hosterror) {+ report_hosterror_in();+ return pmHostError;+ }+ /* if !pm_hosterror, then the error must be pmInsufficientMemory */+ return pmInsufficientMemory;+ /* note: if we return an error code, the device will be+ closed and memory will be freed. It's up to the caller+ to free the parameter midi */+}+++/* winmm_in_close -- close an open midi input device */+/*+ * assume midi is non-null (checked by caller)+ */+static PmError winmm_in_close(PmInternal *midi)+{+ winmm_info_type info = (winmm_info_type) midi->api_info;+ if (!info) return pmBadPtr;+ /* device to close */+ if ((pm_hosterror = midiInStop(info->handle.in))) {+ midiInReset(info->handle.in); /* try to reset and close port */+ midiInClose(info->handle.in);+ } else if ((pm_hosterror = midiInReset(info->handle.in))) {+ midiInClose(info->handle.in); /* best effort to close midi port */+ } else {+ pm_hosterror = midiInClose(info->handle.in);+ }+ midi->api_info = NULL;+ DeleteCriticalSection(&info->lock);+ pm_free(info); /* delete */+ if (pm_hosterror) {+ report_hosterror_in();+ return pmHostError;+ }+ return pmNoError;+}+++/* Callback function executed via midiInput SW interrupt (via midiInOpen). */+static void FAR PASCAL winmm_in_callback(+ HMIDIIN hMidiIn, /* midiInput device Handle */+ UINT wMsg, /* midi msg */+ DWORD_PTR dwInstance, /* application data */+ DWORD_PTR dwParam1, /* MIDI data */+ DWORD_PTR dwParam2) /* device timestamp (wrt most recent midiInStart) */+{+ PmInternal *midi = (PmInternal *) dwInstance;+ winmm_info_type info = (winmm_info_type) midi->api_info;++ /* NOTE: we do not just EnterCriticalSection() here because an+ * MIM_CLOSE message arrives when the port is closed, but then+ * the info->lock has been destroyed.+ */++ switch (wMsg) {+ case MIM_DATA: {+ /* if this callback is reentered with data, we're in trouble. + * It's hard to imagine that Microsoft would allow callbacks + * to be reentrant -- isn't the model that this is like a + * hardware interrupt? -- but I've seen reentrant behavior + * using a debugger, so it happens.+ */+ EnterCriticalSection(&info->lock);++ /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of+ message LOB;+ dwParam2 is time message received by input device driver, specified+ in [ms] from when midiInStart called.+ each message is expanded to include the status byte */++ if ((dwParam1 & 0x80) == 0) {+ /* not a status byte -- ignore it. This happened running the+ sysex.c test under Win2K with MidiMan USB 1x1 interface,+ but I can't reproduce it. -RBD+ */+ /* printf("non-status byte found\n"); */+ } else { /* data to process */+ PmEvent event;+ if (midi->time_proc)+ dwParam2 = (*midi->time_proc)(midi->time_info);+ event.timestamp = (PmTimestamp)dwParam2;+ event.message = (PmMessage)dwParam1;+ pm_read_short(midi, &event);+ }+ LeaveCriticalSection(&info->lock);+ break;+ }+ case MIM_LONGDATA: {+ MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;+ unsigned char *data = (unsigned char *) lpMidiHdr->lpData;+ unsigned int processed = 0;+ int remaining = lpMidiHdr->dwBytesRecorded;++ EnterCriticalSection(&info->lock);+ /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", + lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */+ if (midi->time_proc)+ dwParam2 = (*midi->time_proc)(midi->time_info);+ /* can there be more than one message in one buffer? */+ /* assume yes and iterate through them */+ pm_read_bytes(midi, data + processed, remaining, (PmTimestamp)dwParam2);++ /* when a device is closed, the pending MIM_LONGDATA buffers are+ returned to this callback with dwBytesRecorded == 0. In this+ case, we do not want to send them back to the interface (if+ we do, the interface will not close, and Windows OS may hang). */+ if (lpMidiHdr->dwBytesRecorded > 0) {+ MMRESULT rslt;+ lpMidiHdr->dwBytesRecorded = 0;+ lpMidiHdr->dwFlags = 0;+ + /* note: no error checking -- can this actually fail? */+ rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));+ assert(rslt == MMSYSERR_NOERROR);+ /* note: I don't think this can fail except possibly for+ * MMSYSERR_NOMEM, but the pain of reporting this+ * unlikely but probably catastrophic error does not seem+ * worth it.+ */+ rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));+ assert(rslt == MMSYSERR_NOERROR);+ LeaveCriticalSection(&info->lock);+ } else {+ midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR));+ LeaveCriticalSection(&info->lock);+ pm_free(lpMidiHdr);+ }+ break;+ }+ case MIM_OPEN:+ break;+ case MIM_CLOSE:+ break;+ case MIM_ERROR:+ /* printf("MIM_ERROR\n"); */+ break;+ case MIM_LONGERROR:+ /* printf("MIM_LONGERROR\n"); */+ break;+ default:+ break;+ }+}++/*+===========================================================================+begin midi output implementation+===========================================================================+*/++/* begin helper routines used by midiOutStream interface */++/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */+static int add_to_buffer(winmm_info_type m, LPMIDIHDR hdr,+ unsigned long delta, unsigned long msg)+{+ unsigned long *ptr = (unsigned long *)+ (hdr->lpData + hdr->dwBytesRecorded);+ *ptr++ = delta; /* dwDeltaTime */+ *ptr++ = 0; /* dwStream */+ *ptr++ = msg; /* dwEvent */+ hdr->dwBytesRecorded += 3 * sizeof(long);+ /* if the addition of three more words (a message) would extend beyond+ the buffer length, then return TRUE (full)+ */+ return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength;+}+++static PmTimestamp pm_time_get(winmm_info_type info)+{+ MMTIME mmtime;+ MMRESULT wRtn;+ mmtime.wType = TIME_TICKS;+ mmtime.u.ticks = 0;+ wRtn = midiStreamPosition(info->handle.stream, &mmtime, sizeof(mmtime));+ assert(wRtn == MMSYSERR_NOERROR);+ return mmtime.u.ticks;+}+++/* end helper routines used by midiOutStream interface */+++static PmError winmm_out_open(PmInternal *midi, void *driverInfo)+{+ DWORD dwDevice;+ int i = midi->device_id;+ winmm_info_type info;+ MIDIPROPTEMPO propdata;+ MIDIPROPTIMEDIV divdata;+ int max_sysex_len = midi->buffer_len * 4;+ int output_buffer_len;+ int num_buffers;+ dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;++ /* create system dependent device data */+ info = winmm_info_create();+ midi->api_info = info;+ if (!info) goto no_memory;+ /* create a signal */+ info->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);+ /* this should only fail when there are very serious problems */+ assert(info->buffer_signal);+ /* open device */+ if (midi->latency == 0) {+ /* use simple midi out calls */+ pm_hosterror = midiOutOpen(+ (LPHMIDIOUT) & info->handle.out, /* device Handle */+ dwDevice, /* device ID */+ /* note: same callback fn as for StreamOpen: */+ (DWORD_PTR) winmm_streamout_callback, /* callback fn */+ (DWORD_PTR) midi, /* callback instance data */+ CALLBACK_FUNCTION); /* callback type */+ } else {+ /* use stream-based midi output (schedulable in future) */+ pm_hosterror = midiStreamOpen(+ &info->handle.stream, /* device Handle */+ (LPUINT) & dwDevice, /* device ID pointer */+ 1, /* reserved, must be 1 */+ (DWORD_PTR) winmm_streamout_callback,+ (DWORD_PTR) midi, /* callback instance data */+ CALLBACK_FUNCTION);+ }+ if (pm_hosterror != MMSYSERR_NOERROR) {+ goto free_descriptor;+ }++ if (midi->latency == 0) {+ num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;+ output_buffer_len = max_sysex_len / num_buffers;+ if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)+ output_buffer_len = MIN_SIMPLE_SYSEX_LEN;+ } else {+ num_buffers = max(midi->buffer_len, midi->latency / 2);+ if (num_buffers < MIN_STREAM_BUFFERS)+ num_buffers = MIN_STREAM_BUFFERS;+ output_buffer_len = STREAM_BUFFER_LEN;++ propdata.cbStruct = sizeof(MIDIPROPTEMPO);+ propdata.dwTempo = 480000; /* microseconds per quarter */+ pm_hosterror = midiStreamProperty(info->handle.stream,+ (LPBYTE) & propdata,+ MIDIPROP_SET | MIDIPROP_TEMPO);+ if (pm_hosterror) goto close_device;++ divdata.cbStruct = sizeof(MIDIPROPTEMPO);+ divdata.dwTimeDiv = 480; /* divisions per quarter */+ pm_hosterror = midiStreamProperty(info->handle.stream,+ (LPBYTE) & divdata,+ MIDIPROP_SET | MIDIPROP_TIMEDIV);+ if (pm_hosterror) goto close_device;+ }+ /* allocate buffers */+ if (allocate_buffers(info, output_buffer_len, num_buffers)) + goto free_buffers;+ /* start device */+ if (midi->latency != 0) {+ pm_hosterror = midiStreamRestart(info->handle.stream);+ if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;+ }+ return pmNoError;++free_buffers:+ /* buffers are freed below by winmm_out_delete */+close_device:+ midiOutClose(info->handle.out);+free_descriptor:+ midi->api_info = NULL;+ winmm_out_delete(midi); /* frees buffers and m */+no_memory:+ if (pm_hosterror) {+ report_hosterror_out();+ return pmHostError;+ }+ return pmInsufficientMemory;+}+++/* winmm_out_delete -- carefully free data associated with midi */+/**/+static void winmm_out_delete(PmInternal *midi)+{+ int i;+ /* delete system dependent device data */+ winmm_info_type info = (winmm_info_type) midi->api_info;+ if (info) {+ if (info->buffer_signal) {+ /* don't report errors -- better not to stop cleanup */+ CloseHandle(info->buffer_signal);+ }+ /* if using stream output, free buffers */+ for (i = 0; i < info->num_buffers; i++) {+ if (info->buffers[i]) pm_free(info->buffers[i]);+ }+ info->num_buffers = 0;+ pm_free(info->buffers);+ info->max_buffers = 0;+ }+ midi->api_info = NULL;+ pm_free(info); /* delete */+}+++/* see comments for winmm_in_close */+static PmError winmm_out_close(PmInternal *midi)+{+ winmm_info_type info = (winmm_info_type) midi->api_info;+ if (info->handle.out) {+ /* device to close */+ if (midi->latency == 0) {+ pm_hosterror = midiOutClose(info->handle.out);+ } else {+ pm_hosterror = midiStreamClose(info->handle.stream);+ }+ /* regardless of outcome, free memory */+ winmm_out_delete(midi);+ }+ if (pm_hosterror) {+ report_hosterror_out();+ return pmHostError;+ }+ return pmNoError;+}+++static PmError winmm_out_abort(PmInternal *midi)+{+ winmm_info_type info = (winmm_info_type) midi->api_info;++ /* only stop output streams */+ if (midi->latency > 0) {+ pm_hosterror = midiStreamStop(info->handle.stream);+ if (pm_hosterror) {+ report_hosterror_out();+ return pmHostError;+ }+ }+ return pmNoError;+}+++static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp)+{+ winmm_info_type info = (winmm_info_type) midi->api_info;+ assert(info);+ if (info->hdr) {+ pm_hosterror = midiOutPrepareHeader(info->handle.out, info->hdr, + sizeof(MIDIHDR));+ if (pm_hosterror) {+ /* do not send message */+ } else if (midi->latency == 0) {+ /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded+ * should be zero. This is set in get_free_sysex_buffer(). + * The msg length goes in dwBufferLength in spite of what+ * Microsoft documentation says (or doesn't say). */+ info->hdr->dwBufferLength = info->hdr->dwBytesRecorded;+ info->hdr->dwBytesRecorded = 0;+ pm_hosterror = midiOutLongMsg(info->handle.out, info->hdr, + sizeof(MIDIHDR));+ } else {+ pm_hosterror = midiStreamOut(info->handle.stream, info->hdr, + sizeof(MIDIHDR));+ }+ midi->fill_base = NULL;+ info->hdr = NULL;+ if (pm_hosterror) {+ report_hosterror_out();+ return pmHostError;+ }+ }+ return pmNoError;+}+++static PmError winmm_write_short(PmInternal *midi, PmEvent *event)+{+ winmm_info_type info = (winmm_info_type) midi->api_info;+ PmError rslt = pmNoError;+ assert(info);++ if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */+ pm_hosterror = midiOutShortMsg(info->handle.out, event->message);+ if (pm_hosterror) {+ if (info->hdr) { /* device disconnect may delete hdr */+ info->hdr->dwFlags = 0; /* release the buffer */+ }+ report_hosterror_out();+ return pmHostError;+ }+ } else { /* use midiStream interface -- pass data through buffers */+ unsigned long when = event->timestamp;+ unsigned long delta;+ int full;+ if (when == 0) when = midi->now;+ /* when is in real_time; translate to intended stream time */+ when = when + info->delta + midi->latency;+ /* make sure we don't go backward in time */+ if (when < info->last_time) when = info->last_time;+ delta = when - info->last_time;+ info->last_time = when;+ /* before we insert any data, we must have a buffer */+ if (info->hdr == NULL) {+ /* stream interface: buffers allocated when stream is opened */+ info->hdr = get_free_output_buffer(midi);+ }+ full = add_to_buffer(info, info->hdr, delta, event->message);+ /* note: winmm_write_flush sets pm_hosterror etc. on host error */+ if (full) rslt = winmm_write_flush(midi, when);+ }+ return rslt;+}++#define winmm_begin_sysex winmm_write_flush+#ifndef winmm_begin_sysex+static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)+{+ winmm_info_type m = (winmm_info_type) midi->api_info;+ PmError rslt = pmNoError;++ if (midi->latency == 0) {+ /* do nothing -- it's handled in winmm_write_byte */+ } else {+ /* sysex expects an empty sysex buffer, so send whatever is here */+ rslt = winmm_write_flush(midi);+ }+ return rslt;+}+#endif++static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)+{+ /* could check for callback_error here, but I haven't checked+ * what happens if we exit early and don't finish the sysex msg+ * and clean up+ */+ winmm_info_type info = (winmm_info_type) midi->api_info;+ PmError rslt = pmNoError;+ LPMIDIHDR hdr = info->hdr;+ if (!hdr) return rslt; /* something bad happened earlier,+ do not report an error because it would have been + reported (at least) once already */+ /* a(n old) version of MIDI YOKE requires a zero byte after+ * the sysex message, but do not increment dwBytesRecorded: */+ hdr->lpData[hdr->dwBytesRecorded] = 0;+ if (midi->latency == 0) {+#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX+ /* DEBUG CODE: */+ { int i; int len = info->hdr->dwBufferLength;+ printf("OutLongMsg %d ", len);+ for (i = 0; i < len; i++) {+ printf("%2x ", (unsigned char) (info->hdr->lpData[i]));+ }+ }+#endif+ } else {+ /* Using stream interface. There are accumulated bytes in info->hdr+ to send using midiStreamOut+ */+ /* add bytes recorded to MIDIEVENT length, but don't+ count the MIDIEVENT data (3 longs) */+ MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData);+ evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);+ /* round up BytesRecorded to multiple of 4 */+ hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;+ }+ rslt = winmm_write_flush(midi, timestamp);+ return rslt;+}+++static PmError winmm_write_byte(PmInternal *midi, unsigned char byte,+ PmTimestamp timestamp)+{+ /* write a sysex byte */+ PmError rslt = pmNoError;+ winmm_info_type info = (winmm_info_type) midi->api_info;+ LPMIDIHDR hdr = info->hdr;+ unsigned char *msg_buffer;+ assert(info);+ if (!hdr) {+ info->hdr = hdr = get_free_output_buffer(midi);+ assert(hdr);+ midi->fill_base = (unsigned char *) info->hdr->lpData;+ midi->fill_offset_ptr = (uint32_t *) &(hdr->dwBytesRecorded);+ /* when buffer fills, Pm_WriteSysEx will revert to calling+ * pmwin_write_byte, which expect to have space, so leave+ * one byte free for pmwin_write_byte. Leave another byte+ * of space for zero after message to make early version of + * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */+ midi->fill_length = hdr->dwBufferLength - 2;+ if (midi->latency != 0) {+ unsigned long when = (unsigned long) timestamp;+ unsigned long delta;+ unsigned long *ptr;+ if (when == 0) when = midi->now;+ /* when is in real_time; translate to intended stream time */+ when = when + info->delta + midi->latency;+ /* make sure we don't go backward in time */+ if (when < info->last_time) when = info->last_time;+ delta = when - info->last_time;+ info->last_time = when;++ ptr = (unsigned long *) hdr->lpData;+ *ptr++ = delta;+ *ptr++ = 0;+ *ptr = MEVT_F_LONG;+ hdr->dwBytesRecorded = 3 * sizeof(long);+ /* data will be added at an offset of dwBytesRecorded ... */+ }+ }+ /* add the data byte */+ msg_buffer = (unsigned char *) (hdr->lpData);+ msg_buffer[hdr->dwBytesRecorded++] = byte;++ /* see if buffer is full, leave one byte extra for pad */+ if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {+ /* write what we've got and continue */+ rslt = winmm_end_sysex(midi, timestamp); + }+ return rslt;+}+++static PmTimestamp winmm_synchronize(PmInternal *midi)+{+ winmm_info_type info;+ unsigned long pm_stream_time_2;+ unsigned long real_time;+ unsigned long pm_stream_time;++ /* only synchronize if we are using stream interface */+ if (midi->latency == 0) return 0;++ /* figure out the time */+ info = (winmm_info_type) midi->api_info;+ pm_stream_time_2 = pm_time_get(info);++ do {+ /* read real_time between two reads of stream time */+ pm_stream_time = pm_stream_time_2;+ real_time = (*midi->time_proc)(midi->time_info);+ pm_stream_time_2 = pm_time_get(info);+ /* repeat if more than 1ms elapsed */+ } while (pm_stream_time_2 > pm_stream_time + 1);+ info->delta = pm_stream_time - real_time;+ info->sync_time = real_time;+ return real_time;+}+++/* winmm_streamout_callback -- unprepare (free) buffer header */+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,+ DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)+{+ PmInternal *midi = (PmInternal *) dwInstance;+ winmm_info_type info = (winmm_info_type) midi->api_info;+ LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;+ int err;++ /* Even if an error is pending, I think we should unprepare msgs and+ signal their arrival+ */+ /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", + hdr, wMsg, MOM_DONE); */+ if (wMsg == MOM_DONE) {+ MMRESULT ret = midiOutUnprepareHeader(info->handle.out, hdr, + sizeof(MIDIHDR));+ assert(ret == MMSYSERR_NOERROR);+ } else if (wMsg == MOM_CLOSE) {+ /* The streaming API gets a callback when the device is closed.+ * The non-streaming API gets a callback when the device is+ * removed or closed. It is misleading to set is_removed when+ * the device is closed normally, but in that case, midi itself+ * will be freed immediately, so there should be no way to+ * observe is_removed == TRUE. On the other hand, if the device+ * is removed, setting is_removed will cause PortMidi to return+ * the pmDeviceRemoved error on attempts to output to the device.+ * In the case of normal closing, due to midiOutClose(), + * the call below is reentrant (!), but for some reason this does+ * not cause an error or infinite recursion, so we are not taking+ * any precautions to flag midi as "in the process of closing."+ */+ midi->is_removed = TRUE;+ midiOutClose(info->handle.out);+ }+ /* signal client in case it is blocked waiting for buffer */+ err = SetEvent(info->buffer_signal);+ assert(err); /* false -> error */+}+++/*+===========================================================================+begin exported functions+===========================================================================+*/++#define winmm_in_abort pm_fail_fn+pm_fns_node pm_winmm_in_dictionary = {+ none_write_short,+ none_sysex,+ none_sysex,+ none_write_byte,+ none_write_short,+ none_write_flush,+ winmm_synchronize,+ winmm_in_open,+ winmm_in_abort,+ winmm_in_close,+ success_poll,+ winmm_check_host_error+ };++pm_fns_node pm_winmm_out_dictionary = {+ winmm_write_short,+ winmm_begin_sysex,+ winmm_end_sysex,+ winmm_write_byte,+ /* short realtime message: */ winmm_write_short, + winmm_write_flush,+ winmm_synchronize,+ winmm_out_open,+ winmm_out_abort,+ winmm_out_close,+ none_poll,+ winmm_check_host_error+ };+++/* initialize winmm interface. Note that if there is something wrong+ with winmm (e.g. it is not supported or installed), it is not an+ error. We should simply return without having added any devices to+ the table. Hence, no error code is returned. Furthermore, this init+ code is called along with every other supported interface, so the+ user would have a very hard time figuring out what hardware and API+ generated the error. Finally, it would add complexity to pmwin.c to+ remember where the error code came from in order to convert to text.+ */+void pm_winmm_init( void )+{+ pm_winmm_mapper_input();+ pm_winmm_mapper_output();+ pm_winmm_general_inputs();+ pm_winmm_general_outputs();+}+++/* no error codes are returned, even if errors are encountered, because+ there is probably nothing the user could do (e.g. it would be an error+ to retry.+ */+void pm_winmm_term( void )+{+ int i;+#ifdef MMDEBUG+ int doneAny = 0;+ printf("pm_winmm_term called\n");+#endif+ for (i = 0; i < pm_descriptor_len; i++) {+ PmInternal *midi = pm_descriptors[i].pm_internal;+ if (midi) {+ winmm_info_type info = (winmm_info_type) midi->api_info;+ if (info->handle.out) {+ /* close next open device*/+#ifdef MMDEBUG+ if (doneAny == 0) {+ printf("begin closing open devices...\n");+ doneAny = 1;+ }+#endif+ /* close all open ports */+ (*midi->dictionary->close)(midi);+ }+ }+ }+ if (midi_in_caps) {+ pm_free(midi_in_caps);+ midi_in_caps = NULL;+ }+ if (midi_out_caps) {+ pm_free(midi_out_caps);+ midi_out_caps = NULL;+ }+#ifdef MMDEBUG+ if (doneAny) {+ printf("warning: devices were left open. They have been closed.\n");+ }+ printf("pm_winmm_term exiting\n");+#endif+ pm_descriptor_len = 0;+}
portmidi/pm_win/pmwinmm.h view
@@ -1,5 +1,5 @@-/* midiwin32.h -- system-specific definitions */ - -void pm_winmm_init( void ); -void pm_winmm_term( void ); - +/* pmwinmm.h -- system-specific definitions for windows multimedia API */++void pm_winmm_init( void );+void pm_winmm_term( void );+
portmidi/porttime/porttime.h view
@@ -1,72 +1,106 @@-/* porttime.h -- portable interface to millisecond timer */ - -/* CHANGE LOG FOR PORTTIME - 10-Jun-03 Mark Nelson & RBD - boost priority of timer thread in ptlinux.c implementation - */ - -/* Should there be a way to choose the source of time here? */ - -#ifdef __cplusplus -extern "C" { -#endif - - -typedef enum { - ptNoError = 0, /* success */ - ptHostError = -10000, /* a system-specific error occurred */ - ptAlreadyStarted, /* cannot start timer because it is already started */ - ptAlreadyStopped, /* cannot stop timer because it is already stopped */ - ptInsufficientMemory /* memory could not be allocated */ -} PtError; - - -typedef long PtTimestamp; - -typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); - -/* - Pt_Start() starts a real-time service. - - resolution is the timer resolution in ms. The time will advance every - resolution ms. - - callback is a function pointer to be called every resolution ms. - - userData is passed to callback as a parameter. - - return value: - Upon success, returns ptNoError. See PtError for other values. -*/ -PtError Pt_Start(int resolution, PtCallback *callback, void *userData); - -/* - Pt_Stop() stops the timer. - - return value: - Upon success, returns ptNoError. See PtError for other values. -*/ -PtError Pt_Stop(); - -/* - Pt_Started() returns true iff the timer is running. -*/ -int Pt_Started(); - -/* - Pt_Time() returns the current time in ms. -*/ -PtTimestamp Pt_Time(); - -/* - Pt_Sleep() pauses, allowing other threads to run. - - duration is the length of the pause in ms. The true duration - of the pause may be rounded to the nearest or next clock tick - as determined by resolution in Pt_Start(). -*/ -void Pt_Sleep(long duration); - -#ifdef __cplusplus -} -#endif +/** @file porttime.h portable interface to millisecond timer. */++/* CHANGE LOG FOR PORTTIME+ 10-Jun-03 Mark Nelson & RBD+ boost priority of timer thread in ptlinux.c implementation+ */++#ifndef PORTMIDI_PORTTIME_H+#define PORTMIDI_PORTTIME_H++/* Should there be a way to choose the source of time here? */++#ifdef WIN32+#ifndef INT32_DEFINED+/* rather than having users install a special .h file for windows, + just put the required definitions inline here. portmidi.h uses+ these too, so the definitions are (unfortunately) duplicated there+*/+typedef int int32_t;+typedef unsigned int uint32_t;+#define INT32_DEFINED+#endif+#else+#include <stdint.h> /* needed for int32_t */+#endif++#ifdef __cplusplus+extern "C" {+#endif++#ifndef PMEXPORT+#ifdef _WINDLL+#define PMEXPORT __declspec(dllexport)+#else+#define PMEXPORT +#endif+#endif++/** @defgroup grp_porttime PortTime: Millisecond Timer+ @{+*/++typedef enum {+ ptNoError = 0, /**< success */+ ptHostError = -10000, /**< a system-specific error occurred */+ ptAlreadyStarted, /**< cannot start timer because it is already+ started */+ ptAlreadyStopped, /**< cannot stop timer because it is already+ stopped */+ ptInsufficientMemory /**< memory could not be allocated */+} PtError; /**< @brief @enum PtError PortTime error code; a common return type.+ * No error is indicated by zero; errors are indicated by < 0.+ */++/** real time or time offset in milliseconds. */+typedef int32_t PtTimestamp;++/** a function that gets a current time */+typedef void (PtCallback)(PtTimestamp timestamp, void *userData);++/** start a real-time clock service.++ @param resolution the timer resolution in ms. The time will advance every+ \p resolution ms.++ @param callback a function pointer to be called every resolution ms.++ @param userData is passed to \p callback as a parameter.++ @return #ptNoError on success. See #PtError for other values.+*/+PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);++/** stop the timer.++ @return #ptNoError on success. See #PtError for other values.+*/+PMEXPORT PtError Pt_Stop(void);++/** test if the timer is running.+ + @return TRUE or FALSE+*/+PMEXPORT int Pt_Started(void);++/** get the current time in ms.++ @return the current time+*/+PMEXPORT PtTimestamp Pt_Time(void);++/** pauses the current thread, allowing other threads to run.++ @param duration the length of the pause in ms. The true duration + of the pause may be rounded to the nearest or next clock tick+ as determined by resolution in #Pt_Start().+*/+PMEXPORT void Pt_Sleep(int32_t duration);++/** @} */++#ifdef __cplusplus+}+#endif++#endif /* PORTMIDI_PORTTIME_H */
portmidi/porttime/ptlinux.c view
@@ -1,133 +1,139 @@-/* ptlinux.c -- portable timer implementation for linux */ - - -/* IMPLEMENTATION NOTES (by Mark Nelson): - -Unlike Windows, Linux has no system call to request a periodic callback, -so if Pt_Start() receives a callback parameter, it must create a thread -that wakes up periodically and calls the provided callback function. -If running as superuser, use setpriority() to renice thread to -20. -One could also set the timer thread to a real-time priority (SCHED_FIFO -and SCHED_RR), but this is dangerous for This is necessary because -if the callback hangs it'll never return. A more serious reason -is that the current scheduler implementation busy-waits instead -of sleeping when realtime threads request a sleep of <=2ms (as a way -to get around the 10ms granularity), which means the thread would never -let anyone else on the CPU. - -CHANGE LOG - -18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer - thread. Simplified implementation notes. - -*/ -/* stdlib, stdio, unistd, and sys/types were added because they appeared - * in a Gentoo patch, but I'm not sure why they are needed. -RBD - */ -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <sys/types.h> -#include "porttime.h" -#include "sys/time.h" -#include "sys/resource.h" -#include "sys/timeb.h" -#include "pthread.h" - -#define TRUE 1 -#define FALSE 0 - -static int time_started_flag = FALSE; -static struct timeb time_offset = {0, 0, 0, 0}; -static pthread_t pt_thread_pid; - -/* note that this is static data -- we only need one copy */ -typedef struct { - int id; - int resolution; - PtCallback *callback; - void *userData; -} pt_callback_parameters; - -static int pt_callback_proc_id = 0; - -static void *Pt_CallbackProc(void *p) -{ - pt_callback_parameters *parameters = (pt_callback_parameters *) p; - int mytime = 1; - /* to kill a process, just increment the pt_callback_proc_id */ - /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, - parameters->id); */ - if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); - while (pt_callback_proc_id == parameters->id) { - /* wait for a multiple of resolution ms */ - struct timeval timeout; - int delay = mytime++ * parameters->resolution - Pt_Time(); - if (delay < 0) delay = 0; - timeout.tv_sec = 0; - timeout.tv_usec = delay * 1000; - select(0, NULL, NULL, NULL, &timeout); - (*(parameters->callback))(Pt_Time(), parameters->userData); - } - /* printf("Pt_CallbackProc exiting\n"); */ - // free(parameters); - return NULL; -} - - -PtError Pt_Start(int resolution, PtCallback *callback, void *userData) -{ - if (time_started_flag) return ptNoError; - ftime(&time_offset); /* need this set before process runs */ - if (callback) { - int res; - pt_callback_parameters *parms = (pt_callback_parameters *) - malloc(sizeof(pt_callback_parameters)); - if (!parms) return ptInsufficientMemory; - parms->id = pt_callback_proc_id; - parms->resolution = resolution; - parms->callback = callback; - parms->userData = userData; - res = pthread_create(&pt_thread_pid, NULL, - Pt_CallbackProc, parms); - if (res != 0) return ptHostError; - } - time_started_flag = TRUE; - return ptNoError; -} - - -PtError Pt_Stop() -{ - /* printf("Pt_Stop called\n"); */ - pt_callback_proc_id++; - pthread_join(pt_thread_pid, NULL); - time_started_flag = FALSE; - return ptNoError; -} - - -int Pt_Started() -{ - return time_started_flag; -} - - -PtTimestamp Pt_Time() -{ - long seconds, milliseconds; - struct timeb now; - ftime(&now); - seconds = now.time - time_offset.time; - milliseconds = now.millitm - time_offset.millitm; - return seconds * 1000 + milliseconds; -} - - -void Pt_Sleep(long duration) -{ - usleep(duration * 1000); -} - - - +/* ptlinux.c -- portable timer implementation for linux */+++/* IMPLEMENTATION NOTES (by Mark Nelson): ++Unlike Windows, Linux has no system call to request a periodic callback,+so if Pt_Start() receives a callback parameter, it must create a thread+that wakes up periodically and calls the provided callback function.+If running as superuser, use setpriority() to renice thread to -20. +One could also set the timer thread to a real-time priority (SCHED_FIFO+and SCHED_RR), but this is dangerous for This is necessary because +if the callback hangs it'll never return. A more serious reason+is that the current scheduler implementation busy-waits instead +of sleeping when realtime threads request a sleep of <=2ms (as a way +to get around the 10ms granularity), which means the thread would never +let anyone else on the CPU.++CHANGE LOG++18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer+ thread. Simplified implementation notes. ++*/+/* stdlib, stdio, unistd, and sys/types were added because they appeared+ * in a Gentoo patch, but I'm not sure why they are needed. -RBD+ */+#include <stdlib.h>+#include <stdio.h>+#include <unistd.h>+#include <sys/types.h>+#include "porttime.h"+#include "time.h"+#include "sys/resource.h"+#include "pthread.h"++#define TRUE 1+#define FALSE 0++#ifndef CLOCK_MONOTONIC_RAW+#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC+#endif++static int time_started_flag = FALSE;+static struct timespec time_offset = {0, 0};+static pthread_t pt_thread_pid;+static int pt_thread_created = FALSE;++/* note that this is static data -- we only need one copy */+typedef struct {+ int id;+ int resolution;+ PtCallback *callback;+ void *userData;+} pt_callback_parameters;++static int pt_callback_proc_id = 0;++static void *Pt_CallbackProc(void *p)+{+ pt_callback_parameters *parameters = (pt_callback_parameters *) p;+ int mytime = 1;+ /* to kill a process, just increment the pt_callback_proc_id */+ /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,+ parameters->id); */+ if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);+ while (pt_callback_proc_id == parameters->id) {+ /* wait for a multiple of resolution ms */+ struct timeval timeout;+ int delay = mytime++ * parameters->resolution - Pt_Time();+ if (delay < 0) delay = 0;+ timeout.tv_sec = 0;+ timeout.tv_usec = delay * 1000;+ select(0, NULL, NULL, NULL, &timeout);+ (*(parameters->callback))(Pt_Time(), parameters->userData);+ }+ /* printf("Pt_CallbackProc exiting\n"); */+ // free(parameters);+ return NULL;+}+++PtError Pt_Start(int resolution, PtCallback *callback, void *userData)+{+ if (time_started_flag) return ptNoError;+ /* need this set before process runs: */+ clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset);+ if (callback) {+ int res;+ pt_callback_parameters *parms = (pt_callback_parameters *)+ malloc(sizeof(pt_callback_parameters));+ if (!parms) return ptInsufficientMemory;+ parms->id = pt_callback_proc_id;+ parms->resolution = resolution;+ parms->callback = callback;+ parms->userData = userData;+ res = pthread_create(&pt_thread_pid, NULL,+ Pt_CallbackProc, parms);+ if (res != 0) return ptHostError;+ pt_thread_created = TRUE;+ }+ time_started_flag = TRUE;+ return ptNoError;+}+++PtError Pt_Stop(void)+{+ /* printf("Pt_Stop called\n"); */+ pt_callback_proc_id++;+ if (pt_thread_created) {+ pthread_join(pt_thread_pid, NULL);+ pt_thread_created = FALSE;+ }+ time_started_flag = FALSE;+ return ptNoError;+}+++int Pt_Started(void)+{+ return time_started_flag;+}+++PtTimestamp Pt_Time(void)+{+ long seconds, ms;+ struct timespec now;+ clock_gettime(CLOCK_MONOTONIC_RAW, &now);+ seconds = now.tv_sec - time_offset.tv_sec;+ ms = (now.tv_nsec - time_offset.tv_nsec) / 1000000; /* round down */+ return seconds * 1000 + ms;+}+++void Pt_Sleep(int32_t duration)+{+ usleep(duration * 1000);+}
+ portmidi/porttime/ptmacosx_cf.c view
@@ -0,0 +1,140 @@+/* ptmacosx.c -- portable timer implementation for mac os x */++#include <stdlib.h>+#include <stdio.h>+#include <pthread.h>+#include <CoreFoundation/CoreFoundation.h>++#import <mach/mach.h>+#import <mach/mach_error.h>+#import <mach/mach_time.h>+#import <mach/clock.h>++#include "porttime.h"++#define THREAD_IMPORTANCE 30+#define LONG_TIME 1000000000.0++static int time_started_flag = FALSE;+static CFAbsoluteTime startTime = 0.0;+static CFRunLoopRef timerRunLoop;++typedef struct {+ int resolution;+ PtCallback *callback;+ void *userData;+} PtThreadParams;+++void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info)+{+ PtThreadParams *params = (PtThreadParams*)info;+ (*params->callback)(Pt_Time(), params->userData);+}++static void* Pt_Thread(void *p)+{+ CFTimeInterval timerInterval;+ CFRunLoopTimerContext timerContext;+ CFRunLoopTimerRef timer;+ PtThreadParams *params = (PtThreadParams*)p;+ /* CFTimeInterval timeout; */++ /* raise the thread's priority */+ kern_return_t error;+ thread_extended_policy_data_t extendedPolicy;+ thread_precedence_policy_data_t precedencePolicy;++ extendedPolicy.timeshare = 0;+ error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,+ (thread_policy_t)&extendedPolicy,+ THREAD_EXTENDED_POLICY_COUNT);+ if (error != KERN_SUCCESS) {+ mach_error("Couldn't set thread timeshare policy", error);+ }++ precedencePolicy.importance = THREAD_IMPORTANCE;+ error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,+ (thread_policy_t)&precedencePolicy,+ THREAD_PRECEDENCE_POLICY_COUNT);+ if (error != KERN_SUCCESS) {+ mach_error("Couldn't set thread precedence policy", error);+ }++ /* set up the timer context */+ timerContext.version = 0;+ timerContext.info = params;+ timerContext.retain = NULL;+ timerContext.release = NULL;+ timerContext.copyDescription = NULL;++ /* create a new timer */+ timerInterval = (double)params->resolution / 1000.0;+ timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval,+ 0, 0, Pt_CFTimerCallback, &timerContext);++ timerRunLoop = CFRunLoopGetCurrent();+ CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode"));++ /* run until we're told to stop by Pt_Stop() */+ CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false);+ + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode"));+ CFRelease(timer);+ free(params);++ return NULL;+}++PtError Pt_Start(int resolution, PtCallback *callback, void *userData)+{+ PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams));+ pthread_t pthread_id;++ printf("Pt_Start() called\n");++ /* make sure we're not already playing */+ if (time_started_flag) return ptAlreadyStarted;+ startTime = CFAbsoluteTimeGetCurrent();++ if (callback) {+ + params->resolution = resolution;+ params->callback = callback;+ params->userData = userData;+ + pthread_create(&pthread_id, NULL, Pt_Thread, params);+ }++ time_started_flag = TRUE;+ return ptNoError;+}+++PtError Pt_Stop(void)+{+ printf("Pt_Stop called\n");++ CFRunLoopStop(timerRunLoop);+ time_started_flag = FALSE;+ return ptNoError;+}+++int Pt_Started(void)+{+ return time_started_flag;+}+++PtTimestamp Pt_Time(void)+{+ CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();+ return (PtTimestamp) ((now - startTime) * 1000.0);+}+++void Pt_Sleep(int32_t duration)+{+ usleep(duration * 1000);+}
portmidi/porttime/ptmacosx_mach.c view
@@ -1,130 +1,205 @@-/* ptmacosx.c -- portable timer implementation for mac os x */ - -#include <stdlib.h> -#include <stdio.h> -#include <CoreAudio/HostTime.h> - -#import <mach/mach.h> -#import <mach/mach_error.h> -#import <mach/mach_time.h> -#import <mach/clock.h> -#include <unistd.h> - -#include "porttime.h" -#include "sys/time.h" -#include "pthread.h" - -#define NSEC_PER_MSEC 1000000 -#define THREAD_IMPORTANCE 30 - -static int time_started_flag = FALSE; -static UInt64 start_time; -static pthread_t pt_thread_pid; - -/* note that this is static data -- we only need one copy */ -typedef struct { - int id; - int resolution; - PtCallback *callback; - void *userData; -} pt_callback_parameters; - -static int pt_callback_proc_id = 0; - -static void *Pt_CallbackProc(void *p) -{ - pt_callback_parameters *parameters = (pt_callback_parameters *) p; - int mytime = 1; - - kern_return_t error; - thread_extended_policy_data_t extendedPolicy; - thread_precedence_policy_data_t precedencePolicy; - - extendedPolicy.timeshare = 0; - error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, - (thread_policy_t)&extendedPolicy, - THREAD_EXTENDED_POLICY_COUNT); - if (error != KERN_SUCCESS) { - mach_error("Couldn't set thread timeshare policy", error); - } - - precedencePolicy.importance = THREAD_IMPORTANCE; - error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, - (thread_policy_t)&precedencePolicy, - THREAD_PRECEDENCE_POLICY_COUNT); - if (error != KERN_SUCCESS) { - mach_error("Couldn't set thread precedence policy", error); - } - - - /* to kill a process, just increment the pt_callback_proc_id */ - /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, parameters->id); */ - while (pt_callback_proc_id == parameters->id) { - /* wait for a multiple of resolution ms */ - UInt64 wait_time; - int delay = mytime++ * parameters->resolution - Pt_Time(); - long timestamp; - if (delay < 0) delay = 0; - wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); - wait_time += AudioGetCurrentHostTime(); - error = mach_wait_until(wait_time); - timestamp = Pt_Time(); - (*(parameters->callback))(timestamp, parameters->userData); - } - free(parameters); - return NULL; -} - - -PtError Pt_Start(int resolution, PtCallback *callback, void *userData) -{ - if (time_started_flag) return ptAlreadyStarted; - start_time = AudioGetCurrentHostTime(); - - if (callback) { - int res; - pt_callback_parameters *parms; - - parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters)); - if (!parms) return ptInsufficientMemory; - parms->id = pt_callback_proc_id; - parms->resolution = resolution; - parms->callback = callback; - parms->userData = userData; - res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms); - if (res != 0) return ptHostError; - } - - time_started_flag = TRUE; - return ptNoError; -} - - -PtError Pt_Stop() -{ - /* printf("Pt_Stop called\n"); */ - pt_callback_proc_id++; - time_started_flag = FALSE; - return ptNoError; -} - - -int Pt_Started() -{ - return time_started_flag; -} - - -PtTimestamp Pt_Time() -{ - UInt64 clock_time, nsec_time; - clock_time = AudioGetCurrentHostTime() - start_time; - nsec_time = AudioConvertHostTimeToNanos(clock_time); - return (PtTimestamp)(nsec_time / NSEC_PER_MSEC); -} - - -void Pt_Sleep(long duration) -{ - usleep(duration * 1000); -} +/* ptmacosx.c -- portable timer implementation for mac os x */++#include <stdlib.h>+#include <stdio.h>+#include <CoreAudio/HostTime.h>++#import <mach/mach.h>+#import <mach/mach_error.h>+#import <mach/mach_time.h>+#import <mach/clock.h>+#include <unistd.h>+#include <AvailabilityMacros.h>++#include "porttime.h"+#include "sys/time.h"+#include "pthread.h"++#ifndef NSEC_PER_MSEC+#define NSEC_PER_MSEC 1000000+#endif+#define THREAD_IMPORTANCE 63++/* QOS headers are available as of macOS 10.10 */+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000+#include "sys/qos.h"+#define HAVE_APPLE_QOS 1+#else+#undef HAVE_APPLE_QOS+#endif++static int time_started_flag = FALSE;+static UInt64 start_time;+static pthread_t pt_thread_pid;++/* note that this is static data -- we only need one copy */+typedef struct {+ int id;+ int resolution;+ PtCallback *callback;+ void *userData;+} pt_callback_parameters;++static int pt_callback_proc_id = 0;++static void *Pt_CallbackProc(void *p)+{+ pt_callback_parameters *parameters = (pt_callback_parameters *) p;+ int mytime = 1;++ kern_return_t error;+ thread_extended_policy_data_t extendedPolicy;+ thread_precedence_policy_data_t precedencePolicy;++ extendedPolicy.timeshare = 0;+ error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,+ (thread_policy_t)&extendedPolicy,+ THREAD_EXTENDED_POLICY_COUNT);+ if (error != KERN_SUCCESS) {+ mach_error("Couldn't set thread timeshare policy", error);+ }++ precedencePolicy.importance = THREAD_IMPORTANCE;+ error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,+ (thread_policy_t)&precedencePolicy,+ THREAD_PRECEDENCE_POLICY_COUNT);+ if (error != KERN_SUCCESS) {+ mach_error("Couldn't set thread precedence policy", error);+ }+ + /* Most important, set real-time constraints.+ Define the guaranteed and max fraction of time for the audio thread.+ These "duty cycle" values can range from 0 to 1. A value of 0.5+ means the scheduler would give half the time to the thread.+ These values have empirically been found to yield good behavior.+ Good means that audio performance is high and other threads won't starve.+ */+ const double kGuaranteedAudioDutyCycle = 0.75;+ const double kMaxAudioDutyCycle = 0.85;+ + /* Define constants determining how much time the audio thread can+ use in a given time quantum. All times are in milliseconds.+ */+ /* About 128 frames @44.1KHz */+ const double kTimeQuantum = 2.9;+ + /* Time guaranteed each quantum. */+ const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;+ + /* Maximum time each quantum. */+ const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum;+ + /* Get the conversion factor from milliseconds to absolute time+ which is what the time-constraints call needs.+ */+ mach_timebase_info_data_t tb_info;+ mach_timebase_info(&tb_info);+ double ms_to_abs_time =+ ((double)tb_info.denom / (double)tb_info.numer) * 1000000;+ + thread_time_constraint_policy_data_t time_constraints;+ time_constraints.period = (uint32_t)(kTimeQuantum * ms_to_abs_time);+ time_constraints.computation = (uint32_t)(kAudioTimeNeeded * ms_to_abs_time);+ time_constraints.constraint = (uint32_t)(kMaxTimeAllowed * ms_to_abs_time);+ time_constraints.preemptible = 0;+ + error = thread_policy_set(mach_thread_self(),+ THREAD_TIME_CONSTRAINT_POLICY,+ (thread_policy_t)&time_constraints,+ THREAD_TIME_CONSTRAINT_POLICY_COUNT);+ if (error != KERN_SUCCESS) {+ mach_error("Couldn't set thread precedence policy", error);+ }++ /* to kill a process, just increment the pt_callback_proc_id */+ /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,+ parameters->id); */+ while (pt_callback_proc_id == parameters->id) {+ /* wait for a multiple of resolution ms */+ UInt64 wait_time;+ int delay = mytime++ * parameters->resolution - Pt_Time();+ PtTimestamp timestamp;+ if (delay < 0) delay = 0;+ wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);+ wait_time += AudioGetCurrentHostTime();+ mach_wait_until(wait_time);+ timestamp = Pt_Time();+ (*(parameters->callback))(timestamp, parameters->userData);+ }+ free(parameters);+ return NULL;+}+++PtError Pt_Start(int resolution, PtCallback *callback, void *userData)+{+ if (time_started_flag) return ptAlreadyStarted;+ start_time = AudioGetCurrentHostTime();+ + if (callback) {+ int res;+ pt_callback_parameters *parms;++ parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));+ if (!parms) return ptInsufficientMemory;+ parms->id = pt_callback_proc_id;+ parms->resolution = resolution;+ parms->callback = callback;+ parms->userData = userData;+ +#ifdef HAVE_APPLE_QOS+ pthread_attr_t qosAttribute;+ pthread_attr_init(&qosAttribute);+ pthread_attr_set_qos_class_np(&qosAttribute, + QOS_CLASS_USER_INTERACTIVE, 0);++ res = pthread_create(&pt_thread_pid, &qosAttribute, Pt_CallbackProc, + parms);+#else+ res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);+#endif+ + struct sched_param sp;+ memset(&sp, 0, sizeof(struct sched_param));+ sp.sched_priority = sched_get_priority_max(SCHED_RR);+ if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == -1) {+ return ptHostError;+ }+ + if (res != 0) return ptHostError;+ }+ + time_started_flag = TRUE;+ return ptNoError;+}+++PtError Pt_Stop(void)+{+ /* printf("Pt_Stop called\n"); */+ pt_callback_proc_id++;+ pthread_join(pt_thread_pid, NULL);+ time_started_flag = FALSE;+ return ptNoError;+}+++int Pt_Started(void)+{+ return time_started_flag;+}+++PtTimestamp Pt_Time(void)+{+ UInt64 clock_time, nsec_time;+ clock_time = AudioGetCurrentHostTime() - start_time;+ nsec_time = AudioConvertHostTimeToNanos(clock_time);+ return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);+}+++void Pt_Sleep(int32_t duration)+{+ usleep(duration * 1000);+}
portmidi/porttime/ptwinmm.c view
@@ -1,70 +1,70 @@-/* ptwinmm.c -- portable timer implementation for win32 */ - - -#include "porttime.h" -#include "windows.h" -#include "time.h" - - -TIMECAPS caps; - -static long time_offset = 0; -static int time_started_flag = FALSE; -static long time_resolution; -static MMRESULT timer_id; -static PtCallback *time_callback; - -void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, - DWORD_PTR dw1, DWORD_PTR dw2) -{ - (*time_callback)(Pt_Time(), (void *) dwUser); -} - - -PtError Pt_Start(int resolution, PtCallback *callback, void *userData) -{ - if (time_started_flag) return ptAlreadyStarted; - timeBeginPeriod(resolution); - time_resolution = resolution; - time_offset = timeGetTime(); - time_started_flag = TRUE; - time_callback = callback; - if (callback) { - timer_id = timeSetEvent(resolution, 1, winmm_time_callback, - (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); - if (!timer_id) return ptHostError; - } - return ptNoError; -} - - -PtError Pt_Stop() -{ - if (!time_started_flag) return ptAlreadyStopped; - if (time_callback && timer_id) { - timeKillEvent(timer_id); - time_callback = NULL; - timer_id = 0; - } - time_started_flag = FALSE; - timeEndPeriod(time_resolution); - return ptNoError; -} - - -int Pt_Started() -{ - return time_started_flag; -} - - -PtTimestamp Pt_Time() -{ - return timeGetTime() - time_offset; -} - - -void Pt_Sleep(long duration) -{ - Sleep(duration); -} +/* ptwinmm.c -- portable timer implementation for win32 */+++#include "porttime.h"+#include "windows.h"+#include "time.h"+++TIMECAPS caps;++static long time_offset = 0;+static int time_started_flag = FALSE;+static long time_resolution;+static MMRESULT timer_id;+static PtCallback *time_callback;++void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, + DWORD_PTR dw1, DWORD_PTR dw2)+{+ (*time_callback)(Pt_Time(), (void *) dwUser);+}+ ++PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData)+{+ if (time_started_flag) return ptAlreadyStarted;+ timeBeginPeriod(resolution);+ time_resolution = resolution;+ time_offset = timeGetTime();+ time_started_flag = TRUE;+ time_callback = callback;+ if (callback) {+ timer_id = timeSetEvent(resolution, 1, winmm_time_callback, + (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);+ if (!timer_id) return ptHostError;+ }+ return ptNoError;+}+++PMEXPORT PtError Pt_Stop(void)+{+ if (!time_started_flag) return ptAlreadyStopped;+ if (time_callback && timer_id) {+ timeKillEvent(timer_id);+ time_callback = NULL;+ timer_id = 0;+ }+ time_started_flag = FALSE;+ timeEndPeriod(time_resolution);+ return ptNoError;+}+++PMEXPORT int Pt_Started(void)+{+ return time_started_flag;+}+++PMEXPORT PtTimestamp Pt_Time(void)+{+ return timeGetTime() - time_offset;+}+++PMEXPORT void Pt_Sleep(int32_t duration)+{+ Sleep(duration);+}