From 90d5b8b4a064420d74678654e94ea4755b377f21 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 15 Dec 2005 00:57:02 +0000 Subject: checking in missing files on behalf of Miller (cleared it with him first). The files are from portmidi17nov04.zip svn path=/trunk/; revision=4216 --- pd/portmidi/pm_common/pminternal.h | 173 +++ pd/portmidi/pm_common/pmutil.c | 132 ++ pd/portmidi/pm_common/pmutil.h | 56 + pd/portmidi/pm_common/portmidi.c | 980 +++++++++++++ pd/portmidi/pm_common/portmidi.h | 692 +++++++++ pd/portmidi/pm_linux/README_LINUX.txt | 32 + pd/portmidi/pm_linux/pmlinux.c | 51 + pd/portmidi/pm_linux/pmlinux.h | 5 + pd/portmidi/pm_linux/pmlinuxalsa.c | 724 ++++++++++ pd/portmidi/pm_linux/pmlinuxalsa.h | 6 + pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj | 1324 ++++++++++++++++++ pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser | 1508 ++++++++++++++++++++ pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj | 337 +++++ pd/portmidi/pm_mac/pmmac.c | 42 + pd/portmidi/pm_mac/pmmac.h | 4 + pd/portmidi/pm_mac/pmmacosxcm.c | 701 ++++++++++ pd/portmidi/pm_mac/pmmacosxcm.h | 4 + pd/portmidi/pm_test/latency.c | 278 ++++ pd/portmidi/pm_test/latency.dsp | 102 ++ pd/portmidi/pm_test/midithread.c | 327 +++++ pd/portmidi/pm_test/midithread.dsp | 102 ++ pd/portmidi/pm_test/midithru.c | 364 +++++ pd/portmidi/pm_test/midithru.dsp | 102 ++ pd/portmidi/pm_test/midithru.dsw | 29 + pd/portmidi/pm_test/sysex.c | 319 +++++ pd/portmidi/pm_test/sysex.dsp | 102 ++ pd/portmidi/pm_test/test.c | 469 +++++++ pd/portmidi/pm_test/test.dsp | 102 ++ pd/portmidi/pm_test/txdata.syx | 257 ++++ pd/portmidi/pm_win/README_WIN.txt | 183 +++ pd/portmidi/pm_win/copy-dll.bat | 13 + pd/portmidi/pm_win/debugging_dlls.txt | 145 ++ pd/portmidi/pm_win/pm_dll.dsp | 107 ++ pd/portmidi/pm_win/pmdll.c | 49 + pd/portmidi/pm_win/pmdll.h | 5 + pd/portmidi/pm_win/pmwin.c | 113 ++ pd/portmidi/pm_win/pmwinmm.c | 1547 +++++++++++++++++++++ pd/portmidi/pm_win/pmwinmm.h | 5 + pd/portmidi/porttime/porttime.c | 3 + pd/portmidi/porttime/porttime.dsp | 104 ++ pd/portmidi/porttime/porttime.h | 36 + pd/portmidi/porttime/ptlinux.c | 120 ++ pd/portmidi/porttime/ptmacosx_cf.c | 135 ++ pd/portmidi/porttime/ptmacosx_mach.c | 123 ++ pd/portmidi/porttime/ptwinmm.c | 65 + 45 files changed, 12077 insertions(+) create mode 100644 pd/portmidi/pm_common/pminternal.h create mode 100644 pd/portmidi/pm_common/pmutil.c create mode 100644 pd/portmidi/pm_common/pmutil.h create mode 100644 pd/portmidi/pm_common/portmidi.c create mode 100644 pd/portmidi/pm_common/portmidi.h create mode 100644 pd/portmidi/pm_linux/README_LINUX.txt create mode 100644 pd/portmidi/pm_linux/pmlinux.c create mode 100644 pd/portmidi/pm_linux/pmlinux.h create mode 100644 pd/portmidi/pm_linux/pmlinuxalsa.c create mode 100644 pd/portmidi/pm_linux/pmlinuxalsa.h create mode 100644 pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj create mode 100644 pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser create mode 100644 pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj create mode 100644 pd/portmidi/pm_mac/pmmac.c create mode 100644 pd/portmidi/pm_mac/pmmac.h create mode 100644 pd/portmidi/pm_mac/pmmacosxcm.c create mode 100644 pd/portmidi/pm_mac/pmmacosxcm.h create mode 100644 pd/portmidi/pm_test/latency.c create mode 100644 pd/portmidi/pm_test/latency.dsp create mode 100644 pd/portmidi/pm_test/midithread.c create mode 100644 pd/portmidi/pm_test/midithread.dsp create mode 100644 pd/portmidi/pm_test/midithru.c create mode 100644 pd/portmidi/pm_test/midithru.dsp create mode 100644 pd/portmidi/pm_test/midithru.dsw create mode 100644 pd/portmidi/pm_test/sysex.c create mode 100644 pd/portmidi/pm_test/sysex.dsp create mode 100644 pd/portmidi/pm_test/test.c create mode 100644 pd/portmidi/pm_test/test.dsp create mode 100644 pd/portmidi/pm_test/txdata.syx create mode 100644 pd/portmidi/pm_win/README_WIN.txt create mode 100644 pd/portmidi/pm_win/copy-dll.bat create mode 100644 pd/portmidi/pm_win/debugging_dlls.txt create mode 100644 pd/portmidi/pm_win/pm_dll.dsp create mode 100644 pd/portmidi/pm_win/pmdll.c create mode 100644 pd/portmidi/pm_win/pmdll.h create mode 100644 pd/portmidi/pm_win/pmwin.c create mode 100644 pd/portmidi/pm_win/pmwinmm.c create mode 100644 pd/portmidi/pm_win/pmwinmm.h create mode 100644 pd/portmidi/porttime/porttime.c create mode 100644 pd/portmidi/porttime/porttime.dsp create mode 100644 pd/portmidi/porttime/porttime.h create mode 100644 pd/portmidi/porttime/ptlinux.c create mode 100644 pd/portmidi/porttime/ptmacosx_cf.c create mode 100644 pd/portmidi/porttime/ptmacosx_mach.c create mode 100644 pd/portmidi/porttime/ptwinmm.c diff --git a/pd/portmidi/pm_common/pminternal.h b/pd/portmidi/pm_common/pminternal.h new file mode 100644 index 00000000..20677308 --- /dev/null +++ b/pd/portmidi/pm_common/pminternal.h @@ -0,0 +1,173 @@ +/* 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); +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 */ + PmEvent *buffer; /* storage for: + - midi input + - midi output w/latency != 0 */ + long head; + long tail; + + 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 overflow; /* set to non-zero if input is dropped */ + int flush; /* flag to drop incoming sysex data because of overflow */ + int sysex_in_progress; /* use for overflow management */ + 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; /* 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 *descriptor; /* system-dependent state */ + +} PmInternal; + +typedef struct { + long head; + long tail; + long len; + long msg_size; + long overflow; + char *buffer; +} PmQueueRep; + +/* 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_sysex(PmInternal *midi, PmTimestamp timestamp); +PmError none_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp); +PmTimestamp none_synchronize(PmInternal *midi); + +PmError pm_fail_fn(PmInternal *midi); +PmError pm_success_fn(PmInternal *midi); +PmError pm_add_device(char *interf, char *name, int input, void *descriptor, + pm_fns_type dictionary); +void pm_read_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp); +void pm_begin_sysex(PmInternal *midi); +void pm_end_sysex(PmInternal *midi); +void pm_read_short(PmInternal *midi, PmEvent *event); + +#define none_write_flush pm_fail_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 + diff --git a/pd/portmidi/pm_common/pmutil.c b/pd/portmidi/pm_common/pmutil.c new file mode 100644 index 00000000..1178b80b --- /dev/null +++ b/pd/portmidi/pm_common/pmutil.c @@ -0,0 +1,132 @@ +/* pmutil.c -- some helpful utilities for building midi + applications that use PortMidi + */ +#include "stdlib.h" +#include "memory.h" +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" + + +PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg) +{ + PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); + + /* arg checking */ + if (!queue) + return NULL; + + queue->len = num_msgs * bytes_per_msg; + queue->buffer = pm_alloc(queue->len); + if (!queue->buffer) { + pm_free(queue); + return NULL; + } + queue->head = 0; + queue->tail = 0; + queue->msg_size = bytes_per_msg; + queue->overflow = FALSE; + return queue; +} + + +PmError Pm_QueueDestroy(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + + /* arg checking */ + if (!queue || !queue->buffer) + return pmBadPtr; + + pm_free(queue->buffer); + pm_free(queue); + return pmNoError; +} + + +PmError Pm_Dequeue(PmQueue *q, void *msg) +{ + long head; + PmQueueRep *queue = (PmQueueRep *) q; + + /* arg checking */ + if(!queue) + return pmBadPtr; + + if (queue->overflow) { + queue->overflow = FALSE; + return pmBufferOverflow; + } + + head = queue->head; /* make sure this is written after access */ + if (head == queue->tail) return 0; + memcpy(msg, queue->buffer + head, queue->msg_size); + head += queue->msg_size; + if (head == queue->len) head = 0; + queue->head = head; + return 1; /* success */ +} + + +/* source should not enqueue data if overflow is set */ +/**/ +PmError Pm_Enqueue(PmQueue *q, void *msg) +{ + PmQueueRep *queue = (PmQueueRep *) q; + long tail; + + /* arg checking */ + if (!queue) + return pmBadPtr; + + tail = queue->tail; + memcpy(queue->buffer + tail, msg, queue->msg_size); + tail += queue->msg_size; + if (tail == queue->len) tail = 0; + if (tail == queue->head) { + queue->overflow = TRUE; + /* do not update tail, so message is lost */ + return pmBufferOverflow; + } + queue->tail = tail; + return pmNoError; +} + +int Pm_QueueEmpty(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + if (!queue) return TRUE; + return (queue->head == queue->tail); +} + +int Pm_QueueFull(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + long tail; + + /* arg checking */ + if(!queue) + return pmBadPtr; + + tail = queue->tail; + tail += queue->msg_size; + if (tail == queue->len) { + tail = 0; + } + return (tail == queue->head); +} + +void *Pm_QueuePeek(PmQueue *q) +{ + long head; + PmQueueRep *queue = (PmQueueRep *) q; + + /* arg checking */ + if(!queue) + return NULL; + + head = queue->head; /* make sure this is written after access */ + if (head == queue->tail) return NULL; + return queue->buffer + head; +} + diff --git a/pd/portmidi/pm_common/pmutil.h b/pd/portmidi/pm_common/pmutil.h new file mode 100644 index 00000000..a7fb3ebf --- /dev/null +++ b/pd/portmidi/pm_common/pmutil.h @@ -0,0 +1,56 @@ +/* pmutil.h -- some helpful utilities for building midi + applications that use PortMidi + */ + +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. + + 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, clears the overflow flag, and does not + return a data item if the overflow flag is set. (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. + */ +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. + If queue is in an overflow state, a valid pointer is returned and the + queue remains in the overflow state. + */ +void *Pm_QueuePeek(PmQueue *queue); + diff --git a/pd/portmidi/pm_common/portmidi.c b/pd/portmidi/pm_common/portmidi.c new file mode 100644 index 00000000..e1b962d7 --- /dev/null +++ b/pd/portmidi/pm_common/portmidi.c @@ -0,0 +1,980 @@ +#include "stdlib.h" +#include "string.h" +#include "portmidi.h" +#include "porttime.h" +#include "pminternal.h" +#include + +#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 = FALSE; +char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; + +#ifdef PM_CHECK_ERRORS + +#include + +#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 = + 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 ) +{ + PmError err = Pm_Initialize(); + if (err) + return pm_errmsg(err); + return pm_descriptor_index; +} + + +const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) +{ + PmError err = Pm_Initialize(); + if (err) + return NULL; + 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; +} + +/* none_sysex -- placeholder for begin_sysex and end_sysex */ +PmError none_sysex(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'"; + 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) { /* we have the string already from open or close */ + 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 (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 ) { + pm_hosterror_text[0] = 0; /* the null string */ + if (!pm_initialized) { + pm_init(); + pm_initialized = TRUE; + } + return pmNoError; +} + + +PmError Pm_Terminate( void ) { + if (pm_initialized) { + pm_term(); + 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 + + When the reader wants data: + if overflow_flag: + do not get anything + empty the buffer (read_ptr = write_ptr) + clear overflow_flag + return pmBufferOverflow + get data + return number of messages +*/ +PmError Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) { + PmInternal *midi = (PmInternal *) stream; + int n = 0; + long head; + PmError err = pmNoError; + + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(Pm_HasHostError(midi)) + err = pmHostError; + 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) { + return pm_errmsg(err); + } + + head = midi->head; + while (head != midi->tail && n < length) { + PmEvent event = midi->buffer[head++]; + *buffer++ = event; + if (head == midi->buffer_len) head = 0; + n++; + } + midi->head = head; + if (midi->overflow) { + midi->head = midi->tail; + midi->overflow = FALSE; + return pm_errmsg(pmBufferOverflow); + } + return n; +} + +PmError Pm_Poll( PortMidiStream *stream ) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err; + + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(Pm_HasHostError(midi)) + err = pmHostError; + 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) + return pm_errmsg(err); + else + return midi->head != midi->tail; +} + +/* 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; + int i; + int bits; + + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(Pm_HasHostError(midi)) + err = pmHostError; + 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; + } + } + + 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 non-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 */ + 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) { + midi->sysex_in_progress = FALSE; + if ((err = (*midi->dictionary->end_sysex)(midi, + buffer[i].timestamp)) != pmNoError) + goto pm_write_error; + 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 */ + err = (*midi->dictionary->write_flush)(midi); +pm_write_error: + 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]; + /* 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 (shift == 32) { + shift = 0; + bufx++; + if (bufx == BUFLEN) { + PmError err = Pm_Write(stream, buffer, BUFLEN); + if (err) return err; + /* prepare to fill another buffer */ + bufx = 0; + } + buffer[bufx].message = 0; + buffer[bufx].timestamp = when; + } + /* keep inserting bytes until you find MIDI_EOX */ + if (*msg++ == MIDI_EOX) break; + } + + /* 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 */ + return Pm_Write(stream, buffer, bufx); + } + 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 */ + else bufferSize++; /* buffer holds N-1 msgs, so increase request by 1 */ + midi->buffer_len = bufferSize; /* portMidi input storage */ + midi->buffer = (PmEvent *) pm_alloc(sizeof(PmEvent) * midi->buffer_len); + if (!midi->buffer) { + /* free portMidi data */ + *stream = NULL; + pm_free(midi); + err = pmInsufficientMemory; + goto error_return; + } + midi->head = 0; + midi->tail = 0; + midi->latency = 0; /* not used */ + midi->overflow = FALSE; + midi->flush = FALSE; + 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; + 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_free(midi->buffer); + pm_free(midi); + } else { + /* portMidi input open successful */ + descriptors[inputDevice].pub.opened = TRUE; + } +error_return: + 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 = pmBadPtr; + else if (descriptors[outputDevice].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 = 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; + /* when stream used, this buffer allocated and used by + winmm_out_open; deleted by winmm_out_close */ + midi->buffer_len = bufferSize; + midi->buffer = NULL; + midi->head = 0; /* unused by output */ + midi->tail = 0; /* 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->overflow = FALSE; /* not used */ + midi->flush = FALSE; /* not used */ + 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; /* not used for output */ + midi->sync_time = 0; + midi->first_message = TRUE; + midi->dictionary = descriptors[outputDevice].dictionary; + 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: + 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; + + /* 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; + pm_free(midi->buffer); + pm_free(midi); +error_return: + 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); + return pm_errmsg(err); +} + +/* in win32 multimedia API (via callbacks) some of these functions used; assume never fail */ +long pm_next_time(PmInternal *midi) { + + /* arg checking */ + assert(midi != NULL); + assert(!Pm_HasHostError(midi)); + + return midi->buffer[midi->head].timestamp; +} +/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */ +static int pm_channel_filtered(int status, int mask) +{ + if ((status & 0xF0) == 0xF0) /* 0xF? messages don't have a channel */ + return 0; + return !(Pm_Channel(status & 0x0F) & mask); + /* it'd be easier to return 0 for filtered, 1 for allowed, + but it would different from the other filtering functions + */ + +} +/* 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. + */ +static int pm_realtime_filtered(int status, long 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. + */ + +static int pm_status_filtered(int status, long filters) +{ + status &= 0xF0; /* remove channel information */ + 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)); + +} + +/* pm_read_short and pm_read_byte + 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 sysex byte should be reported by calling pm_read_byte + (which sets midi->sysex_in_progress). After the eox byte, + pm_read_byte will clear sysex_in_progress and midi->flush + (Note that the overflow flag is managed by pm_read_short + and Pm_Read, so the supplier should not read or write it.) + */ + +/* 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. + */ + + /* Algorithnm: + if overflow or flush, return + ATOMIC: + enqueue data + if buffer overflow, set overflow + if buffer overflow and sysex_in_progress, set flush + */ +void pm_read_short(PmInternal *midi, PmEvent *event) +{ + long tail; + int status; + /* arg checking */ + assert(midi != NULL); + assert(!Pm_HasHostError(midi)); + /* midi filtering is applied here */ + status = Pm_MessageStatus(event->message); + if (!pm_status_filtered(status, midi->filters) + && !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) && + !is_real_time(status) && status != MIDI_SYSEX ) { + midi->sysex_in_progress = FALSE; + midi->flush = FALSE; + } + + /* don't try to do anything more in an overflow state */ + if (midi->overflow || midi->flush) return; + + /* insert the message */ + tail = midi->tail; + midi->buffer[tail++] = *event; + if (tail == midi->buffer_len) tail = 0; + if (tail == midi->head || midi->overflow) { + midi->overflow = TRUE; + if (midi->sysex_in_progress) midi->flush = TRUE; + /* drop the rest of the message, this must be cleared + by caller when EOX is received */ + return; + } + midi->tail = tail; /* complete the write */ + } +} + + +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; + pm_read_short(midi, &event); + midi->sysex_message_count = 0; + midi->sysex_message = 0; +} + + +void pm_read_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp) +{ + assert(midi); + assert(!Pm_HasHostError(midi)); + /* here is the logic for controlling sysex_in_progress */ + if (midi->sysex_in_progress) { + if (byte == MIDI_EOX) midi->sysex_in_progress = FALSE; + else if (byte == MIDI_SYSEX) { + /* problem: need to terminate the current sysex and start + a new one + */ + pm_flush_sysex(midi, timestamp); + } + } else if (byte == MIDI_SYSEX) { + midi->sysex_in_progress = TRUE; + } else { + /* error: we're getting data bytes or EOX but we're no sysex is + in progress. Drop the data. (Would it be better to report an + error? Is this a host error or a pmBadData error? Is this + ever possible? + */ +#ifdef DEBUG + printf("PortMidi debug msg: unexpected sysex data or EOX\n"); +#endif + return; + } + + if (pm_realtime_filtered(byte, midi->filters)) + return; + /* this awkward expression places the bytes in increasingly higher- + order bytes of the long message */ + midi->sysex_message |= (byte << (8 * midi->sysex_message_count++)); + if (midi->sysex_message_count == 4 || !midi->sysex_in_progress) { + pm_flush_sysex(midi, timestamp); + if (!midi->sysex_in_progress) midi->flush = FALSE; + } +} + + +int pm_queue_full(PmInternal *midi) +{ + long tail; + + /* arg checking */ + assert(midi != NULL); + assert(!Pm_HasHostError(midi)); + + tail = midi->tail + 1; + if (tail == midi->buffer_len) tail = 0; + return tail == midi->head; +} + diff --git a/pd/portmidi/pm_common/portmidi.h b/pd/portmidi/pm_common/portmidi.h new file mode 100644 index 00000000..8c9c04c2 --- /dev/null +++ b/pd/portmidi/pm_common/portmidi.h @@ -0,0 +1,692 @@ +#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 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. + * + * 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. + * + * 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. + * + */ + +/* CHANGELOG FOR PORTMIDI + * + * 15Nov04 Ben Allison + * - sysex output now uses one buffer/message and reallocates buffer + * - if needed + * - filters expanded for many message types and channels + * - detailed changes are as follows: + * ------------- in pmwinmm.c -------------- + * - new #define symbol: OUTPUT_BYTES_PER_BUFFER + * - change SYSEX_BYTES_PER_BUFFER to 1024 + * - added MIDIHDR_BUFFER_LENGTH(x) to correctly count midihdr buffer length + * - change MIDIHDR_SIZE(x) to (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) + * - change allocate_buffer to use new MIDIHDR_BUFFER_LENGTH macro + * - new macros for MIDIHDR_SYSEX_SIZE and MIDIHDR_SYSEX_BUFFER_LENGTH + * - similar to above, but counts appropriately for sysex messages + * - added the following members to midiwinmm_struct for sysex data: + * - LPMIDIHDR *sysex_buffers; ** pool of buffers for sysex data ** + * - int num_sysex_buffers; ** how many sysex buffers ** + * - int next_sysex_buffer; ** index of next sysexbuffer to send ** + * - HANDLE sysex_buffer_signal; ** to wait for free sysex buffer ** + * - duplicated allocate_buffer, alocate_buffers and get_free_output_buffer + * - into equivalent sysex_buffer form + * - changed winmm_in_open to initialize new midiwinmm_struct members and + * - to use the new allocate_sysex_buffer() function instead of + * - allocate_buffer() + * - changed winmm_out_open to initialize new members, create sysex buffer + * - signal, and allocate 2 sysex buffers + * - changed winmm_out_delete to free sysex buffers and shut down the sysex + * - buffer signal + * - create new function resize_sysex_buffer which resizes m->hdr to the + * - passed size, and corrects the midiwinmm_struct accordingly. + * - changed winmm_write_byte to use new resize_sysex_buffer function, + * - if resize fails, write current buffer to output and continue + * - changed winmm_out_callback to use buffer_signal or sysex_buffer_signal + * - depending on which buffer was finished + * ------------- in portmidi.h -------------- + * - added pmBufferMaxSize to PmError to indicate that the buffer would be + * - too large for the underlying API + * - added additional filters + * - added prototype, documentation, and helper macro for Pm_SetChannelMask + * ------------- in portmidi.c -------------- + * - added pm_status_filtered() and pm_realtime_filtered() functions to + * separate filtering logic from buffer logic in pm_read_short + * - added Pm_SetChannelMask function + * - added pm_channel_filtered() function + * ------------- in pminternal.h -------------- + * - added member to PortMidiStream for channel mask + * + * 25May04 RBD + * - removed support for MIDI THRU + * - moved filtering from Pm_Read to pm_enqueue to avoid buffer ovfl + * - extensive work on Mac OS X port, especially sysex and error handling + * + * 18May04 RBD + * - removed side-effects from assert() calls. Now you can disable assert(). + * - no longer check pm_hosterror everywhere, fixing a bug where an open + * failure could cause a write not to work on a previously opened port + * until you call Pm_GetHostErrorText(). + * 16May04 RBD and Chris Roberts + * - Some documentation wordsmithing in portmidi.h + * - Dynamically allocate port descriptor structures + * - Fixed parameter error in midiInPrepareBuffer and midiInAddBuffer. + * + * 09Oct03 RBD + * - Changed Thru handling. Now the client does all the work and the client + * must poll or read to keep thru messages flowing. + * + * 31May03 RBD + * - Fixed various bugs. + * - Added linux ALSA support with help from Clemens Ladisch + * - Added Mac OS X support, implemented by Jon Parise, updated and + * integrated by Andrew Zeldis and Zico Kolter + * - Added latency program to build histogram of system latency using PortTime. + * + * 30Jun02 RBD Extensive rewrite of sysex handling. It works now. + * Extensive reworking of error reporting and error text -- no + * longer use dictionary call to delete data; instead, Pm_Open + * and Pm_Close clean up before returning an error code, and + * error text is saved in a system-independent location. + * Wrote sysex.c to test sysex message handling. + * + * 15Jun02 BCT changes: + * - Added pmHostError text handling. + * - For robustness, check PortMidi stream args not NULL. + * - Re-C-ANSI-fied code (changed many C++ comments to C style) + * - Reorganized code in pmwinmm according to input/output functionality (made + * cleanup handling easier to reason about) + * - Fixed Pm_Write calls (portmidi.h says these should not return length but Pm_Error) + * - Cleaned up memory handling (now system specific data deleted via dictionary + * call in PortMidi, allows client to query host errors). + * - Added explicit asserts to verify various aspects of pmwinmm implementation behaves as + * logic implies it should. Specifically: verified callback routines not reentrant and + * all verified status for all unchecked Win32 MMedia API calls perform successfully + * - Moved portmidi initialization and clean-up routines into DLL to fix Win32 MMedia API + * bug (i.e. if devices not explicitly closed, must reboot to debug application further). + * With this change, clients no longer need explicitly call Pm_Initialize, Pm_Terminate, or + * explicitly Pm_Close open devices when using WinMM version of PortMidi. + * + * 23Jan02 RBD Fixed bug in pmwinmm.c thru handling + * + * 21Jan02 RBD Added tests in Pm_OpenInput() and Pm_OpenOutput() to prevent + * opening an input as output and vice versa. + * Added comments and documentation. + * Implemented Pm_Terminate(). + * + * + * 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. + * + * 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 + * + * Host-error reporting relies on following assumptions: + * 1) PortMidi routines won't allow system dependent routines to be + * called when args are bogus. + * Thus, in pmwinmm.c it is safe to assume: + * - stream ptr valid + * - currently not operating in "has host error" state + * 2) Host-error reporting relies on a staged delivery of error messages. + * When a host error occurs, the error code is saved with the stream. + * The error is reported as a return code from the next operation on + * the stream. This could be immediately if the error is synchronous, + * or delayed if the error is an asynchronous callback problem. In + * any case, when pmHostError is returned, the error is copied to + * a global, pm_hosterror and the error code stored with the stream + * is cleared. If the user chooses to inquire about the error using + * Pm_GetHostErrorText(), the error will be reported as text. If the + * user ignores the error and makes another call on the stream, the + * call will proceed because the error code associated with the stream + * has been cleared. + * + */ + +#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, + pmHostError = -10000, + pmInvalidDeviceId, /* out of range or output device when input is requested or vice versa */ + pmInsufficientMemory, + pmBufferTooSmall, + pmBufferOverflow, + pmBadPtr, + pmBadData, /* illegal midi data, e.g. missing EOX */ + pmInternalError, + pmBufferMaxSize, /* buffer is already as large as it can be */ +} 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(). 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. +*/ +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: 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. + + 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 0x1 +/* filter system exclusive messages (0xF0): */ +#define PM_FILT_SYSEX 0x2 +/* filter clock messages (0xF8 only, does not filter clock start, etc.): */ +#define PM_FILT_CLOCK 0x4 +/* filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ +#define PM_FILT_PLAY 0x8 +/* filter undefined F9 messages (some equipment uses this as a 10ms 'tick') */ +#define PM_FILT_F9 0x10 +#define PM_FILT_TICK PM_FILT_F9 +/* filter undefined FD messages */ +#define PM_FILT_FD 0x20 +/* filter undefined real-time messages */ +#define PM_FILT_UNDEFINED (PM_FILT_F9 | PM_FILT_FD) +/* filter reset messages (0xFF) */ +#define PM_FILT_RESET 0x40 +/* 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) +/* filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ +#define PM_FILT_NOTE 0x80 +/* filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/ +#define PM_FILT_CHANNEL_AFTERTOUCH 0x100 +/* per-note aftertouch (Ensoniq holds a patent on generating this on keyboards until June 2006) (0xA0-0xAF) */ +#define PM_FILT_POLY_AFTERTOUCH 0x200 +/* 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 0x400 +/* Control Changes (CC's) (0xB0-0xBF)*/ +#define PM_FILT_CONTROL 0x800 +/* Pitch Bender (0xE0-0xEF*/ +#define PM_FILT_PITCHBEND 0x1000 +/* MIDI Time Code (0xF1)*/ +#define PM_FILT_MTC 0x2000 +/* Song Position (0xF2) */ +#define PM_FILT_SONG_POSITION 0x4000 +/* Song Select (0xF3)*/ +#define PM_FILT_SONG_SELECT 0x8000 +/* Tuning request (0xF6)*/ +#define PM_FILT_TUNE 0x10000 +/* 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. + + 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. + +*/ +PmError 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 */ diff --git a/pd/portmidi/pm_linux/README_LINUX.txt b/pd/portmidi/pm_linux/README_LINUX.txt new file mode 100644 index 00000000..e8a4332f --- /dev/null +++ b/pd/portmidi/pm_linux/README_LINUX.txt @@ -0,0 +1,32 @@ +README_LINUX.txt for PortMidi +Roger Dannenberg +8 June 2004 + +To make PortMidi and PortTime, go back up to the portmidi +directory and type make. + +The Makefile will build all test programs and the portmidi +library. You may want to modify the Makefile to remove the +PM_CHECK_ERRORS definition. For experimental software, +especially programs running from the command line, we +recommend using PM_CHECK_ERRORS -- it will terminate your +program and print a helpful message if any PortMidi +function returns an error code. + +If you do not compile with PM_CHECK_ERRORS, you should +check for errors yourself. + +This code has not been carefully tested; however, +all test programs in pm_test seem to run properly. + +CHANGELOG + +08-Jun-2004 Roger B. Dannenberg + Updated code to use new system abstraction. + +12-Apr-2003 Roger B. Dannenberg + Fixed pm_test/test.c to filter clocks and active messages. + Integrated changes from Clemens Ladisch: + cleaned up pmlinuxalsa.c + record timestamp on sysex input + deallocate some resources previously left open diff --git a/pd/portmidi/pm_linux/pmlinux.c b/pd/portmidi/pm_linux/pmlinux.c new file mode 100644 index 00000000..8c70319f --- /dev/null +++ b/pd/portmidi/pm_linux/pmlinux.c @@ -0,0 +1,51 @@ +/* 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. + */ + +#include "stdlib.h" +#include "portmidi.h" +#ifdef PMALSA + #include "pmlinuxalsa.h" +#endif + +#ifdef PMNULL + #include "pmlinuxnull.h" +#endif + +PmError pm_init() +{ + #ifdef PMALSA + pm_linuxalsa_init(); + #endif + #ifdef PMNULL + pm_linuxnull_init(); + #endif +} + +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); } + diff --git a/pd/portmidi/pm_linux/pmlinux.h b/pd/portmidi/pm_linux/pmlinux.h new file mode 100644 index 00000000..75c91f14 --- /dev/null +++ b/pd/portmidi/pm_linux/pmlinux.h @@ -0,0 +1,5 @@ +/* pmlinux.h */ + +extern PmDeviceID pm_default_input_device_id; +extern PmDeviceID pm_default_output_device_id; + diff --git a/pd/portmidi/pm_linux/pmlinuxalsa.c b/pd/portmidi/pm_linux/pmlinuxalsa.c new file mode 100644 index 00000000..9b0eee75 --- /dev/null +++ b/pd/portmidi/pm_linux/pmlinuxalsa.c @@ -0,0 +1,724 @@ +/* + * 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 "pminternal.h" +#include "pmlinuxalsa.h" +#include "string.h" +#include "porttime.h" +#include "pmlinux.h" + +#include + +/* 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*)(((client) << 8) | (port))) +#define GET_DESCRIPTOR_CLIENT(info) ((((int)(info)) >> 8) & 0xff) +#define GET_DESCRIPTOR_PORT(info) (((int)(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; // 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 %d, ", + timestamp, 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) +{ + int err; + 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); + 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); + 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) +{ + int err; + 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) +{ + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + /* This is supposed to flush any pending output. */ + 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) +{ + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + 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; +} + + +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; + int i; + long msg = 0; + int shift = 0; + if (!(midi->filters & PM_FILT_SYSEX)) { + for (i = 0; i < ev->data.ext.len; i++) { + pm_read_byte(midi, *ptr++, timestamp); + } + } + break; + } + } +} + +static PmError alsa_poll(PmInternal *midi) +{ + snd_seq_event_t *ev; + while (snd_seq_event_input(seq, &ev) >= 0) { + if (ev) { + handle_event(ev); + } + } + 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; + + err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); + if (err < 0) 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_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); + } + } + } +} + + +void pm_linuxalsa_term(void) +{ + snd_seq_close(seq); +} diff --git a/pd/portmidi/pm_linux/pmlinuxalsa.h b/pd/portmidi/pm_linux/pmlinuxalsa.h new file mode 100644 index 00000000..d4bff16c --- /dev/null +++ b/pd/portmidi/pm_linux/pmlinuxalsa.h @@ -0,0 +1,6 @@ +/* pmlinuxalsa.h -- system-specific definitions */ + +PmError pm_linuxalsa_init(void); +void pm_linuxalsa_term(void); + + diff --git a/pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj b/pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj new file mode 100644 index 00000000..f35008ed --- /dev/null +++ b/pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj @@ -0,0 +1,1324 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 38; + objects = { + 3D9CFCAA067657D50002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3D9CFCAB067657D50002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3D9CFCAC067657D50002CE69 = { + buildActionMask = 2147483647; + files = ( + 3D9CFCB8067657FA0002CE69, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3D9CFCAD067657D50002CE69 = { + buildActionMask = 2147483647; + files = ( + 3D9CFCBA067658A90002CE69, + 3D9CFCBB067658AA0002CE69, + 3D9CFCBC067658AA0002CE69, + 3D9CFCBD067659FE0002CE69, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3D9CFCAE067657D50002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3D9CFCAF067657D50002CE69 = { + buildPhases = ( + 3D9CFCAA067657D50002CE69, + 3D9CFCAB067657D50002CE69, + 3D9CFCAC067657D50002CE69, + 3D9CFCAD067657D50002CE69, + 3D9CFCAE067657D50002CE69, + ); + buildSettings = { + OPTIMIZATION_CFLAGS = ""; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = latency; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + comments = "a test program to measure OS latency"; + dependencies = ( + ); + isa = PBXApplicationTarget; + name = latency; + productInstallPath = "$(USER_APPS_DIR)"; + productName = latency; + productReference = 3D9CFCB0067657D50002CE69; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + }; + 3D9CFCB0067657D50002CE69 = { + isa = PBXApplicationReference; + path = latency.app; + refType = 3; + }; + 3D9CFCB8067657FA0002CE69 = { + fileRef = 3DA774110663E9DA0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3D9CFCBA067658A90002CE69 = { + fileRef = 3DA774480663EAE60002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3D9CFCBB067658AA0002CE69 = { + fileRef = 3DA7744E0663EBB20002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3D9CFCBC067658AA0002CE69 = { + fileRef = 3DA774500663EBBF0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3D9CFCBD067659FE0002CE69 = { + fileRef = 5851D5380479562300EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712AE0664C7C50002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712AF0664C7C50002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712B00664C7C50002CE69 = { + buildActionMask = 2147483647; + files = ( + 3DA712B90664C8490002CE69, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712B10664C7C50002CE69 = { + buildActionMask = 2147483647; + files = ( + 3DA712B60664C8400002CE69, + 3DA712B70664C8400002CE69, + 3DA712B80664C8410002CE69, + 3DA712BA0664C8A40002CE69, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712B20664C7C50002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712B30664C7C50002CE69 = { + buildPhases = ( + 3DA712AE0664C7C50002CE69, + 3DA712AF0664C7C50002CE69, + 3DA712B00664C7C50002CE69, + 3DA712B10664C7C50002CE69, + 3DA712B20664C7C50002CE69, + ); + buildSettings = { + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = midithread; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + comments = "Demonstrates the use of a low-latency thread for midi processing"; + dependencies = ( + ); + isa = PBXApplicationTarget; + name = midithread; + productInstallPath = "$(USER_APPS_DIR)"; + productName = midithread; + productReference = 3DA712B40664C7C50002CE69; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + }; + 3DA712B40664C7C50002CE69 = { + isa = PBXApplicationReference; + path = midithread.app; + refType = 3; + }; + 3DA712B60664C8400002CE69 = { + fileRef = 3DA774480663EAE60002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712B70664C8400002CE69 = { + fileRef = 3DA7744E0663EBB20002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712B80664C8410002CE69 = { + fileRef = 3DA774500663EBBF0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712B90664C8490002CE69 = { + fileRef = 3DA774160663E9DA0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712BA0664C8A40002CE69 = { + fileRef = 5851D5380479562300EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712BB0664C9B40002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712BC0664C9B40002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712BD0664C9B40002CE69 = { + buildActionMask = 2147483647; + files = ( + 3DA712C40664C9C40002CE69, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712BE0664C9B40002CE69 = { + buildActionMask = 2147483647; + files = ( + 3DA712C30664C9C20002CE69, + 3DA712C50664C9C70002CE69, + 3DA712C60664C9C70002CE69, + 3DA712C70664C9C80002CE69, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712BF0664C9B40002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712C00664C9B40002CE69 = { + buildPhases = ( + 3DA712BB0664C9B40002CE69, + 3DA712BC0664C9B40002CE69, + 3DA712BD0664C9B40002CE69, + 3DA712BE0664C9B40002CE69, + 3DA712BF0664C9B40002CE69, + ); + buildSettings = { + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = midithru; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + comments = "Demonstrates midi thru processing in a low-priority thread"; + dependencies = ( + ); + isa = PBXApplicationTarget; + name = midithru; + productInstallPath = "$(USER_APPS_DIR)"; + productName = midithru; + productReference = 3DA712C10664C9B40002CE69; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + }; + 3DA712C10664C9B40002CE69 = { + isa = PBXApplicationReference; + path = midithru.app; + refType = 3; + }; + 3DA712C30664C9C20002CE69 = { + fileRef = 5851D5380479562300EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712C40664C9C40002CE69 = { + fileRef = 3DA7741B0663E9DA0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712C50664C9C70002CE69 = { + fileRef = 3DA774480663EAE60002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712C60664C9C70002CE69 = { + fileRef = 3DA7744E0663EBB20002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712C70664C9C80002CE69 = { + fileRef = 3DA774500663EBBF0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712C80664CB1B0002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712C90664CB1B0002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712CA0664CB1B0002CE69 = { + buildActionMask = 2147483647; + files = ( + 3DA712D10664CB430002CE69, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712CB0664CB1B0002CE69 = { + buildActionMask = 2147483647; + files = ( + 3DA712D00664CB3F0002CE69, + 3DA712D20664CB470002CE69, + 3DA712D30664CB480002CE69, + 3DA712D40664CB480002CE69, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712CC0664CB1B0002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA712CD0664CB1B0002CE69 = { + buildPhases = ( + 3DA712C80664CB1B0002CE69, + 3DA712C90664CB1B0002CE69, + 3DA712CA0664CB1B0002CE69, + 3DA712CB0664CB1B0002CE69, + 3DA712CC0664CB1B0002CE69, + ); + buildSettings = { + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = sysex; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + comments = "System exclusive input and output test"; + dependencies = ( + ); + isa = PBXApplicationTarget; + name = sysex; + productInstallPath = "$(USER_APPS_DIR)"; + productName = sysex; + productReference = 3DA712CE0664CB1B0002CE69; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + }; + 3DA712CE0664CB1B0002CE69 = { + isa = PBXApplicationReference; + path = sysex.app; + refType = 3; + }; + 3DA712D00664CB3F0002CE69 = { + fileRef = 5851D5380479562300EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712D10664CB430002CE69 = { + fileRef = 3DA774210663E9DA0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712D20664CB470002CE69 = { + fileRef = 3DA774480663EAE60002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712D30664CB480002CE69 = { + fileRef = 3DA7744E0663EBB20002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA712D40664CB480002CE69 = { + fileRef = 3DA774500663EBBF0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA774070663E8EB0002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA774080663E8EB0002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA774090663E8EB0002CE69 = { + buildActionMask = 2147483647; + files = ( + 3DA774390663E9DA0002CE69, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA7740A0663E8EB0002CE69 = { + buildActionMask = 2147483647; + files = ( + 3DA774470663EA6A0002CE69, + 3DA774490663EAE60002CE69, + 3DA7744F0663EBB20002CE69, + 3DA774510663EBBF0002CE69, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA7740B0663E8EB0002CE69 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 3DA7740C0663E8EB0002CE69 = { + buildPhases = ( + 3DA774070663E8EB0002CE69, + 3DA774080663E8EB0002CE69, + 3DA774090663E8EB0002CE69, + 3DA7740A0663E8EB0002CE69, + 3DA7740B0663E8EB0002CE69, + ); + buildSettings = { + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = test; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + comments = "A PortMidi test program"; + dependencies = ( + ); + isa = PBXApplicationTarget; + name = test; + productInstallPath = "$(USER_APPS_DIR)"; + productName = test; + productReference = 3DA7740D0663E8EB0002CE69; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + }; + 3DA7740D0663E8EB0002CE69 = { + isa = PBXApplicationReference; + path = test.app; + refType = 3; + }; + 3DA774100663E9DA0002CE69 = { + children = ( + 3DA774110663E9DA0002CE69, + 3DA774120663E9DA0002CE69, + 3DA774130663E9DA0002CE69, + 3DA774140663E9DA0002CE69, + 3DA774150663E9DA0002CE69, + 3DA774160663E9DA0002CE69, + 3DA774170663E9DA0002CE69, + 3DA774180663E9DA0002CE69, + 3DA774190663E9DA0002CE69, + 3DA7741A0663E9DA0002CE69, + 3DA7741B0663E9DA0002CE69, + 3DA7741C0663E9DA0002CE69, + 3DA7741D0663E9DA0002CE69, + 3DA7741E0663E9DA0002CE69, + 3DA7741F0663E9DA0002CE69, + 3DA774200663E9DA0002CE69, + 3DA774210663E9DA0002CE69, + 3DA774220663E9DA0002CE69, + 3DA774230663E9DA0002CE69, + 3DA774240663E9DA0002CE69, + 3DA774250663E9DA0002CE69, + 3DA774260663E9DA0002CE69, + 3DA774270663E9DA0002CE69, + 3DA774280663E9DA0002CE69, + 3DA774290663E9DA0002CE69, + 3DA7742A0663E9DA0002CE69, + 3DA7742B0663E9DA0002CE69, + ); + isa = PBXGroup; + name = pm_test; + path = /Users/rbd/portmidi/pm_test; + refType = 0; + }; + 3DA774110663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = latency.c; + refType = 4; + }; + 3DA774120663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = latency.dsp; + refType = 4; + }; + 3DA774130663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = latency.plg; + refType = 4; + }; + 3DA774140663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = latencyDebug; + refType = 4; + }; + 3DA774150663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = latencyRelease; + refType = 4; + }; + 3DA774160663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = midithread.c; + refType = 4; + }; + 3DA774170663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = midithread.dsp; + refType = 4; + }; + 3DA774180663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = midithread.plg; + refType = 4; + }; + 3DA774190663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = midithreadDebug; + refType = 4; + }; + 3DA7741A0663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = midithreadRelease; + refType = 4; + }; + 3DA7741B0663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = midithru.c; + refType = 4; + }; + 3DA7741C0663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = midithru.dsp; + refType = 4; + }; + 3DA7741D0663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = midithru.dsw; + refType = 4; + }; + 3DA7741E0663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = midithru.plg; + refType = 4; + }; + 3DA7741F0663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = midithruDebug; + refType = 4; + }; + 3DA774200663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = midithruRelease; + refType = 4; + }; + 3DA774210663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = sysex.c; + refType = 4; + }; + 3DA774220663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = sysex.dsp; + refType = 4; + }; + 3DA774230663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = sysex.plg; + refType = 4; + }; + 3DA774240663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = sysexDebug; + refType = 4; + }; + 3DA774250663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = sysexRelease; + refType = 4; + }; + 3DA774260663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = test.c; + refType = 4; + }; + 3DA774270663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = test.dsp; + refType = 4; + }; + 3DA774280663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = test.plg; + refType = 4; + }; + 3DA774290663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = testDebug; + refType = 4; + }; + 3DA7742A0663E9DA0002CE69 = { + children = ( + ); + isa = PBXGroup; + path = testRelease; + refType = 4; + }; + 3DA7742B0663E9DA0002CE69 = { + fileEncoding = 30; + isa = PBXFileReference; + path = txdata.syx; + refType = 4; + }; + 3DA774390663E9DA0002CE69 = { + fileRef = 3DA774260663E9DA0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA774470663EA6A0002CE69 = { + fileRef = 5851D5380479562300EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA774480663EAE60002CE69 = { + isa = PBXFrameworkReference; + name = CoreMIDI.framework; + path = /System/Library/Frameworks/CoreMIDI.framework; + refType = 0; + }; + 3DA774490663EAE60002CE69 = { + fileRef = 3DA774480663EAE60002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA7744E0663EBB20002CE69 = { + isa = PBXFrameworkReference; + name = CoreFoundation.framework; + path = /System/Library/Frameworks/CoreFoundation.framework; + refType = 0; + }; + 3DA7744F0663EBB20002CE69 = { + fileRef = 3DA7744E0663EBB20002CE69; + isa = PBXBuildFile; + settings = { + }; + }; + 3DA774500663EBBF0002CE69 = { + isa = PBXFrameworkReference; + name = CoreAudio.framework; + path = /System/Library/Frameworks/CoreAudio.framework; + refType = 0; + }; + 3DA774510663EBBF0002CE69 = { + fileRef = 3DA774500663EBBF0002CE69; + isa = PBXBuildFile; + settings = { + }; + }; +//3D0 +//3D1 +//3D2 +//3D3 +//3D4 +//580 +//581 +//582 +//583 +//584 + 5851D5140479552300EE98CD = { + children = ( + 5851D51D047955A100EE98CD, + 5851D51C0479553600EE98CD, + 5851D51B0479553500EE98CD, + 5851D51A0479553300EE98CD, + 5851D5190479552E00EE98CD, + 5851D5390479562300EE98CD, + 3DA774100663E9DA0002CE69, + 3DA774480663EAE60002CE69, + 3DA7744E0663EBB20002CE69, + 3DA774500663EBBF0002CE69, + ); + isa = PBXGroup; + refType = 4; + }; + 5851D5160479552300EE98CD = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = NO; + }; + isa = PBXBuildStyle; + name = Development; + }; + 5851D5170479552300EE98CD = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = YES; + }; + isa = PBXBuildStyle; + name = Deployment; + }; + 5851D5180479552300EE98CD = { + buildStyles = ( + 5851D5160479552300EE98CD, + 5851D5170479552300EE98CD, + ); + hasScannedForEncodings = 1; + isa = PBXProject; + mainGroup = 5851D5140479552300EE98CD; + productRefGroup = 5851D5390479562300EE98CD; + projectDirPath = ""; + targets = ( + 5851D5370479562300EE98CD, + 3DA7740C0663E8EB0002CE69, + 3DA712B30664C7C50002CE69, + 3DA712C00664C9B40002CE69, + 3DA712CD0664CB1B0002CE69, + 3D9CFCAF067657D50002CE69, + ); + }; + 5851D5190479552E00EE98CD = { + children = ( + 5851D5310479560B00EE98CD, + 5851D5320479560B00EE98CD, + 5851D5730479613900EE98CD, + 5851D5740479613900EE98CD, + ); + isa = PBXGroup; + name = pm_mac; + refType = 4; + }; + 5851D51A0479553300EE98CD = { + children = ( + 5851D52C047955FD00EE98CD, + 5851D52D047955FD00EE98CD, + 5851D52E047955FD00EE98CD, + 5851D52F047955FD00EE98CD, + 5851D530047955FD00EE98CD, + ); + isa = PBXGroup; + name = pm_win; + refType = 4; + }; + 5851D51B0479553500EE98CD = { + children = ( + 5851D528047955D700EE98CD, + 5851D529047955D700EE98CD, + 5851D52A047955D700EE98CD, + 5851D52B047955D700EE98CD, + ); + isa = PBXGroup; + name = pm_linux; + refType = 4; + }; + 5851D51C0479553600EE98CD = { + children = ( + 5851D523047955C800EE98CD, + 5851D524047955C800EE98CD, + 5851D525047955C800EE98CD, + 5851D526047955C800EE98CD, + 5851D527047955C800EE98CD, + ); + isa = PBXGroup; + name = pm_common; + path = ""; + refType = 2; + }; + 5851D51D047955A100EE98CD = { + children = ( + 5851D51E047955B800EE98CD, + 5851D51F047955B800EE98CD, + 5851D520047955B800EE98CD, + 5851D521047955B800EE98CD, + 58ED5B81047D41DB00B92E62, + 5851D522047955B800EE98CD, + ); + isa = PBXGroup; + name = porttime; + refType = 4; + }; + 5851D51E047955B800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = porttime.c; + path = ../porttime/porttime.c; + refType = 2; + }; + 5851D51F047955B800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = porttime.h; + path = ../porttime/porttime.h; + refType = 2; + }; + 5851D520047955B800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = ptlinux.c; + path = ../porttime/ptlinux.c; + refType = 2; + }; + 5851D521047955B800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = ptmacosx_cf.c; + path = ../porttime/ptmacosx_cf.c; + refType = 2; + }; + 5851D522047955B800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = ptwinmm.c; + path = ../porttime/ptwinmm.c; + refType = 2; + }; + 5851D523047955C800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pminternal.h; + path = ../pm_common/pminternal.h; + refType = 2; + }; + 5851D524047955C800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmutil.c; + path = ../pm_common/pmutil.c; + refType = 2; + }; + 5851D525047955C800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmutil.h; + path = ../pm_common/pmutil.h; + refType = 2; + }; + 5851D526047955C800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = portmidi.c; + path = ../pm_common/portmidi.c; + refType = 2; + }; + 5851D527047955C800EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = portmidi.h; + path = ../pm_common/portmidi.h; + refType = 2; + }; + 5851D528047955D700EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmlinux.c; + path = ../pm_linux/pmlinux.c; + refType = 2; + }; + 5851D529047955D700EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmlinux.h; + path = ../pm_linux/pmlinux.h; + refType = 2; + }; + 5851D52A047955D700EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmlinuxalsa.c; + path = ../pm_linux/pmlinuxalsa.c; + refType = 2; + }; + 5851D52B047955D700EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmlinuxalsa.h; + path = ../pm_linux/pmlinuxalsa.h; + refType = 2; + }; + 5851D52C047955FD00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmdll.c; + path = ../pm_win/pmdll.c; + refType = 2; + }; + 5851D52D047955FD00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmdll.h; + path = ../pm_win/pmdll.h; + refType = 2; + }; + 5851D52E047955FD00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmwin.c; + path = ../pm_win/pmwin.c; + refType = 2; + }; + 5851D52F047955FD00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmwinmm.c; + path = ../pm_win/pmwinmm.c; + refType = 2; + }; + 5851D530047955FD00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmwinmm.h; + path = ../pm_win/pmwinmm.h; + refType = 2; + }; + 5851D5310479560B00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + path = pmmacosxcm.c; + refType = 2; + }; + 5851D5320479560B00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + path = pmmacosxcm.h; + refType = 2; + }; + 5851D5330479562300EE98CD = { + buildActionMask = 2147483647; + files = ( + 5851D53B0479564000EE98CD, + 5851D53D0479564100EE98CD, + 5851D53F0479564100EE98CD, + 5851D5420479564600EE98CD, + 5851D5430479564F00EE98CD, + 5851D5750479613900EE98CD, + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 5851D5340479562300EE98CD = { + buildActionMask = 2147483647; + files = ( + 5851D53C0479564000EE98CD, + 5851D53E0479564100EE98CD, + 5851D5400479564400EE98CD, + 5851D5760479613900EE98CD, + 58ED5C7A047D694900B92E62, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 5851D5350479562300EE98CD = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 5851D5360479562300EE98CD = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 5851D5370479562300EE98CD = { + buildPhases = ( + 5851D5330479562300EE98CD, + 5851D5340479562300EE98CD, + 5851D5350479562300EE98CD, + 5851D5360479562300EE98CD, + ); + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + LIBRARY_STYLE = STATIC; + OTHER_CFLAGS = "-DPM_CHECK_ERRORS -DDEBUG"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOL_FLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = libportmidi.a; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + }; + dependencies = ( + ); + isa = PBXLibraryTarget; + name = libportmidi.a; + productInstallPath = /usr/local/lib; + productName = libportmidi.a; + productReference = 5851D5380479562300EE98CD; + }; + 5851D5380479562300EE98CD = { + isa = PBXLibraryReference; + path = libportmidi.a; + refType = 3; + }; + 5851D5390479562300EE98CD = { + children = ( + 5851D5380479562300EE98CD, + 3DA7740D0663E8EB0002CE69, + 3DA712B40664C7C50002CE69, + 3DA712C10664C9B40002CE69, + 3DA712CE0664CB1B0002CE69, + 3D9CFCB0067657D50002CE69, + ); + isa = PBXGroup; + name = Products; + refType = 4; + }; + 5851D53B0479564000EE98CD = { + fileRef = 5851D523047955C800EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D53C0479564000EE98CD = { + fileRef = 5851D524047955C800EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D53D0479564100EE98CD = { + fileRef = 5851D525047955C800EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D53E0479564100EE98CD = { + fileRef = 5851D526047955C800EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D53F0479564100EE98CD = { + fileRef = 5851D527047955C800EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D5400479564400EE98CD = { + fileRef = 5851D5310479560B00EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D5420479564600EE98CD = { + fileRef = 5851D5320479560B00EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D5430479564F00EE98CD = { + fileRef = 5851D51F047955B800EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D5730479613900EE98CD = { + fileEncoding = 4; + isa = PBXFileReference; + path = pmmac.h; + refType = 2; + }; + 5851D5740479613900EE98CD = { + fileEncoding = 4; + isa = PBXFileReference; + path = pmmac.c; + refType = 2; + }; + 5851D5750479613900EE98CD = { + fileRef = 5851D5730479613900EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D5760479613900EE98CD = { + fileRef = 5851D5740479613900EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 58ED5B81047D41DB00B92E62 = { + fileEncoding = 30; + isa = PBXFileReference; + name = ptmacosx_mach.c; + path = ../porttime/ptmacosx_mach.c; + refType = 2; + }; + 58ED5C7A047D694900B92E62 = { + fileRef = 58ED5B81047D41DB00B92E62; + isa = PBXBuildFile; + settings = { + }; + }; + }; + rootObject = 5851D5180479552300EE98CD; +} diff --git a/pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser b/pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser new file mode 100644 index 00000000..6f36b563 --- /dev/null +++ b/pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser @@ -0,0 +1,1508 @@ +// !$*UTF8*$! +{ + 3D7903E9066690E00002CE69 = { + fRef = 3DA774260663E9DA0002CE69; + isa = PBXTextBookmark; + name = "test.c: TIME_START"; + rLen = 17; + rLoc = 309; + rType = 0; + vrLen = 955; + vrLoc = 0; + }; + 3D7903EC066690E00002CE69 = { + fRef = 5851D526047955C800EE98CD; + isa = PBXTextBookmark; + name = "portmidi.c: pm_read_short"; + rLen = 0; + rLoc = 24290; + rType = 0; + vrLen = 1795; + vrLoc = 23823; + }; + 3D7903ED066690E00002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_in_open"; + rLen = 6; + rLoc = 10561; + rType = 0; + vrLen = 1218; + vrLoc = 9520; + }; + 3D7903EE066690E00002CE69 = { + fRef = 3DA774210663E9DA0002CE69; + isa = PBXTextBookmark; + name = "sysex.c: loopback_test"; + rLen = 0; + rLoc = 3992; + rType = 0; + vrLen = 1768; + vrLoc = 2466; + }; + 3D7903EF066690E00002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: err"; + rLen = 0; + rLoc = 17896; + rType = 0; + vrLen = 1449; + vrLoc = 16660; + }; + 3D7903F0066690E00002CE69 = { + fRef = 3DA774210663E9DA0002CE69; + isa = PBXTextBookmark; + name = "sysex.c: loopback_test"; + rLen = 0; + rLoc = 3992; + rType = 0; + vrLen = 903; + vrLoc = 0; + }; + 3D7903F1066690E00002CE69 = { + fRef = 3DA774260663E9DA0002CE69; + isa = PBXTextBookmark; + name = "test.c: TIME_START"; + rLen = 17; + rLoc = 309; + rType = 0; + vrLen = 955; + vrLoc = 0; + }; + 3D7903F2066690E00002CE69 = { + fRef = 3DA774210663E9DA0002CE69; + isa = PBXTextBookmark; + name = "sysex.c: loopback_test"; + rLen = 0; + rLoc = 4871; + rType = 0; + vrLen = 804; + vrLoc = 254; + }; + 3D7903F3066690E00002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: SYSEX_BUFFER_SIZE"; + rLen = 0; + rLoc = 1197; + rType = 0; + vrLen = 1450; + vrLoc = 884; + }; + 3D7903F4066690E00002CE69 = { + fRef = 3DA774210663E9DA0002CE69; + isa = PBXTextBookmark; + name = "sysex.c: loopback_test"; + rLen = 0; + rLoc = 3483; + rType = 0; + vrLen = 1752; + vrLoc = 3284; + }; + 3D9CFCA7067651E80002CE69 = { + fileReference = 5851D526047955C800EE98CD; + isa = PBXFileBreakpoint; + lineNumber = 396; + state = 1; + }; + 3D9CFCA90676535F0002CE69 = { + fileReference = 5851D526047955C800EE98CD; + isa = PBXFileBreakpoint; + lineNumber = 390; + state = 1; + }; + 3D9CFCAF067657D50002CE69 = { + activeExec = 0; + executables = ( + 3D9CFCB1067657D50002CE69, + ); + }; + 3D9CFCB1067657D50002CE69 = { + activeArgIndex = 2147483647; + activeArgIndices = ( + ); + argumentStrings = ( + ); + configStateDict = { + }; + debuggerPlugin = GDBDebugging; + enableDebugStr = 1; + environmentEntries = ( + ); + isa = PBXExecutable; + name = latency; + shlibInfoDictList = ( + ); + sourceDirectories = ( + ); + }; + 3D9CFCBE06765A7F0002CE69 = { + fRef = 3D9CFCDB06765A7F0002CE69; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1521; + vrLoc = 0; + }; + 3D9CFCBF06765A7F0002CE69 = { + fRef = 5851D526047955C800EE98CD; + isa = PBXTextBookmark; + name = "portmidi.c: 759"; + rLen = 0; + rLoc = 24290; + rType = 0; + vrLen = 1709; + vrLoc = 25249; + }; + 3D9CFCC006765A7F0002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_begin_sysex"; + rLen = 0; + rLoc = 14525; + rType = 0; + vrLen = 1466; + vrLoc = 13829; + }; + 3D9CFCC106765A7F0002CE69 = { + fRef = 3DA774210663E9DA0002CE69; + isa = PBXTextBookmark; + name = "sysex.c: loopback_test"; + rLen = 0; + rLoc = 3483; + rType = 0; + vrLen = 903; + vrLoc = 0; + }; + 3D9CFCC206765A7F0002CE69 = { + exec = 3DA712CF0664CB1B0002CE69; + isa = PBXExecutableBookmark; + uiCtxt = { + buildSettingsExpandedSubviews = ( + 0, + 1, + 2, + 3, + 4, + 5, + ); + buildSettingsVisRect = "{{0, 0}, {746, 603}}"; + }; + }; + 3D9CFCC306765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3DA712C00664C9B40002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {517, 119}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}"; + TOCViewSelectedItems = ( + 3DA712BE0664C9B40002CE69, + ); + }; + }; + 3D9CFCC406765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3DA712CD0664CB1B0002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {518, 119}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {204, 603}}"; + TOCViewSelectedItems = ( + 3DA712CB0664CB1B0002CE69, + ); + }; + }; + 3D9CFCC506765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3D9CFCAF067657D50002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {518, 102}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {204, 603}}"; + TOCViewSelectedItems = ( + 3D9CFCAD067657D50002CE69, + ); + }; + }; + 3D9CFCC606765A7F0002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_write_short"; + rLen = 0; + rLoc = 13963; + rType = 0; + vrLen = 1621; + vrLoc = 13417; + }; + 3D9CFCC706765A7F0002CE69 = { + fRef = 3D9CFCD906765A7F0002CE69; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1521; + vrLoc = 0; + }; + 3D9CFCC806765A7F0002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: readProc"; + rLen = 0; + rLoc = 5664; + rType = 0; + vrLen = 1840; + vrLoc = 4759; + }; + 3D9CFCC906765A7F0002CE69 = { + fRef = 3D9CFCDA06765A7F0002CE69; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1521; + vrLoc = 0; + }; + 3D9CFCCA06765A7F0002CE69 = { + fRef = 3DA774210663E9DA0002CE69; + isa = PBXTextBookmark; + name = "sysex.c: loopback_test"; + rLen = 0; + rLoc = 3483; + rType = 0; + vrLen = 1712; + vrLoc = 3270; + }; + 3D9CFCCB06765A7F0002CE69 = { + fRef = 5851D526047955C800EE98CD; + isa = PBXTextBookmark; + name = "portmidi.c: 759"; + rLen = 0; + rLoc = 24290; + rType = 0; + vrLen = 1709; + vrLoc = 25249; + }; + 3D9CFCCC06765A7F0002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_begin_sysex"; + rLen = 0; + rLoc = 14525; + rType = 0; + vrLen = 1466; + vrLoc = 13829; + }; + 3D9CFCCD06765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3D9CFCAF067657D50002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {491, 287}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {210, 603}}"; + TOCViewSelectedItems = ( + PBXTargetLdLinkerSettingsModule, + ); + }; + }; + 3D9CFCCE06765A7F0002CE69 = { + fRef = 3DA774210663E9DA0002CE69; + isa = PBXTextBookmark; + name = "sysex.c: loopback_test"; + rLen = 0; + rLoc = 3483; + rType = 0; + vrLen = 903; + vrLoc = 0; + }; + 3D9CFCCF06765A7F0002CE69 = { + exec = 3DA712CF0664CB1B0002CE69; + isa = PBXExecutableBookmark; + uiCtxt = { + buildSettingsExpandedSubviews = ( + 0, + 1, + 2, + 3, + 4, + 5, + ); + buildSettingsVisRect = "{{0, 0}, {746, 603}}"; + }; + }; + 3D9CFCD006765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3DA712C00664C9B40002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {517, 119}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}"; + TOCViewSelectedItems = ( + 3DA712BE0664C9B40002CE69, + ); + }; + }; + 3D9CFCD106765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3DA712CD0664CB1B0002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {517, 233}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}"; + TOCViewSelectedItems = ( + PBXTargetSummarySettingsModule, + ); + }; + }; + 3D9CFCD206765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3D9CFCAF067657D50002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {517, 102}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}"; + TOCViewSelectedItems = ( + 3D9CFCAD067657D50002CE69, + ); + }; + }; + 3D9CFCD306765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3DA712CD0664CB1B0002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {517, 119}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}"; + TOCViewSelectedItems = ( + 3DA712CB0664CB1B0002CE69, + ); + }; + }; + 3D9CFCD406765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3D9CFCAF067657D50002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {518, 102}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {204, 603}}"; + TOCViewSelectedItems = ( + 3D9CFCAD067657D50002CE69, + ); + }; + }; + 3D9CFCD506765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3DA712CD0664CB1B0002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {518, 119}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {204, 603}}"; + TOCViewSelectedItems = ( + 3DA712CB0664CB1B0002CE69, + ); + }; + }; + 3D9CFCD606765A7F0002CE69 = { + isa = PBXTargetBookmark; + trg = 3D9CFCAF067657D50002CE69; + uiCtxt = { + TOCViewDetailVisibleRect = "{{0, 0}, {517, 119}}"; + TOCViewExpandedItems = ( + "com.apple.target-editor-pane.settings", + "com.apple.target-editor-pane.settings.simple", + "com.apple.target-editor-pane.info-plist", + "com.apple.target-editor-pane.info-plist.simple", + "com.apple.target-editor-pane.buildphases", + ); + TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}"; + TOCViewSelectedItems = ( + 3D9CFCAD067657D50002CE69, + ); + }; + }; + 3D9CFCD706765A7F0002CE69 = { + fRef = 5851D526047955C800EE98CD; + isa = PBXTextBookmark; + rLen = 0; + rLoc = 396; + rType = 1; + }; + 3D9CFCD806765A7F0002CE69 = { + fRef = 5851D526047955C800EE98CD; + isa = PBXTextBookmark; + name = "portmidi.c: Pm_Write"; + rLen = 0; + rLoc = 11148; + rType = 0; + vrLen = 876; + vrLoc = 11059; + }; + 3D9CFCD906765A7F0002CE69 = { + isa = PBXFileReference; + name = pmmacosxcm.c; + path = "/Users/rbd/portmidi-27may04/pm_mac/pmmacosxcm.c"; + refType = 0; + }; + 3D9CFCDA06765A7F0002CE69 = { + isa = PBXFileReference; + name = pmmacosxcm.c; + path = "/Users/rbd/portmidi-27may04/pm_mac/pmmacosxcm.c"; + refType = 0; + }; + 3D9CFCDB06765A7F0002CE69 = { + isa = PBXFileReference; + name = pmmacosxcm.c; + path = "/Users/rbd/portmidi-27may04/pm_mac/pmmacosxcm.c"; + refType = 0; + }; + 3DA712B30664C7C50002CE69 = { + activeExec = 0; + executables = ( + 3DA712B50664C7C50002CE69, + ); + }; + 3DA712B50664C7C50002CE69 = { + activeArgIndex = 2147483647; + activeArgIndices = ( + ); + argumentStrings = ( + ); + configStateDict = { + }; + debuggerPlugin = GDBDebugging; + enableDebugStr = 1; + environmentEntries = ( + ); + isa = PBXExecutable; + name = midithread; + shlibInfoDictList = ( + ); + sourceDirectories = ( + ); + }; + 3DA712C00664C9B40002CE69 = { + activeExec = 0; + executables = ( + 3DA712C20664C9B40002CE69, + ); + }; + 3DA712C20664C9B40002CE69 = { + activeArgIndex = 2147483647; + activeArgIndices = ( + ); + argumentStrings = ( + ); + configStateDict = { + }; + debuggerPlugin = GDBDebugging; + dylibVariantSuffix = ""; + enableDebugStr = 1; + environmentEntries = ( + ); + isa = PBXExecutable; + name = midithru; + shlibInfoDictList = ( + ); + sourceDirectories = ( + ); + }; + 3DA712CD0664CB1B0002CE69 = { + activeExec = 0; + executables = ( + 3DA712CF0664CB1B0002CE69, + ); + }; + 3DA712CF0664CB1B0002CE69 = { + activeArgIndex = 2147483647; + activeArgIndices = ( + ); + argumentStrings = ( + ); + configStateDict = { + }; + debuggerPlugin = GDBDebugging; + dylibVariantSuffix = ""; + enableDebugStr = 1; + environmentEntries = ( + ); + isa = PBXExecutable; + name = sysex; + shlibInfoDictList = ( + ); + sourceDirectories = ( + ); + }; + 3DA715FB066511460002CE69 = { + isa = PBXSymbolicBreakpoint; + state = 2; + symbolName = ""; + }; + 3DA7160D066548C80002CE69 = { + fileReference = 3DA774210663E9DA0002CE69; + isa = PBXFileBreakpoint; + lineNumber = 128; + state = 2; + }; + 3DA7161606664C500002CE69 = { + fRef = 5851D527047955C800EE98CD; + isa = PBXTextBookmark; + name = "portmidi.h: 174"; + rLen = 7; + rLoc = 8744; + rType = 0; + vrLen = 466; + vrLoc = 8611; + }; + 3DA7161A06664C500002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_write"; + rLen = 0; + rLoc = 18396; + rType = 0; + vrLen = 605; + vrLoc = 11721; + }; + 3DA7161B06664C500002CE69 = { + fRef = 5851D523047955C800EE98CD; + isa = PBXTextBookmark; + name = "pminternal.h: 124"; + rLen = 0; + rLoc = 6172; + rType = 0; + vrLen = 1926; + vrLoc = 4173; + }; + 3DA7161C06664C500002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_write"; + rLen = 0; + rLoc = 18396; + rType = 0; + vrLen = 1782; + vrLoc = 11284; + }; + 3DA7161D06664C500002CE69 = { + fRef = 5851D523047955C800EE98CD; + isa = PBXTextBookmark; + name = "pminternal.h: 124"; + rLen = 0; + rLoc = 6093; + rType = 0; + vrLen = 1762; + vrLoc = 4480; + }; + 3DA7161E06664C500002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_write"; + rLen = 0; + rLoc = 18396; + rType = 0; + vrLen = 583; + vrLoc = 12640; + }; + 3DA7161F06664C500002CE69 = { + fRef = 5851D523047955C800EE98CD; + isa = PBXTextBookmark; + name = "pminternal.h: 124"; + rLen = 0; + rLoc = 6093; + rType = 0; + vrLen = 606; + vrLoc = 6296; + }; + 3DA7162006664C500002CE69 = { + fRef = 5851D527047955C800EE98CD; + isa = PBXTextBookmark; + name = "portmidi.h: 174"; + rLen = 7; + rLoc = 8744; + rType = 0; + vrLen = 466; + vrLoc = 8611; + }; + 3DA7162106664C500002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_write"; + rLen = 0; + rLoc = 14327; + rType = 0; + vrLen = 1769; + vrLoc = 10723; + }; + 3DA7162206664C500002CE69 = { + fRef = 5851D523047955C800EE98CD; + isa = PBXTextBookmark; + name = "pminternal.h: 133"; + rLen = 17; + rLoc = 6353; + rType = 0; + vrLen = 2777; + vrLoc = 1303; + }; + 3DA7162306664C500002CE69 = { + fRef = 5851D5310479560B00EE98CD; + isa = PBXTextBookmark; + name = "pmmacosxcm.c: midi_end_sysex"; + rLen = 0; + rLoc = 17151; + rType = 0; + vrLen = 1470; + vrLoc = 14541; + }; + 3DA7162406664C500002CE69 = { + fRef = 5851D523047955C800EE98CD; + isa = PBXTextBookmark; + name = "pminternal.h: 133"; + rLen = 17; + rLoc = 6353; + rType = 0; + vrLen = 2610; + vrLoc = 1569; + }; + 3DA716320666705B0002CE69 = { + fileReference = 5851D526047955C800EE98CD; + isa = PBXFileBreakpoint; + lineNumber = 839; + state = 1; + }; + 3DA7740C0663E8EB0002CE69 = { + activeExec = 0; + executables = ( + 3DA7740E0663E8EB0002CE69, + ); + }; + 3DA7740E0663E8EB0002CE69 = { + activeArgIndex = 2147483647; + activeArgIndices = ( + ); + argumentStrings = ( + ); + configStateDict = { + }; + debuggerPlugin = GDBDebugging; + dylibVariantSuffix = ""; + enableDebugStr = 1; + environmentEntries = ( + ); + isa = PBXExecutable; + name = test; + shlibInfoDictList = ( + ); + sourceDirectories = ( + ); + }; + 3DDE89D906667BAD0002CE69 = { + fRef = 5851D523047955C800EE98CD; + isa = PBXTextBookmark; + name = "pminternal.h: none_synchronize"; + rLen = 0; + rLoc = 6900; + rType = 0; + vrLen = 2193; + vrLoc = 0; + }; + 5851D5180479552300EE98CD = { + activeBuildStyle = 5851D5160479552300EE98CD; + activeExecutable = 3D9CFCB1067657D50002CE69; + activeTarget = 3D9CFCAF067657D50002CE69; + addToTargets = ( + 3DA7740C0663E8EB0002CE69, + ); + breakpoints = ( + 3DA715FB066511460002CE69, + 3DA7160D066548C80002CE69, + 3DA716320666705B0002CE69, + 3D9CFCA7067651E80002CE69, + 3D9CFCA90676535F0002CE69, + ); + executables = ( + 3DA7740E0663E8EB0002CE69, + 3DA712B50664C7C50002CE69, + 3DA712C20664C9B40002CE69, + 3DA712CF0664CB1B0002CE69, + 3D9CFCB1067657D50002CE69, + ); + perUserDictionary = { + PBXPerProjectTemplateStateSaveDate = 108416809; + "PBXTemplateGeometry-F5314676015831810DCA290F" = { + ContentSize = "{685, 434}"; + LeftSlideOut = { + Collapsed = NO; + Frame = "{{0, 23}, {685, 411}}"; + Split0 = { + ActiveTab = 2; + ActiveTabName = PBXBuildResultsModule; + Collapsed = NO; + Frame = "{{0, 0}, {685, 411}}"; + Split0 = { + Frame = "{{0, 212}, {685, 199}}"; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {952, 321}}"; + }; + Tab1 = { + Debugger = { + Collapsed = NO; + Frame = "{{0, 0}, {781, 452}}"; + Split0 = { + Frame = "{{0, 24}, {781, 428}}"; + Split0 = { + Frame = "{{0, 0}, {383, 428}}"; + }; + Split1 = { + DebugVariablesTableConfiguration = ( + Name, + 123, + Value, + 85, + Summary, + 155.123, + ); + Frame = "{{392, 0}, {389, 428}}"; + }; + SplitCount = 2; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {100, 50}}"; + }; + Tab1 = { + Frame = "{{0, 0}, {100, 50}}"; + }; + TabCount = 2; + TabsVisible = YES; + }; + Frame = "{{0, 0}, {781, 452}}"; + LauncherConfigVersion = 7; + }; + Tab2 = { + Frame = "{{0, 0}, {685, 215}}"; + LauncherConfigVersion = 3; + Runner = { + Frame = "{{0, 0}, {685, 215}}"; + }; + }; + Tab3 = { + BuildMessageFrame = "{{0, 0}, {687, 117}}"; + BuildTranscriptFrame = "{{0, 126}, {687, 58}}"; + BuildTranscriptFrameExpanded = YES; + Frame = "{{0, 0}, {685, 206}}"; + }; + Tab4 = { + Frame = "{{0, 0}, {612, 295}}"; + }; + TabCount = 5; + TabsVisible = NO; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {300, 533}}"; + GroupTreeTableConfiguration = ( + TargetStatusColumn, + 18, + MainColumn, + 267, + ); + }; + Tab1 = { + ClassesFrame = "{{0, 0}, {280, 398}}"; + ClassesTreeTableConfiguration = ( + PBXBookColumnIdentifier, + 20, + PBXClassColumnIdentifier, + 237, + ); + Frame = "{{0, 0}, {278, 659}}"; + MembersFrame = "{{0, 407}, {280, 252}}"; + MembersTreeTableConfiguration = ( + PBXBookColumnIdentifier, + 20, + PBXMethodColumnIdentifier, + 236, + ); + }; + Tab2 = { + Frame = "{{0, 0}, {200, 100}}"; + }; + Tab3 = { + Frame = "{{0, 0}, {200, 100}}"; + TargetTableConfiguration = ( + ActiveObject, + 16, + ObjectNames, + 202.296, + ); + }; + Tab4 = { + BreakpointsTreeTableConfiguration = ( + breakpointColumn, + 197, + enabledColumn, + 31, + ); + Frame = "{{0, 0}, {250, 100}}"; + }; + TabCount = 5; + TabsVisible = NO; + }; + NavBarShownByDefault = YES; + StatusViewVisible = YES; + Template = F5314676015831810DCA290F; + ToolbarVisible = YES; + WindowLocation = "{325, 250}"; + }; + "PBXTemplateGeometry-F5CA7EC9015C08ED0DCA290F" = { + ContentSize = "{665, 594}"; + LeftSlideOut = { + Collapsed = NO; + Frame = "{{0, 0}, {665, 594}}"; + Split0 = { + ActiveTab = 3; + ActiveTabName = PBXProjectFindModule; + Collapsed = NO; + Frame = "{{0, 0}, {665, 594}}"; + Split0 = { + Frame = "{{0, 301}, {665, 293}}"; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {484, 208}}"; + }; + Tab1 = { + Debugger = { + Collapsed = NO; + Frame = "{{0, 0}, {664, 208}}"; + Split0 = { + Frame = "{{0, 24}, {664, 184}}"; + Split0 = { + Frame = "{{0, 0}, {325, 184}}"; + }; + Split1 = { + DebugVariablesTableConfiguration = ( + Name, + 123, + Value, + 85, + Summary, + 96.123, + ); + Frame = "{{334, 0}, {330, 184}}"; + }; + SplitCount = 2; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {100, 50}}"; + }; + Tab1 = { + Frame = "{{0, 0}, {100, 50}}"; + }; + TabCount = 2; + TabsVisible = YES; + }; + Frame = "{{0, 0}, {664, 208}}"; + LauncherConfigVersion = 7; + }; + Tab2 = { + Frame = "{{0, 0}, {664, 50}}"; + LauncherConfigVersion = 3; + Runner = { + Frame = "{{0, 0}, {664, 50}}"; + }; + }; + Tab3 = { + BuildMessageFrame = "{{0, 0}, {667, 206}}"; + BuildTranscriptFrame = "{{0, 215}, {667, 85}}"; + BuildTranscriptFrameExpanded = YES; + Frame = "{{0, 0}, {665, 298}}"; + }; + Tab4 = { + Frame = "{{0, 0}, {665, 295}}"; + }; + TabCount = 5; + TabsVisible = NO; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {313, 531}}"; + GroupTreeTableConfiguration = ( + TargetStatusColumn, + 18, + MainColumn, + 280, + ); + }; + Tab1 = { + ClassesFrame = "{{0, 0}, {280, 398}}"; + ClassesTreeTableConfiguration = ( + PBXBookColumnIdentifier, + 20, + PBXClassColumnIdentifier, + 237, + ); + Frame = "{{0, 0}, {278, 659}}"; + MembersFrame = "{{0, 407}, {280, 252}}"; + MembersTreeTableConfiguration = ( + PBXBookColumnIdentifier, + 20, + PBXMethodColumnIdentifier, + 236, + ); + }; + Tab2 = { + Frame = "{{0, 0}, {200, 100}}"; + }; + Tab3 = { + Frame = "{{0, 0}, {200, 557}}"; + TargetTableConfiguration = ( + ActiveObject, + 16, + ObjectNames, + 202.296, + ); + }; + Tab4 = { + BreakpointsTreeTableConfiguration = ( + breakpointColumn, + 197, + enabledColumn, + 31, + ); + Frame = "{{0, 0}, {250, 100}}"; + }; + TabCount = 5; + TabsVisible = NO; + }; + NavBarShownByDefault = YES; + StatusViewVisible = NO; + Template = F5CA7EC9015C08ED0DCA290F; + ToolbarVisible = NO; + WindowLocation = "{335, 130}"; + }; + PBXWorkspaceContents = ( + { + LeftSlideOut = { + Split0 = { + Split0 = { + NavContent0 = { + bookmark = 3D9CFCD606765A7F0002CE69; + history = ( + 3DA7161606664C500002CE69, + 3DDE89D906667BAD0002CE69, + 3D7903E9066690E00002CE69, + 3D9CFCBE06765A7F0002CE69, + 3D9CFCBF06765A7F0002CE69, + 3D9CFCC006765A7F0002CE69, + 3D9CFCC106765A7F0002CE69, + 3D9CFCC206765A7F0002CE69, + 3D9CFCC306765A7F0002CE69, + 3D9CFCC406765A7F0002CE69, + 3D9CFCC506765A7F0002CE69, + ); + prevStack = ( + 3DA7161A06664C500002CE69, + 3DA7161B06664C500002CE69, + 3DA7161C06664C500002CE69, + 3DA7161D06664C500002CE69, + 3DA7161E06664C500002CE69, + 3DA7161F06664C500002CE69, + 3DA7162006664C500002CE69, + 3DA7162106664C500002CE69, + 3DA7162206664C500002CE69, + 3DA7162306664C500002CE69, + 3DA7162406664C500002CE69, + 3D7903EC066690E00002CE69, + 3D7903ED066690E00002CE69, + 3D7903EE066690E00002CE69, + 3D7903EF066690E00002CE69, + 3D7903F0066690E00002CE69, + 3D7903F1066690E00002CE69, + 3D7903F2066690E00002CE69, + 3D7903F3066690E00002CE69, + 3D7903F4066690E00002CE69, + 3D9CFCC606765A7F0002CE69, + 3D9CFCC706765A7F0002CE69, + 3D9CFCC806765A7F0002CE69, + 3D9CFCC906765A7F0002CE69, + 3D9CFCCA06765A7F0002CE69, + 3D9CFCCB06765A7F0002CE69, + 3D9CFCCC06765A7F0002CE69, + 3D9CFCCD06765A7F0002CE69, + 3D9CFCCE06765A7F0002CE69, + 3D9CFCCF06765A7F0002CE69, + 3D9CFCD006765A7F0002CE69, + 3D9CFCD106765A7F0002CE69, + 3D9CFCD206765A7F0002CE69, + 3D9CFCD306765A7F0002CE69, + 3D9CFCD406765A7F0002CE69, + 3D9CFCD506765A7F0002CE69, + ); + }; + NavCount = 1; + NavGeometry0 = { + Frame = "{{0, 0}, {761, 657}}"; + NavBarVisible = YES; + }; + NavSplitVertical = NO; + }; + SplitCount = 1; + Tab1 = { + Debugger = { + Split0 = { + SplitCount = 2; + }; + SplitCount = 1; + TabCount = 2; + }; + LauncherConfigVersion = 7; + }; + Tab2 = { + LauncherConfigVersion = 3; + Runner = { + }; + }; + TabCount = 5; + }; + SplitCount = 1; + Tab1 = { + OptionsSetName = "Hierarchy, all classes"; + }; + TabCount = 5; + }; + }, + { + LeftSlideOut = { + Split0 = { + Split0 = { + NavContent0 = { + bookmark = 3D9CFCD806765A7F0002CE69; + history = ( + 3D9CFCD706765A7F0002CE69, + ); + }; + NavCount = 1; + NavGeometry0 = { + Frame = "{{0, 0}, {620, 353}}"; + NavBarVisible = YES; + }; + NavSplitVertical = NO; + }; + SplitCount = 1; + Tab1 = { + Debugger = { + Split0 = { + SplitCount = 2; + }; + SplitCount = 1; + TabCount = 2; + }; + LauncherConfigVersion = 7; + }; + Tab2 = { + LauncherConfigVersion = 3; + Runner = { + }; + }; + TabCount = 5; + }; + SplitCount = 1; + Tab1 = { + OptionsSetName = "Hierarchy, all classes"; + }; + TabCount = 5; + }; + }, + ); + PBXWorkspaceGeometries = ( + { + ContentSize = "{1024, 680}"; + LeftSlideOut = { + ActiveTab = 3; + ActiveTabName = PBXTargetTreeModule; + Collapsed = NO; + Frame = "{{0, 23}, {1024, 657}}"; + Split0 = { + Collapsed = NO; + Frame = "{{263, 0}, {761, 657}}"; + Split0 = { + Frame = "{{0, 0}, {761, 657}}"; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {681, 289}}"; + }; + Tab1 = { + Debugger = { + Collapsed = NO; + Frame = "{{0, 0}, {681, 150}}"; + Split0 = { + Frame = "{{0, 24}, {681, 126}}"; + Split0 = { + Frame = "{{0, 0}, {333, 126}}"; + }; + Split1 = { + DebugVariablesTableConfiguration = ( + Name, + 123, + Value, + 85, + Summary, + 105.123, + ); + Frame = "{{342, 0}, {339, 126}}"; + }; + SplitCount = 2; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {100, 50}}"; + }; + Tab1 = { + Frame = "{{0, 0}, {100, 50}}"; + }; + TabCount = 2; + TabsVisible = YES; + }; + Frame = "{{0, 0}, {681, 120}}"; + LauncherConfigVersion = 7; + }; + Tab2 = { + Frame = "{{0, 0}, {681, 234}}"; + LauncherConfigVersion = 3; + Runner = { + Frame = "{{0, 0}, {681, 234}}"; + }; + }; + Tab3 = { + BuildMessageFrame = "{{0, 0}, {683, 163}}"; + BuildTranscriptFrame = "{{0, 172}, {683, 68}}"; + BuildTranscriptFrameExpanded = YES; + Frame = "{{0, 0}, {681, 238}}"; + }; + Tab4 = { + Frame = "{{0, 0}, {612, 295}}"; + }; + TabCount = 5; + TabsVisible = NO; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {260, 657}}"; + GroupTreeTableConfiguration = ( + TargetStatusColumn, + 18, + MainColumn, + 227, + ); + }; + Tab1 = { + ClassesFrame = "{{0, 0}, {250, 395}}"; + ClassesTreeTableConfiguration = ( + PBXBookColumnIdentifier, + 20, + PBXClassColumnIdentifier, + 207, + ); + Frame = "{{0, 0}, {248, 657}}"; + MembersFrame = "{{0, 404}, {250, 253}}"; + MembersTreeTableConfiguration = ( + PBXBookColumnIdentifier, + 20, + PBXMethodColumnIdentifier, + 206, + ); + }; + Tab2 = { + Frame = "{{0, 0}, {217, 657}}"; + }; + Tab3 = { + Frame = "{{0, 0}, {239, 657}}"; + TargetTableConfiguration = ( + ActiveObject, + 16, + ObjectNames, + 206.296, + ); + }; + Tab4 = { + BreakpointsTreeTableConfiguration = ( + breakpointColumn, + 197, + enabledColumn, + 31, + ); + Frame = "{{0, 0}, {250, 657}}"; + }; + TabCount = 5; + TabsVisible = YES; + }; + NavBarShownByDefault = YES; + StatusViewVisible = YES; + Template = 64ABBB4501FA494900185B06; + ToolbarVisible = YES; + WindowLocation = "{0, 4}"; + }, + { + ContentSize = "{644, 662}"; + LeftSlideOut = { + Collapsed = NO; + Frame = "{{0, 23}, {644, 639}}"; + Split0 = { + ActiveTab = 0; + ActiveTabName = PBXDebugSessionModule; + Collapsed = NO; + Frame = "{{24, 0}, {620, 639}}"; + Split0 = { + Frame = "{{0, 286}, {620, 353}}"; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {749, 267}}"; + }; + Tab1 = { + Debugger = { + ActiveTab = 0; + ActiveTabName = PBXDebugCLIModule; + Collapsed = NO; + Frame = "{{0, 0}, {620, 280}}"; + Split0 = { + Frame = "{{0, 115}, {620, 165}}"; + Split0 = { + Frame = "{{0, 0}, {297, 165}}"; + }; + Split1 = { + DebugVariablesTableConfiguration = ( + Name, + 123, + Value, + 85, + Summary, + 80.123, + ); + Frame = "{{306, 0}, {314, 165}}"; + }; + SplitCount = 2; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {620, 91}}"; + }; + Tab1 = { + Frame = "{{0, 0}, {100, 50}}"; + }; + TabCount = 2; + TabsVisible = YES; + }; + Frame = "{{0, 0}, {620, 280}}"; + LauncherConfigVersion = 7; + }; + Tab2 = { + Frame = "{{0, 0}, {664, 50}}"; + LauncherConfigVersion = 3; + Runner = { + Frame = "{{0, 0}, {664, 50}}"; + }; + }; + Tab3 = { + BuildMessageFrame = "{{0, 0}, {614, 203}}"; + BuildTranscriptFrame = "{{0, 212}, {614, 85}}"; + BuildTranscriptFrameExpanded = YES; + Frame = "{{0, 0}, {612, 295}}"; + }; + Tab4 = { + Frame = "{{0, 0}, {612, 295}}"; + }; + TabCount = 5; + TabsVisible = NO; + }; + SplitCount = 1; + Tab0 = { + Frame = "{{0, 0}, {313, 531}}"; + GroupTreeTableConfiguration = ( + TargetStatusColumn, + 18, + MainColumn, + 280, + ); + }; + Tab1 = { + ClassesFrame = "{{0, 0}, {280, 398}}"; + ClassesTreeTableConfiguration = ( + PBXBookColumnIdentifier, + 20, + PBXClassColumnIdentifier, + 237, + ); + Frame = "{{0, 0}, {278, 659}}"; + MembersFrame = "{{0, 407}, {280, 252}}"; + MembersTreeTableConfiguration = ( + PBXBookColumnIdentifier, + 20, + PBXMethodColumnIdentifier, + 236, + ); + }; + Tab2 = { + Frame = "{{0, 0}, {200, 100}}"; + }; + Tab3 = { + Frame = "{{0, 0}, {200, 557}}"; + TargetTableConfiguration = ( + ActiveObject, + 16, + ObjectNames, + 202.296, + ); + }; + Tab4 = { + BreakpointsTreeTableConfiguration = ( + breakpointColumn, + 197, + enabledColumn, + 31, + ); + Frame = "{{0, 0}, {250, 100}}"; + }; + TabCount = 5; + TabsVisible = YES; + }; + NavBarShownByDefault = YES; + StatusViewVisible = YES; + Template = F5CA7ECC015C09990DCA290F; + ToolbarVisible = YES; + WindowLocation = "{48, 17}"; + }, + ); + PBXWorkspaceStateSaveDate = 108416809; + }; + perUserProjectItems = { + 3D7903E9066690E00002CE69 = 3D7903E9066690E00002CE69; + 3D7903EC066690E00002CE69 = 3D7903EC066690E00002CE69; + 3D7903ED066690E00002CE69 = 3D7903ED066690E00002CE69; + 3D7903EE066690E00002CE69 = 3D7903EE066690E00002CE69; + 3D7903EF066690E00002CE69 = 3D7903EF066690E00002CE69; + 3D7903F0066690E00002CE69 = 3D7903F0066690E00002CE69; + 3D7903F1066690E00002CE69 = 3D7903F1066690E00002CE69; + 3D7903F2066690E00002CE69 = 3D7903F2066690E00002CE69; + 3D7903F3066690E00002CE69 = 3D7903F3066690E00002CE69; + 3D7903F4066690E00002CE69 = 3D7903F4066690E00002CE69; + 3D9CFCBE06765A7F0002CE69 = 3D9CFCBE06765A7F0002CE69; + 3D9CFCBF06765A7F0002CE69 = 3D9CFCBF06765A7F0002CE69; + 3D9CFCC006765A7F0002CE69 = 3D9CFCC006765A7F0002CE69; + 3D9CFCC106765A7F0002CE69 = 3D9CFCC106765A7F0002CE69; + 3D9CFCC206765A7F0002CE69 = 3D9CFCC206765A7F0002CE69; + 3D9CFCC306765A7F0002CE69 = 3D9CFCC306765A7F0002CE69; + 3D9CFCC406765A7F0002CE69 = 3D9CFCC406765A7F0002CE69; + 3D9CFCC506765A7F0002CE69 = 3D9CFCC506765A7F0002CE69; + 3D9CFCC606765A7F0002CE69 = 3D9CFCC606765A7F0002CE69; + 3D9CFCC706765A7F0002CE69 = 3D9CFCC706765A7F0002CE69; + 3D9CFCC806765A7F0002CE69 = 3D9CFCC806765A7F0002CE69; + 3D9CFCC906765A7F0002CE69 = 3D9CFCC906765A7F0002CE69; + 3D9CFCCA06765A7F0002CE69 = 3D9CFCCA06765A7F0002CE69; + 3D9CFCCB06765A7F0002CE69 = 3D9CFCCB06765A7F0002CE69; + 3D9CFCCC06765A7F0002CE69 = 3D9CFCCC06765A7F0002CE69; + 3D9CFCCD06765A7F0002CE69 = 3D9CFCCD06765A7F0002CE69; + 3D9CFCCE06765A7F0002CE69 = 3D9CFCCE06765A7F0002CE69; + 3D9CFCCF06765A7F0002CE69 = 3D9CFCCF06765A7F0002CE69; + 3D9CFCD006765A7F0002CE69 = 3D9CFCD006765A7F0002CE69; + 3D9CFCD106765A7F0002CE69 = 3D9CFCD106765A7F0002CE69; + 3D9CFCD206765A7F0002CE69 = 3D9CFCD206765A7F0002CE69; + 3D9CFCD306765A7F0002CE69 = 3D9CFCD306765A7F0002CE69; + 3D9CFCD406765A7F0002CE69 = 3D9CFCD406765A7F0002CE69; + 3D9CFCD506765A7F0002CE69 = 3D9CFCD506765A7F0002CE69; + 3D9CFCD606765A7F0002CE69 = 3D9CFCD606765A7F0002CE69; + 3D9CFCD706765A7F0002CE69 = 3D9CFCD706765A7F0002CE69; + 3D9CFCD806765A7F0002CE69 = 3D9CFCD806765A7F0002CE69; + 3DA7161606664C500002CE69 = 3DA7161606664C500002CE69; + 3DA7161A06664C500002CE69 = 3DA7161A06664C500002CE69; + 3DA7161B06664C500002CE69 = 3DA7161B06664C500002CE69; + 3DA7161C06664C500002CE69 = 3DA7161C06664C500002CE69; + 3DA7161D06664C500002CE69 = 3DA7161D06664C500002CE69; + 3DA7161E06664C500002CE69 = 3DA7161E06664C500002CE69; + 3DA7161F06664C500002CE69 = 3DA7161F06664C500002CE69; + 3DA7162006664C500002CE69 = 3DA7162006664C500002CE69; + 3DA7162106664C500002CE69 = 3DA7162106664C500002CE69; + 3DA7162206664C500002CE69 = 3DA7162206664C500002CE69; + 3DA7162306664C500002CE69 = 3DA7162306664C500002CE69; + 3DA7162406664C500002CE69 = 3DA7162406664C500002CE69; + 3DDE89D906667BAD0002CE69 = 3DDE89D906667BAD0002CE69; + }; + projectwideBuildSettings = { + }; + wantsIndex = 1; + wantsSCM = -1; + }; + 5851D5370479562300EE98CD = { + activeExec = 0; + }; +} diff --git a/pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj b/pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj new file mode 100644 index 00000000..8e25d1f3 --- /dev/null +++ b/pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj @@ -0,0 +1,337 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 38; + objects = { + 014CEA490018CE3211CA2923 = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = NO; + OPTIMIZATION_CFLAGS = "-O0"; + }; + isa = PBXBuildStyle; + name = Development; + }; + 014CEA4A0018CE3211CA2923 = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = YES; + }; + isa = PBXBuildStyle; + name = Deployment; + }; +//010 +//011 +//012 +//013 +//014 +//030 +//031 +//032 +//033 +//034 + 034768E2FF38A6DC11DB9C8B = { + isa = PBXExecutableFileReference; + path = pm_test; + refType = 3; + }; +//030 +//031 +//032 +//033 +//034 +//080 +//081 +//082 +//083 +//084 + 08FB7793FE84155DC02AAC07 = { + buildStyles = ( + 014CEA490018CE3211CA2923, + 014CEA4A0018CE3211CA2923, + ); + hasScannedForEncodings = 1; + isa = PBXProject; + mainGroup = 08FB7794FE84155DC02AAC07; + projectDirPath = ""; + targets = ( + 08FB779FFE84155DC02AAC07, + ); + }; + 08FB7794FE84155DC02AAC07 = { + children = ( + 08FB7795FE84155DC02AAC07, + 08FB779DFE84155DC02AAC07, + 19C28FBDFE9D53C911CA2CBB, + ); + isa = PBXGroup; + name = pm_test; + refType = 4; + }; + 08FB7795FE84155DC02AAC07 = { + children = ( + 5851D5650479591100EE98CD, + 58ED5B7E047D3A1200B92E62, + 5851D56F04795A1A00EE98CD, + 5851D5690479593D00EE98CD, + 5851D57104795A3C00EE98CD, + 5851D56B0479594B00EE98CD, + ); + isa = PBXGroup; + name = Source; + refType = 4; + }; + 08FB779DFE84155DC02AAC07 = { + children = ( + 09AB6884FE841BABC02AAC07, + 58058934047C96F0009FBA67, + 5851D5670479592300EE98CD, + 5851D56D0479596A00EE98CD, + ); + isa = PBXGroup; + name = "External Frameworks and Libraries"; + refType = 4; + }; + 08FB779FFE84155DC02AAC07 = { + buildPhases = ( + 08FB77A0FE84155DC02AAC07, + 08FB77A1FE84155DC02AAC07, + 08FB77A3FE84155DC02AAC07, + 08FB77A5FE84155DC02AAC07, + C6859E980290922304C91782, + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = /Users/zk/Downloads/portmidi.1/pm_mac/build; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = pm_test; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + }; + dependencies = ( + ); + isa = PBXToolTarget; + name = pm_test; + productInstallPath = "$(HOME)/bin"; + productName = pm_test; + productReference = 034768E2FF38A6DC11DB9C8B; + }; + 08FB77A0FE84155DC02AAC07 = { + buildActionMask = 2147483647; + files = ( + 5851D56A0479593D00EE98CD, + 5851D56C0479594B00EE98CD, + 5851D57204795A3C00EE98CD, + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 08FB77A1FE84155DC02AAC07 = { + buildActionMask = 2147483647; + files = ( + 58ED5C7B047D697A00B92E62, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 08FB77A3FE84155DC02AAC07 = { + buildActionMask = 2147483647; + files = ( + 09AB6885FE841BABC02AAC07, + 5851D5680479592300EE98CD, + 5851D56E0479596A00EE98CD, + 58058935047C96F0009FBA67, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 08FB77A5FE84155DC02AAC07 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; +//080 +//081 +//082 +//083 +//084 +//090 +//091 +//092 +//093 +//094 + 09AB6884FE841BABC02AAC07 = { + isa = PBXFrameworkReference; + name = CoreFoundation.framework; + path = /System/Library/Frameworks/CoreFoundation.framework; + refType = 0; + }; + 09AB6885FE841BABC02AAC07 = { + fileRef = 09AB6884FE841BABC02AAC07; + isa = PBXBuildFile; + settings = { + }; + }; +//090 +//091 +//092 +//093 +//094 +//190 +//191 +//192 +//193 +//194 + 19C28FBDFE9D53C911CA2CBB = { + children = ( + 034768E2FF38A6DC11DB9C8B, + ); + isa = PBXGroup; + name = Products; + refType = 4; + }; +//190 +//191 +//192 +//193 +//194 +//580 +//581 +//582 +//583 +//584 + 58058934047C96F0009FBA67 = { + isa = PBXFrameworkReference; + name = CoreAudio.framework; + path = /System/Library/Frameworks/CoreAudio.framework; + refType = 0; + }; + 58058935047C96F0009FBA67 = { + fileRef = 58058934047C96F0009FBA67; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D5650479591100EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = test.c; + path = ../pm_test/test.c; + refType = 2; + }; + 5851D5670479592300EE98CD = { + isa = PBXFrameworkReference; + name = CoreMIDI.framework; + path = /System/Library/Frameworks/CoreMIDI.framework; + refType = 0; + }; + 5851D5680479592300EE98CD = { + fileRef = 5851D5670479592300EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D5690479593D00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = portmidi.h; + path = ../pm_common/portmidi.h; + refType = 2; + }; + 5851D56A0479593D00EE98CD = { + fileRef = 5851D5690479593D00EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D56B0479594B00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = porttime.h; + path = ../porttime/porttime.h; + refType = 2; + }; + 5851D56C0479594B00EE98CD = { + fileRef = 5851D56B0479594B00EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D56D0479596A00EE98CD = { + isa = PBXFileReference; + name = libportmidi.a; + path = build/libportmidi.a; + refType = 4; + }; + 5851D56E0479596A00EE98CD = { + fileRef = 5851D56D0479596A00EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 5851D56F04795A1A00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = midithread.c; + path = ../pm_test/midithread.c; + refType = 2; + }; + 5851D57104795A3C00EE98CD = { + fileEncoding = 30; + isa = PBXFileReference; + name = pmutil.h; + path = ../pm_common/pmutil.h; + refType = 2; + }; + 5851D57204795A3C00EE98CD = { + fileRef = 5851D57104795A3C00EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; + 58ED5B7E047D3A1200B92E62 = { + fileEncoding = 30; + isa = PBXFileReference; + name = latency.c; + path = ../pm_test/latency.c; + refType = 2; + }; + 58ED5C7B047D697A00B92E62 = { + fileRef = 5851D5650479591100EE98CD; + isa = PBXBuildFile; + settings = { + }; + }; +//580 +//581 +//582 +//583 +//584 +//C60 +//C61 +//C62 +//C63 +//C64 + C6859E980290922304C91782 = { + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + isa = PBXCopyFilesBuildPhase; + runOnlyForDeploymentPostprocessing = 1; + }; + }; + rootObject = 08FB7793FE84155DC02AAC07; +} diff --git a/pd/portmidi/pm_mac/pmmac.c b/pd/portmidi/pm_mac/pmmac.c new file mode 100644 index 00000000..fbf31c83 --- /dev/null +++ b/pd/portmidi/pm_mac/pmmac.c @@ -0,0 +1,42 @@ +/* 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); } + + diff --git a/pd/portmidi/pm_mac/pmmac.h b/pd/portmidi/pm_mac/pmmac.h new file mode 100644 index 00000000..2d714254 --- /dev/null +++ b/pd/portmidi/pm_mac/pmmac.h @@ -0,0 +1,4 @@ +/* pmmac.h */ + +extern PmDeviceID pm_default_input_device_id; +extern PmDeviceID pm_default_output_device_id; \ No newline at end of file diff --git a/pd/portmidi/pm_mac/pmmacosxcm.c b/pd/portmidi/pm_mac/pmmacosxcm.c new file mode 100644 index 00000000..55b9b084 --- /dev/null +++ b/pd/portmidi/pm_mac/pmmacosxcm.c @@ -0,0 +1,701 @@ +/* + * Platform interface to the MacOS X CoreMIDI framework + * + * Jon Parise + * and subsequent work by Andrew Zeldis and Zico Kolter + * and Roger B. Dannenberg + * + * $Id: pmmacosxcm.c,v 1.1 2005-12-15 00:56:57 eighthave Exp $ + */ + +/* 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. As in pmwinmm.c, 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 + +#include "portmidi.h" +#include "pminternal.h" +#include "porttime.h" +#include "pmmac.h" +#include "pmmacosxcm.h" + +#include +#include + +#include +#include +#include + +#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 */ +} 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, 1, 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 != 0xF0) ? 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; +} + + +/* 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; + + /* 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 */ + if (status == MIDI_SYSEX || status == MIDI_EOX || + !(status & MIDI_STATUS_MASK)) { + int i = 0; + while (i < packet->length) { + pm_read_byte(midi, packet->data[i], event.timestamp); + i++; + } + } else { + /* Build the PmMessage for the PmEvent structure */ + switch (packet->length) { + case 1: + event.message = Pm_Message(packet->data[0], 0, 0); + break; + case 2: + event.message = Pm_Message(packet->data[0], + packet->data[1], 0); + break; + case 3: + event.message = Pm_Message(packet->data[0], + packet->data[1], + packet->data[2]); + break; + default: + /* Skip packets that are too large to fit in a PmMessage */ +#ifdef DEBUG + printf("PortMidi debug msg: large packet skipped\n"); +#endif + continue; + } + pm_read_short(midi, &event); + } + packet = MIDIPacketNext(packet); + } +} + +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; + + 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; + + 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) +{ + 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)) != 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); +} + + +char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint) +{ + MIDIEntityRef entity; + MIDIDeviceRef device; + CFStringRef endpointName = NULL, deviceName = NULL, fullName = NULL; + CFStringEncoding defaultEncoding; + char* newName; + + /* get the default string encoding */ + defaultEncoding = CFStringGetSystemEncoding(); + + /* 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; + } + + /* copy the string into our buffer */ + newName = (char*)malloc(CFStringGetLength(fullName) + 1); + CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1, + defaultEncoding); + + /* clean up */ + if (endpointName) CFRelease(endpointName); + if (deviceName) CFRelease(deviceName); + 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); +} diff --git a/pd/portmidi/pm_mac/pmmacosxcm.h b/pd/portmidi/pm_mac/pmmacosxcm.h new file mode 100644 index 00000000..17259359 --- /dev/null +++ b/pd/portmidi/pm_mac/pmmacosxcm.h @@ -0,0 +1,4 @@ +/* system-specific definitions */ + +PmError pm_macosxcm_init(void); +void pm_macosxcm_term(void); \ No newline at end of file diff --git a/pd/portmidi/pm_test/latency.c b/pd/portmidi/pm_test/latency.c new file mode 100644 index 00000000..87b1965b --- /dev/null +++ b/pd/portmidi/pm_test/latency.c @@ -0,0 +1,278 @@ +/* latency.c -- measure latency of OS */ + +#include "porttime.h" +#include "portmidi.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +/* Latency is defined here to mean the time starting when a + process becomes ready to run, and ending when the process + actually runs. Latency is due to contention for the + processor, usually due to other processes, OS activity + including device drivers handling interrupts, and + waiting for the scheduler to suspend the currently running + process and activate the one that is waiting. + + Latency can affect PortMidi applications: if a process fails + to wake up promptly, MIDI input may sit in the input buffer + waiting to be handled, and MIDI output may not be generated + with accurate timing. Using the latency parameter when + opening a MIDI output port allows the caller to defer timing + to PortMidi, which in most implementations will pass the + data on to the OS. By passing timestamps and data to the + OS kernel, device driver, or even hardware, there are fewer + sources of latency that can affect the ultimate timing of + the data. On the other hand, the application must generate + and deliver the data ahead of the timestamp. The amount by + which data is computed early must be at least as large as + the worst-case latency to avoid timing problems. + + Latency is even more important in audio applications. If an + application lets an audio output buffer underflow, an audible + pop or click is produced. Audio input buffers can overflow, + causing data to be lost. In general the audio buffers must + be large enough to buffer the worst-case latency that the + application will encounter. + + This program measures latency by recording the difference + between the scheduled callback time and the current real time. + We do not really know the scheduled callback time, so we will + record the differences between the real time of each callback + and the real time of the previous callback. Differences that + are larger than the scheduled difference are recorded. Smaller + differences indicate the system is recovering from an earlier + latency, so these are ignored. + Since printing by the callback process can cause all sorts of + delays, this program records latency observations in a + histogram. When the program is stopped, the histogram is + printed to the console. + + Optionally the system can be tested under a load of MIDI input, + MIDI output, or both. If MIDI input is selected, the callback + thread will read any waiting MIDI events each iteration. You + must generate events on this interface for the test to actually + put any appreciable load on PortMidi. If MIDI output is + selected, alternating note on and note off events are sent each + X iterations, where you specify X. For example, with a timer + callback period of 2ms and X=1, a MIDI event is sent every 2ms. + + + INTERPRETING RESULTS: Time is quantized to 1ms, so there is + some uncertainty due to rounding. A microsecond latency that + spans the time when the clock is incremented will be reported + as a latency of 1. On the other hand, a latency of almost + 1ms that falls between two clock ticks will be reported as + zero. In general, if the highest nonzero bin is numbered N, + then the maximum latency is N+1. + +CHANGE LOG + +18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive + MIDI during test, and made period user-settable. + */ + +#define HIST_LEN 21 /* how many 1ms bins in the histogram */ + +#define STRING_MAX 80 /* used for console input */ + +#define INPUT_BUFFER_SIZE 100 +#define OUTPUT_BUFFER_SIZE 0 + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef min +#define min(a, b) ((a) <= (b) ? (a) : (b)) +#endif + +int get_number(char *prompt); + +PtTimestamp previous_callback_time = 0; + +int period; /* milliseconds per callback */ + +long histogram[HIST_LEN]; +long max_latency = 0; /* worst latency observed */ +long out_of_range = 0; /* how many points outside of HIST_LEN? */ + +int test_in, test_out; /* test MIDI in and/or out? */ +int output_period; /* output MIDI every __ iterations if test_out true */ +int iteration = 0; +PmStream *in, *out; +int note_on = 0; /* is the note currently on? */ + +/* callback function for PortTime -- computes histogram */ +void pt_callback(PtTimestamp timestamp, void *userData) +{ + PtTimestamp difference = timestamp - previous_callback_time - period; + previous_callback_time = timestamp; + + /* allow 5 seconds for the system to settle down */ + if (timestamp < 5000) return; + + iteration++; + /* send a note on/off if user requested it */ + if (test_out && (iteration % output_period == 0)) { + PmEvent buffer[1]; + buffer[0].timestamp = Pt_Time(NULL); + if (note_on) { + /* note off */ + buffer[0].message = Pm_Message(0x90, 60, 0); + note_on = 0; + } else { + /* note on */ + buffer[0].message = Pm_Message(0x90, 60, 100); + note_on = 1; + } + Pm_Write(out, buffer, 1); + iteration = 0; + } + + /* read all waiting events (if user requested) */ + if (test_in) { + PmError status; + PmEvent buffer[1]; + do { + status = Pm_Poll(in); + if (status == TRUE) { + Pm_Read(in,buffer,1); + } + } while (status == TRUE); + } + + if (difference < 0) return; /* ignore when system is "catching up" */ + + /* update the histogram */ + if (difference < HIST_LEN) { + histogram[difference]++; + } else { + out_of_range++; + } + + if (max_latency < difference) max_latency = difference; +} + + +int main() +{ + char line[STRING_MAX]; + int i; + int len; + int choice; + PtTimestamp stop; + printf("Latency histogram.\n"); + period = get_number("Choose timer period (in ms): "); + assert(period >= 1); + printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n", + "1. No MIDI traffic", + "2. MIDI input", + "3. MIDI output", + "4. MIDI input and output"); + choice = get_number("? "); + switch (choice) { + case 1: test_in = 0; test_out = 0; break; + case 2: test_in = 1; test_out = 0; break; + case 3: test_in = 0; test_out = 1; break; + case 4: test_in = 1; test_out = 1; break; + default: assert(0); + } + if (test_in || test_out) { + /* list device information */ + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if ((test_in && info->input) || + (test_out && info->output)) { + printf("%d: %s, %s", i, info->interf, info->name); + if (info->input) printf(" (input)"); + if (info->output) printf(" (output)"); + printf("\n"); + } + } + /* open stream(s) */ + if (test_in) { + int i = get_number("MIDI input device number: "); + Pm_OpenInput(&in, + i, + NULL, + INPUT_BUFFER_SIZE, + (long (*)(void *)) Pt_Time, + NULL); + /* turn on filtering; otherwise, input might overflow in the + 5-second period before timer callback starts reading midi */ + Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK); + } + if (test_out) { + int i = get_number("MIDI output device number: "); + PmEvent buffer[1]; + Pm_OpenOutput(&out, + i, + NULL, + OUTPUT_BUFFER_SIZE, + (long (*)(void *)) Pt_Time, + NULL, + 0); /* no latency scheduling */ + + /* send a program change to force a status byte -- this fixes + a problem with a buggy linux MidiSport driver, and shouldn't + hurt anything else + */ + buffer[0].timestamp = 0; + buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */ + Pm_Write(out, buffer, 1); + + output_period = get_number( + "MIDI out should be sent every __ callback iterations: "); + + assert(output_period >= 1); + } + } + + printf("%s%s", "Latency measurements will start in 5 seconds. ", + "Type return to stop: "); + Pt_Start(period, &pt_callback, 0); + fgets(line, STRING_MAX, stdin); + stop = Pt_Time(); + Pt_Stop(); + + /* courteously turn off the last note, if necessary */ + if (note_on) { + PmEvent buffer[1]; + buffer[0].timestamp = Pt_Time(NULL); + buffer[0].message = Pm_Message(0x90, 60, 0); + Pm_Write(out, buffer, 1); + } + + /* print the histogram */ + printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001); + printf("Latency(ms) Number of occurrences\n"); + /* avoid printing beyond last non-zero histogram entry */ + len = min(HIST_LEN, max_latency + 1); + for (i = 0; i < len; i++) { + printf("%2d %10ld\n", i, histogram[i]); + } + printf("Number of points greater than %dms: %ld\n", + HIST_LEN - 1, out_of_range); + printf("Maximum latency: %ld milliseconds\n", max_latency); + printf("\nNote that due to rounding, actual latency can be 1ms higher\n"); + printf("than the numbers reported here.\n"); + printf("Type return to exit..."); + fgets(line, STRING_MAX, stdin); + return 0; +} + + +/* read a number from console */ +int get_number(char *prompt) +{ + char line[STRING_MAX]; + int n = 0, i; + printf(prompt); + while (n != 1) { + n = scanf("%d", &i); + fgets(line, STRING_MAX, stdin); + + } + return i; +} diff --git a/pd/portmidi/pm_test/latency.dsp b/pd/portmidi/pm_test/latency.dsp new file mode 100644 index 00000000..ff6bbbd7 --- /dev/null +++ b/pd/portmidi/pm_test/latency.dsp @@ -0,0 +1,102 @@ +# Microsoft Developer Studio Project File - Name="latency" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=latency - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "latency.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "latency.mak" CFG="latency - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "latency - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "latency - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "latency - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "latency___Win32_Release" +# PROP BASE Intermediate_Dir "latency___Win32_Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "latencyRelease" +# PROP Intermediate_Dir "latencyRelease" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../porttime" /I "../pm_common" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "latency - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "latency___Win32_Debug" +# PROP BASE Intermediate_Dir "latency___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "latencyDebug" +# PROP Intermediate_Dir "latencyDebug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../porttime" /I "../pm_common" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "latency - Win32 Release" +# Name "latency - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\latency.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/pd/portmidi/pm_test/midithread.c b/pd/portmidi/pm_test/midithread.c new file mode 100644 index 00000000..861b347c --- /dev/null +++ b/pd/portmidi/pm_test/midithread.c @@ -0,0 +1,327 @@ +/* midithread.c -- example program showing how to do midi processing + in a preemptive thread + + Notes: if you handle midi I/O from your main program, there will be + some delay before handling midi messages whenever the program is + doing something like file I/O, graphical interface updates, etc. + + To handle midi with minimal delay, you should do all midi processing + in a separate, high priority thread. A convenient way to get a high + priority thread in windows is to use the timer callback provided by + the PortTime library. That is what we show here. + + If the high priority thread writes to a file, prints to the console, + or does just about anything other than midi processing, this may + create delays, so all this processing should be off-loaded to the + "main" process or thread. Communication between threads can be tricky. + If one thread is writing at the same time the other is reading, very + tricky race conditions can arise, causing programs to behave + incorrectly, but only under certain timing conditions -- a terrible + thing to debug. Advanced programmers know this as a synchronization + problem. See any operating systems textbook for the complete story. + + To avoid synchronization problems, a simple, reliable approach is + to communicate via messages. PortMidi offers a message queue as a + datatype, and operations to insert and remove messages. Use two + queues as follows: midi_to_main transfers messages from the midi + thread to the main thread, and main_to_midi transfers messages from + the main thread to the midi thread. Queues are safe for use between + threads as long as ONE thread writes and ONE thread reads. You must + NEVER allow two threads to write to the same queue. + + This program transposes incoming midi data by an amount controlled + by the main program. To change the transposition, type an integer + followed by return. The main program sends this via a message queue + to the midi thread. To quit, type 'q' followed by return. + + The midi thread can also send a pitch to the main program on request. + Type 'm' followed by return to wait for the next midi message and + print the pitch. + + This program illustrates: + Midi processing in a high-priority thread. + Communication with a main process via message queues. + + */ + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "assert.h" +#include "portmidi.h" +#include "pmutil.h" +#include "porttime.h" + +/* if INPUT_BUFFER_SIZE is 0, PortMidi uses a default value */ +#define INPUT_BUFFER_SIZE 0 + +#define OUTPUT_BUFFER_SIZE 100 +#define DRIVER_INFO NULL +#define TIME_PROC NULL +#define TIME_INFO NULL +/* use zero latency because we want output to be immediate */ +#define LATENCY 0 + +#define STRING_MAX 80 + +/**********************************/ +/* DATA USED ONLY BY process_midi */ +/* (except during initialization) */ +/**********************************/ + +int active = FALSE; +int monitor = FALSE; +int midi_thru = TRUE; + +long transpose; +PmStream *midi_in; +PmStream *midi_out; + +/****************************/ +/* END OF process_midi DATA */ +/****************************/ + +/* shared queues */ +PmQueue *midi_to_main; +PmQueue *main_to_midi; + +#define QUIT_MSG 1000 +#define MONITOR_MSG 1001 +#define THRU_MSG 1002 + +/* timer interrupt for processing midi data */ +void process_midi(PtTimestamp timestamp, void *userData) +{ + PmError result; + PmEvent buffer; /* just one message at a time */ + long msg; + + /* do nothing until initialization completes */ + if (!active) + return; + + /* check for messages */ + do { + result = Pm_Dequeue(main_to_midi, &msg); + if (result) { + if (msg >= -127 && msg <= 127) + transpose = msg; + else if (msg == QUIT_MSG) { + /* acknowledge receipt of quit message */ + Pm_Enqueue(midi_to_main, &msg); + active = FALSE; + return; + } else if (msg == MONITOR_MSG) { + /* main has requested a pitch. monitor is a flag that + * records the request: + */ + monitor = TRUE; + } else if (msg == THRU_MSG) { + /* toggle Thru on or off */ + midi_thru = !midi_thru; + } + } + } while (result); + + /* see if there is any midi input to process */ + do { + result = Pm_Poll(midi_in); + if (result) { + long status, data1, data2; + if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow) + continue; + if (midi_thru) + Pm_Write(midi_out, &buffer, 1); + /* unless there was overflow, we should have a message now */ + status = Pm_MessageStatus(buffer.message); + data1 = Pm_MessageData1(buffer.message); + data2 = Pm_MessageData2(buffer.message); + if ((status & 0xF0) == 0x90 || + (status & 0xF0) == 0x80) { + + /* this is a note-on or note-off, so transpose and send */ + data1 += transpose; + + /* keep within midi pitch range, keep proper pitch class */ + while (data1 > 127) + data1 -= 12; + while (data1 < 0) + data1 += 12; + + /* send the message */ + buffer.message = Pm_Message(status, data1, data2); + Pm_Write(midi_out, &buffer, 1); + + /* if monitor is set, send the pitch to the main thread */ + if (monitor) { + Pm_Enqueue(midi_to_main, &data1); + monitor = FALSE; /* only send one pitch per request */ + } + } + } + } while (result); +} + +void exit_with_message(char *msg) +{ + char line[STRING_MAX]; + printf("%s\n", msg); + fgets(line, STRING_MAX, stdin); + exit(1); +} + +int main() +{ + int id; + long n; + const PmDeviceInfo *info; + char line[STRING_MAX]; + int spin; + int done = FALSE; + + /* determine what type of test to run */ + printf("begin PortMidi multithread test...\n"); + + /* note that it is safe to call PortMidi from the main thread for + initialization and opening devices. You should not make any + calls to PortMidi from this thread once the midi thread begins. + to make PortMidi calls. + */ + + /* make the message queues */ + /* messages can be of any size and any type, but all messages in + * a given queue must have the same size. We'll just use long's + * for our messages in this simple example + */ + midi_to_main = Pm_QueueCreate(32, sizeof(long)); + assert(midi_to_main != NULL); + main_to_midi = Pm_QueueCreate(32, sizeof(long)); + assert(main_to_midi != NULL); + + /* a little test of enqueue and dequeue operations. Ordinarily, + * you would call Pm_Enqueue from one thread and Pm_Dequeue from + * the other. Since the midi thread is not running, this is safe. + */ + n = 1234567890; + Pm_Enqueue(midi_to_main, &n); + n = 987654321; + Pm_Enqueue(midi_to_main, &n); + Pm_Dequeue(midi_to_main, &n); + if (n != 1234567890) { + exit_with_message("Pm_Dequeue produced unexpected result."); + } + Pm_Dequeue(midi_to_main, &n); + if(n != 987654321) { + exit_with_message("Pm_Dequeue produced unexpected result."); + } + + /* always start the timer before you start midi */ + Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */ + /* the timer will call our function, process_midi() every millisecond */ + + Pm_Initialize(); + + id = Pm_GetDefaultOutputDeviceID(); + info = Pm_GetDeviceInfo(id); + if (info == NULL) { + printf("Could not open default output device (%d).", id); + exit_with_message(""); + } + printf("Opening output device %s %s\n", info->interf, info->name); + + /* use zero latency because we want output to be immediate */ + Pm_OpenOutput(&midi_out, + id, + DRIVER_INFO, + OUTPUT_BUFFER_SIZE, + TIME_PROC, + TIME_INFO, + LATENCY); + + id = Pm_GetDefaultInputDeviceID(); + info = Pm_GetDeviceInfo(id); + if (info == NULL) { + printf("Could not open default input device (%d).", id); + exit_with_message(""); + } + printf("Opening input device %s %s\n", info->interf, info->name); + Pm_OpenInput(&midi_in, + id, + DRIVER_INFO, + INPUT_BUFFER_SIZE, + TIME_PROC, + TIME_INFO); + + active = TRUE; /* enable processing in the midi thread -- yes, this + is a shared variable without synchronization, but + this simple assignment is safe */ + + printf("Enter midi input; it will be transformed as specified by...\n"); + printf("%s\n%s\n%s\n", + "Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or", + "type a number to specify transposition.", + "Must terminate with [ENTER]"); + + while (!done) { + long msg; + int len; + fgets(line, STRING_MAX, stdin); + /* remove the newline: */ + len = strlen(line); + if (len > 0) line[len - 1] = 0; /* overwrite the newline char */ + if (strcmp(line, "q") == 0) { + msg = QUIT_MSG; + Pm_Enqueue(main_to_midi, &msg); + /* wait for acknowlegement */ + do { + spin = Pm_Dequeue(midi_to_main, &msg); + } while (spin == 0); /* spin */ ; + done = TRUE; /* leave the command loop and wrap up */ + } else if (strcmp(line, "m") == 0) { + msg = MONITOR_MSG; + Pm_Enqueue(main_to_midi, &msg); + printf("Waiting for note...\n"); + do { + spin = Pm_Dequeue(midi_to_main, &msg); + } while (spin == 0); /* spin */ ; + printf("... pitch is %ld\n", msg); + } else if (strcmp(line, "t") == 0) { + /* reading midi_thru asynchronously could give incorrect results, + e.g. if you type "t" twice before the midi thread responds to + the first one, but we'll do it this way anyway. Perhaps a more + correct way would be to wait for an acknowledgement message + containing the new state. */ + printf("Setting THRU %s\n", (midi_thru ? "off" : "on")); + msg = THRU_MSG; + Pm_Enqueue(main_to_midi, &msg); + } else if (sscanf(line, "%ld", &msg) == 1) { + if (msg >= -127 && msg <= 127) { + /* send transposition value */ + printf("Transposing by %ld\n", msg); + Pm_Enqueue(main_to_midi, &msg); + } else { + printf("Transposition must be within -127...127\n"); + } + } else { + printf("%s\n%s\n%s\n", + "Type 'q' to quit, 'm' to monitor next pitch, or", + "type a number to specify transposition.", + "Must terminate with [ENTER]"); + } + } + + /* at this point, midi thread is inactive and we need to shut down + * the midi input and output + */ + Pt_Stop(); /* stop the timer */ + Pm_QueueDestroy(midi_to_main); + Pm_QueueDestroy(main_to_midi); + + /* Belinda! if close fails here, some memory is deleted, right??? */ + Pm_Close(midi_in); + Pm_Close(midi_out); + + printf("finished portMidi multithread test...enter any character to quit [RETURN]..."); + fgets(line, STRING_MAX, stdin); + return 0; +} diff --git a/pd/portmidi/pm_test/midithread.dsp b/pd/portmidi/pm_test/midithread.dsp new file mode 100644 index 00000000..7dc0a16f --- /dev/null +++ b/pd/portmidi/pm_test/midithread.dsp @@ -0,0 +1,102 @@ +# Microsoft Developer Studio Project File - Name="midithread" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=midithread - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "midithread.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "midithread.mak" CFG="midithread - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "midithread - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "midithread - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "midithread - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "midithreadRelease" +# PROP Intermediate_Dir "midithreadRelease" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "midithread - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "midithreadDebug" +# PROP BASE Intermediate_Dir "midithreadDebug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "midithreadDebug" +# PROP Intermediate_Dir "midithreadDebug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "midithread - Win32 Release" +# Name "midithread - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\midithread.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/pd/portmidi/pm_test/midithru.c b/pd/portmidi/pm_test/midithru.c new file mode 100644 index 00000000..270246fb --- /dev/null +++ b/pd/portmidi/pm_test/midithru.c @@ -0,0 +1,364 @@ +/* midithru.c -- example program implementing background thru processing */ + +/* suppose you want low-latency midi-thru processing, but your application + wants to take advantage of the input buffer and timestamped data so that + it does not have to operate with very low latency. + + This program illustrates how to use a timer callback from PortTime to + implement a low-latency process that handles midi thru, including correctly + merging midi data from the application with midi data from the input port. + + The main application, which runs in the main program thread, will use an + interface similar to that of PortMidi, but since PortMidi does not allow + concurrent threads to share access to a stream, the application will + call private methods that transfer MIDI messages to and from the timer + thread. All PortMidi API calls are made from the timer thread. + */ + +/* DESIGN + +All setup will be done by the main thread. Then, all direct access to +PortMidi will be handed off to the timer callback thread. + +After this hand-off, the main thread will get/send messages via a queue. + +The goal is to send incoming messages to the midi output while merging +any midi data generated by the application. Sysex is a problem here +because you cannot insert (merge) a midi message while a sysex is in +progress. There are at least three ways to implement midi thru with +sysex messages: + +1) Turn them off. If your application does not need them, turn them off + with Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_SYSEX). You will + not receive sysex (or active sensing messages), so you will not have + to handle them. + +2) Make them atomic. As you receive sysex messages, copy the data into + a (big) buffer. Ideally, expand the buffer as needed -- sysex messages + do not have any maximum length. Even more ideally, use a list structure + and real-time memory allocation to avoid latency in the timer thread. + When a full sysex message is received, send it to the midi output all + at once. + +3) Process sysex incrementally. Send sysex data to midi output as it + arrives. Block any non-real-time messages from the application until + the sysex message completes. There is the risk that an incomplete + sysex message will block messages forever, so implement a 5-second + timeout: if no sysex data is seen for 5 seconds, release the block, + possibly losing the rest of the sysex message. + + Application messages must be processed similarly: once started, a + sysex message will block MIDI THRU processing. We will assume that + the application will not abort a sysex message, so timeouts are not + necessary here. + +This code implements (3). + +Latency is also an issue. PortMidi requires timestamps to be in +non-decreasing order. Since we'll be operating with a low-latency +timer thread, we can just set the latency to zero meaning timestamps +are ignored by PortMidi. This will allow thru to go through with +minimal latency. The application, however, needs to use timestamps +because we assume it is high latency (the whole purpose of this +example is to illustrate how to get low-latency thru with a high-latency +application.) So the callback thread will implement midi timing by +observing timestamps. The current timestamp will be available in the +global variable current_timestamp. + +*/ + + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "assert.h" +#include "portmidi.h" +#include "pmutil.h" +#include "porttime.h" + +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 + +/* active is set true when midi processing should start */ +int active = FALSE; +/* process_midi_exit_flag is set when the timer thread shuts down */ +int process_midi_exit_flag; + +PmStream *midi_in; +PmStream *midi_out; + +/* shared queues */ +#define IN_QUEUE_SIZE 1024 +#define OUT_QUEUE_SIZE 1024 +PmQueue *in_queue; +PmQueue *out_queue; +PmTimestamp current_timestamp = 0; +int thru_sysex_in_progress = FALSE; +int app_sysex_in_progress = FALSE; +PmTimestamp last_timestamp = 0; + + +/* time proc parameter for Pm_MidiOpen */ +long midithru_time_proc(void *info) +{ + return current_timestamp; +} + + +/* timer interrupt for processing midi data. + Incoming data is delivered to main program via in_queue. + Outgoing data from main program is delivered via out_queue. + Incoming data from midi_in is copied with low latency to midi_out. + Sysex messages from either source block messages from the other. + */ +void process_midi(PtTimestamp timestamp, void *userData) +{ + PmError result; + PmEvent buffer; /* just one message at a time */ + + current_timestamp++; /* update every millisecond */ + /* if (current_timestamp % 1000 == 0) + printf("time %d\n", current_timestamp); */ + + /* do nothing until initialization completes */ + if (!active) { + /* this flag signals that no more midi processing will be done */ + process_midi_exit_flag = TRUE; + return; + } + + /* see if there is any midi input to process */ + if (!app_sysex_in_progress) { + do { + result = Pm_Poll(midi_in); + if (result) { + long status; + if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow) + continue; + + /* record timestamp of most recent data */ + last_timestamp = current_timestamp; + + /* the data might be the end of a sysex message that + has timed out, in which case we must ignore it. + It's a continuation of a sysex message if status + is actually a data byte (high-order bit is zero). */ + status = Pm_MessageStatus(buffer.message); + if (((status & 0x80) == 0) && !thru_sysex_in_progress) { + continue; /* ignore this data */ + } + + /* implement midi thru */ + /* note that you could output to multiple ports or do other + processing here if you wanted + */ + /* printf("thru: %x\n", buffer.message); */ + Pm_Write(midi_out, &buffer, 1); + + /* send the message to the application */ + /* you might want to filter clock or active sense messages here + to avoid sending a bunch of junk to the application even if + you want to send it to MIDI THRU + */ + Pm_Enqueue(in_queue, &buffer); + + /* sysex processing */ + if (status == MIDI_SYSEX) thru_sysex_in_progress = TRUE; + else if ((status & 0xF8) != 0xF8) { + /* not MIDI_SYSEX and not real-time, so */ + thru_sysex_in_progress = FALSE; + } + if (thru_sysex_in_progress && /* look for EOX */ + (((buffer.message & 0xFF) == MIDI_EOX) || + (((buffer.message >> 8) & 0xFF) == MIDI_EOX) || + (((buffer.message >> 16) & 0xFF) == MIDI_EOX) || + (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) { + thru_sysex_in_progress = FALSE; + } + } + } while (result); + } + + + /* see if there is application midi data to process */ + while (!Pm_QueueEmpty(out_queue)) { + /* see if it is time to output the next message */ + PmEvent *next = (PmEvent *) Pm_QueuePeek(out_queue); + assert(next); /* must be non-null because queue is not empty */ + if (next->timestamp <= current_timestamp) { + /* time to send a message, first make sure it's not blocked */ + long status = Pm_MessageStatus(buffer.message); + if ((status & 0xF8) == 0xF8) { + ; /* real-time messages are not blocked */ + } else if (thru_sysex_in_progress) { + /* maybe sysex has timed out (output becomes unblocked) */ + if (last_timestamp + 5000 < current_timestamp) { + thru_sysex_in_progress = FALSE; + } else break; /* output is blocked, so exit loop */ + } + Pm_Dequeue(out_queue, &buffer); + Pm_Write(midi_out, &buffer, 1); + + /* inspect message to update app_sysex_in_progress */ + if (status == MIDI_SYSEX) app_sysex_in_progress = TRUE; + else if ((status & 0xF8) != 0xF8) { + /* not MIDI_SYSEX and not real-time, so */ + app_sysex_in_progress = FALSE; + } + if (app_sysex_in_progress && /* look for EOX */ + (((buffer.message & 0xFF) == MIDI_EOX) || + (((buffer.message >> 8) & 0xFF) == MIDI_EOX) || + (((buffer.message >> 16) & 0xFF) == MIDI_EOX) || + (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) { + app_sysex_in_progress = FALSE; + } + } else break; /* wait until indicated timestamp */ + } +} + + +void exit_with_message(char *msg) +{ +#define STRING_MAX 80 + char line[STRING_MAX]; + printf("%s\nType ENTER...", msg); + fgets(line, STRING_MAX, stdin); + exit(1); +} + + +void initialize() +/* set up midi processing thread and open midi streams */ +{ + /* note that it is safe to call PortMidi from the main thread for + initialization and opening devices. You should not make any + calls to PortMidi from this thread once the midi thread begins. + to make PortMidi calls. + */ + + /* note that this routine provides minimal error checking. If + you use the PortMidi library compiled with PM_CHECK_ERRORS, + then error messages will be printed and the program will exit + if an error is encountered. Otherwise, you should add some + error checking to this code. + */ + + const PmDeviceInfo *info; + int id; + + /* make the message queues */ + in_queue = Pm_QueueCreate(IN_QUEUE_SIZE, sizeof(PmEvent)); + assert(in_queue != NULL); + out_queue = Pm_QueueCreate(OUT_QUEUE_SIZE, sizeof(PmEvent)); + assert(out_queue != NULL); + + /* always start the timer before you start midi */ + Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */ + /* the timer will call our function, process_midi() every millisecond */ + + Pm_Initialize(); + + id = Pm_GetDefaultOutputDeviceID(); + info = Pm_GetDeviceInfo(id); + if (info == NULL) { + printf("Could not open default output device (%d).", id); + exit_with_message(""); + } + printf("Opening output device %s %s\n", info->interf, info->name); + + /* use zero latency because we want output to be immediate */ + Pm_OpenOutput(&midi_out, + id, + NULL /* driver info */, + OUT_QUEUE_SIZE, + &midithru_time_proc, + NULL /* time info */, + 0 /* Latency */); + + id = Pm_GetDefaultInputDeviceID(); + info = Pm_GetDeviceInfo(id); + if (info == NULL) { + printf("Could not open default input device (%d).", id); + exit_with_message(""); + } + printf("Opening input device %s %s\n", info->interf, info->name); + Pm_OpenInput(&midi_in, + id, + NULL /* driver info */, + 0 /* use default input size */, + &midithru_time_proc, + NULL /* time info */); + /* Note: if you set a filter here, then this will filter what goes + to the MIDI THRU port. You may not want to do this. + */ + Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK); + + active = TRUE; /* enable processing in the midi thread -- yes, this + is a shared variable without synchronization, but + this simple assignment is safe */ + +} + + +void finalize() +{ + /* the timer thread could be in the middle of accessing PortMidi stuff */ + /* to detect that it is done, we first clear process_midi_exit_flag and + then wait for the timer thread to set it + */ + process_midi_exit_flag = FALSE; + active = FALSE; + /* busy wait for flag from timer thread that it is done */ + while (!process_midi_exit_flag) ; + /* at this point, midi thread is inactive and we need to shut down + * the midi input and output + */ + Pt_Stop(); /* stop the timer */ + Pm_QueueDestroy(in_queue); + Pm_QueueDestroy(out_queue); + + Pm_Close(midi_in); + Pm_Close(midi_out); + + Pm_Terminate(); +} + + +int main(int argc, char *argv[]) +{ + PmTimestamp last_time = 0; + PmEvent buffer; + + /* determine what type of test to run */ + printf("begin PortMidi midithru program...\n"); + + initialize(); /* set up and start midi processing */ + + printf("%s\n%s\n", + "This program will run for 60 seconds, or until you play middle C,", + "echoing all input with a 2 second delay."); + + while (current_timestamp < 60000) { + /* just to make the point that this is not a low-latency process, + spin until half a second has elapsed */ + last_time = last_time + 500; + while (last_time > current_timestamp) ; + + /* now read data and send it after changing timestamps */ + while (Pm_Dequeue(in_queue, &buffer) == 1) { + /* printf("timestamp %d\n", buffer.timestamp); */ + /* printf("message %x\n", buffer.message); */ + buffer.timestamp = buffer.timestamp + 2000; /* delay */ + Pm_Enqueue(out_queue, &buffer); + /* play middle C to break out of loop */ + if (Pm_MessageStatus(buffer.message) == 0x90 && + Pm_MessageData1(buffer.message) == 60) { + goto quit_now; + } + } + } +quit_now: + finalize(); + exit_with_message("finished PortMidi midithru program."); + return 0; /* never executed, but keeps the compiler happy */ +} diff --git a/pd/portmidi/pm_test/midithru.dsp b/pd/portmidi/pm_test/midithru.dsp new file mode 100644 index 00000000..83f28cfc --- /dev/null +++ b/pd/portmidi/pm_test/midithru.dsp @@ -0,0 +1,102 @@ +# Microsoft Developer Studio Project File - Name="midithru" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=midithru - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "midithru.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "midithru.mak" CFG="midithru - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "midithru - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "midithru - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "midithru - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "midithruRelease" +# PROP Intermediate_Dir "midithruRelease" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "midithru - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "midithruDebug" +# PROP BASE Intermediate_Dir "midithruDebug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "midithruDebug" +# PROP Intermediate_Dir "midithruDebug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "midithru - Win32 Release" +# Name "midithru - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\midithru.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/pd/portmidi/pm_test/midithru.dsw b/pd/portmidi/pm_test/midithru.dsw new file mode 100644 index 00000000..b244a104 --- /dev/null +++ b/pd/portmidi/pm_test/midithru.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "midithread"=.\midithru.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/pd/portmidi/pm_test/sysex.c b/pd/portmidi/pm_test/sysex.c new file mode 100644 index 00000000..f49bf962 --- /dev/null +++ b/pd/portmidi/pm_test/sysex.c @@ -0,0 +1,319 @@ +/* sysex.c -- example program showing how to send and receive sysex + messages + + Messages are stored in a file using 2-digit hexadecimal numbers, + one per byte, separated by blanks, with up to 32 numbers per line: + F0 14 A7 4B ... + + */ + +#include "stdio.h" +#include "stdlib.h" +#include "assert.h" +#include "portmidi.h" +#include "porttime.h" +#include "string.h" + +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 + +#define STRING_MAX 80 + +int latency = 0; + +/* read a number from console */ +/**/ +int get_number(char *prompt) +{ + char line[STRING_MAX]; + int n = 0, i; + printf(prompt); + while (n != 1) { + n = scanf("%d", &i); + fgets(line, STRING_MAX, stdin); + + } + return i; +} + + +/* loopback test -- send/rcv from 2 to 1000 bytes of random midi data */ +/**/ +void loopback_test() +{ + int outp; + int inp; + PmStream *midi_in; + PmStream *midi_out; + unsigned char msg[1024]; + char line[80]; + long len; + int i; + int data; + PmEvent event; + int shift; + + Pt_Start(1, 0, 0); + + printf("Connect a midi cable from an output port to an input port.\n"); + printf("This test will send random data via sysex message from output\n"); + printf("to input and check that the correct data was received.\n"); + outp = get_number("Type output device number: "); + /* Open output with 1ms latency -- when latency is non-zero, the Win32 + implementation supports sending sysex messages incrementally in a + series of buffers. This is nicer than allocating a big buffer for the + message, and it also seems to work better. Either way works. + */ + Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency); + inp = get_number("Type input device number: "); + /* since we are going to send and then receive, make sure the input buffer + is large enough for the entire message */ + Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL); + + srand((unsigned int) Pt_Time()); /* seed for random numbers */ + + while (1) { + PmError count; + long start_time; + long error_position; + long expected = 0; + long actual = 0; + printf("Type return to send message, q to quit: "); + fgets(line, STRING_MAX, stdin); + if (line[0] == 'q') goto cleanup; + + /* compose the message */ + len = rand() % 998 + 2; /* len only counts data bytes */ + msg[0] = (char) MIDI_SYSEX; /* start of SYSEX message */ + /* data bytes go from 1 to len */ + for (i = 0; i < len; i++) { + msg[i + 1] = rand() & 0x7f; /* MIDI data */ + } + /* final EOX goes in len+1, total of len+2 bytes in msg */ + msg[len + 1] = (char) MIDI_EOX; + + /* sanity check: before we send, there should be no queued data */ + count = Pm_Read(midi_in, &event, 1); + + if (count != 0) { + printf("Before sending anything, a MIDI message was found in\n"); + printf("the input buffer. Please try again.\n"); + break; + } + + /* send the message */ + printf("Sending %ld byte sysex message.\n", len + 2); + Pm_WriteSysEx(midi_out, 0, msg); + + /* receive the message and compare to msg[] */ + data = 0; + shift = 0; + i = 0; + start_time = Pt_Time(); + error_position = -1; + /* allow up to 2 seconds for transmission */ + while (data != MIDI_EOX && start_time + 2000 > Pt_Time()) { + count = Pm_Read(midi_in, &event, 1); + /* CAUTION: this causes busy waiting. It would be better to + be in a polling loop to avoid being compute bound. PortMidi + does not support a blocking read since this is so seldom + useful. There is no timeout, so if we don't receive a sysex + message, or at least an EOX, the program will hang here. + */ + if (count == 0) continue; + + /* printf("read %lx ", event.message); + fflush(stdout); */ + + /* compare 4 bytes of data until you reach an eox */ + for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) { + data = (event.message >> shift) & 0xFF; + if (data != msg[i] && error_position < 0) { + error_position = i; + expected = msg[i]; + actual = data; + } + i++; + } + } + if (error_position >= 0) { + printf("Error at byte %ld: sent %lx recd %lx\n", error_position, expected, actual); + } else if (i != len + 2) { + printf("Error: byte %d not received\n", i); + } else { + printf("Correctly "); + } + printf("received %d byte sysex message.\n", i); + } +cleanup: + Pm_Close(midi_out); + Pm_Close(midi_in); + return; +} + + +#define is_real_time_msg(msg) ((0xF0 & Pm_MessageStatus(msg)) == 0xF8) + + +void receive_sysex() +{ + char line[80]; + FILE *f; + PmStream *midi; + int shift = 0; + int data = 0; + int bytes_on_line = 0; + PmEvent msg; + + /* determine which output device to use */ + int i = get_number("Type input device number: "); + + /* open input device */ + Pm_OpenInput(&midi, i, NULL, 512, NULL, NULL); + printf("Midi Input opened, type file for sysex data: "); + + /* open file */ + fgets(line, STRING_MAX, stdin); + /* remove the newline character */ + if (strlen(line) > 0) line[strlen(line) - 1] = 0; + f = fopen(line, "w"); + if (!f) { + printf("Could not open %s\n", line); + Pm_Close(midi); + return; + } + + printf("Ready to receive a sysex message\n"); + + /* read data and write to file */ + while (data != MIDI_EOX) { + PmError count; + count = Pm_Read(midi, &msg, 1); + /* CAUTION: this causes busy waiting. It would be better to + be in a polling loop to avoid being compute bound. PortMidi + does not support a blocking read since this is so seldom + useful. + */ + if (count == 0) continue; + /* ignore real-time messages */ + if (is_real_time_msg(Pm_MessageStatus(msg.message))) continue; + + /* write 4 bytes of data until you reach an eox */ + for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) { + data = (msg.message >> shift) & 0xFF; + /* if this is a status byte that's not MIDI_EOX, the sysex + message is incomplete and there is no more sysex data */ + if (data & 0x80 && data != MIDI_EOX) break; + fprintf(f, "%2x ", data); + if (++bytes_on_line >= 16) { + fprintf(f, "\n"); + bytes_on_line = 0; + } + } + } + fclose(f); + Pm_Close(midi); +} + + +void send_sysex() +{ + char line[80]; + FILE *f; + PmStream *midi; + int data; + int shift = 0; + PmEvent msg; + + /* determine which output device to use */ + int i = get_number("Type output device number: "); + + msg.timestamp = 0; /* no need for timestamp */ + + /* open output device */ + Pm_OpenOutput(&midi, i, NULL, 0, NULL, NULL, latency); + printf("Midi Output opened, type file with sysex data: "); + + /* open file */ + fgets(line, STRING_MAX, stdin); + /* remove the newline character */ + if (strlen(line) > 0) line[strlen(line) - 1] = 0; + f = fopen(line, "r"); + if (!f) { + printf("Could not open %s\n", line); + Pm_Close(midi); + return; + } + + /* read file and send data */ + msg.message = 0; + while (1) { + /* get next byte from file */ + + if (fscanf(f, "%x", &data) == 1) { + /* printf("read %x, ", data); */ + /* OR byte into message at proper offset */ + msg.message |= (data << shift); + shift += 8; + } + /* send the message if it's full (shift == 32) or if we are at end */ + if (shift == 32 || data == MIDI_EOX) { + /* this will send sysex data 4 bytes at a time -- it would + be much more efficient to send multiple PmEvents at once + but this method is simpler. See Pm_WriteSysex for a more + efficient code example. + */ + Pm_Write(midi, &msg, 1); + msg.message = 0; + shift = 0; + } + if (data == MIDI_EOX) { /* end of message */ + fclose(f); + Pm_Close(midi); + return; + } + } +} + + +int main() +{ + int i; + char line[80]; + + /* list device information */ + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + printf("%d: %s, %s", i, info->interf, info->name); + if (info->input) printf(" (input)"); + if (info->output) printf(" (output)"); + printf("\n"); + } + latency = get_number("Latency in milliseconds (0 to send data immediatedly,\n" + " >0 to send timestamped messages): "); + while (1) { + printf("Type r to receive sysex, s to send," + " l for loopback test, q to quit: "); + fgets(line, STRING_MAX, stdin); + switch (line[0]) { + case 'r': + receive_sysex(); + break; + case 's': + send_sysex(); + break; + case 'l': + loopback_test(); + case 'q': + exit(0); + default: + break; + } + } + return 0; +} + + + + + diff --git a/pd/portmidi/pm_test/sysex.dsp b/pd/portmidi/pm_test/sysex.dsp new file mode 100644 index 00000000..329d3ef9 --- /dev/null +++ b/pd/portmidi/pm_test/sysex.dsp @@ -0,0 +1,102 @@ +# Microsoft Developer Studio Project File - Name="sysex" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=sysex - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "sysex.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "sysex.mak" CFG="sysex - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "sysex - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "sysex - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "sysex - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "sysexRelease" +# PROP Intermediate_Dir "sysexRelease" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\pm_common" /I "..\porttime" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\Release\portmidi.lib ..\porttime\Release\porttime.lib ..\pm_win\Release\pm_dll.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "sysex - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "sysexDebug" +# PROP Intermediate_Dir "sysexDebug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "..\pm_common" /I "..\porttime" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "sysex - Win32 Release" +# Name "sysex - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\sysex.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/pd/portmidi/pm_test/test.c b/pd/portmidi/pm_test/test.c new file mode 100644 index 00000000..ade8564d --- /dev/null +++ b/pd/portmidi/pm_test/test.c @@ -0,0 +1,469 @@ +#include "portmidi.h" +#include "porttime.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +#define INPUT_BUFFER_SIZE 100 +#define OUTPUT_BUFFER_SIZE 0 +#define DRIVER_INFO NULL +#define TIME_PROC ((long (*)(void *)) Pt_Time) +#define TIME_INFO NULL +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + +#define STRING_MAX 80 /* used for console input */ + +long latency = 0; + +/* crash the program to test whether midi ports are closed */ +/**/ +void doSomethingReallyStupid() { + int * tmp = NULL; + *tmp = 5; +} + + +/* exit the program without any explicit cleanup */ +/**/ +void doSomethingStupid() { + assert(0); +} + + +/* read a number from console */ +/**/ +int get_number(char *prompt) +{ + char line[STRING_MAX]; + int n = 0, i; + printf(prompt); + while (n != 1) { + n = scanf("%d", &i); + fgets(line, STRING_MAX, stdin); + + } + return i; +} + + +/* + * the somethingStupid parameter can be set to simulate a program crash. + * We want PortMidi to close Midi ports automatically in the event of a + * crash because Windows does not (and this may cause an OS crash) + */ +void main_test_input(unsigned int somethingStupid) { + PmStream * midi; + PmError status, length; + PmEvent buffer[1]; + int num = 10; + int i = get_number("Type input number: "); + /* It is recommended to start timer before Midi; otherwise, PortMidi may + start the timer with its (default) parameters + */ + TIME_START; + + /* open input device */ + Pm_OpenInput(&midi, + i, + DRIVER_INFO, + INPUT_BUFFER_SIZE, + TIME_PROC, + TIME_INFO); + + printf("Midi Input opened. Reading %d Midi messages...\n",num); + Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK); + /* empty the buffer after setting filter, just in case anything + got through */ + while (Pm_Poll(midi)) { + Pm_Read(midi, buffer, 1); + } + /* now start paying attention to messages */ + i = 0; /* count messages as they arrive */ + while (i < num) { + status = Pm_Poll(midi); + if (status == TRUE) { + length = Pm_Read(midi,buffer, 1); + if (length > 0) { + printf("Got message %d: time %ld, %2lx %2lx %2lx\n", + i, + buffer[0].timestamp, + Pm_MessageStatus(buffer[0].message), + Pm_MessageData1(buffer[0].message), + Pm_MessageData2(buffer[0].message)); + i++; + } else { + assert(0); + } + } + /* simulate crash if somethingStupid is 1 or 2 */ + if ((i > (num/2)) && (somethingStupid == 1)) { + doSomethingStupid(); + } else if ((i > (num/2)) && (somethingStupid == 2)) { + doSomethingReallyStupid(); + } + } + + /* close device (this not explicitly needed in most implementations) */ + printf("ready to close..."); + + Pm_Close(midi); + printf("done closing..."); +} + + + +void main_test_output() { + PmStream * midi; + char line[80]; + long off_time; + int chord[] = { 60, 67, 76, 83, 90 }; + #define chord_size 5 + PmEvent buffer[chord_size]; + PmTimestamp timestamp; + + /* determine which output device to use */ + int i = get_number("Type output number: "); + + /* It is recommended to start timer before PortMidi */ + TIME_START; + + /* open output device -- since PortMidi avoids opening a timer + when latency is zero, we will pass in a NULL timer pointer + for that case. If PortMidi tries to access the time_proc, + we will crash, so this test will tell us something. */ + Pm_OpenOutput(&midi, + i, + DRIVER_INFO, + OUTPUT_BUFFER_SIZE, + (latency == 0 ? NULL : TIME_PROC), + (latency == 0 ? NULL : TIME_INFO), + latency); + printf("Midi Output opened with %ld ms latency.\n", latency); + + /* output note on/off w/latency offset; hold until user prompts */ + printf("ready to send program 1 change... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + /* if we were writing midi for immediate output, we could always use + timestamps of zero, but since we may be writing with latency, we + will explicitly set the timestamp to "now" by getting the time. + The source of timestamps should always correspond to the TIME_PROC + and TIME_INFO parameters used in Pm_OpenOutput(). */ + buffer[0].timestamp = TIME_PROC(TIME_INFO); + buffer[0].message = Pm_Message(0xC0, 0, 0); + Pm_Write(midi, buffer, 1); + + printf("ready to note-on... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + buffer[0].timestamp = TIME_PROC(TIME_INFO); + buffer[0].message = Pm_Message(0x90, 60, 100); + Pm_Write(midi, buffer, 1); + printf("ready to note-off... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + buffer[0].timestamp = TIME_PROC(TIME_INFO); + buffer[0].message = Pm_Message(0x90, 60, 0); + Pm_Write(midi, buffer, 1); + + /* output short note on/off w/latency offset; hold until user prompts */ + printf("ready to note-on (short form)... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + Pm_WriteShort(midi, TIME_PROC(TIME_INFO), + Pm_Message(0x90, 60, 100)); + printf("ready to note-off (short form)... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + Pm_WriteShort(midi, TIME_PROC(TIME_INFO), + Pm_Message(0x90, 60, 0)); + + /* output several note on/offs to test timing. + Should be 1s between notes */ + printf("chord will arpeggiate if latency > 0\n"); + printf("ready to chord-on/chord-off... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + timestamp = TIME_PROC(TIME_INFO); + for (i = 0; i < chord_size; i++) { + buffer[i].timestamp = timestamp + 1000 * i; + buffer[i].message = Pm_Message(0x90, chord[i], 100); + } + Pm_Write(midi, buffer, chord_size); + + off_time = timestamp + 1000 + chord_size * 1000; + while (TIME_PROC(TIME_INFO) < off_time) + /* busy wait */; + for (i = 0; i < chord_size; i++) { + buffer[i].timestamp = timestamp + 1000 * i; + buffer[i].message = Pm_Message(0x90, chord[i], 0); + } + Pm_Write(midi, buffer, chord_size); + + /* close device (this not explicitly needed in most implementations) */ + printf("ready to close and terminate... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + + Pm_Close(midi); + Pm_Terminate(); + printf("done closing and terminating...\n"); +} + + +void main_test_both() +{ + int i = 0; + int in, out; + PmStream * midi, * midiOut; + PmEvent buffer[1]; + PmError status, length; + int num = 10; + + in = get_number("Type input number: "); + out = get_number("Type output number: "); + + /* In is recommended to start timer before PortMidi */ + TIME_START; + + Pm_OpenOutput(&midiOut, + out, + DRIVER_INFO, + OUTPUT_BUFFER_SIZE, + TIME_PROC, + TIME_INFO, + latency); + printf("Midi Output opened with %ld ms latency.\n", latency); + /* open input device */ + Pm_OpenInput(&midi, + in, + DRIVER_INFO, + INPUT_BUFFER_SIZE, + TIME_PROC, + TIME_INFO); + printf("Midi Input opened. Reading %d Midi messages...\n",num); + Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK); + /* empty the buffer after setting filter, just in case anything + got through */ + while (Pm_Poll(midi)) { + Pm_Read(midi, buffer, 1); + } + i = 0; + while (i < num) { + status = Pm_Poll(midi); + if (status == TRUE) { + length = Pm_Read(midi,buffer,1); + if (length > 0) { + Pm_Write(midiOut, buffer, 1); + printf("Got message %d: time %ld, %2lx %2lx %2lx\n", + i, + buffer[0].timestamp, + Pm_MessageStatus(buffer[0].message), + Pm_MessageData1(buffer[0].message), + Pm_MessageData2(buffer[0].message)); + i++; + } else { + assert(0); + } + } + } + + /* since close device should not needed, lets get + rid of it just to make sure program exit closes MIDI devices */ + /* Pm_Close(midi); + Pm_Close(midiOut); + Pm_Terminate(); */ +} + + +/* main_test_stream exercises windows winmm API's stream mode */ +/* The winmm stream mode is used for latency>0, and sends + timestamped messages. The timestamps are relative (delta) + times, whereas PortMidi times are absolute. Since peculiar + things happen when messages are not always sent in advance, + this function allows us to exercise the system and test it. + */ +void main_test_stream() { + PmStream * midi; + char line[80]; + PmEvent buffer[16]; + + /* determine which output device to use */ + int i = get_number("Type output number: "); + + latency = 500; /* ignore LATENCY for this test and + fix the latency at 500ms */ + + /* It is recommended to start timer before PortMidi */ + TIME_START; + + /* open output device */ + Pm_OpenOutput(&midi, + i, + DRIVER_INFO, + OUTPUT_BUFFER_SIZE, + TIME_PROC, + TIME_INFO, + latency); + printf("Midi Output opened with %ld ms latency.\n", latency); + + /* output note on/off w/latency offset; hold until user prompts */ + printf("ready to send output... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + + /* if we were writing midi for immediate output, we could always use + timestamps of zero, but since we may be writing with latency, we + will explicitly set the timestamp to "now" by getting the time. + The source of timestamps should always correspond to the TIME_PROC + and TIME_INFO parameters used in Pm_OpenOutput(). */ + buffer[0].timestamp = TIME_PROC(TIME_INFO); + buffer[0].message = Pm_Message(0xC0, 0, 0); + buffer[1].timestamp = buffer[0].timestamp; + buffer[1].message = Pm_Message(0x90, 60, 100); + buffer[2].timestamp = buffer[0].timestamp + 1000; + buffer[2].message = Pm_Message(0x90, 62, 100); + buffer[3].timestamp = buffer[0].timestamp + 2000; + buffer[3].message = Pm_Message(0x90, 64, 100); + buffer[4].timestamp = buffer[0].timestamp + 3000; + buffer[4].message = Pm_Message(0x90, 66, 100); + buffer[5].timestamp = buffer[0].timestamp + 4000; + buffer[5].message = Pm_Message(0x90, 60, 0); + buffer[6].timestamp = buffer[0].timestamp + 4000; + buffer[6].message = Pm_Message(0x90, 62, 0); + buffer[7].timestamp = buffer[0].timestamp + 4000; + buffer[7].message = Pm_Message(0x90, 64, 0); + buffer[8].timestamp = buffer[0].timestamp + 4000; + buffer[8].message = Pm_Message(0x90, 66, 0); + + Pm_Write(midi, buffer, 9); +#ifdef SEND8 + /* Now, we're ready for the real test. + Play 4 notes at now, now+500, now+1000, and now+1500 + Then wait until now+2000. + Play 4 more notes as before. + We should hear 8 evenly spaced notes. */ + now = TIME_PROC(TIME_INFO); + for (i = 0; i < 4; i++) { + buffer[i * 2].timestamp = now + (i * 500); + buffer[i * 2].message = Pm_Message(0x90, 60, 100); + buffer[i * 2 + 1].timestamp = now + 250 + (i * 500); + buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0); + } + Pm_Write(midi, buffer, 8); + + while (Pt_Time() < now + 2500) + /* busy wait */; + /* now we are 500 ms behind schedule, but since the latency + is 500, the delay should not be audible */ + now += 2000; + for (i = 0; i < 4; i++) { + buffer[i * 2].timestamp = now + (i * 500); + buffer[i * 2].message = Pm_Message(0x90, 60, 100); + buffer[i * 2 + 1].timestamp = now + 250 + (i * 500); + buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0); + } + Pm_Write(midi, buffer, 8); +#endif + /* close device (this not explicitly needed in most implementations) */ + printf("ready to close and terminate... (type RETURN):"); + fgets(line, STRING_MAX, stdin); + + Pm_Close(midi); + Pm_Terminate(); + printf("done closing and terminating...\n"); +} + + +void show_usage() +{ + printf("Usage: test [-h] [-l latency-in-ms]\n"); + exit(0); +} + +int main(int argc, char *argv[]) +{ + int i = 0, n = 0; + char line[STRING_MAX]; + int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0; + int stream_test = 0; + int latency_valid = FALSE; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0) { + show_usage(); + } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { + i = i + 1; + latency = atoi(argv[i]); + printf("Latency will be %ld\n", latency); + latency_valid = TRUE; + } else { + show_usage(); + } + } + + while (!latency_valid) { + printf("Latency in ms: "); + if (scanf("%ld", &latency) == 1) { + latency_valid = TRUE; + } + } + + /* determine what type of test to run */ + printf("begin portMidi test...\n"); + printf("%s%s%s%s%s", + "enter your choice...\n 1: test input\n", + " 2: test input (fail w/assert)\n", + " 3: test input (fail w/NULL assign)\n", + " 4: test output\n 5: test both\n", + " 6: stream test\n"); + while (n != 1) { + n = scanf("%d", &i); + fgets(line, STRING_MAX, stdin); + switch(i) { + case 1: + test_input = 1; + break; + case 2: + test_input = 1; + somethingStupid = 1; + break; + case 3: + test_input = 1; + somethingStupid = 2; + break; + case 4: + test_output = 1; + break; + case 5: + test_both = 1; + break; + case 6: + stream_test = 1; + break; + default: + printf("got %d (invalid input)\n", n); + break; + } + } + + /* list device information */ + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (((test_input | test_both) & info->input) | + ((test_output | test_both | stream_test) & info->output)) { + printf("%d: %s, %s", i, info->interf, info->name); + if (info->input) printf(" (input)"); + if (info->output) printf(" (output)"); + printf("\n"); + } + } + + /* run test */ + if (stream_test) { + main_test_stream(); + } else if (test_input) { + main_test_input(somethingStupid); + } else if (test_output) { + main_test_output(); + } else if (test_both) { + main_test_both(); + } + + printf("finished portMidi test...type ENTER to quit..."); + fgets(line, STRING_MAX, stdin); + return 0; +} diff --git a/pd/portmidi/pm_test/test.dsp b/pd/portmidi/pm_test/test.dsp new file mode 100644 index 00000000..66ba3e91 --- /dev/null +++ b/pd/portmidi/pm_test/test.dsp @@ -0,0 +1,102 @@ +# Microsoft Developer Studio Project File - Name="test" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=test - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "test.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "test.mak" CFG="test - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "test - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "test - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "test - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "testRelease" +# PROP Intermediate_Dir "testRelease" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\Release\portmidi.lib ..\porttime\Release\porttime.lib ..\pm_win\Release\pm_dll.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "test - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "testDebug" +# PROP Intermediate_Dir "testDebug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "test - Win32 Release" +# Name "test - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\test.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/pd/portmidi/pm_test/txdata.syx b/pd/portmidi/pm_test/txdata.syx new file mode 100644 index 00000000..1e06e5a6 --- /dev/null +++ b/pd/portmidi/pm_test/txdata.syx @@ -0,0 +1,257 @@ +20 0 1d 4 c 6 0 34 1 4d 4 d 1f 7 3 6 + c 5e 4 4d d b 18 5 3 6 0 3d 1 4a 16 18 +1f 8 3 6 d 0 1 63 4 13 3a 23 0 0 0 2 + c 2 4 0 63 32 0 0 0 32 0 47 72 61 6e 64 +50 69 61 6e 6f 63 63 63 32 32 32 0 0 0 0 0 +10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 9 9 f c 27 2 35 37 10 1f 4 3 4 + d 19 4 56 5 16 1f f 8 d c 0 43 60 4 e +1f c 3 7 e 0 43 63 5 10 3c 14 8 2 1b 56 + 5 2 4 0 63 32 0 0 0 32 0 4c 6f 54 69 6e +65 38 31 5a 20 63 63 63 32 32 32 0 7f 0 1 0 +18 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f e f e 9 0 3 43 2d e 1f f 5 7 + f 16 43 5a 0 0 1f 12 6 8 d 0 3 63 4 0 +1f 12 6 8 f 0 2 63 4 6 34 14 0 1 2 4e +18 2 4 0 63 32 0 32 0 32 0 44 79 6e 6f 6d +69 74 65 45 50 63 63 63 32 32 32 0 70 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f b 1 b 8 18 40 5f a e 1f 1f 0 a + f 0 40 5f 4 0 1f 1f 0 a f 0 40 63 5 6 +1f 1f 0 a f 0 40 5f 0 8 1f 20 0 3 0 5a +18 4 4 0 63 32 32 0 0 32 0 50 65 72 63 4f +72 67 61 6e 20 63 63 63 32 32 32 0 0 0 0 0 + 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f b 7 f 9 0 4 49 13 13 1f 8 7 5 + e 0 2 58 0 c 1f 6 4 6 f 23 3 46 10 a +1f 7 8 c d 0 2 63 8 b 2 1c 0 0 0 52 +18 4 4 0 63 32 0 32 0 32 0 54 68 69 6e 20 +43 6c 61 76 20 63 63 63 32 32 32 0 70 0 20 0 +10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f c 0 6 1 a 4 50 20 e 1f c 0 6 + 1 a 4 50 1f 8 1f b 9 5 e 0 2 63 5 e +1f b 9 5 e 0 3 63 4 8 4 1a 0 0 0 52 +1d 2 4 0 63 32 0 32 0 32 0 42 72 69 74 65 +43 65 6c 73 74 63 63 63 32 32 32 0 20 0 26 0 + 1 0 8 4 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 f 1f 4 8 f 0 3a 51 4 b e 1f 0 8 + f 0 22 4b 4 3 f 1a b 8 d 0 3b 36 9 3 +12 1f 0 8 f 0 22 5d 4 b 3a 1e 19 5 0 52 +18 4 4 0 63 32 0 0 0 32 0 54 72 75 6d 70 +65 74 38 31 5a 63 63 63 32 32 32 0 0 0 50 0 +51 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 c 5 0 8 0 0 2 4a 4 b f 1f 0 8 + f 0 2 3f 4 3 1f f 0 8 0 23 3 44 b 3 +10 1f 0 9 f 0 2 5e 4 c 3a 1f 19 7 0 52 +18 4 4 0 63 32 0 0 0 32 0 46 6c 75 67 65 +6c 68 6f 72 6e 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 10 1f 0 8 f 0 42 4a 0 3 11 1f 0 8 + f a 43 51 0 3 11 9 0 8 d 0 42 2b 16 6 +10 1f 0 9 f 0 42 63 4 b 3a 1e 9 9 0 5a +24 4 4 0 63 32 31 0 0 32 0 52 61 73 70 41 +6c 74 6f 20 20 63 63 63 32 32 32 0 10 0 20 0 +54 0 20 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 10 9 2 6 d 0 41 3e 4 15 c b 2 3 + e 0 41 4f 4 12 c e 2 8 d 0 42 4b a 1c + d b 1 9 e 0 3 63 a 14 0 23 f 2 1b 5e +18 4 5 0 63 28 50 32 0 32 0 48 61 72 6d 6f +6e 69 63 61 20 63 63 63 32 32 32 0 50 10 50 0 +50 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1c 2 0 4 e 63 0 4e 4 3 d 5 0 6 + e 63 1 56 a 8 12 7 0 6 9 63 2 47 1b e + a a 0 5 f 0 1 63 4 b 32 1a 8 d 0 52 + c 4 4 0 63 32 0 0 0 32 0 44 6f 75 62 6c +65 42 61 73 73 63 63 63 32 32 32 0 10 0 0 0 + 3 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 b 4 0 4 f 14 2 49 9 6 a 7 0 4 + f 14 2 51 a 0 8 1f 0 5 f 0 1 63 9 6 + a 1f 0 5 f 0 1 63 a 0 3c 1f 6 9 0 52 + 5 4 4 0 63 32 0 0 0 32 0 48 69 53 74 72 +69 6e 67 20 31 63 63 63 32 32 32 0 2 0 30 0 +32 0 10 5 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 10 13 f 4 a 0 3 3b 14 14 1f e 8 7 + 9 0 2 42 5 e 18 13 d 9 c 0 2 3c 13 8 +1f 11 7 4 f 0 42 63 4 10 3a 1b 0 0 0 52 +1d 4 4 0 63 32 0 0 0 32 0 48 61 72 70 20 +20 20 20 20 20 63 63 63 32 32 32 8 0 0 21 0 + 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 6 6 4 f 0 40 48 5 0 c 8 7 5 + f 5 0 52 4 0 f 7 3 7 e 8 3 63 4 6 + f 8 4 5 f 0 3 63 4 6 7c 1f 0 6 0 4a +11 2 4 0 63 32 0 0 0 32 0 46 61 6e 66 61 +72 54 70 74 73 63 63 63 32 32 32 6 1 0 38 0 + 8 0 48 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 d b 0 1 c 0 2 2c 3d 3 d 7 0 1 + c 0 2 1f 3c 3 d 1f 0 5 f 0 2 63 5 6 + d 1f 0 5 f 0 2 63 4 0 3c 63 0 2f 0 53 +11 4 4 0 63 32 0 0 0 32 0 42 72 65 61 74 +68 4f 72 67 6e 63 63 63 32 32 32 4 30 5 50 0 +11 0 18 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 9 0 6 0 27 2 51 19 b 1c 6 0 8 + 0 37 2 47 a 3 1f a 0 9 0 3d 2 4d a e +1f 12 8 8 f 0 3 61 4 b 28 1f 0 3 0 52 + c 3 4 0 63 32 1 32 0 32 0 4e 79 6c 6f 6e +47 75 69 74 20 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f e e f f 0 3 48 2d 6 1f f 4 f + f 25 3 5b 0 0 1f 12 6 c e 1c 3 55 0 10 +1f 13 7 8 e 6 4 62 4 e 3b 14 0 0 0 42 +18 2 4 0 63 32 0 32 0 32 0 47 75 69 74 61 +72 20 23 31 20 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 19 8 a 3 0 3 63 10 18 1f c 5 b + 5 0 3 52 0 b 1f 19 6 b 5 0 3 63 a 16 +1f f 11 9 7 0 4 63 4 3 3a 14 0 0 0 42 +18 2 4 0 63 32 0 32 0 32 0 46 75 6e 6b 79 +20 50 69 63 6b 63 63 63 32 32 32 0 30 0 0 0 + 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 1 0 8 4 0 3 3d a 1e 1f 1 0 8 + 0 0 0 43 0 10 1f 9 6 8 c 1b 7 46 1c 1e +1f 9 0 9 9 0 1 63 4 3 3a 1c 0 0 0 52 + c 4 5 0 63 4b 0 0 0 32 0 45 6c 65 63 42 +61 73 73 20 31 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f f f e 9 0 3 46 1d 16 1f f 5 e + e d 3 63 0 b 1f 13 6 5 d 1c 3 63 0 0 +1f 13 6 8 f 0 4 63 4 6 3b 1f 0 0 0 42 + c 4 4 0 63 32 0 32 0 32 0 53 79 6e 46 75 +6e 6b 42 61 73 63 63 63 32 32 32 d 6c 0 0 0 +70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 10 7 8 3 0 3 4f 4 3 1f 9 0 8 + 0 0 1 4a 0 b 1f 11 0 8 0 0 1 47 4 8 +1f 9 0 8 0 0 0 63 0 b 39 19 0 7 0 52 + c 2 4 0 63 32 0 32 0 32 0 4c 61 74 65 6c +79 42 61 73 73 63 63 63 32 32 32 2 0 0 0 0 +40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 13 12 0 9 d 22 0 51 0 b 1f 14 0 5 + 8 24 40 5c 0 3 1f 11 0 6 c 2c 0 53 9 0 +10 1f 0 b f 0 0 5c a e 3a 22 11 e 1e 5e +18 7 4 0 63 32 0 32 0 32 0 53 79 6e 63 20 +4c 65 61 64 20 63 63 63 32 32 32 0 70 0 40 0 + 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 13 1e 0 9 e 0 0 63 3f b 1f 14 0 5 + e 24 1 51 4 3 1f 14 0 f 1 0 41 4d 8 3 + f 1f 0 b f 0 2 63 4 b 3b 20 11 12 33 56 +18 4 4 0 63 37 e 0 0 32 0 4a 61 7a 7a 20 +46 6c 75 74 65 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 15 13 d 3 d 1e 2 50 18 e 15 14 9 4 + c 1e 2 56 11 8 1b 1f f 7 f 0 1 63 4 6 +1a 1f e 6 f 0 2 63 4 0 7c b 0 8 0 62 +18 4 4 0 63 32 0 0 0 32 0 4a 61 76 61 20 +4a 69 76 65 20 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 0 0 4 f 0 40 63 3c 0 b 8 7 7 + f 5 0 63 4 6 f 5 3 7 f 8 0 3b 5 6 + e 8 4 5 f 0 3 63 3 0 7e 1d 6 f 0 4a +11 0 4 0 63 32 0 0 0 32 0 42 61 61 64 42 +72 65 61 74 68 63 63 63 32 32 32 6 30 0 38 0 + 1 0 46 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 0 0 4 f 0 40 47 2f 0 e 8 7 7 + f 5 0 4c 0 6 13 1c d c 6 8 0 63 5 6 +14 11 d b 0 0 3 63 4 0 7a 10 0 51 0 68 +17 0 4 0 63 32 0 0 0 32 0 56 6f 63 61 6c +4e 75 74 73 20 63 63 63 32 32 32 6 30 0 30 0 + 1 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 1f 0 5 f 0 0 41 32 3 1f 14 10 5 + 5 1 2 63 7 3 1f b 12 8 f 0 1 63 c 3 +1f 1f f 8 f 0 1 63 4 3 39 23 0 0 0 62 +18 7 4 0 63 32 0 0 0 32 0 57 61 74 65 72 +47 6c 61 73 73 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 16 2 0 4 6 9 1 4f 8 0 19 e 1 4 + 0 20 1 43 19 0 1f 12 10 6 7 0 0 54 3d 3 +16 d 6 6 2 1e 3 61 8 e 3a 20 1 14 0 42 + c 2 4 2 63 63 63 0 0 32 0 46 75 7a 7a 79 +20 4b 6f 74 6f 63 63 63 32 32 32 0 0 0 0 b +50 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1c 8 0 3 e 0 1 55 12 3 1c 7 0 1 + e 2e 1 58 27 b e 4 0 2 a 0 2 63 4 a + d 9 0 2 c 1 2 63 10 b 4 54 0 47 0 53 +18 7 4 0 63 32 0 0 0 32 0 42 72 74 68 62 +65 6c 6c 73 20 63 63 63 32 32 32 0 4 0 40 0 +40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1a 4 1 1 b 16 0 47 5 3 15 e 0 1 + d 0 0 4c 5 16 1c 6 4 2 7 0 0 63 4 16 +18 18 3 1 e 0 0 5e 4 10 24 7 0 4 0 62 +24 4 4 0 63 32 0 0 0 32 0 54 75 62 65 20 +42 65 6c 6c 73 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 1f 13 3 0 0 0 5f 3d 6 1f 12 13 2 + 0 0 1 52 5 2 1f 14 13 3 0 0 1 56 28 5 +1e b 13 f 9 0 0 63 6 3 3b 63 0 63 0 73 +23 7 4 0 63 32 0 0 0 32 0 4e 6f 69 73 65 +20 53 68 6f 74 63 63 63 32 32 32 8 0 0 0 8 + 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 16 0 3 7 0 1 50 0 3 1f 18 3 3 + 3 22 0 63 0 14 1d 7 6 3 6 0 1 3c 8 3 +1f 5 7 3 0 0 1 63 4 1b 39 23 0 8 0 42 +18 4 4 0 63 32 0 0 0 32 0 48 61 6e 64 20 +44 72 75 6d 20 63 63 63 32 32 32 0 1 0 3 0 + 1 0 1 3 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 7d f7 \ No newline at end of file diff --git a/pd/portmidi/pm_win/README_WIN.txt b/pd/portmidi/pm_win/README_WIN.txt new file mode 100644 index 00000000..df120e96 --- /dev/null +++ b/pd/portmidi/pm_win/README_WIN.txt @@ -0,0 +1,183 @@ +File: PortMidi Win32 Readme +Author: Belinda Thom, June 16 2002 +Revised by: Roger Dannenberg, June 2002, May 2004 + +============================================================================= +USING PORTMIDI: +============================================================================= + +PortMidi has been created using a DLL because the Win32 MMedia API doesn't +handle midiInput properly in the debugger. Specifically, it doesn't clean up +after itself if the user (i.e. you, a PortMidi application) hasn't explicitly +closed all open midi input devices. This lack of cleanup can lead to much +pain and agony, including the blue-screen-of-death. This situation becomes +increasingly unacceptable when you are debugging your code, so a portMidi DLL +seemed to be the most elegant solution. + +Using Microsoft Visual C++ project files (provided with PortMidi), there +are two configurations of the PortMidi library. The Debug version is +intended for debugging, especially in a console application. The Debug +version enables some extra error checking and outputs some text as well +as a prompt to type ENTER so that you don't lose any debugging text when +the program exits. You can turn off this extra debugging info by taking +out the compile-time definition for DEBUG. This debugging version also +defines PM_CHECK_ERRORS, which forces a check for error return codes from +every call to PortMidi. You can disable this checking (especially if you +want to handle error codes in your own way) by removing PM_CHECK_ERRORS +from the predefined symbols list in the Settings dialog box. + +PortMidi is designed to run without a console and should work perfectly +well within a graphical user interface application. The Release version +is both optimized and lacking the debugging printout code of the Debug +version. + +Read the portmidi.h file for PortMidi API details on using the PortMidi API. +See <...>\pm_dll_test\test.c or <...>\multithread\test.c for usage examples. + +============================================================================= +TO INSTALL PORTMIDI: +============================================================================= +1) download portmidi.zip + +2) unzip portmidi.zip into directory: <...>\portmidi + +============================================================================= +TO COMPILE PORTMIDI: +============================================================================= + +3) go to this directory + +4) click on the portmidi.dsw workspace + +5) the following projects exist within this workspace: + - portmidi (the PortMidi library) + - pm_dll (the dll library used to close midi ports on program exit) + - porttime (a small portable library implementing timer facilities) + - test (simple midi I/O testing) + - multithread (an example illustrating low-latency MIDI processing + using a dedicated low-latency thread) + - sysex (simple sysex message I/O testing) + - latency (uses porttime to measure system latency) + +6) verify that all project settings are for Win32 Debug release: + - hit Alt-F7 + - highlight all three projects in left part of Project Settings window; + - "Settings For" should say "Win32 Debug" + +7) set pm_dll as the active project (e.g. Project->Select Active Project) + +8) use Build->Batch Build ... to build everything in the project + +9) The settings for these projects were distributed in the zip file, so + compile should just work. + +10) IMPORTANT! PortMidi uses a DLL, pm_dll.dll, but there is no simple way + to set up projects to use pm_dll. THEREFORE, you need to copy DLLs + as follows (you can do this with <...>\portmidi\pm_win\copy-dll.bat): + copy <...>\portmidi\pm_win\Debug\pm_dll.dll to: + <...>\portmidi\pm_test\latencyDebug\pm_dll.dll + <...>\portmidi\pm_test\midithreadDebug\pm_dll.dll + <...>\portmidi\pm_test\sysexDebug\pm_dll.dll + <...>\portmidi\pm_test\testDebug\pm_dll.dll + <...>\portmidi\pm_test\midithruDebug\pm_dll.dll + and copy <...>\portmidi\pm_win\Release\pm_dll.dll to: + <...>\portmidi\pm_test\latencyRelease\pm_dll.dll + <...>\portmidi\pm_test\midithreadRelease\pm_dll.dll + <...>\portmidi\pm_test\sysexRelease\pm_dll.dll + <...>\portmidi\pm_test\testRelease\pm_dll.dll + <...>\portmidi\pm_test\midithruRelease\pm_dll.dll + each time you rebuild the pm_dll project, these copies must be redone! + + Since Windows will look in the executable directory for DLLs, we + recommend that you always install a copy of pm_dll.dll (either the + debug version or the release version) in the same directory as the + application using PortMidi. The release DLL is about 40KB. This will + ensure that the application uses the correct DLL. + +11) run test project; use the menu that shows up from the command prompt to + test that portMidi works on your system. tests include: + - verify midi output works + - verify midi input works + - verify midi input w/midi thru works + +12) run other projects if you wish: sysex, latency, and midithread + +============================================================================ +TO CREATE YOUR OWN PORTMIDI CLIENT APPLICATION: +============================================================================ + +NOTE: this section needs to be reviewed and tested. My suggestion would +be to copy the test project file (test.dsp) and modify it. -RBD + +The easiest way is to start a new project w/in the portMidi workspace: + +1) To open new project: + - File->New->Projects + - Location: <...>\portmidi\ + - check Add to current workspace + - select Win32 Console Application (recommended for now) + - do *NOT* select the "make dependency" box (you will explicitly do this + in the next step) + - Click OK + - Select "An Empty Project" and click Finish + +2) Now this project will be the active project. Make it explicitly depend + on PortMidi dll: + - Project->Dependencies + - Click pm_dll + +3) Important! in order to be able to use portMidi DLL from your new project + and set breakpoints, copy following files from <...>\pm_dll\Debug into + <...>\\Debug directory: + pm_dll.lib + pm_dll.dll + each time you rebuild pm_dll, these copies must be redone! + +4) add whatever files you wish to add to your new project, using portMidi + calls as desired (see USING PORTMIDI at top of this readme) + +5) when you include portMidi files, do so like this: + - #include "..\pm_dll\portmidi.h" + - etc. + +6) build and run your project + +============================================================================ +DESIGN NOTES +============================================================================ + +The DLL is used so that PortMidi can (usually) close open devices when the +program terminates. Failure to close input devices under WinNT, Win2K, and +probably later systems causes the OS to crash. + +This is accomplished with a .LIB/.DLL pair, linking to the .LIB +in order to access functions in the .DLL. + +PortMidi for Win32 exists as a simple library, +with Win32-specific code in pmwin.c and MM-specific code in pmwinmm.c. +pmwin.c uses a DLL in pmdll.c to call Pm_Terminate() when the program +exits to make sure that all MIDI ports are closed. + +Orderly cleanup after errors are encountered is based on a fixed order of +steps and state changes to reflect each step. Here's the order: + +To open input: + initialize return value to NULL + - allocate the PmInternal strucure (representation of PortMidiStream) + return value is (non-null) PmInternal structure + - allocate midi buffer + set buffer field of PmInternal structure + - call system-dependent open code + - allocate midiwinmm_type for winmm dependent data + set descriptor field of PmInternal structure + - open device + set handle field of midiwinmm_type structure + - allocate buffer 1 for sysex + buffer is added to input port + - allocate buffer 2 for sysex + buffer is added to input port + - return + - return + + + diff --git a/pd/portmidi/pm_win/copy-dll.bat b/pd/portmidi/pm_win/copy-dll.bat new file mode 100644 index 00000000..34ccbedd --- /dev/null +++ b/pd/portmidi/pm_win/copy-dll.bat @@ -0,0 +1,13 @@ +copy Debug\pm_dll.dll ..\pm_test\testDebug\pm_dll.dll +copy Debug\pm_dll.dll ..\pm_test\sysexDebug\pm_dll.dll +copy Debug\pm_dll.dll ..\pm_test\midithreadDebug\pm_dll.dll +copy Debug\pm_dll.dll ..\pm_test\latencyDebug\pm_dll.dll +copy Debug\pm_dll.dll ..\pm_test\midithruDebug\pm_dll.dll + +copy Release\pm_dll.dll ..\pm_test\testRelease\pm_dll.dll +copy Release\pm_dll.dll ..\pm_test\sysexRelease\pm_dll.dll +copy Release\pm_dll.dll ..\pm_test\midithreadRelease\pm_dll.dll +copy Release\pm_dll.dll ..\pm_test\latencyRelease\pm_dll.dll +copy Release\pm_dll.dll ..\pm_test\midithruRelease\pm_dll.dll + + diff --git a/pd/portmidi/pm_win/debugging_dlls.txt b/pd/portmidi/pm_win/debugging_dlls.txt new file mode 100644 index 00000000..82b81a5b --- /dev/null +++ b/pd/portmidi/pm_win/debugging_dlls.txt @@ -0,0 +1,145 @@ +======================================================================================================================== +Methods for Debugging DLLs +======================================================================================================================== +If you have the source for both the DLL and the calling program, open the project for the calling executable file and +debug the DLL from there. If you load a DLL dynamically, you must specify it in the Additional DLLs category of the +Debug tab in the Project Settings dialog box. + +If you have the source for the DLL only, open the project that builds the DLL. Use the Debug tab in the Project +Settings dialog box to specify the executable file that calls the DLL. + +You can also debug a DLL without a project. For example, maybe you just picked up a DLL and source code but you +don’t have an associated project or workspace. You can use the Open command on the File menu to select the .DLL +file you want to debug. The debug information should be in either the .DLL or the related .PDB file. After +Visual C++ opens the file, on the Build menu click Start Debug and Go to begin debugging. + +To debug a DLL using the project for the executable file + +From the Project menu, click Settings. +The Project Settings dialog box appears. + +Choose the Debug tab. + + +In the Category drop-down list box, select General. + + +In the Program Arguments text box, type any command-line arguments required by the executable file. + + +In the Category drop-down list box, select Additional DLLs. + + +In the Local Name column, type the names of DLLs to debug. +If you are debugging remotely, the Remote Name column appears. In this column, type the complete path for the +remote module to map to the local module name. + +In the Preload column, select the check box if you want to load the module before debugging begins. + + +Click OK to store the information in your project. + + +From the Build menu, click Start Debug and Go to start the debugger. +You can set breakpoints in the DLL or the calling program. You can open a source file for the DLL and set breakpoints +in that file, even though it is not a part of the executable file’s project. + +To debug a DLL using the project for the DLL + +From the Project menu, click Settings. +The Project Settings dialog box appears. + +Choose the Debug tab. + + +In the Category drop-down list box, select General. + + +In the Executable For Debug Session text box, type the name of the executable file that calls the DLL. + + +In the Category list box, select Additional DLLs. + + +In the Local Module Name column, type the name of the DLLs you want to debug. + + +Click OK to store the information in your project. + + +Set breakpoints as required in your DLL source files or on function symbols in the DLL. + + +From the Build menu, click Start Debug and Go to start the debugger. +To debug a DLL created with an external project + +From the Project menu, click Settings. +The Project Settings dialog box appears. + +Choose the Debug tab. + + +In the Category drop-down list box, select General. + + +In the Executable For Debug Session text box, type the name of the DLL that your external makefile builds. + + +Click OK to store the information in your project. + + +Build a debug version of the DLL with symbolic debugging information, if you don’t already have one. + + +Follow one of the two procedures immediately preceding this one to debug the DLL. + +======================================================================================================================== +Why Don’t My DLL Breakpoints Work? +======================================================================================================================== +Some reasons why your breakpoints don’t work as expected are listed here, along with solutions or work-arounds for each. +If you follow the instructions in one topic and are still having breakpoint problems, look at some of the other topics. +Often breakpoint problems result from a combination of conditions. + +You can't set a breakpoint in a source file when the corresponding symbolic information isn't loaded into memory by +the debugger. +You cannot set a breakpoint in any source file when the corresponding symbolic information will not be loaded into memory +by the debugger. +Symptoms include messages such as "the breakpoint cannot be set" or a simple, noninformational beep. + +When setting breakpoints before the code to be debugged has been started, the debugger uses a breakpoint list to keep +track of how and where to set breakpoints. When you actually begin the debugging session, the debugger loads the symbolic +information for all the code to be debugged and then walks through its breakpoint list, attempting to set the +breakpoints. + +However, if one or more of the code modules have not been designated to the debugger, there will be no symbolic +information for the debugger to use when walking through its breakpoint list. Situations where this is likely to +occur include: + +Attempts to set breakpoints in a DLL before the call to LoadLibrary. + +Setting a breakpoint in an ActiveX server before the container has started the server. + +Other similar cases. + +To prevent this behavior in Visual C++, specify all additional DLLs and COM servers in the Additional DLLs field +in the Debug/Options dialog box to notify the debugger that you want it to load symbolic debug information for +additional .DLL files. When this has been done, breakpoints set in code that has not yet been loaded into memory +will be "virtual" breakpoints. When the code is actually loaded into memory by the loader, these become physical +breakpoints. Make sure that these additional debugging processes are not already running when you start your +debugging session. The debugging process and these additional processes must be sychronized at the same beginning +point to work correctly, hitting all breakpoints. + +Breakpoints are missed when more than one copy of a DLL is on your hard disk. +Having more than one copy of a DLL on your hard drive, especially if it is in your Windows directory, can cause +debugger confusion. The debugger will load the symbolic information for the DLL specified to it at run time (with the +Additional DLLs field in the Debug/Options dialog box), while Windows has actually loaded a different copy of the +DLL itself into memory. Because there is no way to force the debugger to load a specific DLL, it is a good idea to +keep only one version of a DLL at a time in your path, current directory, and Windows directory. + +You can’t set "Break When Expression Has Changed" breakpoints on a variable local to a DLL. +Setting a "Break When Expression Has Changed" breakpoint on a variable local to a DLL function before the call +to LoadLibrary causes the breakpoint to be virtual (there are no physical addresses for the DLL in memory yet). +Virtual breakpoints involving expressions pose a special problem. The DLL must be specified to the debugger at +startup (causing its symbolic information to be loaded). In addition, the DLL's executable code must also be loaded +into memory before this kind of breakpoint can be set. This means that the calling application's code must be +executed to the point after its call to LoadLibrary before the debugger will allow this type of breakpoint to be set. diff --git a/pd/portmidi/pm_win/pm_dll.dsp b/pd/portmidi/pm_win/pm_dll.dsp new file mode 100644 index 00000000..d08e2de7 --- /dev/null +++ b/pd/portmidi/pm_win/pm_dll.dsp @@ -0,0 +1,107 @@ +# Microsoft Developer Studio Project File - Name="pm_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=pm_dll - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "pm_dll.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "pm_dll.mak" CFG="pm_dll - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "pm_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "pm_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "pm_dll - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "pm_win\Release" +# PROP Intermediate_Dir "pm_win\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 + +!ELSEIF "$(CFG)" == "pm_dll - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "pm_win\Debug" +# PROP Intermediate_Dir "pm_win\Debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "pm_common" /D "_WINDOWS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "pm_dll - Win32 Release" +# Name "pm_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\pmdll.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\pmdll.h +# End Source File +# End Group +# End Target +# End Project diff --git a/pd/portmidi/pm_win/pmdll.c b/pd/portmidi/pm_win/pmdll.c new file mode 100644 index 00000000..c3acba33 --- /dev/null +++ b/pd/portmidi/pm_win/pmdll.c @@ -0,0 +1,49 @@ +/* +==================================================================== +DLL to perform action when program shuts down +==================================================================== +*/ + +#include "windows.h" +#include "pmdll.h" + +static close_fn_ptr_type close_function = NULL; + + +DLL_EXPORT pm_set_close_function(close_fn_ptr_type close_fn_ptr) +{ + close_function = close_fn_ptr; +} + + +static void Initialize( void ) { + return; +} + +static void Terminate( void ) { + if (close_function) { + (*close_function)(); + } +} + + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, //DLL module handle + DWORD fdwReason, //for calling function + LPVOID lbpvReserved)//reserved +{ + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + /* when DLL starts, run this */ + Initialize(); + break; + case DLL_PROCESS_DETACH: + /* when DLL ends, this run (note: verified this run */ + Terminate(); + break; + default: + break; + } + return TRUE; +} + + diff --git a/pd/portmidi/pm_win/pmdll.h b/pd/portmidi/pm_win/pmdll.h new file mode 100644 index 00000000..e5ad1c5b --- /dev/null +++ b/pd/portmidi/pm_win/pmdll.h @@ -0,0 +1,5 @@ +#define DLL_EXPORT __declspec( dllexport ) + +typedef void (*close_fn_ptr_type)(); + +DLL_EXPORT pm_set_close_function(close_fn_ptr_type close_fn_ptr); diff --git a/pd/portmidi/pm_win/pmwin.c b/pd/portmidi/pm_win/pmwin.c new file mode 100644 index 00000000..b289194b --- /dev/null +++ b/pd/portmidi/pm_win/pmwin.c @@ -0,0 +1,113 @@ +/* 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 "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); +} + diff --git a/pd/portmidi/pm_win/pmwinmm.c b/pd/portmidi/pm_win/pmwinmm.c new file mode 100644 index 00000000..5bfb0cff --- /dev/null +++ b/pd/portmidi/pm_win/pmwinmm.c @@ -0,0 +1,1547 @@ +/* pmwinmm.c -- system specific definitions */ + +#include "windows.h" +#include "mmsystem.h" +#include "portmidi.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 + +#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 dwInstance, DWORD dwParam1, DWORD dwParam2); +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); +static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD 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 */ + +#define SYSEX_BYTES_PER_BUFFER 1024 +/* 3 midi messages per buffer */ +#define OUTPUT_BYTES_PER_BUFFER 36 + +#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3) +#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) +#define MIDIHDR_BUFFER_LENGTH(x) (x) +#define MIDIHDR_SIZE(x) (MIDIHDR_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 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 num_buffers; /* how many buffers */ + int next_buffer; /* index of next buffer to send */ + HANDLE buffer_signal; /* used to wait for buffer to become free */ + + LPMIDIHDR *sysex_buffers; /* pool of buffers for sysex data */ + int num_sysex_buffers; /* how many sysex buffers */ + int next_sysex_buffer; /* index of next sysexbuffer to send */ + HANDLE sysex_buffer_signal; /* used to wait for sysex 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 or to send */ + 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 */ + int callback_error; /* host error from midi in or out callback */ +} +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 = 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. This + // may be the wrong thing to do, especially in this case. + 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->callback_error || 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 if (m->callback_error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr2, len); + int err = midiInGetErrorText(m->callback_error, msg + n, + len - n); + assert(err == MMSYSERR_NOERROR); + m->callback_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; + } else if (m->callback_error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr2, len); + int err = midiOutGetErrorText(m->callback_error, msg + n, + len = n); + assert(err == MMSYSERR_NOERROR); + m->callback_error = MMSYSERR_NOERROR; + } + } + } +} + + +/* +============================================================================= +buffer handling +============================================================================= +*/ +static MIDIHDR *allocate_buffer(long data_size) +{ + /* + * with short messages, the MIDIEVENT structure contains the midi message, + * so there is no need for additional data + */ + + LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SIZE(data_size)); + MIDIEVENT *evt; + if (!hdr) return NULL; + evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ + hdr->lpData = (LPSTR) evt; + hdr->dwBufferLength = MIDIHDR_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */ + hdr->dwFlags = 0; + hdr->dwUser = 0; + return hdr; +} + +static MIDIHDR *allocate_sysex_buffer(long data_size) +{ + /* we're actually allocating slightly more than data_size because one more word of + * data is contained in MIDIEVENT. We include the size of MIDIEVENT because we need + * 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->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */ + hdr->dwFlags = 0; + hdr->dwUser = 0; + return hdr; +} + +static PmError allocate_buffers(midiwinmm_type m, long data_size, long count) +{ + PmError rslt = pmNoError; + /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!m->buffers) return pmInsufficientMemory; + m->num_buffers = count; + while (count > 0) { + LPMIDIHDR hdr = allocate_buffer(data_size); + if (!hdr) rslt = pmInsufficientMemory; + count--; + m->buffers[count] = hdr; /* this may be NULL if allocation fails */ + } + return rslt; +} + +static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size, long count) +{ + PmError rslt = pmNoError; + /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + m->sysex_buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!m->sysex_buffers) return pmInsufficientMemory; + m->num_sysex_buffers = count; + while (count > 0) { + LPMIDIHDR hdr = allocate_sysex_buffer(data_size); + if (!hdr) rslt = pmInsufficientMemory; + count--; + m->sysex_buffers[count] = hdr; /* this may be NULL if allocation fails */ + } + return rslt; +} + +static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m->sysex_buffers) { + if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) { + return NULL; + } + } + /* busy wait until we find a free buffer */ + while (TRUE) { + int i; + for (i = 0; i < m->num_sysex_buffers; i++) { + m->next_sysex_buffer++; + if (m->next_sysex_buffer >= m->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 */ + WaitForSingleObject(m->sysex_buffer_signal, INFINITE); + } +found_sysex_buffer: + r->dwBytesRecorded = 0; + m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR)); + return r; +} + + +static LPMIDIHDR get_free_output_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m->buffers) { + if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, 2)) { + return NULL; + } + } + /* busy wait until we find a free buffer */ + while (TRUE) { + int i; + for (i = 0; i < m->num_buffers; i++) { + 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 */ + WaitForSingleObject(m->buffer_signal, INFINITE); + } +found_buffer: + r->dwBytesRecorded = 0; + m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR)); + return r; +} + +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 be 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); + + /* find which buffer this was, and replace it */ + + for (i = 0;i < m->num_sysex_buffers;i++) { + if (m->sysex_buffers[i] == m->hdr) { + m->sysex_buffers[i] = big; + pm_free(m->hdr); + m->hdr = big; + break; + } + } + assert(i != m->num_sysex_buffers); + + return pmNoError; + +} +/* +========================================================================================= +begin midi input implementation +========================================================================================= +*/ + +static PmError winmm_in_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + midiwinmm_type m; + LPMIDIHDR hdr; + long buffer_len; + 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; + m->num_buffers = 0; + m->next_buffer = 0; + m->sysex_buffers = NULL; + m->num_sysex_buffers = 0; + m->next_sysex_buffer = 0; + 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->sync_time = 0; + m->delta = 0; + m->error = MMSYSERR_NOERROR; + m->callback_error = MMSYSERR_NOERROR; + + /* open device */ + pm_hosterror = midiInOpen(&(m->handle.in), /* input device handle */ + dwDevice, /* device ID */ + (DWORD) winmm_in_callback, /* callback address */ + (DWORD) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback is a procedure */ + if (pm_hosterror) goto free_descriptor; + + /* allocate first buffer for sysex data */ + buffer_len = midi->buffer_len - 1; + if (midi->buffer_len < 32) + buffer_len = PM_DEFAULT_SYSEX_BUFFER_SIZE; + + hdr = allocate_sysex_buffer(buffer_len); + if (!hdr) goto close_device; + pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) { + pm_free(hdr); + goto close_device; + } + pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) goto close_device; + + /* allocate second buffer */ + hdr = allocate_sysex_buffer(buffer_len); + if (!hdr) goto close_device; + pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) { + pm_free(hdr); + goto reset_device; /* because first buffer was added */ + } + pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) goto reset_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 */ +} + + +/* 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; + 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 dwInstance, /* application data */ + DWORD dwParam1, /* MIDI data */ + DWORD dwParam2) /* device timestamp (wrt most recent midiInStart) */ +{ + static int entry = 0; + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + + if (++entry > 1) { + assert(FALSE); + } + + /* for simplicity, this logic perhaps overly conservative */ + /* note also that this might leak memory if buffers are being + returned as a result of midiInReset */ + if (m->callback_error) { + entry--; + return ; + } + + switch (wMsg) { + case MIM_DATA: { + /* 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 happens running the + sysex.c test under Win2K with MidiMan USB 1x1 interface. + Is it a driver bug or a Win32 bug? If not, there's a bug + here somewhere. -RBD + */ + } 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); + } + break; + } + case MIM_LONGDATA: { + MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1; + unsigned char *data = lpMidiHdr->lpData; + unsigned int i = 0; + long size = sizeof(MIDIHDR) + lpMidiHdr->dwBufferLength; + /* ignore sysex data, but free returned buffers */ + if (lpMidiHdr->dwBytesRecorded > 0 && + midi->filters & PM_FILT_SYSEX) { + m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr, + sizeof(MIDIHDR)); + break; + } + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + + while (i < lpMidiHdr->dwBytesRecorded) { + /* collect bytes from *data into a word */ + pm_read_byte(midi, *data, dwParam2); + data++; + i++; + } + /* 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) { + m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr, + sizeof(MIDIHDR)); + } else { + pm_free(lpMidiHdr); + } + break; + } + case MIM_OPEN: /* fall thru */ + case MIM_CLOSE: + case MIM_ERROR: + case MIM_LONGERROR: + default: + break; + } + entry--; +} + +/* +========================================================================================= +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; +} + +#ifdef GARBAGE +static void start_sysex_buffer(LPMIDIHDR hdr, unsigned long delta) +{ + unsigned long *ptr = (unsigned long *) hdr->lpData; + *ptr++ = delta; + *ptr++ = 0; + *ptr = MEVT_F_LONG; + hdr->dwBytesRecorded = 3 * sizeof(long); +} + +static int add_byte_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, + unsigned char midi_byte) +{ + allocate message if hdr is null + send message if it is full + add byte to non - full message + unsigned char *ptr = (unsigned char *) (hdr->lpData + hdr->dwBytesRecorded); + *ptr = midi_byte; + return ++hdr->dwBytesRecorded >= hdr->dwBufferLength; +} +#endif + + +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; +} + +#ifdef GARBAGE +static unsigned long synchronize(PmInternal *midi, midiwinmm_type m) +{ + unsigned long pm_stream_time_2 = pm_time_get(m); + unsigned long real_time; + unsigned long pm_stream_time; + /* figure out the time */ + 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; +} +#endif + + +/* 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; + 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->next_buffer = 0; + m->sysex_buffers = NULL; + m->num_sysex_buffers = 0; + m->next_sysex_buffer = 0; + 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; + m->callback_error = MMSYSERR_NOERROR; + + /* create a signal */ + m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); + m->sysex_buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* this should only fail when there are very serious problems */ + assert(m->buffer_signal); + assert(m->sysex_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 */ + (DWORD) winmm_out_callback, + (DWORD) 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) winmm_streamout_callback, + (DWORD) midi, /* callback instance data */ + CALLBACK_FUNCTION); + } + if (pm_hosterror != MMSYSERR_NOERROR) { + goto free_descriptor; + } + + if (midi->latency != 0) { + long dur = 0; + /* with stream output, specified number of buffers allocated here */ + int count = midi->buffer_len; + if (count == 0) + count = midi->latency / 2; /* how many buffers to get */ + + 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 at least 3 buffers */ + if (count < 3) count = 3; + if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, count)) goto free_buffers; + if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) goto free_buffers; + /* start device */ + 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) +{ + /* 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 (m->sysex_buffer_signal) { + /* don't report errors -- better not to stop cleanup */ + CloseHandle(m->sysex_buffer_signal); + } + if (m->buffers) { + /* if using stream output, free buffers */ + int i; + for (i = 0; i < m->num_buffers; i++) { + if (m->buffers[i]) pm_free(m->buffers[i]); + } + pm_free(m->buffers); + } + + if (m->sysex_buffers) { + /* free sysex buffers */ + int i; + for (i = 0; i < m->num_sysex_buffers; i++) { + if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); + } + pm_free(m->sysex_buffers); + } + } + 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; +} + +#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) { + m->error = midiStreamOut(m->handle.stream, m->hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + m->hdr = NULL; + } + } + return rslt; +} + + +static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + PmError rslt = pmNoError; + if (midi->latency == 0) { + /* do nothing -- it's handled in winmm_write_byte */ + } else { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + /* sysex expects an empty buffer */ + if (m->hdr) { + m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + m->hdr = NULL; + } + return rslt; +} + + +static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + PmError rslt = pmNoError; + assert(m); + + if (midi->latency == 0) { + /* Not using the stream interface. The entire sysex message is + in m->hdr, and we send it using midiOutLongMsg. + */ + 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)); + if (m->error) rslt = pmHostError; + } else if (m->hdr) { + /* 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 *) m->hdr->lpData; + evt->dwEvent += m->hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + m->hdr->dwBytesRecorded = (m->hdr->dwBytesRecorded + 3) & ~3; + + m->error = midiStreamOut(m->handle.stream, m->hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + m->hdr = NULL; /* make sure we don't send it again */ + 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; + assert(m); + if (midi->latency == 0) { + /* Not using stream interface. Accumulate the entire message into + m->hdr */ + 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_sysex_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)); + + /* append byte to message */ + msg_buffer[m->sysex_byte_count++] = byte; + + /* check for overflow */ + if (m->sysex_byte_count >= m->hdr->dwBufferLength) { + rslt = resize_sysex_buffer(midi, m->sysex_byte_count, m->sysex_byte_count * 2); + + if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */ + rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */ + + } + + } else { /* latency is not zero, use stream interface: accumulate + sysex data in m->hdr and send whenever the buffer fills */ + int full; + unsigned char *ptr; + + /* if m->hdr does not exist, allocate it */ + if (m->hdr == NULL) { + 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; + + m->hdr = get_free_sysex_buffer(midi); + assert(m->hdr); + ptr = (unsigned long *) m->hdr->lpData; + *ptr++ = delta; + *ptr++ = 0; + *ptr = MEVT_F_LONG; + m->hdr->dwBytesRecorded = 3 * sizeof(long); + } + + /* add the data byte */ + ptr = (unsigned char *) (m->hdr->lpData + m->hdr->dwBytesRecorded); + *ptr = byte; + full = ++m->hdr->dwBytesRecorded >= m->hdr->dwBufferLength; + + /* see if we need to resize */ + if (full) { + int bytesRecorded = m->hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ + rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); + m->hdr->dwBytesRecorded = bytesRecorded; + + if (rslt == pmBufferMaxSize) /* if buffer can't be resized */ + rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */ + } + } + return rslt; +} + + + +static PmError winmm_write_flush(PmInternal *midi) +{ + PmError rslt = pmNoError; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + assert(m); + if (midi->latency == 0) { + /* all messages are sent immediately */ + } else if ((m->hdr) && (!midi->sysex_in_progress)) { + /* sysex messages are sent upon completion, but ordinary messages + may be sitting in a buffer + */ + m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR)); + m->hdr = NULL; + if (m->error) rslt = pmHostError; + } + return rslt; +} + +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 GARBAGE +static PmError winmm_write(PmInternal *midi, + PmEvent *buffer, + long length) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + unsigned long now; + int i; + long msg; + PmError rslt = pmNoError; + + m->error = MMSYSERR_NOERROR; + if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ + for (i = 0; (i < length) && (rslt == pmNoError); i++) { + int b = 0; /* count sysex bytes as they are handled */ + msg = buffer[i].message; + if ((msg & 0xFF) == MIDI_SYSEX) { + /* start a sysex message */ + m->sysex_mode = TRUE; + unsigned char midi_byte = (unsigned char) msg; + rslt = winmm_write_sysex_byte(midi, midi_byte); + b = 8; + } else if ((msg & 0x80) && ((msg & 0xFF) != MIDI_EOX)) { + /* a non-sysex message */ + m->error = midiOutShortMsg(m->handle.out, msg); + if (m->error) rslt = pmHostError; + /* any non-real-time message will terminate sysex message */ + if (!is_real_time(msg)) m->sysex_mode = FALSE; + } + /* transmit sysex bytes until we find EOX */ + if (m->sysex_mode) { + while (b < 32 /*bits*/ && (rslt == pmNoError)) { + unsigned char midi_byte = (unsigned char) (msg >> b); + rslt = winmm_write_sysex_byte(midi, midi_byte); + if (midi_byte == MIDI_EOX) { + b = 24; /* end of message */ + m->sysex_mode = FALSE; + } + b += 8; + } + } + } + } else { /* use midiStream interface -- pass data through buffers */ + LPMIDIHDR hdr = NULL; + now = (*midi->time_proc)(midi->time_info); + if (m->first_message || m->sync_time + 100 /*ms*/ < now) { + /* time to resync */ + now = synchronize(midi, m); + m->first_message = FALSE; + } + for (i = 0; i < length && rslt == pmNoError; i++) { + unsigned long when = buffer[i].timestamp; + unsigned long delta; + if (when == 0) when = 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 (hdr == NULL) { + /* stream interface: buffers allocated when stream is opened */ + hdr = get_free_output_buffer(midi); + assert(hdr); + if (m->sysex_mode) { + /* we are in the middle of a sysex message */ + start_sysex_buffer(hdr, delta); + } + } + msg = buffer[i].message; + if ((msg & 0xFF) == MIDI_SYSEX) { + /* sysex expects an empty buffer */ + if (hdr->dwBytesRecorded != 0) { + m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + hdr = get_free_output_buffer(midi); + assert(hdr); + } + /* when we see a MIDI_SYSEX, we always enter sysex mode and call + start_sysex_buffer() */ + start_sysex_buffer(hdr, delta); + m->sysex_mode = TRUE; + } + /* allow a non-real-time status byte to terminate sysex message */ + if (m->sysex_mode && (msg & 0x80) && (msg & 0xFF) != MIDI_SYSEX && + !is_real_time(msg)) { + /* I'm not sure what WinMM does if you send an incomplete sysex + message, but the best way out of this mess seems to be to + recreate the code used when you encounter an EOX, so ... + */ + 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; + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) { + rslt = pmHostError; + } + hdr = NULL; /* make sure we don't send it again */ + m->sysex_mode = FALSE; /* skip to normal message send code */ + } + if (m->sysex_mode) { + int b = 0; /* count bytes as they are handled */ + while (b < 32 /* bits per word */ && (rslt == pmNoError)) { + int full; + unsigned char midi_byte = (unsigned char) (msg >> b); + if (!hdr) { + hdr = get_free_output_buffer(midi); + assert(hdr); + /* get ready to put sysex bytes in buffer */ + start_sysex_buffer(hdr, delta); + } + full = add_byte_to_buffer(m, hdr, midi_byte); + if (midi_byte == MIDI_EOX) { + b = 24; /* pretend this is last byte to exit loop */ + m->sysex_mode = FALSE; + } + /* see if it's time to send buffer, note that by always + sending complete sysex message right away, we can use + this code to set up the MIDIEVENT properly + */ + if (full || midi_byte == MIDI_EOX) { + /* 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; + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) { + rslt = pmHostError; + } + hdr = NULL; /* make sure we don't send it again */ + } + b += 8; /* shift to next byte */ + } + /* test rslt here in case it was set when we terminated a sysex early + (see above) */ + } else if (rslt == pmNoError) { + int full = add_to_buffer(m, hdr, delta, msg); + if (full) { + m->error = midiStreamOut(m->handle.stream, hdr, + sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + hdr = NULL; + } + } + } + if (hdr && rslt == pmNoError) { + if (m->sysex_mode) { + 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; + } + m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR)); + if (m->error) rslt = pmHostError; + } + } + return rslt; +} +#endif + + +/* winmm_out_callback -- recycle sysex buffers */ +static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + int i; + 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 */ + static int entry = 0; + if (++entry > 1) { + assert(FALSE); + } + if (m->callback_error || wMsg != MOM_DONE) { + entry--; + return ; + } + /* 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 + */ + m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + /* notify waiting sender that a buffer is available */ + /* any errors could be reported via callback_error, but this is always + treated as a Midi error, so we'd have to write a lot more code to + detect that a non-Midi error occurred and do the right thing to find + the corresponding error message text. Therefore, just use assert() + */ + + /* determine if this is an output buffer or a sysex buffer */ + + for (i = 0 ;i < m->num_buffers;i++) { + if (hdr == m->buffers[i]) { + err = SetEvent(m->buffer_signal); + break; + } + } + for (i = 0 ;i < m->num_sysex_buffers;i++) { + if (hdr == m->sysex_buffers[i]) { + err = SetEvent(m->sysex_buffer_signal); + break; + } + } + assert(err); /* false -> error */ + entry--; +} + + +/* winmm_streamout_callback -- unprepare (free) buffer header */ +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; + int err; + static int entry = 0; + if (++entry > 1) { + /* We've reentered this routine. I assume this never happens, but + check to make sure. Apparently, it is possible that this callback + can be called reentrantly because it happened once while debugging. + It looks like this routine is actually reentrant so we can remove + the assertion if necessary. */ + assert(FALSE); + } + if (m->callback_error || wMsg != MOM_DONE) { + entry--; + return ; + } + m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + err = SetEvent(m->buffer_signal); + assert(err); /* false -> error */ + entry--; +} + + +/* +========================================================================================= +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_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(msg); + } +#endif + /* close all open ports */ + (*midi->dictionary->close)(midi); + } + } + } +#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; +} diff --git a/pd/portmidi/pm_win/pmwinmm.h b/pd/portmidi/pm_win/pmwinmm.h new file mode 100644 index 00000000..53c5fe28 --- /dev/null +++ b/pd/portmidi/pm_win/pmwinmm.h @@ -0,0 +1,5 @@ +/* midiwin32.h -- system-specific definitions */ + +void pm_winmm_init( void ); +void pm_winmm_term( void ); + diff --git a/pd/portmidi/porttime/porttime.c b/pd/portmidi/porttime/porttime.c new file mode 100644 index 00000000..71b06f4a --- /dev/null +++ b/pd/portmidi/porttime/porttime.c @@ -0,0 +1,3 @@ +/* porttime.c -- portable API for millisecond timer */ + +/* There is no machine-independent implementation code to put here */ diff --git a/pd/portmidi/porttime/porttime.dsp b/pd/portmidi/porttime/porttime.dsp new file mode 100644 index 00000000..364373c9 --- /dev/null +++ b/pd/portmidi/porttime/porttime.dsp @@ -0,0 +1,104 @@ +# Microsoft Developer Studio Project File - Name="porttime" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=porttime - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "porttime.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "porttime.mak" CFG="porttime - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "porttime - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "porttime - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "porttime - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "porttime - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "_LIB" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "porttime - Win32 Release" +# Name "porttime - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\porttime.c +# End Source File +# Begin Source File + +SOURCE=.\ptwinmm.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\porttime.h +# End Source File +# End Group +# End Target +# End Project diff --git a/pd/portmidi/porttime/porttime.h b/pd/portmidi/porttime/porttime.h new file mode 100644 index 00000000..029ea777 --- /dev/null +++ b/pd/portmidi/porttime/porttime.h @@ -0,0 +1,36 @@ +/* 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, + ptHostError = -10000, + ptAlreadyStarted, + ptAlreadyStopped, + ptInsufficientMemory +} PtError; + + +typedef long PtTimestamp; + +typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); + + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData); +PtError Pt_Stop(); +int Pt_Started(); +PtTimestamp Pt_Time(); + +#ifdef __cplusplus +} +#endif diff --git a/pd/portmidi/porttime/ptlinux.c b/pd/portmidi/porttime/ptlinux.c new file mode 100644 index 00000000..c99abf04 --- /dev/null +++ b/pd/portmidi/porttime/ptlinux.c @@ -0,0 +1,120 @@ +/* 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. + +*/ + +#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++; + 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; +} + + + diff --git a/pd/portmidi/porttime/ptmacosx_cf.c b/pd/portmidi/porttime/ptmacosx_cf.c new file mode 100644 index 00000000..c0c22a32 --- /dev/null +++ b/pd/portmidi/porttime/ptmacosx_cf.c @@ -0,0 +1,135 @@ +/* ptmacosx.c -- portable timer implementation for mac os x */ + +#include +#include +#include +#include + +#import +#import +#import +#import + +#include "porttime.h" + +#define THREAD_IMPORTANCE 30 +#define LONG_TIME 1000000000.0 + +static int time_started_flag = FALSE; +static CFAbsoluteTime startTime = 0.0; +static CFRunLoopRef timerRunLoop; + +typedef struct { + int resolution; + PtCallback *callback; + void *userData; +} PtThreadParams; + + +void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info) +{ + PtThreadParams *params = (PtThreadParams*)info; + (*params->callback)(Pt_Time(), params->userData); +} + +static void* Pt_Thread(void *p) +{ + CFTimeInterval timerInterval; + CFRunLoopTimerContext timerContext; + CFRunLoopTimerRef timer; + PtThreadParams *params = (PtThreadParams*)p; + //CFTimeInterval timeout; + + /* raise the thread's priority */ + kern_return_t error; + thread_extended_policy_data_t extendedPolicy; + thread_precedence_policy_data_t precedencePolicy; + + extendedPolicy.timeshare = 0; + error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, + (thread_policy_t)&extendedPolicy, + THREAD_EXTENDED_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread timeshare policy", error); + } + + precedencePolicy.importance = THREAD_IMPORTANCE; + error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, + (thread_policy_t)&precedencePolicy, + THREAD_PRECEDENCE_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread precedence policy", error); + } + + /* set up the timer context */ + timerContext.version = 0; + timerContext.info = params; + timerContext.retain = NULL; + timerContext.release = NULL; + timerContext.copyDescription = NULL; + + /* create a new timer */ + timerInterval = (double)params->resolution / 1000.0; + timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval, + 0, 0, Pt_CFTimerCallback, &timerContext); + + timerRunLoop = CFRunLoopGetCurrent(); + CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode")); + + /* run until we're told to stop by Pt_Stop() */ + CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false); + + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode")); + CFRelease(timer); + free(params); + + return NULL; +} + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams)); + pthread_t pthread_id; + + printf("Pt_Start() called\n"); + + // /* make sure we're not already playing */ + if (time_started_flag) return ptAlreadyStarted; + startTime = CFAbsoluteTimeGetCurrent(); + + if (callback) { + + params->resolution = resolution; + params->callback = callback; + params->userData = userData; + + pthread_create(&pthread_id, NULL, Pt_Thread, params); + } + + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop() +{ + printf("Pt_Stop called\n"); + + CFRunLoopStop(timerRunLoop); + time_started_flag = FALSE; + return ptNoError; +} + + +int Pt_Started() +{ + return time_started_flag; +} + + +PtTimestamp Pt_Time() +{ + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + return (PtTimestamp) ((now - startTime) * 1000.0); +} + diff --git a/pd/portmidi/porttime/ptmacosx_mach.c b/pd/portmidi/porttime/ptmacosx_mach.c new file mode 100644 index 00000000..935d99bb --- /dev/null +++ b/pd/portmidi/porttime/ptmacosx_mach.c @@ -0,0 +1,123 @@ +/* ptmacosx.c -- portable timer implementation for mac os x */ + +#include +#include +#include + +#import +#import +#import +#import + +#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(); + if (delay < 0) delay = 0; + wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); + wait_time += AudioGetCurrentHostTime(); + error = mach_wait_until(wait_time); + (*(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 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); +} + diff --git a/pd/portmidi/porttime/ptwinmm.c b/pd/portmidi/porttime/ptwinmm.c new file mode 100644 index 00000000..bbfebb0e --- /dev/null +++ b/pd/portmidi/porttime/ptwinmm.c @@ -0,0 +1,65 @@ +/* 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 dwUser, + DWORD dw1, DWORD 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) 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; +} + -- cgit v1.2.1