diff options
author | 2013-01-01 16:11:32 +0000 | |
---|---|---|
committer | 2013-01-01 16:11:32 +0000 | |
commit | ad80ff6c3f58529f95ca71d4ec397df6cb7eb25d (patch) | |
tree | 965b7d6f81b42ce36d685246ea1d067b88067111 /src | |
parent | e000eedfbbc6c8b5edd9f9e738e2ef12c9f326dc (diff) |
portmidi: Initial commit. [R. Belmont]
(nw: this isn't enabled to compile yet, this is just to make it easier to run the final tests on my Mac and my Windows laptop)
Diffstat (limited to 'src')
33 files changed, 8093 insertions, 1 deletions
diff --git a/src/lib/lib.mak b/src/lib/lib.mak index 180db2e5963..0a660b48d28 100644 --- a/src/lib/lib.mak +++ b/src/lib/lib.mak @@ -22,7 +22,7 @@ OBJDIRS += \ $(LIBOBJ)/libjpeg \ $(LIBOBJ)/libflac \ $(LIBOBJ)/lib7z \ - + $(LIBOBJ)/portmidi \ #------------------------------------------------- @@ -382,3 +382,50 @@ $(OBJ)/lib7z.a: $(LIB7ZOBJS) $(LIBOBJ)/lib7z/%.o: $(LIBSRC)/lib7z/%.c | $(OSPREBUILD) @echo Compiling $<... $(CC) $(CDEFS) $(7ZOPTS) $(CCOMFLAGS) $(CONLYFLAGS) -I$(LIBSRC)/lib7z/ -c $< -o $@ + +#------------------------------------------------- +# portmidi library objects +#------------------------------------------------- + +PMOPTS = + +# common objects +LIBPMOBJS = \ + $(LIBOBJ)/portmidi/portmidi.o \ + $(LIBOBJ)/portmidi/porttime.o \ + $(LIBOBJ)/portmidi/pmutil.o + +ifeq ($(TARGETOS),linux) +PMOPTS = -DPMALSA=1 + +LIBPMOBJS += \ + $(LIBOBJ)/portmidi/pmlinux.o \ + $(LIBOBJ)/portmidi/pmlinuxalsa.o \ + $(LIBOBJ)/portmidi/finddefaultlinux.o \ + $(LIBOBJ)/portmidi/ptlinux.o + +endif + +ifeq ($(TARGETOS),macosx) +LIBPMOBJS += \ + $(LIBOBJ)/portmidi/pmmac.o \ + $(LIBOBJ)/portmidi/pmmacosxcm.o \ + $(LIBOBJ)/portmidi/finddefault.o \ + $(LIBOBJ)/portmidi/readbinaryplist.o \ + $(LIBOBJ)/portmidi/ptmacosx_mach.o \ + $(LIBOBJ)/portmidi/osxsupport.o +endif + +ifeq ($(TARGETOS),win32) +LIBPMOBJS += \ + $(LIBOBJ)/portmidi/pmwin.o \ + $(LIBOBJ)/portmidi/pmwinmm.o \ + $(LIBOBJ)/portmidi/ptwinmm.o +endif + +$(OBJ)/portmidi.a: $(LIBPMOBJS) + +$(LIBOBJ)/portmidi/%.o: $(LIBSRC)/portmidi/%.c | $(OSPREBUILD) + @echo Compiling $<... + $(CC) $(CDEFS) $(PMOPTS) $(CCOMFLAGS) $(CONLYFLAGS) -I$(LIBSRC)/portmidi/ -c $< -o $@ + diff --git a/src/lib/portmidi/finddefault.c b/src/lib/portmidi/finddefault.c new file mode 100644 index 00000000000..bc95a4001a6 --- /dev/null +++ b/src/lib/portmidi/finddefault.c @@ -0,0 +1,57 @@ +/* finddefault.c -- find_default_device() implementation + Roger Dannenberg, June 2008 +*/ + +#include <stdlib.h> +#include <string.h> +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" +#include "pmmacosxcm.h" +#include "readbinaryplist.h" + +/* Parse preference files, find default device, search devices -- + This parses the preference file(s) once for input and once for + output, which is inefficient but much simpler to manage. Note + that using the readbinaryplist.c module, you cannot keep two + plist files (user and system) open at once (due to a simple + memory management scheme). +*/ +PmDeviceID find_default_device(char *path, int input, PmDeviceID id) +/* path -- the name of the preference we are searching for + input -- true iff this is an input device + id -- current default device id + returns matching device id if found, otherwise id +*/ +{ + static char *pref_file = (char *)"com.apple.java.util.prefs.plist"; + char *pref_str = NULL; + // read device preferences + value_ptr prefs = bplist_read_user_pref(pref_file); + if (prefs) { + value_ptr pref_val = value_dict_lookup_using_path(prefs, path); + if (pref_val) { + pref_str = value_get_asciistring(pref_val); + } + } + if (!pref_str) { + bplist_free_data(); /* look elsewhere */ + prefs = bplist_read_system_pref(pref_file); + if (prefs) { + value_ptr pref_val = value_dict_lookup_using_path(prefs, path); + if (pref_val) { + pref_str = value_get_asciistring(pref_val); + } + } + } + if (pref_str) { /* search devices for match */ + int i = pm_find_default_device(pref_str, input); + if (i != pmNoDevice) { + id = i; + } + } + if (prefs) { + bplist_free_data(); + } + return id; +} diff --git a/src/lib/portmidi/finddefaultlinux.c b/src/lib/portmidi/finddefaultlinux.c new file mode 100644 index 00000000000..8a92b6ebe0a --- /dev/null +++ b/src/lib/portmidi/finddefaultlinux.c @@ -0,0 +1,94 @@ +/* finddefault.c -- find_default_device() implementation + Roger Dannenberg, Jan 2009 +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include "portmidi.h" + +extern int pm_find_default_device(char *pattern, int is_input); + +#define STRING_MAX 256 + +/* skip over spaces, return first non-space */ +void skip_spaces(FILE *inf) +{ + char c; + while (isspace(c = getc(inf))) ; + ungetc(c, inf); +} + +/* trim leading spaces and match a string */ +int match_string(FILE *inf, char *s) +{ + skip_spaces(inf); + while (*s && *s == getc(inf)) s++; + return (*s == 0); +} + + +/* Parse preference files, find default device, search devices -- + */ +PmDeviceID find_default_device(char *path, int input, PmDeviceID id) +/* path -- the name of the preference we are searching for + input -- true iff this is an input device + id -- current default device id + returns matching device id if found, otherwise id +*/ +{ + static char *pref_2 = (char *)"/.java/.userPrefs/"; + static char *pref_3 = (char *)"prefs.xml"; + char *pref_1 = getenv("HOME"); + char *full_name, *path_ptr; + FILE *inf; + int c, i; + if (!pref_1) goto nopref; // cannot find preference file + // full_name will be larger than necessary + full_name = malloc(strlen(pref_1) + strlen(pref_2) + strlen(pref_3) + + strlen(path) + 2); + strcpy(full_name, pref_1); + strcat(full_name, pref_2); + // copy all but last path segment to full_name + if (*path == '/') path++; // skip initial slash in path + path_ptr = strrchr(path, '/'); + if (path_ptr) { // copy up to slash after full_name + path_ptr++; + int offset = strlen(full_name); + memcpy(full_name + offset, path, path_ptr - path); + full_name[offset + path_ptr - path] = 0; // end of string + } else { + path_ptr = path; + } + strcat(full_name, pref_3); + inf = fopen(full_name, "r"); + if (!inf) goto nopref; // cannot open preference file + // We're not going to build or link in a full XML parser. + // Instead, find the path string and quoute. Then, look for + // "value", "=", quote. Then get string up to quote. + while ((c = getc(inf)) != EOF) { + char pref_str[STRING_MAX]; + if (c != '"') continue; // scan up to quote + // look for quote string quote + if (!match_string(inf, path_ptr)) continue; // path not found + if (getc(inf) != '"') continue; // path not found, keep scanning + if (!match_string(inf, (char *)"value")) goto nopref; // value not found + if (!match_string(inf, (char *)"=")) goto nopref; // = not found + if (!match_string(inf, (char *)"\"")) goto nopref; // quote not found + // now read the value up to the close quote + for (i = 0; i < STRING_MAX; i++) { + if ((c = getc(inf)) == '"') break; + pref_str[i] = c; + } + if (i == STRING_MAX) continue; // value too long, ignore + pref_str[i] = 0; + i = pm_find_default_device(pref_str, input); + if (i != pmNoDevice) { + id = i; + } + break; + } + nopref: + return id; +} diff --git a/src/lib/portmidi/osxsupport.h b/src/lib/portmidi/osxsupport.h new file mode 100644 index 00000000000..7cbe1f5a4e8 --- /dev/null +++ b/src/lib/portmidi/osxsupport.h @@ -0,0 +1,19 @@ +/* + osxsupport.h - Cocoa glue to emulated deprecated old Carbon path finder functions +*/ + +#ifndef _OSXSUPPORT_H_ +#define _OSXSUPPORT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +char *FindPrefsDir(void); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/lib/portmidi/osxsupport.m b/src/lib/portmidi/osxsupport.m new file mode 100644 index 00000000000..cb182870865 --- /dev/null +++ b/src/lib/portmidi/osxsupport.m @@ -0,0 +1,30 @@ +/* + osxsupport.m - Cocoa glue to emulated deprecated old Carbon path finder functions +*/ + +#import <Cocoa/Cocoa.h> +#import <AvailabilityMacros.h> +#include "osxsupport.h" + +// convert an NSString to a C string +static char *StringToChar(NSString *str) +{ + const char *charstr = [str UTF8String]; + char *resstr = (char *)malloc(strlen(charstr)+1); + + strcpy(resstr, charstr); + return resstr; +} + +char *FindPrefsDir(void) +{ + char *resstr = NULL; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSPreferencePanesDirectory, NSUserDomainMask, YES); + + if ([paths count] > 0) + { + resstr = StringToChar([paths objectAtIndex:0]) ; + } + + return resstr; +} diff --git a/src/lib/portmidi/pminternal.h b/src/lib/portmidi/pminternal.h new file mode 100644 index 00000000000..6b6242026dd --- /dev/null +++ b/src/lib/portmidi/pminternal.h @@ -0,0 +1,178 @@ +/* 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 + +extern int pm_initialized; /* see note in portmidi.c */ + +/* these are defined in system-specific file */ +void *pm_alloc(size_t s); +void pm_free(void *ptr); + +/* if an error occurs while opening or closing a midi stream, set these: */ +extern int pm_hosterror; +extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; + +struct pm_internal_struct; + +/* these do not use PmInternal because it is not defined yet... */ +typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, + PmEvent *buffer); +typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi, + PmTimestamp timestamp); +typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi, + PmTimestamp timestamp); +typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi, + unsigned char byte, PmTimestamp timestamp); +typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi, + PmEvent *buffer); +typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi, + PmTimestamp timestamp); +typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi); +/* pm_open_fn should clean up all memory and close the device if any part + of the open fails */ +typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi, + void *driverInfo); +typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi); +/* pm_close_fn should clean up all memory and close the device if any + part of the close fails. */ +typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi); +typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi); +typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg, + unsigned int len); +typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi); + +typedef struct { + pm_write_short_fn write_short; /* output short MIDI msg */ + pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */ + pm_end_sysex_fn end_sysex; /* marks end of sysex message */ + pm_write_byte_fn write_byte; /* accumulate one more sysex byte */ + pm_write_realtime_fn write_realtime; /* send real-time message within sysex */ + pm_write_flush_fn write_flush; /* send any accumulated but unsent data */ + pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */ + pm_open_fn open; /* open MIDI device */ + pm_abort_fn abort; /* abort */ + pm_close_fn close; /* close device */ + pm_poll_fn poll; /* read pending midi events into portmidi buffer */ + pm_has_host_error_fn has_host_error; /* true when device has had host + error message */ + pm_host_error_fn host_error; /* provide text readable host error message + for device (clears and resets) */ +} pm_fns_node, *pm_fns_type; + + +/* when open fails, the dictionary gets this set of functions: */ +extern pm_fns_node pm_none_dictionary; + +typedef struct { + PmDeviceInfo pub; /* some portmidi state also saved in here (for autmatic + device closing (see PmDeviceInfo struct) */ + void *descriptor; /* ID number passed to win32 multimedia API open */ + void *internalDescriptor; /* points to PmInternal device, allows automatic + device closing */ + pm_fns_type dictionary; +} descriptor_node, *descriptor_type; + +extern int pm_descriptor_max; +extern descriptor_type descriptors; +extern int pm_descriptor_index; + +typedef uint32_t (*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() */ + int32_t buffer_len; /* how big is the buffer or queue? */ + PmQueue *queue; + + int32_t latency; /* time delay in ms between timestamps and actual output */ + /* set to zero to get immediate, simple blocking output */ + /* if latency is zero, timestamps will be ignored; */ + /* if midi input device, this field ignored */ + + int sysex_in_progress; /* when sysex status is seen, this flag becomes + * true until EOX is seen. When true, new data is appended to the + * stream of outgoing bytes. When overflow occurs, sysex data is + * dropped (until an EOX or non-real-timei status byte is seen) so + * that, if the overflow condition is cleared, we don't start + * sending data from the middle of a sysex message. If a sysex + * message is filtered, sysex_in_progress is false, causing the + * message to be dropped. */ + PmMessage sysex_message; /* buffer for 4 bytes of sysex data */ + int sysex_message_count; /* how many bytes in sysex_message so far */ + + int32_t filters; /* flags that filter incoming message classes */ + int32_t channel_mask; /* filter incoming messages based on channel */ + PmTimestamp last_msg_time; /* timestamp of last message */ + PmTimestamp sync_time; /* time of last synchronization */ + PmTimestamp now; /* set by PmWrite to current time */ + int first_message; /* initially true, used to run first synchronization */ + pm_fns_type dictionary; /* implementation functions */ + void *descriptor; /* system-dependent state */ + /* the following are used to expedite sysex data */ + /* on windows, in debug mode, based on some profiling, these optimizations + * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte, + * but this does not count time in the driver, so I don't know if it is + * important + */ + unsigned char *fill_base; /* addr of ptr to sysex data */ + uint32_t *fill_offset_ptr; /* offset of next sysex byte */ + int32_t fill_length; /* how many sysex bytes to write */ +} PmInternal; + + +/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */ +void pm_init(void); +void pm_term(void); + +/* defined by portMidi, used by pmwinmm */ +PmError none_write_short(PmInternal *midi, PmEvent *buffer); +PmError none_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp); +PmTimestamp none_synchronize(PmInternal *midi); + +PmError pm_fail_fn(PmInternal *midi); +PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); +PmError pm_success_fn(PmInternal *midi); +PmError pm_add_device(char *interf, char *name, int input, void *descriptor, + pm_fns_type dictionary); +uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, + PmTimestamp timestamp); +void pm_read_short(PmInternal *midi, PmEvent *event); + +#define none_write_flush pm_fail_timestamp_fn +#define none_sysex pm_fail_timestamp_fn +#define none_poll pm_fail_fn +#define success_poll pm_success_fn + +#define MIDI_REALTIME_MASK 0xf8 +#define is_real_time(msg) \ + ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK) + +int pm_find_default_device(char *pattern, int is_input); + +#ifdef __cplusplus +} +#endif + diff --git a/src/lib/portmidi/pmlinux.c b/src/lib/portmidi/pmlinux.c new file mode 100644 index 00000000000..4e2196294bb --- /dev/null +++ b/src/lib/portmidi/pmlinux.c @@ -0,0 +1,75 @@ +/* pmlinux.c -- PortMidi os-dependent code */ + +/* This file only needs to implement pm_init(), which calls various + routines to register the available midi devices. This file must + be separate from the main portmidi.c file because it is system + dependent, and it is separate from, pmlinuxalsa.c, because it + might need to register non-alsa devices as well. + + NOTE: if you add non-ALSA support, you need to fix :alsa_poll() + in pmlinuxalsa.c, which assumes all input devices are ALSA. + */ + +#include "stdlib.h" +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" + +#ifdef PMALSA + #include "pmlinuxalsa.h" +#endif + +#ifdef PMNULL + #include "pmlinuxnull.h" +#endif + +PmDeviceID pm_default_input_device_id = -1; +PmDeviceID pm_default_output_device_id = -1; + +extern PmDeviceID find_default_device(char *path, int input, PmDeviceID id); + +void pm_init() +{ + /* Note: it is not an error for PMALSA to fail to initialize. + * It may be a design error that the client cannot query what subsystems + * are working properly other than by looking at the list of available + * devices. + */ + #ifdef PMALSA + pm_linuxalsa_init(); + #endif + #ifdef PMNULL + pm_linuxnull_init(); + #endif + // this is set when we return to Pm_Initialize, but we need it + // now in order to (successfully) call Pm_CountDevices() + pm_initialized = TRUE; + pm_default_input_device_id = find_default_device( + (char *)"/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE, + pm_default_input_device_id); + pm_default_output_device_id = find_default_device( + (char *)"/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE, + pm_default_output_device_id); +} + +void pm_term(void) +{ + #ifdef PMALSA + pm_linuxalsa_term(); + #endif +} + +PmDeviceID Pm_GetDefaultInputDeviceID() { + Pm_Initialize(); + return pm_default_input_device_id; +} + +PmDeviceID Pm_GetDefaultOutputDeviceID() { + Pm_Initialize(); + 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/src/lib/portmidi/pmlinux.h b/src/lib/portmidi/pmlinux.h new file mode 100644 index 00000000000..4042cce8245 --- /dev/null +++ b/src/lib/portmidi/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/src/lib/portmidi/pmlinuxalsa.c b/src/lib/portmidi/pmlinuxalsa.c new file mode 100644 index 00000000000..1e69ea00e60 --- /dev/null +++ b/src/lib/portmidi/pmlinuxalsa.c @@ -0,0 +1,787 @@ +/* + * pmlinuxalsa.c -- system specific definitions + * + * written by: + * Roger Dannenberg (port to Alsa 0.9.x) + * Clemens Ladisch (provided code examples and invaluable consulting) + * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation) + */ + +#include "stdlib.h" +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" +#include "pmlinuxalsa.h" +#include "string.h" +#include "porttime.h" +#include "pmlinux.h" +#include "osdcomm.h" + +#ifdef PTR64 +typedef UINT64 FPTR; +#else +typedef UINT32 FPTR; +#endif + +#include <alsa/asoundlib.h> + +/* I used many print statements to debug this code. I left them in the + * source, and you can turn them on by changing false to true below: + */ +#define VERBOSE_ON 0 +#define VERBOSE if (VERBOSE_ON) + +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 + +#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9 +#error needs ALSA 0.9.0 or later +#endif + +/* to store client/port in the device descriptor */ + +#define MAKE_DESCRIPTOR(client, port) ((void*)(FPTR)(((client) << 8) | (port))) +#define GET_DESCRIPTOR_CLIENT(info) ((((int)(FPTR)(info)) >> 8) & 0xff) +#define GET_DESCRIPTOR_PORT(info) (((int)(FPTR)(info)) & 0xff) + +#define BYTE unsigned char + +extern pm_fns_node pm_linuxalsa_in_dictionary; +extern pm_fns_node pm_linuxalsa_out_dictionary; + +static snd_seq_t *seq = NULL; // all input comes here, + // output queue allocated on seq +static int queue, queue_used; /* one for all ports, reference counted */ + +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()); + int when = timestamp; + /* if timestamp is zero, send immediately */ + /* otherwise compute time delay and use delay if positive */ + if (when == 0) when = now; + when = (when - now) + midi->latency; + if (when < 0) when = 0; + VERBOSE printf("timestamp %d now %d latency %d, ", + (int) timestamp, (int) now, midi->latency); + VERBOSE printf("scheduling event after %d\n", when); + /* message is sent in relative ticks, where 1 tick = 1 ms */ + snd_seq_ev_schedule_tick(&ev, queue, 1, when); + /* NOTE: for cases where the user does not supply a time function, + we could optimize the code by not starting Pt_Time and using + the alsa tick time instead. I didn't do this because it would + entail changing the queue management to start the queue tick + count when PortMidi is initialized and keep it running until + PortMidi is terminated. (This should be simple, but it's not + how the code works now.) -RBD */ + } else { /* send event out without queueing */ + VERBOSE printf("direct\n"); + /* ev.queue = SND_SEQ_QUEUE_DIRECT; + ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */ + snd_seq_ev_set_direct(&ev); + } + VERBOSE printf("sending event\n"); + err = snd_seq_event_output(seq, &ev); + if (err < 0) { + desc->error = err; + return pmHostError; + } + } + return pmNoError; +} + + +static PmError alsa_out_close(PmInternal *midi) +{ + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + if (!desc) return pmBadPtr; + + if ((pm_hosterror = snd_seq_disconnect_to(seq, desc->this_port, + desc->client, desc->port))) { + // if there's an error, try to delete the port anyway, but don't + // change the pm_hosterror value so we retain the first error + snd_seq_delete_port(seq, desc->this_port); + } else { // if there's no error, delete the port and retain any error + pm_hosterror = snd_seq_delete_port(seq, desc->this_port); + } + if (midi->latency > 0) alsa_unuse_queue(); + snd_midi_event_free(desc->parser); + midi->descriptor = NULL; /* destroy the pointer to signify "closed" */ + pm_free(desc); + if (pm_hosterror) { + get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, + pm_hosterror); + return pmHostError; + } + return pmNoError; +} + + +static PmError alsa_in_open(PmInternal *midi, void *driverInfo) +{ + void *client_port = descriptors[midi->device_id].descriptor; + alsa_descriptor_type desc = (alsa_descriptor_type) + pm_alloc(sizeof(alsa_descriptor_node)); + snd_seq_port_info_t *info; + snd_seq_port_subscribe_t *sub; + snd_seq_addr_t addr; + int err; + + if (!desc) return pmInsufficientMemory; + + err = alsa_use_queue(); + if (err < 0) goto free_desc; + + snd_seq_port_info_alloca(&info); + snd_seq_port_info_set_port(info, midi->device_id); + snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_READ); + snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_port_specified(info, 1); + err = snd_seq_create_port(seq, info); + if (err < 0) goto free_queue; + + /* fill in fields of desc, which is passed to pm_write routines */ + midi->descriptor = desc; + desc->client = GET_DESCRIPTOR_CLIENT(client_port); + desc->port = GET_DESCRIPTOR_PORT(client_port); + desc->this_port = midi->device_id; + desc->in_sysex = 0; + + desc->error = 0; + + VERBOSE printf("snd_seq_connect_from: %d %d %d\n", + desc->this_port, desc->client, desc->port); + snd_seq_port_subscribe_alloca(&sub); + addr.client = snd_seq_client_id(seq); + addr.port = desc->this_port; + snd_seq_port_subscribe_set_dest(sub, &addr); + addr.client = desc->client; + addr.port = desc->port; + snd_seq_port_subscribe_set_sender(sub, &addr); + snd_seq_port_subscribe_set_time_update(sub, 1); + /* this doesn't seem to work: messages come in with real timestamps */ + snd_seq_port_subscribe_set_time_real(sub, 0); + err = snd_seq_subscribe_port(seq, sub); + /* err = + snd_seq_connect_from(seq, desc->this_port, desc->client, desc->port); */ + if (err < 0) goto free_this_port; /* clean up and return on error */ + return pmNoError; + + free_this_port: + snd_seq_delete_port(seq, desc->this_port); + free_queue: + alsa_unuse_queue(); + free_desc: + pm_free(desc); + pm_hosterror = err; + if (err < 0) { + get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); + } + return pmHostError; +} + +static PmError alsa_in_close(PmInternal *midi) +{ + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + if (!desc) return pmBadPtr; + if ((pm_hosterror = snd_seq_disconnect_from(seq, desc->this_port, + desc->client, desc->port))) { + snd_seq_delete_port(seq, desc->this_port); /* try to close port */ + } else { + pm_hosterror = snd_seq_delete_port(seq, desc->this_port); + } + alsa_unuse_queue(); + pm_free(desc); + if (pm_hosterror) { + get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, + pm_hosterror); + return pmHostError; + } + return pmNoError; +} + + +static PmError alsa_abort(PmInternal *midi) +{ + /* NOTE: ALSA documentation is vague. This is supposed to + * remove any pending output messages. If you can test and + * confirm this code is correct, please update this comment. -RBD + */ + /* Unfortunately, I can't even compile it -- my ALSA version + * does not implement snd_seq_remove_events_t, so this does + * not compile. I'll try again, but it looks like I'll need to + * upgrade my entire Linux OS -RBD + */ + /* + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + snd_seq_remove_events_t info; + snd_seq_addr_t addr; + addr.client = desc->client; + addr.port = desc->port; + snd_seq_remove_events_set_dest(&info, &addr); + snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST); + pm_hosterror = snd_seq_remove_events(seq, &info); + if (pm_hosterror) { + get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, + pm_hosterror); + return pmHostError; + } + */ + printf("WARNING: alsa_abort not implemented\n"); + return pmNoError; +} + + +#ifdef GARBAGE +This is old code here temporarily for reference +static PmError alsa_write(PmInternal *midi, PmEvent *buffer, int32_t length) +{ + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + int i, bytes; + unsigned char byte; + PmMessage 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", (unsigned int) seq); + desc->error = snd_seq_drain_output(seq); + if (desc->error < 0) return pmHostError; + + desc->error = pmNoError; + return pmNoError; +} +#endif + + +static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp) +{ + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + VERBOSE printf("snd_seq_drain_output: 0x%x\n", (unsigned int)(FPTR) 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); + PmMessage msg = event->message; + int i; + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + for (i = 0; i < bytes; i++) { + unsigned char byte = msg; + VERBOSE printf("sending 0x%x\n", byte); + alsa_write_byte(midi, byte, event->timestamp); + if (desc->error < 0) break; + msg >>= 8; /* shift next byte into position */ + } + if (desc->error < 0) return pmHostError; + desc->error = pmNoError; + return pmNoError; +} + + +/* alsa_sysex -- implements begin_sysex and end_sysex */ +PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) { + return pmNoError; +} + + +static PmTimestamp alsa_synchronize(PmInternal *midi) +{ + return 0; /* linux implementation does not use this synchronize function */ + /* Apparently, Alsa data is relative to the time you send it, and there + is no reference. If this is true, this is a serious shortcoming of + Alsa. If not true, then PortMidi has a serious shortcoming -- it + should be scheduling relative to Alsa's time reference. */ +} + + +static void handle_event(snd_seq_event_t *ev) +{ + int device_id = ev->dest.port; + PmInternal *midi = descriptors[device_id].internalDescriptor; + PmEvent pm_ev; + PmTimeProcPtr time_proc = midi->time_proc; + PmTimestamp timestamp; + + /* time stamp should be in ticks, using our queue where 1 tick = 1ms */ + assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK); + + /* if no time_proc, just return "native" ticks (ms) */ + if (time_proc == NULL) { + timestamp = ev->time.tick; + } else { /* translate time to time_proc basis */ + snd_seq_queue_status_t *queue_status; + snd_seq_queue_status_alloca(&queue_status); + snd_seq_get_queue_status(seq, queue, queue_status); + /* return (now - alsa_now) + alsa_timestamp */ + timestamp = (*time_proc)(midi->time_info) + ev->time.tick - + snd_seq_queue_status_get_tick_time(queue_status); + } + pm_ev.timestamp = timestamp; + switch (ev->type) { + case SND_SEQ_EVENT_NOTEON: + pm_ev.message = Pm_Message(0x90 | ev->data.note.channel, + ev->data.note.note & 0x7f, + ev->data.note.velocity & 0x7f); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_NOTEOFF: + pm_ev.message = Pm_Message(0x80 | ev->data.note.channel, + ev->data.note.note & 0x7f, + ev->data.note.velocity & 0x7f); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_KEYPRESS: + pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel, + ev->data.note.note & 0x7f, + ev->data.note.velocity & 0x7f); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_CONTROLLER: + pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, + ev->data.control.param & 0x7f, + ev->data.control.value & 0x7f); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_PGMCHANGE: + pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel, + ev->data.control.value & 0x7f, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_CHANPRESS: + pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel, + ev->data.control.value & 0x7f, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_PITCHBEND: + pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel, + (ev->data.control.value + 0x2000) & 0x7f, + ((ev->data.control.value + 0x2000) >> 7) & 0x7f); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_CONTROL14: + if (ev->data.control.param < 0x20) { + pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, + ev->data.control.param, + (ev->data.control.value >> 7) & 0x7f); + pm_read_short(midi, &pm_ev); + pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, + ev->data.control.param + 0x20, + ev->data.control.value & 0x7f); + pm_read_short(midi, &pm_ev); + } else { + pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, + ev->data.control.param & 0x7f, + ev->data.control.value & 0x7f); + + pm_read_short(midi, &pm_ev); + } + break; + case SND_SEQ_EVENT_SONGPOS: + pm_ev.message = Pm_Message(0xf2, + ev->data.control.value & 0x7f, + (ev->data.control.value >> 7) & 0x7f); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_SONGSEL: + pm_ev.message = Pm_Message(0xf3, + ev->data.control.value & 0x7f, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_QFRAME: + pm_ev.message = Pm_Message(0xf1, + ev->data.control.value & 0x7f, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_START: + pm_ev.message = Pm_Message(0xfa, 0, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_CONTINUE: + pm_ev.message = Pm_Message(0xfb, 0, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_STOP: + pm_ev.message = Pm_Message(0xfc, 0, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_CLOCK: + pm_ev.message = Pm_Message(0xf8, 0, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_TUNE_REQUEST: + pm_ev.message = Pm_Message(0xf6, 0, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_RESET: + pm_ev.message = Pm_Message(0xff, 0, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_SENSING: + pm_ev.message = Pm_Message(0xfe, 0, 0); + pm_read_short(midi, &pm_ev); + break; + case SND_SEQ_EVENT_SYSEX: { + const BYTE *ptr = (const BYTE *) ev->data.ext.ptr; + /* assume there is one sysex byte to process */ + pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp); + break; + } + } +} + + +static PmError alsa_poll(PmInternal *midi) +{ + snd_seq_event_t *ev; + /* expensive check for input data, gets data from device: */ + while (snd_seq_event_input_pending(seq, TRUE) > 0) { + /* cheap check on local input buffer */ + while (snd_seq_event_input_pending(seq, FALSE) > 0) { + /* check for and ignore errors, e.g. input overflow */ + /* note: if there's overflow, this should be reported + * all the way through to client. Since input from all + * devices is merged, we need to find all input devices + * and set all to the overflow state. + * NOTE: this assumes every input is ALSA based. + */ + int rslt = snd_seq_event_input(seq, &ev); + if (rslt >= 0) { + handle_event(ev); + } else if (rslt == -ENOSPC) { + int i; + for (i = 0; i < pm_descriptor_index; i++) { + if (descriptors[i].pub.input) { + PmInternal *midi = (PmInternal *) + descriptors[i].internalDescriptor; + /* careful, device may not be open! */ + if (midi) Pm_SetOverflow(midi->queue); + } + } + } + } + } + return pmNoError; +} + + +static unsigned int alsa_has_host_error(PmInternal *midi) +{ + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + return desc->error; +} + + +static void alsa_get_host_error(PmInternal *midi, char *msg, unsigned int len) +{ + alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + int err = (pm_hosterror || desc->error); + get_alsa_error_text(msg, len, err); +} + + +pm_fns_node pm_linuxalsa_in_dictionary = { + none_write_short, + none_sysex, + none_sysex, + none_write_byte, + none_write_short, + none_write_flush, + alsa_synchronize, + alsa_in_open, + alsa_abort, + alsa_in_close, + alsa_poll, + alsa_has_host_error, + alsa_get_host_error +}; + +pm_fns_node pm_linuxalsa_out_dictionary = { + alsa_write_short, + alsa_sysex, + alsa_sysex, + alsa_write_byte, + alsa_write_short, /* short realtime message */ + alsa_write_flush, + alsa_synchronize, + alsa_out_open, + alsa_abort, + alsa_out_close, + none_poll, + alsa_has_host_error, + alsa_get_host_error +}; + + +/* pm_strdup -- copy a string to the heap. Use this rather than strdup so + * that we call pm_alloc, not malloc. This allows portmidi to avoid + * malloc which might cause priority inversion. Probably ALSA is going + * to call malloc anyway, so this extra work here may be pointless. + */ +char *pm_strdup(const char *s) +{ + int len = strlen(s); + char *dup = (char *) pm_alloc(len + 1); + strcpy(dup, s); + return dup; +} + + +PmError pm_linuxalsa_init( void ) +{ + int err; + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + unsigned int caps; + + /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this + * would cause messages to be dropped if the ALSA buffer fills up. + * The correct behavior is for writes to block until there is + * room to send all the data. The client should normally allocate + * a large enough buffer to avoid blocking on output. + * Now that blocking is enabled, the seq_event_input() will block + * if there is no input data. This is not what we want, so must + * call seq_event_input_pending() to avoid blocking. + */ + err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); + if (err < 0) return err; + + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_alloca(&pinfo); + + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(seq, cinfo) == 0) { + snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(seq, pinfo) == 0) { + if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM) + continue; /* ignore Timer and Announce ports on client 0 */ + caps = snd_seq_port_info_get_capability(pinfo); + if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE))) + continue; /* ignore if you cannot read or write port */ + if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) { + if (pm_default_output_device_id == -1) + pm_default_output_device_id = pm_descriptor_index; + pm_add_device((char *)"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((char *)"ALSA", + pm_strdup(snd_seq_port_info_get_name(pinfo)), + TRUE, + MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), + snd_seq_port_info_get_port(pinfo)), + &pm_linuxalsa_in_dictionary); + } + } + } + return pmNoError; +} + + +void pm_linuxalsa_term(void) +{ + if (seq) { + snd_seq_close(seq); + pm_free(descriptors); + descriptors = NULL; + pm_descriptor_index = 0; + pm_descriptor_max = 0; + } +} diff --git a/src/lib/portmidi/pmlinuxalsa.h b/src/lib/portmidi/pmlinuxalsa.h new file mode 100644 index 00000000000..c16f61d391a --- /dev/null +++ b/src/lib/portmidi/pmlinuxalsa.h @@ -0,0 +1,6 @@ +/* pmlinuxalsa.h -- system-specific definitions */ + +PmError pm_linuxalsa_init(void); +void pm_linuxalsa_term(void); + + diff --git a/src/lib/portmidi/pmmac.c b/src/lib/portmidi/pmmac.c new file mode 100644 index 00000000000..427c0378ea3 --- /dev/null +++ b/src/lib/portmidi/pmmac.c @@ -0,0 +1,59 @@ +/* pmmac.c -- PortMidi os-dependent code */ + +/* This file only needs to implement: +pm_init(), which calls various routines to register the +available midi devices, +Pm_GetDefaultInputDeviceID(), and +Pm_GetDefaultOutputDeviceID(). +It is seperate from pmmacosxcm because we might want to register +non-CoreMIDI devices. +*/ + +#include "stdlib.h" +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" +#include "pmmacosxcm.h" + +PmDeviceID pm_default_input_device_id = -1; +PmDeviceID pm_default_output_device_id = -1; + +void pm_init() +{ + PmError err = pm_macosxcm_init(); + // this is set when we return to Pm_Initialize, but we need it + // now in order to (successfully) call Pm_CountDevices() + pm_initialized = TRUE; + if (!err) { + pm_default_input_device_id = find_default_device( + (char *)"/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE, + pm_default_input_device_id); + pm_default_output_device_id = find_default_device( + (char *)"/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE, + pm_default_output_device_id); + } +} + + +void pm_term(void) +{ + pm_macosxcm_term(); +} + + +PmDeviceID Pm_GetDefaultInputDeviceID() +{ + Pm_Initialize(); + return pm_default_input_device_id; +} + +PmDeviceID Pm_GetDefaultOutputDeviceID() { + Pm_Initialize(); + 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/src/lib/portmidi/pmmac.h b/src/lib/portmidi/pmmac.h new file mode 100644 index 00000000000..6cc0392c3f4 --- /dev/null +++ b/src/lib/portmidi/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/src/lib/portmidi/pmmacosxcm.c b/src/lib/portmidi/pmmacosxcm.c new file mode 100644 index 00000000000..71177dded11 --- /dev/null +++ b/src/lib/portmidi/pmmacosxcm.c @@ -0,0 +1,1010 @@ +/* + * Platform interface to the MacOS X CoreMIDI framework + * + * Jon Parise <jparise at cmu.edu> + * and subsequent work by Andrew Zeldis and Zico Kolter + * and Roger B. Dannenberg + * + * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon 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. We use a structure with two kinds of + host error: "error" and "callback_error". That way, asynchronous callbacks + do not interfere with other error information. + + OS X does not seem to have an error-code-to-text function, so we will + just use text messages instead of error codes. + */ + +#include <stdlib.h> + +//#define CM_DEBUG 1 + +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" +#include "porttime.h" +#include "pmmac.h" +#include "pmmacosxcm.h" + +#include <stdio.h> +#include <string.h> + +#include <CoreServices/CoreServices.h> +#include <CoreMIDI/MIDIServices.h> +#include <CoreAudio/HostTime.h> +#include <unistd.h> + +#define PACKET_BUFFER_SIZE 1024 +/* maximum overall data rate (OS X limit is 15000 bytes/second) */ +#define MAX_BYTES_PER_S 14000 + +/* Apple reports that packets are dropped when the MIDI bytes/sec + exceeds 15000. This is computed by "tracking the number of MIDI + bytes scheduled into 1-second buckets over the last six seconds + and averaging these counts." + + This is apparently based on timestamps, not on real time, so + we have to avoid constructing packets that schedule high speed + output even if the actual writes are delayed (which was my first + solution). + + The LIMIT_RATE symbol, if defined, enables code to modify + timestamps as follows: + After each packet is formed, the next allowable timestamp is + computed as this_packet_time + this_packet_len * delay_per_byte + + This is the minimum timestamp allowed in the next packet. + + Note that this distorts accurate timestamps somewhat. + */ +#define LIMIT_RATE 1 + +#define SYSEX_BUFFER_SIZE 128 + +#define VERBOSE_ON 1 +#define VERBOSE if (VERBOSE_ON) + +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 +#define MIDI_STATUS_MASK 0x80 + +// "Ref"s are pointers on 32-bit machines and ints on 64 bit machines +// NULL_REF is our representation of either 0 or NULL +#ifdef __LP64__ +#define NULL_REF 0 +#else +#define NULL_REF NULL +#endif + +static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */ +static MIDIPortRef portIn = NULL_REF; /* Input port handle */ +static MIDIPortRef portOut = NULL_REF; /* Output port handle */ + +extern pm_fns_node pm_macosx_in_dictionary; +extern pm_fns_node pm_macosx_out_dictionary; + +typedef struct midi_macosxcm_struct { + PmTimestamp 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 in host units*/ + int first_message; /* tells midi_write to sychronize timestamps */ + int sysex_mode; /* middle of sending sysex */ + uint32_t sysex_word; /* accumulate data when receiving sysex */ + uint32_t sysex_byte_count; /* count how many received */ + char error[PM_HOST_ERROR_MSG_LEN]; + char callback_error[PM_HOST_ERROR_MSG_LEN]; + Byte packetBuffer[PACKET_BUFFER_SIZE]; + MIDIPacketList *packetList; /* a pointer to packetBuffer */ + MIDIPacket *packet; + Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */ + MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */ + /* allow for running status (is running status possible here? -rbd): -cpr */ + unsigned char last_command; + int32_t last_msg_length; + /* limit midi data rate (a CoreMidi requirement): */ + UInt64 min_next_time; /* when can the next send take place? */ + int byte_count; /* how many bytes in the next packet list? */ + Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */ + UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */ +} 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(int32_t msg) +{ + int status, high, low; + static int high_lengths[] = { + 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */ + 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */ + }; + static int low_lengths[] = { + 1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */ + 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */ + }; + + status = msg & 0xFF; + high = status >> 4; + low = status & 15; + + return (high != 0xF) ? high_lengths[high] : low_lengths[low]; +} + +static PmTimestamp midi_synchronize(PmInternal *midi) +{ + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + UInt64 pm_stream_time_2 = + AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); + PmTimestamp real_time; + UInt64 pm_stream_time; + /* if latency is zero and this is an output, there is no + time reference and midi_synchronize should never be called */ + assert(midi->time_proc); + assert(!(midi->write_flag && midi->latency == 0)); + do { + /* read real_time between two reads of stream time */ + pm_stream_time = pm_stream_time_2; + real_time = (*midi->time_proc)(midi->time_info); + pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); + /* repeat if more than 0.5 ms has elapsed */ + } while (pm_stream_time_2 > pm_stream_time + 500000); + m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000); + m->sync_time = real_time; + return real_time; +} + + +static void +process_packet(MIDIPacket *packet, PmEvent *event, + PmInternal *midi, midi_macosxcm_type m) +{ + /* handle a packet of MIDI messages from CoreMIDI */ + /* there may be multiple short messages in one packet (!) */ + unsigned int remaining_length = packet->length; + unsigned char *cur_packet_data = packet->data; + while (remaining_length > 0) { + if (cur_packet_data[0] == MIDI_SYSEX || + /* are we in the middle of a sysex message? */ + (m->last_command == 0 && + !(cur_packet_data[0] & MIDI_STATUS_MASK))) { + m->last_command = 0; /* no running status */ + unsigned int amt = pm_read_bytes(midi, cur_packet_data, + remaining_length, + event->timestamp); + remaining_length -= amt; + cur_packet_data += amt; + } else if (cur_packet_data[0] == MIDI_EOX) { + /* this should never happen, because pm_read_bytes should + * get and read all EOX bytes*/ + midi->sysex_in_progress = FALSE; + m->last_command = 0; + } else if (cur_packet_data[0] & MIDI_STATUS_MASK) { + /* compute the length of the next (short) msg in packet */ + unsigned int cur_message_length = midi_length(cur_packet_data[0]); + if (cur_message_length > remaining_length) { +#ifdef DEBUG + printf("PortMidi debug msg: not enough data"); +#endif + /* since there's no more data, we're done */ + return; + } + m->last_msg_length = cur_message_length; + m->last_command = cur_packet_data[0]; + switch (cur_message_length) { + case 1: + event->message = Pm_Message(cur_packet_data[0], 0, 0); + break; + case 2: + event->message = Pm_Message(cur_packet_data[0], + cur_packet_data[1], 0); + break; + case 3: + event->message = Pm_Message(cur_packet_data[0], + cur_packet_data[1], + cur_packet_data[2]); + break; + default: + /* PortMIDI internal error; should never happen */ + assert(cur_message_length == 1); + return; /* give up on packet if continued after assert */ + } + pm_read_short(midi, event); + remaining_length -= m->last_msg_length; + cur_packet_data += m->last_msg_length; + } else if (m->last_msg_length > remaining_length + 1) { + /* we have running status, but not enough data */ +#ifdef DEBUG + printf("PortMidi debug msg: not enough data in CoreMIDI packet"); +#endif + /* since there's no more data, we're done */ + return; + } else { /* output message using running status */ + switch (m->last_msg_length) { + case 1: + event->message = Pm_Message(m->last_command, 0, 0); + break; + case 2: + event->message = Pm_Message(m->last_command, + cur_packet_data[0], 0); + break; + case 3: + event->message = Pm_Message(m->last_command, + cur_packet_data[0], + cur_packet_data[1]); + break; + default: + /* last_msg_length is invalid -- internal PortMIDI error */ + assert(m->last_msg_length == 1); + } + pm_read_short(midi, event); + remaining_length -= (m->last_msg_length - 1); + cur_packet_data += (m->last_msg_length - 1); + } + } +} + + + +/* called when MIDI packets are received */ +static void +readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon) +{ + PmInternal *midi; + midi_macosxcm_type m; + PmEvent event; + MIDIPacket *packet; + unsigned int packetIndex; + uint32_t now; + unsigned int status; + +#ifdef CM_DEBUG + printf("readProc: numPackets %d: ", newPackets->numPackets); +#endif + + /* Retrieve the context for this connection */ + midi = (PmInternal *) connRefCon; + m = (midi_macosxcm_type) midi->descriptor; + assert(m); + + /* synchronize time references every 100ms */ + now = (*midi->time_proc)(midi->time_info); + if (m->first_message || m->sync_time + 100 /*ms*/ < now) { + /* time to resync */ + now = midi_synchronize(midi); + m->first_message = FALSE; + } + + packet = (MIDIPacket *) &newPackets->packet[0]; + /* printf("readproc packet status %x length %d\n", packet->data[0], + packet->length); */ + for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) { + /* Set the timestamp and dispatch this message */ + event.timestamp = (PmTimestamp) /* explicit conversion */ ( + (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) / + (UInt64) 1000000); + status = packet->data[0]; + /* process packet as sysex data if it begins with MIDI_SYSEX, or + MIDI_EOX or non-status byte with no running status */ +#ifdef CM_DEBUG + printf(" %d", packet->length); +#endif + if (status == MIDI_SYSEX || status == MIDI_EOX || + ((!(status & MIDI_STATUS_MASK)) && !m->last_command)) { + /* previously was: !(status & MIDI_STATUS_MASK)) { + * but this could mistake running status for sysex data + */ + /* reset running status data -cpr */ + m->last_command = 0; + m->last_msg_length = 0; + /* printf("sysex packet length: %d\n", packet->length); */ + pm_read_bytes(midi, packet->data, packet->length, event.timestamp); + } else { + process_packet(packet, &event, midi, m); + } + packet = MIDIPacketNext(packet); + } +#ifdef CM_DEBUG + printf("\n"); +#endif +} + +static PmError +midi_in_open(PmInternal *midi, void *driverInfo) +{ + MIDIEndpointRef endpoint; + midi_macosxcm_type m; + OSStatus macHostError; + + /* insure that we have a time_proc for timing */ + if (midi->time_proc == NULL) { + if (!Pt_Started()) + Pt_Start(1, 0, 0); + /* time_get does not take a parameter, so coerce */ + midi->time_proc = (PmTimeProcPtr) Pt_Time; + } + endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; + if (endpoint == NULL_REF) { + return pmInvalidDeviceId; + } + + m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ + midi->descriptor = m; + if (!m) { + return pmInsufficientMemory; + } + m->error[0] = 0; + m->callback_error[0] = 0; + m->sync_time = 0; + m->delta = 0; + m->last_time = 0; + m->first_message = TRUE; + m->sysex_mode = FALSE; + m->sysex_word = 0; + m->sysex_byte_count = 0; + m->packetList = NULL; + m->packet = NULL; + m->last_command = 0; + m->last_msg_length = 0; + + macHostError = MIDIPortConnectSource(portIn, endpoint, midi); + if (macHostError != noErr) { + pm_hosterror = macHostError; + sprintf(pm_hosterror_text, + "Host error %ld: MIDIPortConnectSource() in midi_in_open()", + (long) 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) (long) descriptors[midi->device_id].descriptor; + if (endpoint == NULL_REF) { + 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()", + (long) macHostError); + err = pmHostError; + } + + midi->descriptor = NULL; + pm_free(midi->descriptor); + + return err; +} + + +static PmError +midi_out_open(PmInternal *midi, void *driverInfo) +{ + midi_macosxcm_type m; + + m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ + midi->descriptor = m; + if (!m) { + return pmInsufficientMemory; + } + m->error[0] = 0; + m->callback_error[0] = 0; + m->sync_time = 0; + m->delta = 0; + m->last_time = 0; + m->first_message = TRUE; + m->sysex_mode = FALSE; + m->sysex_word = 0; + m->sysex_byte_count = 0; + m->packetList = (MIDIPacketList *) m->packetBuffer; + m->packet = NULL; + m->last_command = 0; + m->last_msg_length = 0; + m->min_next_time = 0; + m->byte_count = 0; + m->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency(); + m->host_ticks_per_byte = (UInt64) (1000000.0 / + (m->us_per_host_tick * MAX_BYTES_PER_S)); + 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) +{ + PmError err = pmNoError; + OSStatus macHostError; + MIDIEndpointRef endpoint = + (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; + macHostError = MIDIFlushOutput(endpoint); + if (macHostError != noErr) { + pm_hosterror = macHostError; + sprintf(pm_hosterror_text, + "Host error %ld: MIDIFlushOutput()", (long) macHostError); + err = pmHostError; + } + return err; +} + + +static PmError +midi_write_flush(PmInternal *midi, PmTimestamp timestamp) +{ + OSStatus macHostError; + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + MIDIEndpointRef endpoint = + (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; + assert(m); + assert(endpoint); + if (m->packet != NULL) { + /* out of space, send the buffer and start refilling it */ + /* before we can send, maybe delay to limit data rate. OS X allows + * 15KB/s. */ + UInt64 now = AudioGetCurrentHostTime(); + if (now < m->min_next_time) { + usleep((useconds_t) + ((m->min_next_time - now) * m->us_per_host_tick)); + } + macHostError = MIDISend(portOut, endpoint, m->packetList); + m->packet = NULL; /* indicate no data in packetList now */ + m->min_next_time = now + m->byte_count * m->host_ticks_per_byte; + m->byte_count = 0; + 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()", + (long) 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 %p len %d\n", message[0], m->packet, messageLength); */ + m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), + m->packet, timestamp, messageLength, + message); + m->byte_count += messageLength; + 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; + /* timestamp is 0 because midi_write_flush ignores timestamp since + * timestamps are already in packets. The timestamp parameter is here + * because other API's need it. midi_write_flush can be called + * from system-independent code that must be cross-API. + */ + if ((err = midi_write_flush(midi, 0)) != pmNoError) return err; + 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) +{ + PmTimestamp when = event->timestamp; + PmMessage 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; + timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); + + message[0] = Pm_MessageStatus(what); + message[1] = Pm_MessageData1(what); + message[2] = Pm_MessageData2(what); + messageLength = midi_length(what); + + /* make sure we go foreward in time */ + if (timestamp < m->min_next_time) timestamp = m->min_next_time; + + #ifdef LIMIT_RATE + if (timestamp < m->last_time) + timestamp = m->last_time; + m->last_time = timestamp + messageLength * m->host_ticks_per_byte; + #endif + + /* Add this message to the packet list */ + return send_packet(midi, message, messageLength, timestamp); +} + + +static PmError +midi_begin_sysex(PmInternal *midi, PmTimestamp when) +{ + UInt64 when_ns; + 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 go foreward in time */ + if (m->sysex_timestamp < m->min_next_time) + m->sysex_timestamp = m->min_next_time; + + #ifdef LIMIT_RATE + if (m->sysex_timestamp < m->last_time) + m->sysex_timestamp = m->last_time; + m->last_time = m->sysex_timestamp + m->sysex_byte_count * + m->host_ticks_per_byte; + #endif + + /* now send what's in the buffer */ + err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count, + m->sysex_timestamp); + m->sysex_byte_count = 0; + if (err != pmNoError) { + m->packet = NULL; /* flush everything in the packet list */ + return err; + } + return pmNoError; +} + + +static PmError +midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp) +{ + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + assert(m); + if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) { + PmError err = midi_end_sysex(midi, timestamp); + if (err != pmNoError) return err; + } + m->sysex_buffer[m->sysex_byte_count++] = byte; + return pmNoError; +} + + +static PmError +midi_write_realtime(PmInternal *midi, PmEvent *event) +{ + /* to send a realtime message during a sysex message, first + flush all pending sysex bytes into packet list */ + PmError err = midi_end_sysex(midi, 0); + if (err != pmNoError) return err; + /* then we can just do a normal midi_write_short */ + return midi_write_short(midi, event); +} + +static unsigned int midi_has_host_error(PmInternal *midi) +{ + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + return (m->callback_error[0] != 0) || (m->error[0] != 0); +} + + +static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len) +{ + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + msg[0] = 0; /* initialize to empty string */ + if (m) { /* make sure there is an open device to examine */ + if (m->error[0]) { + strncpy(msg, m->error, len); + m->error[0] = 0; /* clear the error */ + } else if (m->callback_error[0]) { + strncpy(msg, m->callback_error, len); + m->callback_error[0] = 0; /* clear the error */ + } + msg[len - 1] = 0; /* make sure string is terminated */ + } +} + + +MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp) +{ + UInt64 nanos; + if (timestamp <= 0) { + return (MIDITimeStamp)0; + } else { + nanos = (UInt64)timestamp * (UInt64)1000000; + return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos); + } +} + +PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp) +{ + UInt64 nanos; + nanos = AudioConvertHostTimeToNanos(timestamp); + return (PmTimestamp)(nanos / (UInt64)1000000); +} + + +// +// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html +////////////////////////////////////// +// Obtain the name of an endpoint without regard for whether it has connections. +// The result should be released by the caller. +CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) +{ + CFMutableStringRef result = CFStringCreateMutable(NULL, 0); + CFStringRef str; + + // begin with the endpoint's name + str = NULL; + MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str); + if (str != NULL) { + CFStringAppend(result, str); + CFRelease(str); + } + + MIDIEntityRef entity = NULL_REF; + MIDIEndpointGetEntity(endpoint, &entity); + if (entity == NULL_REF) + // probably virtual + return result; + + if (CFStringGetLength(result) == 0) { + // endpoint name has zero length -- try the entity + str = NULL; + MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str); + if (str != NULL) { + CFStringAppend(result, str); + CFRelease(str); + } + } + // now consider the device's name + MIDIDeviceRef device = NULL_REF; + MIDIEntityGetDevice(entity, &device); + if (device == NULL_REF) + return result; + + str = NULL; + MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str); + if (CFStringGetLength(result) == 0) { + CFRelease(result); + return str; + } + if (str != NULL) { + // if an external device has only one entity, throw away + // the endpoint name and just use the device name + if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) { + CFRelease(result); + return str; + } else { + if (CFStringGetLength(str) == 0) { + CFRelease(str); + return result; + } + // does the entity name already start with the device name? + // (some drivers do this though they shouldn't) + // if so, do not prepend + if (CFStringCompareWithOptions( result, /* endpoint name */ + str /* device name */, + CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) { + // prepend the device name to the entity name + if (CFStringGetLength(result) > 0) + CFStringInsert(result, 0, CFSTR(" ")); + CFStringInsert(result, 0, str); + } + CFRelease(str); + } + } + return result; +} + + +// Obtain the name of an endpoint, following connections. +// The result should be released by the caller. +static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint) +{ + CFMutableStringRef result = CFStringCreateMutable(NULL, 0); + CFStringRef str; + OSStatus err; + long i; + + // Does the endpoint have connections? + CFDataRef connections = NULL; + long nConnected = 0; + bool anyStrings = false; + err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections); + if (connections != NULL) { + // It has connections, follow them + // Concatenate the names of all connected devices + nConnected = CFDataGetLength(connections) / (int32_t) sizeof(MIDIUniqueID); + if (nConnected) { + const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); + for (i = 0; i < nConnected; ++i, ++pid) { + MIDIUniqueID id = EndianS32_BtoN(*pid); + MIDIObjectRef connObject; + MIDIObjectType connObjectType; + err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); + if (err == noErr) { + if (connObjectType == kMIDIObjectType_ExternalSource || + connObjectType == kMIDIObjectType_ExternalDestination) { + // Connected to an external device's endpoint (10.3 and later). + str = EndpointName((MIDIEndpointRef)(connObject), true); + } else { + // Connected to an external device (10.2) (or something else, catch-all) + str = NULL; + MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str); + } + if (str != NULL) { + if (anyStrings) + CFStringAppend(result, CFSTR(", ")); + else anyStrings = true; + CFStringAppend(result, str); + CFRelease(str); + } + } + } + } + CFRelease(connections); + } + if (anyStrings) + return result; + + // Here, either the endpoint had no connections, or we failed to obtain names for any of them. + return EndpointName(endpoint, false); +} + + +char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint) +{ +#ifdef OLDCODE + MIDIEntityRef entity; + MIDIDeviceRef device; + + CFStringRef endpointName = NULL; + CFStringRef deviceName = NULL; +#endif + CFStringRef fullName = NULL; + CFStringEncoding defaultEncoding; + char* newName; + + /* get the default string encoding */ + defaultEncoding = CFStringGetSystemEncoding(); + + fullName = ConnectedEndpointName(endpoint); + +#ifdef OLDCODE + /* get the entity and device info */ + MIDIEndpointGetEntity(endpoint, &entity); + MIDIEntityGetDevice(entity, &device); + + /* create the nicely formated name */ + MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName); + MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName); + if (deviceName != NULL) { + fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"), + deviceName, endpointName); + } else { + fullName = endpointName; + } +#endif + /* copy the string into our buffer */ + newName = (char *) malloc(CFStringGetLength(fullName) + 1); + CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1, + defaultEncoding); + + /* clean up */ +#ifdef OLDCODE + if (endpointName) CFRelease(endpointName); + if (deviceName) CFRelease(deviceName); +#endif + if (fullName) CFRelease(fullName); + + return newName; +} + + + +pm_fns_node pm_macosx_in_dictionary = { + none_write_short, + none_sysex, + none_sysex, + none_write_byte, + none_write_short, + none_write_flush, + none_synchronize, + midi_in_open, + midi_abort, + midi_in_close, + success_poll, + midi_has_host_error, + midi_get_host_error, +}; + +pm_fns_node pm_macosx_out_dictionary = { + midi_write_short, + midi_begin_sysex, + midi_end_sysex, + midi_write_byte, + midi_write_realtime, + midi_write_flush, + midi_synchronize, + midi_out_open, + midi_abort, + midi_out_close, + success_poll, + midi_has_host_error, + midi_get_host_error, +}; + + +PmError pm_macosxcm_init(void) +{ + ItemCount numInputs, numOutputs, numDevices; + MIDIEndpointRef endpoint; + int i; + OSStatus macHostError; + char *error_text; + + /* Determine the number of MIDI devices on the system */ + numDevices = MIDIGetNumberOfDevices(); + numInputs = MIDIGetNumberOfSources(); + numOutputs = MIDIGetNumberOfDestinations(); + + /* Return prematurely if no devices exist on the system + Note that this is not an error. There may be no devices. + Pm_CountDevices() will return zero, which is correct and + useful information + */ + if (numDevices <= 0) { + return pmNoError; + } + + + /* Initialize the client handle */ + macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client); + if (macHostError != noErr) { + error_text = (char *)"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 = (char *)"MIDIInputPortCreate() in pm_macosxcm_init()"; + goto error_return; + } + + /* Create the output port */ + macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut); + if (macHostError != noErr) { + error_text = (char *)"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_REF) { + 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((char *)"CoreMIDI", cm_get_full_endpoint_name(endpoint), + TRUE, (void *) (long) endpoint, &pm_macosx_in_dictionary); + } + + /* Iterate over the MIDI output devices */ + for (i = 0; i < numOutputs; i++) { + endpoint = MIDIGetDestination(i); + if (endpoint == NULL_REF) { + 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((char *)"CoreMIDI", cm_get_full_endpoint_name(endpoint), + FALSE, (void *) (long) endpoint, + &pm_macosx_out_dictionary); + } + return pmNoError; + +error_return: + pm_hosterror = macHostError; + sprintf(pm_hosterror_text, "Host error %ld: %s\n", (long) macHostError, + error_text); + pm_macosxcm_term(); /* clear out any opened ports */ + return pmHostError; +} + +void pm_macosxcm_term(void) +{ + if (client != NULL_REF) MIDIClientDispose(client); + if (portIn != NULL_REF) MIDIPortDispose(portIn); + if (portOut != NULL_REF) MIDIPortDispose(portOut); +} diff --git a/src/lib/portmidi/pmmacosxcm.h b/src/lib/portmidi/pmmacosxcm.h new file mode 100644 index 00000000000..ea79902d40b --- /dev/null +++ b/src/lib/portmidi/pmmacosxcm.h @@ -0,0 +1,6 @@ +/* system-specific definitions */ + +PmError pm_macosxcm_init(void); +void pm_macosxcm_term(void); + +PmDeviceID find_default_device(char *path, int input, PmDeviceID id); diff --git a/src/lib/portmidi/pmutil.c b/src/lib/portmidi/pmutil.c new file mode 100644 index 00000000000..a70fe2fa1f8 --- /dev/null +++ b/src/lib/portmidi/pmutil.c @@ -0,0 +1,284 @@ +/* pmutil.c -- some helpful utilities for building midi + applications that use PortMidi + */ +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" + +#ifdef WIN32 +#define bzero(addr, siz) memset(addr, 0, siz) +#endif + +// #define QUEUE_DEBUG 1 +#ifdef QUEUE_DEBUG +#include "stdio.h" +#endif + +typedef struct { + long head; + long tail; + long len; + long overflow; + int32_t msg_size; /* number of int32_t in a message including extra word */ + int32_t peek_overflow; + int32_t *buffer; + int32_t *peek; + int32_t peek_flag; +} PmQueueRep; + + +PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg) +{ + int32_t int32s_per_msg = + (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) & + ~(sizeof(int32_t) - 1)) / sizeof(int32_t)); + PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); + if (!queue) /* memory allocation failed */ + return NULL; + + /* need extra word per message for non-zero encoding */ + queue->len = num_msgs * (int32s_per_msg + 1); + queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t)); + bzero(queue->buffer, queue->len * sizeof(int32_t)); + if (!queue->buffer) { + pm_free(queue); + return NULL; + } else { /* allocate the "peek" buffer */ + queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t)); + if (!queue->peek) { + /* free everything allocated so far and return */ + pm_free(queue->buffer); + pm_free(queue); + return NULL; + } + } + bzero(queue->buffer, queue->len * sizeof(int32_t)); + queue->head = 0; + queue->tail = 0; + /* msg_size is in words */ + queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */ + queue->overflow = FALSE; + queue->peek_overflow = FALSE; + queue->peek_flag = FALSE; + return queue; +} + + +PMEXPORT PmError Pm_QueueDestroy(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + + /* arg checking */ + if (!queue || !queue->buffer || !queue->peek) + return pmBadPtr; + + pm_free(queue->peek); + pm_free(queue->buffer); + pm_free(queue); + return pmNoError; +} + + +PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg) +{ + long head; + PmQueueRep *queue = (PmQueueRep *) q; + int i; + int32_t *msg_as_int32 = (int32_t *) msg; + + /* arg checking */ + if (!queue) + return pmBadPtr; + /* a previous peek operation encountered an overflow, but the overflow + * has not yet been reported to client, so do it now. No message is + * returned, but on the next call, we will return the peek buffer. + */ + if (queue->peek_overflow) { + queue->peek_overflow = FALSE; + return pmBufferOverflow; + } + if (queue->peek_flag) { + memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t)); + queue->peek_flag = FALSE; + return pmGotData; + } + + head = queue->head; + /* if writer overflows, it writes queue->overflow = tail+1 so that + * when the reader gets to that position in the buffer, it can + * return the overflow condition to the reader. The problem is that + * at overflow, things have wrapped around, so tail == head, and the + * reader will detect overflow immediately instead of waiting until + * it reads everything in the buffer, wrapping around again to the + * point where tail == head. So the condition also checks that + * queue->buffer[head] is zero -- if so, then the buffer is now + * empty, and we're at the point in the msg stream where overflow + * occurred. It's time to signal overflow to the reader. If + * queue->buffer[head] is non-zero, there's a message there and we + * should read all the way around the buffer before signalling overflow. + * There is a write-order dependency here, but to fail, the overflow + * field would have to be written while an entire buffer full of + * writes are still pending. I'm assuming out-of-order writes are + * possible, but not that many. + */ + if (queue->overflow == head + 1 && !queue->buffer[head]) { + queue->overflow = 0; /* non-overflow condition */ + return pmBufferOverflow; + } + + /* test to see if there is data in the queue -- test from back + * to front so if writer is simultaneously writing, we don't + * waste time discovering the write is not finished + */ + for (i = queue->msg_size - 1; i >= 0; i--) { + if (!queue->buffer[head + i]) { + return pmNoData; + } + } + memcpy(msg, (char *) &queue->buffer[head + 1], + sizeof(int32_t) * (queue->msg_size - 1)); + /* fix up zeros */ + i = queue->buffer[head]; + while (i < queue->msg_size) { + int32_t j; + i--; /* msg does not have extra word so shift down */ + j = msg_as_int32[i]; + msg_as_int32[i] = 0; + i = j; + } + /* signal that data has been removed by zeroing: */ + bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size); + + /* update head */ + head += queue->msg_size; + if (head == queue->len) head = 0; + queue->head = head; + return pmGotData; /* success */ +} + + + +PMEXPORT PmError Pm_SetOverflow(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + long tail; + /* arg checking */ + if (!queue) + return pmBadPtr; + /* no more enqueue until receiver acknowledges overflow */ + if (queue->overflow) return pmBufferOverflow; + tail = queue->tail; + queue->overflow = tail + 1; + return pmBufferOverflow; +} + + +PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg) +{ + PmQueueRep *queue = (PmQueueRep *) q; + long tail; + int i; + int32_t *src = (int32_t *) msg; + int32_t *ptr; + int32_t *dest; + int rslt; + if (!queue) + return pmBadPtr; + /* no more enqueue until receiver acknowledges overflow */ + if (queue->overflow) return pmBufferOverflow; + rslt = Pm_QueueFull(q); + /* already checked above: if (rslt == pmBadPtr) return rslt; */ + tail = queue->tail; + if (rslt) { + queue->overflow = tail + 1; + return pmBufferOverflow; + } + + /* queue is has room for message, and overflow flag is cleared */ + ptr = &queue->buffer[tail]; + dest = ptr + 1; + for (i = 1; i < queue->msg_size; i++) { + int32_t j = src[i - 1]; + if (!j) { + *ptr = i; + ptr = dest; + } else { + *dest = j; + } + dest++; + } + *ptr = i; + tail += queue->msg_size; + if (tail == queue->len) tail = 0; + queue->tail = tail; + return pmNoError; +} + + +PMEXPORT int Pm_QueueEmpty(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + return (!queue) || /* null pointer -> return "empty" */ + (queue->buffer[queue->head] == 0 && !queue->peek_flag); +} + + +PMEXPORT int Pm_QueueFull(PmQueue *q) +{ + long tail; + int i; + PmQueueRep *queue = (PmQueueRep *) q; + /* arg checking */ + if (!queue) + return pmBadPtr; + tail = queue->tail; + /* test to see if there is space in the queue */ + for (i = 0; i < queue->msg_size; i++) { + if (queue->buffer[tail + i]) { + return TRUE; + } + } + return FALSE; +} + + +PMEXPORT void *Pm_QueuePeek(PmQueue *q) +{ + PmError rslt; + int32_t temp; + PmQueueRep *queue = (PmQueueRep *) q; + /* arg checking */ + if (!queue) + return NULL; + + if (queue->peek_flag) { + return queue->peek; + } + /* this is ugly: if peek_overflow is set, then Pm_Dequeue() + * returns immediately with pmBufferOverflow, but here, we + * want Pm_Dequeue() to really check for data. If data is + * there, we can return it + */ + temp = queue->peek_overflow; + queue->peek_overflow = FALSE; + rslt = Pm_Dequeue(q, queue->peek); + queue->peek_overflow = temp; + + if (rslt == 1) { + queue->peek_flag = TRUE; + return queue->peek; + } else if (rslt == pmBufferOverflow) { + /* when overflow is indicated, the queue is empty and the + * first message that was dropped by Enqueue (signalling + * pmBufferOverflow to its caller) would have been the next + * message in the queue. Pm_QueuePeek will return NULL, but + * remember that an overflow occurred. (see Pm_Dequeue) + */ + queue->peek_overflow = TRUE; + } + return NULL; +} + diff --git a/src/lib/portmidi/pmutil.h b/src/lib/portmidi/pmutil.h new file mode 100644 index 00000000000..ef5ee4bf84a --- /dev/null +++ b/src/lib/portmidi/pmutil.h @@ -0,0 +1,127 @@ +/* pmutil.h -- some helpful utilities for building midi + applications that use PortMidi + */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef void PmQueue; + +/* + A single-reader, single-writer queue is created by + Pm_QueueCreate(), which takes the number of messages and + the message size as parameters. The queue only accepts + fixed sized messages. Returns NULL if memory cannot be allocated. + + This queue implementation uses the "light pipe" algorithm which + operates correctly even with multi-processors and out-of-order + memory writes. (see Alexander Dokumentov, "Lock-free Interprocess + Communication," Dr. Dobbs Portal, http://www.ddj.com/, + articleID=189401457, June 15, 2006. This algorithm requires + that messages be translated to a form where no words contain + zeros. Each word becomes its own "data valid" tag. Because of + this translation, we cannot return a pointer to data still in + the queue when the "peek" method is called. Instead, a buffer + is preallocated so that data can be copied there. Pm_QueuePeek() + dequeues a message into this buffer and returns a pointer to + it. A subsequent Pm_Dequeue() will copy from this buffer. + + This implementation does not try to keep reader/writer data in + separate cache lines or prevent thrashing on cache lines. + However, this algorithm differs by doing inserts/removals in + units of messages rather than units of machine words. Some + performance improvement might be obtained by not clearing data + immediately after a read, but instead by waiting for the end + of the cache line, especially if messages are smaller than + cache lines. See the Dokumentov article for explanation. + + The algorithm is extended to handle "overflow" reporting. To report + an overflow, the sender writes the current tail position to a field. + The receiver must acknowlege receipt by zeroing the field. The sender + will not send more until the field is zeroed. + + Pm_QueueDestroy() destroys the queue and frees its storage. + */ + +PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); +PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); + +/* + Pm_Dequeue() removes one item from the queue, copying it into msg. + Returns 1 if successful, and 0 if the queue is empty. + Returns pmBufferOverflow if what would have been the next thing + in the queue was dropped due to overflow. (So when overflow occurs, + the receiver can receive a queue full of messages before getting the + overflow report. This protocol ensures that the reader will be + notified when data is lost due to overflow. + */ +PMEXPORT 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. + */ +PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); + + +/* + Pm_QueueFull() returns non-zero if the queue is full + Pm_QueueEmpty() returns non-zero if the queue is empty + + Either condition may change immediately because a parallel + enqueue or dequeue operation could be in progress. Furthermore, + Pm_QueueEmpty() is optimistic: it may say false, when due to + out-of-order writes, the full message has not arrived. Therefore, + Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns + false. On the other hand, Pm_QueueFull() is pessimistic: if it + returns false, then Pm_Enqueue() is guaranteed to succeed. + + Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. + Pm_QueueEmpty() returns FALSE if queue is NULL. + */ +PMEXPORT int Pm_QueueFull(PmQueue *queue); +PMEXPORT int Pm_QueueEmpty(PmQueue *queue); + + +/* + Pm_QueuePeek() returns a pointer to the item at the head of the queue, + or NULL if the queue is empty. The item is not removed from the queue. + Pm_QueuePeek() will not indicate when an overflow occurs. If you want + to get and check pmBufferOverflow messages, use the return value of + Pm_QueuePeek() *only* as an indication that you should call + Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would + return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally + clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume + enqueuing messages. A subsequent call to Pm_QueuePeek() + will return a pointer to the first message *after* the overflow. + Using this as an indication to call Pm_Dequeue(), the first call + to Pm_Dequeue() will return pmBufferOverflow. The second call will + return success, copying the same message pointed to by the previous + Pm_QueuePeek(). + + When to use Pm_QueuePeek(): (1) when you need to look at the message + data to decide who should be called to receive it. (2) when you need + to know a message is ready but cannot accept the message. + + Note that Pm_QueuePeek() is not a fast check, so if possible, you + might as well just call Pm_Dequeue() and accept the data if it is there. + */ +PMEXPORT void *Pm_QueuePeek(PmQueue *queue); + +/* + Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow + condition to the reader (dequeuer). E.g. when transfering data from + the OS to an application, if the OS indicates a buffer overrun, + Pm_SetOverflow() can be used to insure that the reader receives a + pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue + is NULL, returns pmBufferOverflow if buffer is already in an overflow + state, returns pmNoError if successfully set overflow state. + */ +PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ diff --git a/src/lib/portmidi/pmwin.c b/src/lib/portmidi/pmwin.c new file mode 100644 index 00000000000..a57ed878df6 --- /dev/null +++ b/src/lib/portmidi/pmwin.c @@ -0,0 +1,144 @@ +/* pmwin.c -- PortMidi os-dependent code */ + +/* This file only needs to implement: + pm_init(), which calls various routines to register the + available midi devices, + Pm_GetDefaultInputDeviceID(), and + Pm_GetDefaultOutputDeviceID(). + This file must + be separate from the main portmidi.c file because it is system + dependent, and it is separate from, say, pmwinmm.c, because it + might need to register devices for winmm, directx, and others. + + */ + +#include "stdlib.h" +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" +#include "pmwinmm.h" +#ifdef DEBUG +#include "stdio.h" +#endif +#undef UNICODE +#include <windows.h> + +/* pm_exit is called when the program exits. + It calls pm_term to make sure PortMidi is properly closed. + If DEBUG is on, we prompt for input to avoid losing error messages. + */ +static void pm_exit(void) { + pm_term(); +#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) +{ + atexit(pm_exit); +#ifdef DEBUG + printf("registered pm_exit with atexit()\n"); +#endif + pm_winmm_init(); + /* initialize other APIs (DirectX?) here */ +} + + +void pm_term(void) { + pm_winmm_term(); +} + + +static PmDeviceID pm_get_default_device_id(int is_input, char *key) { + HKEY hkey; +#define PATTERN_MAX 256 + char pattern[PATTERN_MAX]; + DWORD pattern_max = PATTERN_MAX; + DWORD dwType; + /* Find first input or device -- this is the default. */ + PmDeviceID id = pmNoDevice; + int i, j; + Pm_Initialize(); /* make sure descriptors exist! */ + for (i = 0; i < pm_descriptor_index; i++) { + if (descriptors[i].pub.input == is_input) { + id = i; + break; + } + } + /* Look in registry for a default device name pattern. */ + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software", 0, KEY_READ, &hkey) != + ERROR_SUCCESS) { + return id; + } + if (RegOpenKeyExA(hkey, "JavaSoft", 0, KEY_READ, &hkey) != + ERROR_SUCCESS) { + return id; + } + if (RegOpenKeyExA(hkey, "Prefs", 0, KEY_READ, &hkey) != + ERROR_SUCCESS) { + return id; + } + if (RegOpenKeyExA(hkey, "/Port/Midi", 0, KEY_READ, &hkey) != + ERROR_SUCCESS) { + return id; + } + if (RegQueryValueExA(hkey, key, NULL, &dwType, (BYTE *)pattern, &pattern_max) != + ERROR_SUCCESS) { + return id; + } + + /* decode pattern: upper case encoded with "/" prefix */ + i = j = 0; + while (pattern[i]) { + if (pattern[i] == '/' && pattern[i + 1]) { + pattern[j++] = toupper(pattern[++i]); + } else { + pattern[j++] = tolower(pattern[i]); + } + i++; + } + pattern[j] = 0; /* end of string */ + + /* now pattern is the string from the registry; search for match */ + i = pm_find_default_device(pattern, is_input); + if (i != pmNoDevice) { + id = i; + } + return id; +} + + +PmDeviceID Pm_GetDefaultInputDeviceID() { + return pm_get_default_device_id(TRUE, + (char *)"/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E"); +} + + +PmDeviceID Pm_GetDefaultOutputDeviceID() { + return pm_get_default_device_id(FALSE, + (char *)"/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E"); +} + + +#include "stdio.h" + +void *pm_alloc(size_t s) { + return malloc(s); +} + + +void pm_free(void *ptr) { + free(ptr); +} + + diff --git a/src/lib/portmidi/pmwinmm.c b/src/lib/portmidi/pmwinmm.c new file mode 100644 index 00000000000..5303ed4c4dd --- /dev/null +++ b/src/lib/portmidi/pmwinmm.c @@ -0,0 +1,1482 @@ +/* pmwinmm.c -- system specific definitions */ + +#ifdef _MSC_VER + #pragma warning(disable: 4133) // stop warnings about implicit typecasts +#endif + +#ifndef _WIN32_WINNT + /* without this define, InitializeCriticalSectionAndSpinCount is + * undefined. This version level means "Windows 2000 and higher" + */ + #define _WIN32_WINNT 0x0500 +#endif +#undef UNICODE +#include "windows.h" +#include "mmsystem.h" +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" +#include "pmwinmm.h" +#include <string.h> +#include "porttime.h" +#include "osdcomm.h" + +#ifdef PTR64 +typedef UINT64 FPTR; +#else +typedef UINT32 FPTR; +#endif + +/* asserts used to verify portMidi code logic is sound; later may want + something more graceful */ +#include <assert.h> +#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); +#ifdef USE_SYSEX_BUFFERS +static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, + DWORD dwParam2); +#endif + +extern pm_fns_node pm_winmm_in_dictionary; +extern pm_fns_node pm_winmm_out_dictionary; + +static void winmm_out_delete(PmInternal *midi); /* forward reference */ + +/* +A note about buffers: WinMM seems to hold onto buffers longer than +one would expect, e.g. when I tried using 2 small buffers to send +long sysex messages, at some point WinMM held both buffers. This problem +was fixed by making buffers bigger. Therefore, it seems that there should +be enough buffer space to hold a whole sysex message. + +The bufferSize passed into Pm_OpenInput (passed into here as buffer_len) +will be used to estimate the largest sysex message (= buffer_len * 4 bytes). +Call that the max_sysex_len = buffer_len * 4. + +For simple midi output (latency == 0), allocate 3 buffers, each with half +the size of max_sysex_len, but each at least 256 bytes. + +For stream output, there will already be enough space in very short +buffers, so use them, but make sure there are at least 16. + +For input, use many small buffers rather than 2 large ones so that when +there are short sysex messages arriving frequently (as in control surfaces) +there will be more free buffers to fill. Use max_sysex_len / 64 buffers, +but at least 16, of size 64 bytes each. + +The following constants help to represent these design parameters: +*/ +#define NUM_SIMPLE_SYSEX_BUFFERS 3 +#define MIN_SIMPLE_SYSEX_LEN 256 + +#define MIN_STREAM_BUFFERS 16 +#define STREAM_BUFFER_LEN 24 + +#define INPUT_SYSEX_LEN 64 +#define MIN_INPUT_BUFFERS 16 + +/* if we run out of space for output (assume this is due to a sysex msg, + expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN + */ +#define NUM_EXPANSION_BUFFERS 128 +#define EXPANSION_BUFFER_LEN 1024 + +/* A sysex buffer has 3 DWORDS as a header plus the actual message size */ +#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3) +/* A MIDIHDR with a sysex message is the buffer length plus the header size */ +#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) +#ifdef USE_SYSEX_BUFFERS +/* Size of a MIDIHDR with a buffer contaning multiple MIDIEVENT structures */ +#define MIDIHDR_SIZE(x) ((x) + sizeof(MIDIHDR)) +#endif + +/* +============================================================================== +win32 mmedia system specific structure passed to midi callbacks +============================================================================== +*/ + +/* global winmm device info */ +MIDIINCAPS *midi_in_caps = NULL; +MIDIINCAPS midi_in_mapper_caps; +UINT midi_num_inputs = 0; +MIDIOUTCAPS *midi_out_caps = NULL; +MIDIOUTCAPS midi_out_mapper_caps; +UINT midi_num_outputs = 0; + +/* per device info */ +typedef struct midiwinmm_struct { + union { + HMIDISTRM stream; /* windows handle for stream */ + HMIDIOUT out; /* windows handle for out calls */ + HMIDIIN in; /* windows handle for in calls */ + } handle; + + /* midi output messages are sent in these buffers, which are allocated + * in a round-robin fashion, using next_buffer as an index + */ + LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */ + int max_buffers; /* length of buffers array */ + int buffers_expanded; /* buffers array expanded for extra msgs? */ + int num_buffers; /* how many buffers allocated in buffers array */ + int next_buffer; /* index of next buffer to send */ + HANDLE buffer_signal; /* used to wait for buffer to become free */ +#ifdef USE_SYSEX_BUFFERS + /* sysex buffers will be allocated only when + * a sysex message is sent. The size of the buffer is fixed. + */ + LPMIDIHDR sysex_buffers[NUM_SYSEX_BUFFERS]; /* pool of buffers for sysex data */ + int next_sysex_buffer; /* index of next sysexbuffer to send */ +#endif + unsigned long last_time; /* last output time */ + int first_message; /* flag: treat first message differently */ + int sysex_mode; /* middle of sending sysex */ + unsigned long sysex_word; /* accumulate data when receiving sysex */ + unsigned int sysex_byte_count; /* count how many received */ + LPMIDIHDR hdr; /* the message accumulating sysex to send */ + unsigned long sync_time; /* when did we last determine delta? */ + long delta; /* difference between stream time and + real time */ + int error; /* host error from doing port midi call */ + CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */ +} midiwinmm_node, *midiwinmm_type; + + +/* +============================================================================= +general MIDI device queries +============================================================================= +*/ +static void pm_winmm_general_inputs(void) +{ + UINT i; + WORD wRtn; + midi_num_inputs = midiInGetNumDevs(); + midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) * + midi_num_inputs); + if (midi_in_caps == NULL) { + /* if you can't open a particular system-level midi interface + * (such as winmm), we just consider that system or API to be + * unavailable and move on without reporting an error. + */ + return; + } + + for (i = 0; i < midi_num_inputs; i++) { + wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i], + sizeof(MIDIINCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + /* ignore errors here -- if pm_descriptor_max is exceeded, some + devices will not be accessible. */ + pm_add_device((char *)"MMSystem", midi_in_caps[i].szPname, TRUE, + (void *)(FPTR)i, + &pm_winmm_in_dictionary); + } + } +} + + +static void pm_winmm_mapper_input(void) +{ + WORD wRtn; + /* Note: if MIDIMAPPER opened as input (documentation implies you + can, but current system fails to retrieve input mapper + capabilities) then you still should retrieve some formof + setup info. */ + wRtn = midiInGetDevCaps((UINT) MIDIMAPPER, + (LPMIDIINCAPS) & midi_in_mapper_caps, + sizeof(MIDIINCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device((char *)"MMSystem", midi_in_mapper_caps.szPname, TRUE, + (void *) MIDIMAPPER, &pm_winmm_in_dictionary); + } +} + + +static void pm_winmm_general_outputs(void) +{ + UINT i; + DWORD wRtn; + midi_num_outputs = midiOutGetNumDevs(); + midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs ); + + if (midi_out_caps == NULL) { + /* no error is reported -- see pm_winmm_general_inputs */ + return ; + } + + for (i = 0; i < midi_num_outputs; i++) { + wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i], + sizeof(MIDIOUTCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device((char *)"MMSystem", midi_out_caps[i].szPname, FALSE, + (void *)(FPTR)i, + &pm_winmm_out_dictionary); + } + } +} + + +static void pm_winmm_mapper_output(void) +{ + WORD wRtn; + /* Note: if MIDIMAPPER opened as output (pseudo MIDI device + maps device independent messages into device dependant ones, + via NT midimapper program) you still should get some setup info */ + wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS) + & midi_out_mapper_caps, sizeof(MIDIOUTCAPS)); + if (wRtn == MMSYSERR_NOERROR) { + pm_add_device((char *)"MMSystem", midi_out_mapper_caps.szPname, FALSE, + (void *) MIDIMAPPER, &pm_winmm_out_dictionary); + } +} + + +/* +========================================================================================= +host error handling +========================================================================================= +*/ +static unsigned int winmm_has_host_error(PmInternal * midi) +{ + midiwinmm_type m = (midiwinmm_type)midi->descriptor; + return m->error; +} + + +/* str_copy_len -- like strcat, but won't overrun the destination string */ +/* + * returns length of resulting string + */ +static int str_copy_len(char *dst, char *src, int len) +{ + strncpy(dst, src, len); + /* just in case suffex is greater then len, terminate with zero */ + dst[len - 1] = 0; + return strlen(dst); +} + + +static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) +{ + /* precondition: midi != NULL */ + midiwinmm_node * m = (midiwinmm_node *) midi->descriptor; + char *hdr1 = (char *)"Host error: "; + //char *hdr2 = (char *)"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 */ + midiInGetErrorText(m->error, msg + n, len - n); + //assert(err == MMSYSERR_NOERROR); + m->error = MMSYSERR_NOERROR; + } + } + } else { /* output port */ + if (m) { + if (m->error != MMSYSERR_NOERROR) { + int n = str_copy_len(msg, hdr1, len); + midiOutGetErrorText(m->error, msg + n, len - n); + //assert(err == MMSYSERR_NOERROR); + m->error = MMSYSERR_NOERROR; + } + } + } +} + + +/* +============================================================================= +buffer handling +============================================================================= +*/ +static MIDIHDR *allocate_buffer(long data_size) +{ + LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); + MIDIEVENT *evt; + if (!hdr) return NULL; + evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ + hdr->lpData = (LPSTR) evt; + hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); + hdr->dwBytesRecorded = 0; + hdr->dwFlags = 0; + hdr->dwUser = hdr->dwBufferLength; + return hdr; +} + +#ifdef USE_SYSEX_BUFFERS +static MIDIHDR *allocate_sysex_buffer(long data_size) +{ + /* we're actually allocating more than data_size because the buffer + * will include the MIDIEVENT header in addition to the data + */ + LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); + MIDIEVENT *evt; + if (!hdr) return NULL; + evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ + hdr->lpData = (LPSTR) evt; + hdr->dwFlags = 0; + hdr->dwUser = 0; + return hdr; +} +#endif + +static PmError allocate_buffers(midiwinmm_type m, long data_size, long count) +{ + int i; + /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + m->num_buffers = 0; /* in case no memory can be allocated */ + m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!m->buffers) return pmInsufficientMemory; + m->max_buffers = count; + for (i = 0; i < count; i++) { + LPMIDIHDR hdr = allocate_buffer(data_size); + if (!hdr) { /* free everything allocated so far and return */ + for (i = i - 1; i >= 0; i--) pm_free(m->buffers[i]); + pm_free(m->buffers); + m->max_buffers = 0; + return pmInsufficientMemory; + } + m->buffers[i] = hdr; /* this may be NULL if allocation fails */ + } + m->num_buffers = count; + return pmNoError; +} + +#ifdef USE_SYSEX_BUFFERS +static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size) +{ + PmError rslt = pmNoError; + /* sysex_buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ + int i; + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { + LPMIDIHDR hdr = allocate_sysex_buffer(data_size); + + if (!hdr) rslt = pmInsufficientMemory; + m->sysex_buffers[i] = hdr; /* this may be NULL if allocation fails */ + hdr->dwFlags = 0; /* mark as free */ + } + return rslt; +} +#endif + +#ifdef USE_SYSEX_BUFFERS +static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m->sysex_buffers[0]) { + if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER)) { + return NULL; + } + } + /* busy wait until we find a free buffer */ + while (TRUE) { + int i; + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { + /* cycle through buffers, modulo NUM_SYSEX_BUFFERS */ + m->next_sysex_buffer++; + if (m->next_sysex_buffer >= NUM_SYSEX_BUFFERS) m->next_sysex_buffer = 0; + r = m->sysex_buffers[m->next_sysex_buffer]; + if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer; + } + /* after scanning every buffer and not finding anything, block */ + if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { +#ifdef DEBUG + printf("PortMidi warning: get_free_sysex_buffer() wait timed out after 1000ms\n"); +#endif + } + } +found_sysex_buffer: + r->dwBytesRecorded = 0; + r->dwBufferLength = 0; /* changed to correct value later */ + return r; +} +#endif + +static LPMIDIHDR get_free_output_buffer(PmInternal *midi) +{ + LPMIDIHDR r = NULL; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + while (TRUE) { + int i; + for (i = 0; i < m->num_buffers; i++) { + /* cycle through buffers, modulo m->num_buffers */ + m->next_buffer++; + if (m->next_buffer >= m->num_buffers) m->next_buffer = 0; + r = m->buffers[m->next_buffer]; + if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer; + } + /* after scanning every buffer and not finding anything, block */ + if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { +#ifdef DEBUG + printf("PortMidi warning: get_free_output_buffer() wait timed out after 1000ms\n"); +#endif + /* if we're trying to send a sysex message, maybe the + * message is too big and we need more message buffers. + * Expand the buffer pool by 128KB using 1024-byte buffers. + */ + /* first, expand the buffers array if necessary */ + if (!m->buffers_expanded) { + LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc( + (m->num_buffers + NUM_EXPANSION_BUFFERS) * + sizeof(LPMIDIHDR)); + /* if no memory, we could return a no-memory error, but user + * probably will be unprepared to deal with it. Maybe the + * MIDI driver is temporarily hung so we should just wait. + * I don't know the right answer, but waiting is easier. + */ + if (!new_buffers) continue; + /* copy buffers to new_buffers and replace buffers */ + memcpy(new_buffers, m->buffers, + m->num_buffers * sizeof(LPMIDIHDR)); + pm_free(m->buffers); + m->buffers = new_buffers; + m->max_buffers = m->num_buffers + NUM_EXPANSION_BUFFERS; + m->buffers_expanded = TRUE; + } + /* next, add one buffer and return it */ + if (m->num_buffers < m->max_buffers) { + r = allocate_buffer(EXPANSION_BUFFER_LEN); + /* again, if there's no memory, we may not really be + * dead -- maybe the system is temporarily hung and + * we can just wait longer for a message buffer */ + if (!r) continue; + m->buffers[m->num_buffers++] = r; + goto found_buffer; /* break out of 2 loops */ + } + /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers, + * and we have no free buffers to send. We'll just keep + * polling to see if any buffers show up. + */ + } + } +found_buffer: + r->dwBytesRecorded = 0; + /* actual buffer length is saved in dwUser field */ + r->dwBufferLength = (DWORD) r->dwUser; + return r; +} + +#ifdef EXPANDING_SYSEX_BUFFERS +note: this is not working code, but might be useful if you want + to grow sysex buffers. +static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size) +{ + LPMIDIHDR big; + int i; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + /* buffer must be smaller than 64k, but be also a multiple of 4 */ + if (new_size > 65520) { + if (old_size >= 65520) + return pmBufferMaxSize; + else + new_size = 65520; + } + /* allocate a bigger message */ + big = allocate_sysex_buffer(new_size); + /* printf("expand to %d bytes\n", new_size);*/ + if (!big) return pmInsufficientMemory; + m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR)); + if (m->error) { + pm_free(big); + return pmHostError; + } + /* make sure we're not going to overwrite any memory */ + assert(old_size <= new_size); + memcpy(big->lpData, m->hdr->lpData, old_size); + /* keep track of how many sysex bytes are in message so far */ + big->dwBytesRecorded = m->hdr->dwBytesRecorded; + big->dwBufferLength = new_size; + /* find which buffer this was, and replace it */ + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { + if (m->sysex_buffers[i] == m->hdr) { + m->sysex_buffers[i] = big; + m->sysex_buffer_size[i] = new_size; + pm_free(m->hdr); + m->hdr = big; + break; + } + } + assert(i != NUM_SYSEX_BUFFERS); + + return pmNoError; +} +#endif + +/* +========================================================================================= +begin midi input implementation +========================================================================================= +*/ + + +static PmError allocate_input_buffer(HMIDIIN h, long buffer_len) +{ + LPMIDIHDR hdr = allocate_buffer(buffer_len); + if (!hdr) return pmInsufficientMemory; + pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR)); + if (pm_hosterror) { + pm_free(hdr); + return pm_hosterror; + } + pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR)); + return pm_hosterror; +} + + +static PmError winmm_in_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + int max_sysex_len = midi->buffer_len * 4; + int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN; + midiwinmm_type m; + + dwDevice = (DWORD)(FPTR)descriptors[i].descriptor; + + /* create system dependent device data */ + m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ + midi->descriptor = m; + if (!m) goto no_memory; + m->handle.in = NULL; + m->buffers = NULL; /* not used for input */ + m->num_buffers = 0; /* not used for input */ + m->max_buffers = FALSE; /* not used for input */ + m->buffers_expanded = 0; /* not used for input */ + m->next_buffer = 0; /* not used for input */ + m->buffer_signal = 0; /* not used for input */ +#ifdef USE_SYSEX_BUFFERS + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) + m->sysex_buffers[i] = NULL; /* not used for input */ + m->next_sysex_buffer = 0; /* not used for input */ +#endif + m->last_time = 0; + m->first_message = TRUE; /* not used for input */ + m->sysex_mode = FALSE; + m->sysex_word = 0; + m->sysex_byte_count = 0; + m->hdr = NULL; /* not used for input */ + m->sync_time = 0; + m->delta = 0; + m->error = MMSYSERR_NOERROR; + /* 4000 is based on Windows documentation -- that's the value used in the + memory manager. It's small enough that it should not hurt performance even + if it's not optimal. + */ + InitializeCriticalSectionAndSpinCount(&m->lock, 4000); + /* open device */ + pm_hosterror = midiInOpen( + &(m->handle.in), /* input device handle */ + dwDevice, /* device ID */ + (DWORD_PTR) winmm_in_callback, /* callback address */ + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback is a procedure */ + if (pm_hosterror) goto free_descriptor; + + if (num_input_buffers < MIN_INPUT_BUFFERS) + num_input_buffers = MIN_INPUT_BUFFERS; + for (i = 0; i < num_input_buffers; i++) { + if (allocate_input_buffer(m->handle.in, INPUT_SYSEX_LEN)) { + /* either pm_hosterror was set, or the proper return code + is pmInsufficientMemory */ + goto close_device; + } + } + /* start device */ + pm_hosterror = midiInStart(m->handle.in); + if (pm_hosterror) goto reset_device; + return pmNoError; + + /* undo steps leading up to the detected error */ +reset_device: + /* ignore return code (we already have an error to report) */ + midiInReset(m->handle.in); +close_device: + midiInClose(m->handle.in); /* ignore return code */ +free_descriptor: + midi->descriptor = NULL; + pm_free(m); +no_memory: + if (pm_hosterror) { + midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + //assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + /* if !pm_hosterror, then the error must be pmInsufficientMemory */ + return pmInsufficientMemory; + /* note: if we return an error code, the device will be + closed and memory will be freed. It's up to the caller + to free the parameter midi */ +} + +static PmError winmm_in_poll(PmInternal *midi) { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + return m->error; +} + + + +/* winmm_in_close -- close an open midi input device */ +/* + * assume midi is non-null (checked by caller) + */ +static PmError winmm_in_close(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (!m) return pmBadPtr; + /* device to close */ + if ((pm_hosterror = midiInStop(m->handle.in))) { + midiInReset(m->handle.in); /* try to reset and close port */ + midiInClose(m->handle.in); + } else if ((pm_hosterror = midiInReset(m->handle.in))) { + midiInClose(m->handle.in); /* best effort to close midi port */ + } else { + pm_hosterror = midiInClose(m->handle.in); + } + midi->descriptor = NULL; + DeleteCriticalSection(&m->lock); + pm_free(m); /* delete */ + if (pm_hosterror) { + 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 *)(FPTR) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + + /* NOTE: we do not just EnterCriticalSection() here because an + * MIM_CLOSE message arrives when the port is closed, but then + * the m->lock has been destroyed. + */ + + switch (wMsg) { + case MIM_DATA: { + /* if this callback is reentered with data, we're in trouble. + * It's hard to imagine that Microsoft would allow callbacks + * to be reentrant -- isn't the model that this is like a + * hardware interrupt? -- but I've seen reentrant behavior + * using a debugger, so it happens. + */ + //long new_driver_time; + EnterCriticalSection(&m->lock); + + /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of + message LOB; + dwParam2 is time message received by input device driver, specified + in [ms] from when midiInStart called. + each message is expanded to include the status byte */ + + //new_driver_time = dwParam2; + + if ((dwParam1 & 0x80) == 0) { + /* not a status byte -- ignore it. This happened running the + sysex.c test under Win2K with MidiMan USB 1x1 interface, + but I can't reproduce it. -RBD + */ + /* printf("non-status byte found\n"); */ + } else { /* data to process */ + PmEvent event; + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + event.timestamp = dwParam2; + event.message = dwParam1; + pm_read_short(midi, &event); + } + LeaveCriticalSection(&m->lock); + break; + } + case MIM_LONGDATA: { + MIDIHDR *lpMidiHdr = (MIDIHDR *)(FPTR)dwParam1; + unsigned char *data = (unsigned char *) lpMidiHdr->lpData; + unsigned int processed = 0; + int remaining = lpMidiHdr->dwBytesRecorded; + + EnterCriticalSection(&m->lock); + /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", + lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */ + if (midi->time_proc) + dwParam2 = (*midi->time_proc)(midi->time_info); + /* can there be more than one message in one buffer? */ + /* assume yes and iterate through them */ + while (remaining > 0) { + unsigned int amt = pm_read_bytes(midi, data + processed, + remaining, dwParam2); + remaining -= amt; + processed += amt; + } + + /* when a device is closed, the pending MIM_LONGDATA buffers are + returned to this callback with dwBytesRecorded == 0. In this + case, we do not want to send them back to the interface (if + we do, the interface will not close, and Windows OS may hang). */ + if (lpMidiHdr->dwBytesRecorded > 0) { + //MMRESULT rslt; + lpMidiHdr->dwBytesRecorded = 0; + lpMidiHdr->dwFlags = 0; + + /* note: no error checking -- can this actually fail? */ + midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); + //assert(rslt == MMSYSERR_NOERROR); + /* note: I don't think this can fail except possibly for + * MMSYSERR_NOMEM, but the pain of reporting this + * unlikely but probably catastrophic error does not seem + * worth it. + */ + midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); + //assert(rslt == MMSYSERR_NOERROR); + LeaveCriticalSection(&m->lock); + } else { + midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR)); + LeaveCriticalSection(&m->lock); + pm_free(lpMidiHdr); + } + break; + } + case MIM_OPEN: + break; + case MIM_CLOSE: + break; + case MIM_ERROR: + /* printf("MIM_ERROR\n"); */ + break; + case MIM_LONGERROR: + /* printf("MIM_LONGERROR\n"); */ + break; + default: + break; + } +} + +/* +========================================================================================= +begin midi output implementation +========================================================================================= +*/ + +/* begin helper routines used by midiOutStream interface */ + +/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */ +static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, + unsigned long delta, unsigned long msg) +{ + unsigned long *ptr = (unsigned long *) + (hdr->lpData + hdr->dwBytesRecorded); + *ptr++ = delta; /* dwDeltaTime */ + *ptr++ = 0; /* dwStream */ + *ptr++ = msg; /* dwEvent */ + hdr->dwBytesRecorded += 3 * sizeof(long); + /* if the addition of three more words (a message) would extend beyond + the buffer length, then return TRUE (full) + */ + return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength; +} + + +static PmTimestamp pm_time_get(midiwinmm_type m) +{ + MMTIME mmtime; + //MMRESULT wRtn; + mmtime.wType = TIME_TICKS; + mmtime.u.ticks = 0; + midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime)); + //assert(wRtn == MMSYSERR_NOERROR); + return mmtime.u.ticks; +} + + +/* end helper routines used by midiOutStream interface */ + + +static PmError winmm_out_open(PmInternal *midi, void *driverInfo) +{ + DWORD dwDevice; + int i = midi->device_id; + midiwinmm_type m; + MIDIPROPTEMPO propdata; + MIDIPROPTIMEDIV divdata; + int max_sysex_len = midi->buffer_len * 4; + int output_buffer_len; + int num_buffers; + dwDevice = (DWORD)(FPTR) descriptors[i].descriptor; + /* create system dependent device data */ + m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ + midi->descriptor = m; + if (!m) goto no_memory; + m->handle.out = NULL; + m->buffers = NULL; + m->num_buffers = 0; + m->max_buffers = 0; + m->buffers_expanded = FALSE; + m->next_buffer = 0; +#ifdef USE_SYSEX_BUFFERS + m->sysex_buffers[0] = NULL; + m->sysex_buffers[1] = NULL; + m->next_sysex_buffer = 0; +#endif + m->last_time = 0; + m->first_message = TRUE; /* we treat first message as special case */ + m->sysex_mode = FALSE; + m->sysex_word = 0; + m->sysex_byte_count = 0; + m->hdr = NULL; + m->sync_time = 0; + m->delta = 0; + m->error = MMSYSERR_NOERROR; + + /* create a signal */ + m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* this should only fail when there are very serious problems */ + assert(m->buffer_signal); + + /* open device */ + if (midi->latency == 0) { + /* use simple midi out calls */ + pm_hosterror = midiOutOpen( + (LPHMIDIOUT) & m->handle.out, /* device Handle */ + dwDevice, /* device ID */ + /* note: same callback fn as for StreamOpen: */ + (DWORD_PTR) winmm_streamout_callback, /* callback fn */ + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback type */ + } else { + /* use stream-based midi output (schedulable in future) */ + pm_hosterror = midiStreamOpen( + &m->handle.stream, /* device Handle */ + (LPUINT) & dwDevice, /* device ID pointer */ + 1, /* reserved, must be 1 */ + (DWORD_PTR) winmm_streamout_callback, + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); + } + if (pm_hosterror != MMSYSERR_NOERROR) { + goto free_descriptor; + } + + if (midi->latency == 0) { + num_buffers = NUM_SIMPLE_SYSEX_BUFFERS; + output_buffer_len = max_sysex_len / num_buffers; + if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN) + output_buffer_len = MIN_SIMPLE_SYSEX_LEN; + } else { + //long dur = 0; + //num_buffers = (int)(double)max((double)midi->buffer_len, (double)midi->latency / 2); + if (midi->buffer_len > (midi->latency / 2)) + { + num_buffers = midi->buffer_len; + } + else + { + num_buffers = (midi->latency / 2); + } + + if (num_buffers < MIN_STREAM_BUFFERS) + num_buffers = MIN_STREAM_BUFFERS; + output_buffer_len = STREAM_BUFFER_LEN; + + propdata.cbStruct = sizeof(MIDIPROPTEMPO); + propdata.dwTempo = 480000; /* microseconds per quarter */ + pm_hosterror = midiStreamProperty(m->handle.stream, + (LPBYTE) & propdata, + MIDIPROP_SET | MIDIPROP_TEMPO); + if (pm_hosterror) goto close_device; + + divdata.cbStruct = sizeof(MIDIPROPTEMPO); + divdata.dwTimeDiv = 480; /* divisions per quarter */ + pm_hosterror = midiStreamProperty(m->handle.stream, + (LPBYTE) & divdata, + MIDIPROP_SET | MIDIPROP_TIMEDIV); + if (pm_hosterror) goto close_device; + } + /* allocate buffers */ + if (allocate_buffers(m, output_buffer_len, num_buffers)) + goto free_buffers; + /* start device */ + if (midi->latency != 0) { + pm_hosterror = midiStreamRestart(m->handle.stream); + if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers; + } + return pmNoError; + +free_buffers: + /* buffers are freed below by winmm_out_delete */ +close_device: + midiOutClose(m->handle.out); +free_descriptor: + midi->descriptor = NULL; + winmm_out_delete(midi); /* frees buffers and m */ +no_memory: + if (pm_hosterror) { + midiOutGetErrorText(pm_hosterror, (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + //assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + return pmInsufficientMemory; +} + + +/* winmm_out_delete -- carefully free data associated with midi */ +/**/ +static void winmm_out_delete(PmInternal *midi) +{ + int i; + /* delete system dependent device data */ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m) { + if (m->buffer_signal) { + /* don't report errors -- better not to stop cleanup */ + CloseHandle(m->buffer_signal); + } + /* if using stream output, free buffers */ + for (i = 0; i < m->num_buffers; i++) { + if (m->buffers[i]) pm_free(m->buffers[i]); + } + m->num_buffers = 0; + pm_free(m->buffers); + m->max_buffers = 0; +#ifdef USE_SYSEX_BUFFERS + /* free sysex buffers */ + for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { + if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); + } +#endif + } + midi->descriptor = NULL; + pm_free(m); /* delete */ +} + + +/* see comments for winmm_in_close */ +static PmError winmm_out_close(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m->handle.out) { + /* device to close */ + if (midi->latency == 0) { + pm_hosterror = midiOutClose(m->handle.out); + } else { + pm_hosterror = midiStreamClose(m->handle.stream); + } + /* regardless of outcome, free memory */ + winmm_out_delete(midi); + } + if (pm_hosterror) { + midiOutGetErrorText(pm_hosterror, + (char *) pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + //assert(err == MMSYSERR_NOERROR); + return pmHostError; + } + return pmNoError; +} + + +static PmError winmm_out_abort(PmInternal *midi) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + m->error = MMSYSERR_NOERROR; + + /* only stop output streams */ + if (midi->latency > 0) { + m->error = midiStreamStop(m->handle.stream); + } + return m->error ? pmHostError : pmNoError; +} + + +static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + assert(m); + if (m->hdr) { + m->error = midiOutPrepareHeader(m->handle.out, m->hdr, + sizeof(MIDIHDR)); + if (m->error) { + /* do not send message */ + } else if (midi->latency == 0) { + /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded + * should be zero. This is set in get_free_sysex_buffer(). + * The msg length goes in dwBufferLength in spite of what + * Microsoft documentation says (or doesn't say). */ + m->hdr->dwBufferLength = m->hdr->dwBytesRecorded; + m->hdr->dwBytesRecorded = 0; + m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); + } else { + m->error = midiStreamOut(m->handle.stream, m->hdr, + sizeof(MIDIHDR)); + } + midi->fill_base = NULL; + m->hdr = NULL; + if (m->error) { + m->hdr->dwFlags = 0; /* release the buffer */ + return pmHostError; + } + } + return pmNoError; +} + + + +#ifdef GARBAGE +static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + unsigned char *msg_buffer; + + /* at the beginning of sysex, m->hdr is NULL */ + if (!m->hdr) { /* allocate a buffer if none allocated yet */ + m->hdr = get_free_output_buffer(midi); + if (!m->hdr) return pmInsufficientMemory; + m->sysex_byte_count = 0; + } + /* figure out where to write byte */ + msg_buffer = (unsigned char *) (m->hdr->lpData); + assert(m->hdr->lpData == (char *) (m->hdr + 1)); + + /* check for overflow */ + if (m->sysex_byte_count >= m->hdr->dwBufferLength) { + /* allocate a bigger message -- double it every time */ + LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2); + /* printf("expand to %d bytes\n", m->sysex_byte_count * 2); */ + if (!big) return pmInsufficientMemory; + m->error = midiOutPrepareHeader(m->handle.out, big, + sizeof(MIDIHDR)); + if (m->error) { + m->hdr = NULL; + return pmHostError; + } + memcpy(big->lpData, msg_buffer, m->sysex_byte_count); + msg_buffer = (unsigned char *) (big->lpData); + if (m->buffers[0] == m->hdr) { + m->buffers[0] = big; + pm_free(m->hdr); + /* printf("freed m->hdr\n"); */ + } else if (m->buffers[1] == m->hdr) { + m->buffers[1] = big; + pm_free(m->hdr); + /* printf("freed m->hdr\n"); */ + } + m->hdr = big; + } + + /* append byte to message */ + msg_buffer[m->sysex_byte_count++] = byte; + + /* see if we have a complete message */ + if (byte == MIDI_EOX) { + m->hdr->dwBytesRecorded = m->sysex_byte_count; + /* + { int i; int len = m->hdr->dwBytesRecorded; + printf("OutLongMsg %d ", len); + for (i = 0; i < len; i++) { + printf("%2x ", msg_buffer[i]); + } + } + */ + m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); + m->hdr = NULL; /* stop using this message buffer */ + if (m->error) return pmHostError; + } + return pmNoError; +} +#endif + + +static PmError winmm_write_short(PmInternal *midi, PmEvent *event) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + PmError rslt = pmNoError; + assert(m); + + if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ + m->error = midiOutShortMsg(m->handle.out, event->message); + if (m->error) rslt = pmHostError; + } else { /* use midiStream interface -- pass data through buffers */ + unsigned long when = event->timestamp; + unsigned long delta; + int full; + if (when == 0) when = midi->now; + /* when is in real_time; translate to intended stream time */ + when = when + m->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < m->last_time) when = m->last_time; + delta = when - m->last_time; + m->last_time = when; + /* before we insert any data, we must have a buffer */ + if (m->hdr == NULL) { + /* stream interface: buffers allocated when stream is opened */ + m->hdr = get_free_output_buffer(midi); + } + full = add_to_buffer(m, m->hdr, delta, event->message); + if (full) rslt = winmm_write_flush(midi, when); + } + return rslt; +} + +#define winmm_begin_sysex winmm_write_flush +#ifndef winmm_begin_sysex +static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + PmError rslt = pmNoError; + + if (midi->latency == 0) { + /* do nothing -- it's handled in winmm_write_byte */ + } else { + /* sysex expects an empty sysex buffer, so send whatever is here */ + rslt = winmm_write_flush(midi); + } + return rslt; +} +#endif + +static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + /* could check for callback_error here, but I haven't checked + * what happens if we exit early and don't finish the sysex msg + * and clean up + */ + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + PmError rslt = pmNoError; + LPMIDIHDR hdr = m->hdr; + if (!hdr) return rslt; /* something bad happened earlier, + do not report an error because it would have been + reported (at least) once already */ + /* a(n old) version of MIDI YOKE requires a zero byte after + * the sysex message, but do not increment dwBytesRecorded: */ + hdr->lpData[hdr->dwBytesRecorded] = 0; + if (midi->latency == 0) { +#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX + /* DEBUG CODE: */ + { int i; int len = m->hdr->dwBufferLength; + printf("OutLongMsg %d ", len); + for (i = 0; i < len; i++) { + printf("%2x ", (unsigned char) (m->hdr->lpData[i])); + } + } +#endif + } else { + /* Using stream interface. There are accumulated bytes in m->hdr + to send using midiStreamOut + */ + /* add bytes recorded to MIDIEVENT length, but don't + count the MIDIEVENT data (3 longs) */ + MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData); + evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); + /* round up BytesRecorded to multiple of 4 */ + hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; + } + rslt = winmm_write_flush(midi, timestamp); + return rslt; +} + + +static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp) +{ + /* write a sysex byte */ + PmError rslt = pmNoError; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + LPMIDIHDR hdr = m->hdr; + unsigned char *msg_buffer; + assert(m); + if (!hdr) { + m->hdr = hdr = get_free_output_buffer(midi); + assert(hdr); + midi->fill_base = (unsigned char *)(FPTR) m->hdr->lpData; + midi->fill_offset_ptr = (uint32_t *)&(hdr->dwBytesRecorded); + + /* when buffer fills, Pm_WriteSysEx will revert to calling + * pmwin_write_byte, which expect to have space, so leave + * one byte free for pmwin_write_byte. Leave another byte + * of space for zero after message to make early version of + * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */ + midi->fill_length = hdr->dwBufferLength - 2; + if (midi->latency != 0) { + unsigned long when = (unsigned long) timestamp; + unsigned long delta; + unsigned long *ptr; + if (when == 0) when = midi->now; + /* when is in real_time; translate to intended stream time */ + when = when + m->delta + midi->latency; + /* make sure we don't go backward in time */ + if (when < m->last_time) when = m->last_time; + delta = when - m->last_time; + m->last_time = when; + + ptr = (unsigned long *) hdr->lpData; + *ptr++ = delta; + *ptr++ = 0; + *ptr = MEVT_F_LONG; + hdr->dwBytesRecorded = 3 * sizeof(long); + /* data will be added at an offset of dwBytesRecorded ... */ + } + } + /* add the data byte */ + msg_buffer = (unsigned char *) (hdr->lpData); + msg_buffer[hdr->dwBytesRecorded++] = byte; + + /* see if buffer is full, leave one byte extra for pad */ + if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) { + /* write what we've got and continue */ + rslt = winmm_end_sysex(midi, timestamp); + } + return rslt; +} + +#ifdef EXPANDING_SYSEX_BUFFERS +note: this code is here as an aid in case you want sysex buffers + to expand to hold large messages completely. If so, you + will want to change SYSEX_BYTES_PER_BUFFER above to some + variable that remembers the buffer size. A good place to + put this value would be in the hdr->dwUser field. + + rslt = resize_sysex_buffer(midi, m->sysex_byte_count, + m->sysex_byte_count * 2); + + if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */ +#endif +#ifdef EXPANDING_SYSEX_BUFFERS + int bytesRecorded = hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ + rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); + hdr->dwBytesRecorded = bytesRecorded; + + if (rslt == pmBufferMaxSize) /* if buffer can't be resized */ +#endif + + + +static PmTimestamp winmm_synchronize(PmInternal *midi) +{ + midiwinmm_type m; + unsigned long pm_stream_time_2; + unsigned long real_time; + unsigned long pm_stream_time; + + /* only synchronize if we are using stream interface */ + if (midi->latency == 0) return 0; + + /* figure out the time */ + m = (midiwinmm_type) midi->descriptor; + pm_stream_time_2 = pm_time_get(m); + + do { + /* read real_time between two reads of stream time */ + pm_stream_time = pm_stream_time_2; + real_time = (*midi->time_proc)(midi->time_info); + pm_stream_time_2 = pm_time_get(m); + /* repeat if more than 1ms elapsed */ + } while (pm_stream_time_2 > pm_stream_time + 1); + m->delta = pm_stream_time - real_time; + m->sync_time = real_time; + return real_time; +} + +#ifdef USE_SYSEX_BUFFERS +/* winmm_out_callback -- recycle sysex buffers */ +static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, + DWORD dwParam2) +{ + PmInternal *midi = (PmInternal *) dwInstance; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; + int err = 0; /* set to 0 so that no buffer match will also be an error */ + + /* Future optimization: eliminate UnprepareHeader calls -- they aren't + necessary; however, this code uses the prepared-flag to indicate which + buffers are free, so we need to do something to flag empty buffers if + we leave them prepared + */ + /* + printf("out_callback: hdr %x, wMsg %x, MOM_DONE %x\n", + hdr, wMsg, MOM_DONE); + */ + if (wMsg == MOM_DONE) { + MMRESULT ret = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + assert(ret == MMSYSERR_NOERROR); + } + /* notify waiting sender that a buffer is available */ + err = SetEvent(m->buffer_signal); + assert(err); /* false -> error */ +} +#endif + +/* winmm_streamout_callback -- unprepare (free) buffer header */ +static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, + DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + PmInternal *midi = (PmInternal *)(FPTR) dwInstance; + LPMIDIHDR hdr = (LPMIDIHDR)(FPTR) dwParam1; + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + //int err; + + /* Even if an error is pending, I think we should unprepare msgs and + signal their arrival + */ + /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", + hdr, wMsg, MOM_DONE); */ + if (wMsg == MOM_DONE) { + midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + //assert(ret == MMSYSERR_NOERROR); + } + /* signal client in case it is blocked waiting for buffer */ + SetEvent(m->buffer_signal); + //assert(err); /* false -> error */ +} + + +/* +========================================================================================= +begin exported functions +========================================================================================= +*/ + +#define winmm_in_abort pm_fail_fn +pm_fns_node pm_winmm_in_dictionary = { + none_write_short, + none_sysex, + none_sysex, + none_write_byte, + none_write_short, + none_write_flush, + winmm_synchronize, + winmm_in_open, + winmm_in_abort, + winmm_in_close, + winmm_in_poll, + winmm_has_host_error, + winmm_get_host_error + }; + +pm_fns_node pm_winmm_out_dictionary = { + winmm_write_short, + winmm_begin_sysex, + winmm_end_sysex, + winmm_write_byte, + winmm_write_short, /* short realtime message */ + winmm_write_flush, + winmm_synchronize, + winmm_out_open, + winmm_out_abort, + winmm_out_close, + none_poll, + winmm_has_host_error, + winmm_get_host_error + }; + + +/* initialize winmm interface. Note that if there is something wrong + with winmm (e.g. it is not supported or installed), it is not an + error. We should simply return without having added any devices to + the table. Hence, no error code is returned. Furthermore, this init + code is called along with every other supported interface, so the + user would have a very hard time figuring out what hardware and API + generated the error. Finally, it would add complexity to pmwin.c to + remember where the error code came from in order to convert to text. + */ +void pm_winmm_init( void ) +{ + pm_winmm_mapper_input(); + pm_winmm_mapper_output(); + pm_winmm_general_inputs(); + pm_winmm_general_outputs(); +} + + +/* no error codes are returned, even if errors are encountered, because + there is probably nothing the user could do (e.g. it would be an error + to retry. + */ +void pm_winmm_term( void ) +{ + int i; +#ifdef DEBUG + char msg[PM_HOST_ERROR_MSG_LEN]; +#endif + //int doneAny = 0; +#ifdef DEBUG + printf("pm_winmm_term called\n"); +#endif + for (i = 0; i < pm_descriptor_index; i++) { + PmInternal * midi = descriptors[i].internalDescriptor; + if (midi) { + midiwinmm_type m = (midiwinmm_type) midi->descriptor; + if (m->handle.out) { + /* close next open device*/ +#ifdef DEBUG + if (doneAny == 0) { + printf("begin closing open devices...\n"); + doneAny = 1; + } + /* report any host errors; this EXTEREMELY useful when + trying to debug client app */ + if (winmm_has_host_error(midi)) { + winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN); + printf("%s\n", msg); + } +#endif + /* close all open ports */ + (*midi->dictionary->close)(midi); + } + } + } + if (midi_in_caps) { + pm_free(midi_in_caps); + midi_in_caps = NULL; + } + if (midi_out_caps) { + pm_free(midi_out_caps); + midi_out_caps = NULL; + } +#ifdef DEBUG + if (doneAny) { + printf("warning: devices were left open. They have been closed.\n"); + } + printf("pm_winmm_term exiting\n"); +#endif + pm_descriptor_index = 0; +} diff --git a/src/lib/portmidi/pmwinmm.h b/src/lib/portmidi/pmwinmm.h new file mode 100644 index 00000000000..94742001bcd --- /dev/null +++ b/src/lib/portmidi/pmwinmm.h @@ -0,0 +1,5 @@ +/* midiwin32.h -- system-specific definitions */ + +void pm_winmm_init( void ); +void pm_winmm_term( void ); + diff --git a/src/lib/portmidi/portmidi.c b/src/lib/portmidi/portmidi.c new file mode 100644 index 00000000000..2ed9171e781 --- /dev/null +++ b/src/lib/portmidi/portmidi.c @@ -0,0 +1,1137 @@ +#ifdef _MSC_VER + #pragma warning(disable: 4244) // stop warnings about downsize typecasts + #pragma warning(disable: 4018) // stop warnings about signed/unsigned +#endif + +#include "stdlib.h" +#include "string.h" +#include "portmidi.h" +#include "porttime.h" +#include "pmutil.h" +#include "pminternal.h" +#include <assert.h> + +#define MIDI_CLOCK 0xf8 +#define MIDI_ACTIVE 0xfe +#define MIDI_STATUS_MASK 0x80 +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 +#define MIDI_START 0xFA +#define MIDI_STOP 0xFC +#define MIDI_CONTINUE 0xFB +#define MIDI_F9 0xF9 +#define MIDI_FD 0xFD +#define MIDI_RESET 0xFF +#define MIDI_NOTE_ON 0x90 +#define MIDI_NOTE_OFF 0x80 +#define MIDI_CHANNEL_AT 0xD0 +#define MIDI_POLY_AT 0xA0 +#define MIDI_PROGRAM 0xC0 +#define MIDI_CONTROL 0xB0 +#define MIDI_PITCHBEND 0xE0 +#define MIDI_MTC 0xF1 +#define MIDI_SONGPOS 0xF2 +#define MIDI_SONGSEL 0xF3 +#define MIDI_TUNE 0xF6 + +#define is_empty(midi) ((midi)->tail == (midi)->head) + +/* this is not static so that pm_init can set it directly if + * (see pmmac.c:pm_init()) + */ +int pm_initialized = FALSE; + +int pm_hosterror; +char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; + +#ifdef PM_CHECK_ERRORS + +#include <stdio.h> + +#define STRING_MAX 80 + +static void prompt_and_exit(void) +{ + char line[STRING_MAX]; + printf("type ENTER..."); + fgets(line, STRING_MAX, stdin); + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError pm_errmsg(PmError err) +{ + if (err == pmHostError) { + /* it seems pointless to allocate memory and copy the string, + * so I will do the work of Pm_GetHostErrorText directly + */ + printf("PortMidi found host error...\n %s\n", pm_hosterror_text); + pm_hosterror = FALSE; + pm_hosterror_text[0] = 0; /* clear the message */ + prompt_and_exit(); + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + prompt_and_exit(); + } + return err; +} +#else +#define pm_errmsg(err) err +#endif + +/* +==================================================================== +system implementation of portmidi interface +==================================================================== +*/ + +int pm_descriptor_max = 0; +int pm_descriptor_index = 0; +descriptor_type descriptors = NULL; + +/* pm_add_device -- describe interface/device pair to library + * + * This is called at intialization time, once for each + * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1) + * The strings are retained but NOT COPIED, so do not destroy them! + * + * returns pmInvalidDeviceId if device memory is exceeded + * otherwise returns pmNoError + */ +PmError pm_add_device(char *interf, char *name, int input, + void *descriptor, pm_fns_type dictionary) { + if (pm_descriptor_index >= pm_descriptor_max) { + // expand descriptors + descriptor_type new_descriptors = (descriptor_type) + pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32)); + if (!new_descriptors) return pmInsufficientMemory; + if (descriptors) { + memcpy(new_descriptors, descriptors, + sizeof(descriptor_node) * pm_descriptor_max); + free(descriptors); + } + pm_descriptor_max += 32; + descriptors = new_descriptors; + } + descriptors[pm_descriptor_index].pub.interf = interf; + descriptors[pm_descriptor_index].pub.name = name; + descriptors[pm_descriptor_index].pub.input = input; + descriptors[pm_descriptor_index].pub.output = !input; + + /* default state: nothing to close (for automatic device closing) */ + descriptors[pm_descriptor_index].pub.opened = FALSE; + + /* ID number passed to win32 multimedia API open */ + descriptors[pm_descriptor_index].descriptor = descriptor; + + /* points to PmInternal, allows automatic device closing */ + descriptors[pm_descriptor_index].internalDescriptor = NULL; + + descriptors[pm_descriptor_index].dictionary = dictionary; + + pm_descriptor_index++; + + return pmNoError; +} + + +/* utility to look up device, given a pattern, + note: pattern is modified + */ +int pm_find_default_device(char *pattern, int is_input) +{ + int id = pmNoDevice; + int i; + /* first parse pattern into name, interf parts */ + char *interf_pref = (char *)""; /* initially assume it is not there */ + char *name_pref = strstr(pattern, ", "); + + if (name_pref) { /* found separator, adjust the pointer */ + interf_pref = pattern; + name_pref[0] = 0; + name_pref += 2; + } else { + name_pref = pattern; /* whole string is the name pattern */ + } + for (i = 0; i < pm_descriptor_index; i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (info->input == is_input && + strstr(info->name, name_pref) && + strstr(info->interf, interf_pref)) { + id = i; + break; + } + } + return id; +} + + +/* +==================================================================== +portmidi implementation +==================================================================== +*/ + +PMEXPORT int Pm_CountDevices( void ) { + Pm_Initialize(); + /* no error checking -- Pm_Initialize() does not fail */ + return pm_descriptor_index; +} + + +PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) { + Pm_Initialize(); /* no error check needed */ + if (id >= 0 && id < pm_descriptor_index) { + return &descriptors[id].pub; + } + return NULL; +} + +/* pm_success_fn -- "noop" function pointer */ +PmError pm_success_fn(PmInternal *midi) { + return pmNoError; +} + +/* none_write -- returns an error if called */ +PmError none_write_short(PmInternal *midi, PmEvent *buffer) { + return pmBadPtr; +} + +/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */ +PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp) { + return pmBadPtr; +} + +PmError none_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp) { + return pmBadPtr; +} + +/* pm_fail_fn -- generic function, returns error if called */ +PmError pm_fail_fn(PmInternal *midi) { + return pmBadPtr; +} + +static PmError none_open(PmInternal *midi, void *driverInfo) { + return pmBadPtr; +} +static void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) { + *msg = 0; // empty string +} +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 +}; + + +PMEXPORT const char *Pm_GetErrorText( PmError errnum ) { + const char *msg; + + switch(errnum) + { + case pmNoError: + msg = ""; + break; + case pmHostError: + msg = "PortMidi: `Host error'"; + break; + case pmInvalidDeviceId: + msg = "PortMidi: `Invalid device ID'"; + break; + case pmInsufficientMemory: + msg = "PortMidi: `Insufficient memory'"; + break; + case pmBufferTooSmall: + msg = "PortMidi: `Buffer too small'"; + break; + case pmBadPtr: + msg = "PortMidi: `Bad pointer'"; + break; + case pmInternalError: + msg = "PortMidi: `Internal PortMidi Error'"; + break; + case pmBufferOverflow: + msg = "PortMidi: `Buffer overflow'"; + break; + case pmBadData: + msg = "PortMidi: `Invalid MIDI message Data'"; + break; + case pmBufferMaxSize: + msg = "PortMidi: `Buffer cannot be made larger'"; + break; + 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. + */ +PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len) { + assert(msg); + assert(len > 0); + if (pm_hosterror) { + strncpy(msg, (char *) pm_hosterror_text, len); + pm_hosterror = FALSE; + pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it + might help with debugging */ + msg[len - 1] = 0; /* make sure string is terminated */ + } else { + msg[0] = 0; /* no string to return */ + } +} + + +PMEXPORT int Pm_HasHostError(PortMidiStream * stream) { + if (pm_hosterror) return TRUE; + if (stream) { + PmInternal * midi = (PmInternal *) stream; + pm_hosterror = (*midi->dictionary->has_host_error)(midi); + if (pm_hosterror) { + midi->dictionary->host_error(midi, pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + /* now error message is global */ + return TRUE; + } + } + return FALSE; +} + + +PMEXPORT PmError Pm_Initialize( void ) { + if (!pm_initialized) { + pm_hosterror = FALSE; + pm_hosterror_text[0] = 0; /* the null string */ + pm_init(); + pm_initialized = TRUE; + } + return pmNoError; +} + + +PMEXPORT PmError Pm_Terminate( void ) { + if (pm_initialized) { + pm_term(); + // if there are no devices, descriptors might still be NULL + if (descriptors != NULL) { + free(descriptors); + descriptors = NULL; + } + pm_descriptor_index = 0; + pm_descriptor_max = 0; + pm_initialized = FALSE; + } + return pmNoError; +} + + +/* Pm_Read -- read up to length messages from source into buffer */ +/* + * returns number of messages actually read, or error code + */ +PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) { + PmInternal *midi = (PmInternal *) stream; + int n = 0; + PmError err = pmNoError; + pm_hosterror = FALSE; + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else if(!descriptors[midi->device_id].pub.input) + err = pmBadPtr; + /* First poll for data in the buffer... + * This either simply checks for data, or attempts first to fill the buffer + * with data from the MIDI hardware; this depends on the implementation. + * We could call Pm_Poll here, but that would redo a lot of redundant + * parameter checking, so I copied some code from Pm_Poll to here: */ + else err = (*(midi->dictionary->poll))(midi); + + if (err != pmNoError) { + if (err == pmHostError) { + midi->dictionary->host_error(midi, pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + pm_hosterror = TRUE; + } + return pm_errmsg(err); + } + + while (n < length) { + PmError err = Pm_Dequeue(midi->queue, buffer++); + if (err == pmBufferOverflow) { + /* ignore the data we have retreived so far */ + return pm_errmsg(pmBufferOverflow); + } else if (err == 0) { /* empty queue */ + break; + } + n++; + } + return n; +} + +PMEXPORT PmError Pm_Poll( PortMidiStream *stream ) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err; + + pm_hosterror = FALSE; + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.input) + err = pmBadPtr; + else + err = (*(midi->dictionary->poll))(midi); + + if (err != pmNoError) { + if (err == pmHostError) { + midi->dictionary->host_error(midi, pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + pm_hosterror = TRUE; + } + return pm_errmsg(err); + } + + return !Pm_QueueEmpty(midi->queue); +} + + +/* this is called from Pm_Write and Pm_WriteSysEx to issue a + * call to the system-dependent end_sysex function and handle + * the error return + */ +static PmError pm_end_sysex(PmInternal *midi) +{ + PmError err = (*midi->dictionary->end_sysex)(midi, 0); + midi->sysex_in_progress = FALSE; + if (err == pmHostError) { + midi->dictionary->host_error(midi, pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + pm_hosterror = TRUE; + } + return err; +} + + +/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and + Pm_WriteSysEx all operate a state machine that "outputs" calls to + write_short, begin_sysex, write_byte, end_sysex, and write_realtime */ + +PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + int i; + int bits; + + pm_hosterror = FALSE; + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else if(!descriptors[midi->device_id].pub.output) + err = pmBadPtr; + else + err = pmNoError; + + if (err != pmNoError) goto pm_write_error; + + if (midi->latency == 0) { + midi->now = 0; + } else { + midi->now = (*(midi->time_proc))(midi->time_info); + if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) { + /* time to resync */ + midi->now = (*midi->dictionary->synchronize)(midi); + midi->first_message = FALSE; + } + } + /* error recovery: when a sysex is detected, we call + * dictionary->begin_sysex() followed by calls to + * dictionary->write_byte() and dictionary->write_realtime() + * until an end-of-sysex is detected, when we call + * dictionary->end_sysex(). After an error occurs, + * Pm_Write() continues to call functions. For example, + * it will continue to call write_byte() even after + * an error sending a sysex message, and end_sysex() will be + * called when an EOX or non-real-time status is found. + * When errors are detected, Pm_Write() returns immediately, + * so it is possible that this will drop data and leave + * sysex messages in a partially transmitted state. + */ + for (i = 0; i < length; i++) { + uint32_t msg = buffer[i].message; + bits = 0; + /* is this a sysex message? */ + if (Pm_MessageStatus(msg) == MIDI_SYSEX) { + if (midi->sysex_in_progress) { + /* error: previous sysex was not terminated by EOX */ + midi->sysex_in_progress = FALSE; + err = pmBadData; + goto pm_write_error; + } + midi->sysex_in_progress = TRUE; + if ((err = (*midi->dictionary->begin_sysex)(midi, + buffer[i].timestamp)) != pmNoError) + goto pm_write_error; + if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX, + buffer[i].timestamp)) != pmNoError) + goto pm_write_error; + bits = 8; + /* fall through to continue sysex processing */ + } else if ((msg & MIDI_STATUS_MASK) && + (Pm_MessageStatus(msg) != MIDI_EOX)) { + /* a non-sysex message */ + if (midi->sysex_in_progress) { + /* this should be a realtime message */ + if (is_real_time(msg)) { + if ((err = (*midi->dictionary->write_realtime)(midi, + &(buffer[i]))) != pmNoError) + goto pm_write_error; + } else { + midi->sysex_in_progress = FALSE; + err = pmBadData; + /* ignore any error from this, because we already have one */ + /* pass 0 as timestamp -- it's ignored */ + (*midi->dictionary->end_sysex)(midi, 0); + goto pm_write_error; + } + } else { /* regular short midi message */ + if ((err = (*midi->dictionary->write_short)(midi, + &(buffer[i]))) != pmNoError) + goto pm_write_error; + continue; + } + } + if (midi->sysex_in_progress) { /* send sysex bytes until EOX */ + /* see if we can accelerate data transfer */ + if (bits == 0 && midi->fill_base && /* 4 bytes to copy */ + (*midi->fill_offset_ptr) + 4 <= midi->fill_length && + (msg & 0x80808080) == 0) { /* all data */ + /* copy 4 bytes from msg to fill_base + fill_offset */ + unsigned char *ptr = midi->fill_base + + *(midi->fill_offset_ptr); + ptr[0] = msg; ptr[1] = msg >> 8; + ptr[2] = msg >> 16; ptr[3] = msg >> 24; + (*midi->fill_offset_ptr) += 4; + continue; + } + /* no acceleration, so do byte-by-byte copying */ + while (bits < 32) { + unsigned char midi_byte = (unsigned char) (msg >> bits); + if ((err = (*midi->dictionary->write_byte)(midi, midi_byte, + buffer[i].timestamp)) != pmNoError) + goto pm_write_error; + if (midi_byte == MIDI_EOX) { + err = pm_end_sysex(midi); + if (err != pmNoError) goto error_exit; + break; /* from while loop */ + } + bits += 8; + } + } else { + /* not in sysex mode, but message did not start with status */ + err = pmBadData; + goto pm_write_error; + } + } + /* after all messages are processed, send the data */ + if (!midi->sysex_in_progress) + err = (*midi->dictionary->write_flush)(midi, 0); +pm_write_error: + if (err == pmHostError) { + midi->dictionary->host_error(midi, pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + pm_hosterror = TRUE; + } +error_exit: + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when, PmMessage msg) +{ + PmEvent event; + + event.timestamp = when; + event.message = msg; + return Pm_Write(stream, &event, 1); +} + + +PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, + unsigned char *msg) +{ + /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */ + /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */ + #define BUFLEN ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage))) + PmEvent buffer[BUFLEN]; + int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */ + PmInternal *midi = (PmInternal *) stream; + /* the next byte in the buffer is represented by an index, bufx, and + a shift in bits */ + int shift = 0; + int bufx = 0; + buffer[0].message = 0; + buffer[0].timestamp = when; + + while (1) { + /* insert next byte into buffer */ + buffer[bufx].message |= ((*msg) << shift); + shift += 8; + if (*msg++ == MIDI_EOX) break; + if (shift == 32) { + shift = 0; + bufx++; + if (bufx == buffer_size) { + PmError err = Pm_Write(stream, buffer, buffer_size); + /* note: Pm_Write has already called errmsg() */ + if (err) return err; + /* prepare to fill another buffer */ + bufx = 0; + buffer_size = BUFLEN; + /* optimization: maybe we can just copy bytes */ + if (midi->fill_base) { + PmError err; + while (*(midi->fill_offset_ptr) < midi->fill_length) { + midi->fill_base[(*midi->fill_offset_ptr)++] = *msg; + if (*msg++ == MIDI_EOX) { + err = pm_end_sysex(midi); + if (err != pmNoError) return pm_errmsg(err); + goto end_of_sysex; + } + } + /* I thought that I could do a pm_Write here and + * change this if to a loop, avoiding calls in Pm_Write + * to the slower write_byte, but since + * sysex_in_progress is true, this will not flush + * the buffer, and we'll infinite loop: */ + /* err = Pm_Write(stream, buffer, 0); + if (err) return err; */ + /* instead, the way this works is that Pm_Write calls + * write_byte on 4 bytes. The first, since the buffer + * is full, will flush the buffer and allocate a new + * one. This primes the buffer so + * that we can return to the loop above and fill it + * efficiently without a lot of function calls. + */ + buffer_size = 1; /* get another message started */ + } + } + buffer[bufx].message = 0; + buffer[bufx].timestamp = when; + } + /* keep inserting bytes until you find MIDI_EOX */ + } +end_of_sysex: + /* we're finished sending full buffers, but there may + * be a partial one left. + */ + if (shift != 0) bufx++; /* add partial message to buffer len */ + if (bufx) { /* bufx is number of PmEvents to send from buffer */ + PmError err = Pm_Write(stream, buffer, bufx); + if (err) return err; + } + return pmNoError; +} + + + +PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream, + PmDeviceID inputDevice, + void *inputDriverInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info) +{ + PmInternal *midi; + PmError err = pmNoError; + pm_hosterror = FALSE; + *stream = NULL; + + /* arg checking */ + if (inputDevice < 0 || inputDevice >= pm_descriptor_index) + err = pmInvalidDeviceId; + else if (!descriptors[inputDevice].pub.input) + err = pmInvalidDeviceId; + else if(descriptors[inputDevice].pub.opened) + err = pmInvalidDeviceId; + + if (err != pmNoError) + goto error_return; + + /* create portMidi internal data */ + midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); + *stream = midi; + if (!midi) { + err = pmInsufficientMemory; + goto error_return; + } + midi->device_id = inputDevice; + midi->write_flag = FALSE; + midi->time_proc = time_proc; + midi->time_info = time_info; + /* windows adds timestamps in the driver and these are more accurate than + using a time_proc, so do not automatically provide a time proc. Non-win + implementations may want to provide a default time_proc in their + system-specific midi_out_open() method. + */ + if (bufferSize <= 0) bufferSize = 256; /* default buffer size */ + midi->queue = Pm_QueueCreate(bufferSize, (int32_t) sizeof(PmEvent)); + if (!midi->queue) { + /* free portMidi data */ + *stream = NULL; + pm_free(midi); + err = pmInsufficientMemory; + goto error_return; + } + midi->buffer_len = bufferSize; /* portMidi input storage */ + midi->latency = 0; /* not used */ + midi->sysex_in_progress = FALSE; + midi->sysex_message = 0; + midi->sysex_message_count = 0; + midi->filters = PM_FILT_ACTIVE; + midi->channel_mask = 0xFFFF; + midi->sync_time = 0; + midi->first_message = TRUE; + midi->dictionary = descriptors[inputDevice].dictionary; + midi->fill_base = NULL; + midi->fill_offset_ptr = NULL; + midi->fill_length = 0; + descriptors[inputDevice].internalDescriptor = midi; + /* open system dependent input device */ + err = (*midi->dictionary->open)(midi, inputDriverInfo); + if (err) { + *stream = NULL; + descriptors[inputDevice].internalDescriptor = NULL; + /* free portMidi data */ + Pm_QueueDestroy(midi->queue); + pm_free(midi); + } else { + /* portMidi input open successful */ + descriptors[inputDevice].pub.opened = TRUE; + } +error_return: + /* note: if there is a pmHostError, it is the responsibility + * of the system-dependent code (*midi->dictionary->open)() + * to set pm_hosterror and pm_hosterror_text + */ + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream, + PmDeviceID outputDevice, + void *outputDriverInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info, + int32_t latency ) +{ + PmInternal *midi; + PmError err = pmNoError; + pm_hosterror = FALSE; + *stream = NULL; + + /* arg checking */ + if (outputDevice < 0 || outputDevice >= pm_descriptor_index) + err = pmInvalidDeviceId; + else if (!descriptors[outputDevice].pub.output) + err = pmInvalidDeviceId; + else if (descriptors[outputDevice].pub.opened) + err = pmInvalidDeviceId; + if (err != pmNoError) + goto error_return; + + /* create portMidi internal data */ + midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); + *stream = midi; + if (!midi) { + err = pmInsufficientMemory; + goto error_return; + } + midi->device_id = outputDevice; + midi->write_flag = TRUE; + midi->time_proc = time_proc; + /* if latency > 0, we need a time reference. If none is provided, + use PortTime library */ + if (time_proc == NULL && latency != 0) { + if (!Pt_Started()) + Pt_Start(1, 0, 0); + /* time_get does not take a parameter, so coerce */ + midi->time_proc = (PmTimeProcPtr) Pt_Time; + } + midi->time_info = time_info; + midi->buffer_len = bufferSize; + midi->queue = NULL; /* unused by output */ + /* if latency zero, output immediate (timestamps ignored) */ + /* if latency < 0, use 0 but don't return an error */ + if (latency < 0) latency = 0; + midi->latency = latency; + midi->sysex_in_progress = FALSE; + midi->sysex_message = 0; /* unused by output */ + midi->sysex_message_count = 0; /* unused by output */ + midi->filters = 0; /* not used for output */ + midi->channel_mask = 0xFFFF; + midi->sync_time = 0; + midi->first_message = TRUE; + midi->dictionary = descriptors[outputDevice].dictionary; + midi->fill_base = NULL; + midi->fill_offset_ptr = NULL; + midi->fill_length = 0; + descriptors[outputDevice].internalDescriptor = midi; + /* open system dependent output device */ + err = (*midi->dictionary->open)(midi, outputDriverInfo); + if (err) { + *stream = NULL; + descriptors[outputDevice].internalDescriptor = NULL; + /* free portMidi data */ + pm_free(midi); + } else { + /* portMidi input open successful */ + descriptors[outputDevice].pub.opened = TRUE; + } +error_return: + /* note: system-dependent code must set pm_hosterror and + * pm_hosterror_text if a pmHostError occurs + */ + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + + if (midi == NULL) + err = pmBadPtr; + else + midi->channel_mask = mask; + + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters) { + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + + /* arg checking */ + if (midi == NULL) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + midi->filters = filters; + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_Close( PortMidiStream *stream ) { + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + + pm_hosterror = FALSE; + /* arg checking */ + if (midi == NULL) /* midi must point to something */ + err = pmBadPtr; + /* if it is an open device, the device_id will be valid */ + else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_index) + err = pmBadPtr; + /* and the device should be in the opened state */ + else if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + + if (err != pmNoError) + goto error_return; + + /* close the device */ + err = (*midi->dictionary->close)(midi); + /* even if an error occurred, continue with cleanup */ + descriptors[midi->device_id].internalDescriptor = NULL; + descriptors[midi->device_id].pub.opened = FALSE; + if (midi->queue) Pm_QueueDestroy(midi->queue); + pm_free(midi); +error_return: + /* system dependent code must set pm_hosterror and + * pm_hosterror_text if a pmHostError occurs. + */ + return pm_errmsg(err); +} + +PmError Pm_Synchronize( PortMidiStream* stream ) { + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + if (midi == NULL) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.output) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + midi->first_message = TRUE; + return err; +} + +PMEXPORT PmError Pm_Abort( PortMidiStream* stream ) { + PmInternal *midi = (PmInternal *) stream; + PmError err; + /* arg checking */ + if (midi == NULL) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.output) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + err = (*midi->dictionary->abort)(midi); + + if (err == pmHostError) { + midi->dictionary->host_error(midi, pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + pm_hosterror = TRUE; + } + return pm_errmsg(err); +} + + + +/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */ +#define pm_channel_filtered(status, mask) \ + ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask)))) + + +/* The following two functions will checks to see if a MIDI message matches + the filtering criteria. Since the sysex routines only want to filter realtime messages, + we need to have separate routines. + */ + + +/* pm_realtime_filtered returns non-zero if the filter will kill the current message. + Note that only realtime messages are checked here. + */ +#define pm_realtime_filtered(status, filters) \ + ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters))) + +/* + return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE)) + || ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK)) + || ((status == MIDI_START) && (filters & PM_FILT_PLAY)) + || ((status == MIDI_STOP) && (filters & PM_FILT_PLAY)) + || ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY)) + || ((status == MIDI_F9) && (filters & PM_FILT_F9)) + || ((status == MIDI_FD) && (filters & PM_FILT_FD)) + || ((status == MIDI_RESET) && (filters & PM_FILT_RESET)) + || ((status == MIDI_MTC) && (filters & PM_FILT_MTC)) + || ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION)) + || ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT)) + || ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE)); +}*/ + + +/* pm_status_filtered returns non-zero if a filter will kill the current message, based on status. + Note that sysex and real time are not checked. It is up to the subsystem (winmm, core midi, alsa) + to filter sysex, as it is handled more easily and efficiently at that level. + Realtime message are filtered in pm_realtime_filtered. + */ +#define pm_status_filtered(status, filters) ((1 << (16 + ((status) >> 4))) & (filters)) + + +/* + return ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE)) + || ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE)) + || ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH)) + || ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH)) + || ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM)) + || ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL)) + || ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND)); + +} +*/ + +static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + PmEvent event; + + /* there may be nothing in the buffer */ + if (midi->sysex_message_count == 0) return; /* nothing to flush */ + + event.message = midi->sysex_message; + event.timestamp = timestamp; + /* copied from pm_read_short, avoids filtering */ + if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { + midi->sysex_in_progress = FALSE; + } + midi->sysex_message_count = 0; + midi->sysex_message = 0; +} + + +/* pm_read_short and pm_read_bytes + are the interface between system-dependent MIDI input handlers + and the system-independent PortMIDI code. + The input handler MUST obey these rules: + 1) all short input messages must be sent to pm_read_short, which + enqueues them to a FIFO for the application. + 2) each buffer of sysex bytes should be reported by calling pm_read_bytes + (which sets midi->sysex_in_progress). After the eox byte, + pm_read_bytes will clear sysex_in_progress + */ + +/* pm_read_short is the place where all input messages arrive from + system-dependent code such as pmwinmm.c. Here, the messages + are entered into the PortMidi input buffer. + */ +void pm_read_short(PmInternal *midi, PmEvent *event) +{ + int status; + /* arg checking */ + assert(midi != NULL); + /* midi filtering is applied here */ + status = Pm_MessageStatus(event->message); + if (!pm_status_filtered(status, midi->filters) + && (!is_real_time(status) || + !pm_realtime_filtered(status, midi->filters)) + && !pm_channel_filtered(status, midi->channel_mask)) { + /* if sysex is in progress and we get a status byte, it had + better be a realtime message or the starting SYSEX byte; + otherwise, we exit the sysex_in_progress state + */ + if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) { + /* two choices: real-time or not. If it's real-time, then + * this should be delivered as a sysex byte because it is + * embedded in a sysex message + */ + if (is_real_time(status)) { + midi->sysex_message |= + (status << (8 * midi->sysex_message_count++)); + if (midi->sysex_message_count == 4) { + pm_flush_sysex(midi, event->timestamp); + } + } else { /* otherwise, it's not real-time. This interrupts + * a sysex message in progress */ + midi->sysex_in_progress = FALSE; + } + } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) { + midi->sysex_in_progress = FALSE; + } + } +} + +/* pm_read_bytes -- read one (partial) sysex msg from MIDI data */ +/* + * returns how many bytes processed + */ +unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, + int len, PmTimestamp timestamp) +{ + int i = 0; /* index into data, must not be unsigned (!) */ + PmEvent event; + event.timestamp = timestamp; + assert(midi); + /* note that since buffers may not have multiples of 4 bytes, + * pm_read_bytes may be called in the middle of an outgoing + * 4-byte PortMidi message. sysex_in_progress indicates that + * a sysex has been sent but no eox. + */ + if (len == 0) return 0; /* sanity check */ + if (!midi->sysex_in_progress) { + while (i < len) { /* process all data */ + unsigned char byte = data[i++]; + if (byte == MIDI_SYSEX && + !pm_realtime_filtered(byte, midi->filters)) { + midi->sysex_in_progress = TRUE; + i--; /* back up so code below will get SYSEX byte */ + break; /* continue looping below to process msg */ + } else if (byte == MIDI_EOX) { + midi->sysex_in_progress = FALSE; + return i; /* done with one message */ + } else if (byte & MIDI_STATUS_MASK) { + /* We're getting MIDI but no sysex in progress. + * Either the SYSEX status byte was dropped or + * the message was filtered. Drop the data, but + * send any embedded realtime bytes. + */ + /* assume that this is a real-time message: + * it is an error to pass non-real-time messages + * to pm_read_bytes + */ + event.message = byte; + pm_read_short(midi, &event); + } + } /* all bytes in the buffer are processed */ + } + /* Now, i<len implies sysex_in_progress. If sysex_in_progress + * becomes false in the loop, there must have been an overflow + * and we can just drop all remaining bytes + */ + while (i < len && midi->sysex_in_progress) { + if (midi->sysex_message_count == 0 && i <= len - 4 && + ((event.message = (((PmMessage) data[i]) | + (((PmMessage) data[i+1]) << 8) | + (((PmMessage) data[i+2]) << 16) | + (((PmMessage) data[i+3]) << 24))) & + 0x80808080) == 0) { /* all data, no status */ + if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { + midi->sysex_in_progress = FALSE; + } + i += 4; + } else { + while (i < len) { + /* send one byte at a time */ + unsigned char byte = data[i++]; + if (is_real_time(byte) && + pm_realtime_filtered(byte, midi->filters)) { + continue; /* real-time data is filtered, so omit */ + } + midi->sysex_message |= + (byte << (8 * midi->sysex_message_count++)); + if (byte == MIDI_EOX) { + midi->sysex_in_progress = FALSE; + pm_flush_sysex(midi, event.timestamp); + return i; + } else if (midi->sysex_message_count == 4) { + pm_flush_sysex(midi, event.timestamp); + /* after handling at least one non-data byte + * and reaching a 4-byte message boundary, + * resume trying to send 4 at a time in outer loop + */ + break; + } + } + } + } + return i; +} + + diff --git a/src/lib/portmidi/portmidi.h b/src/lib/portmidi/portmidi.h new file mode 100644 index 00000000000..c57432fae12 --- /dev/null +++ b/src/lib/portmidi/portmidi.h @@ -0,0 +1,654 @@ +#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://sourceforge.net/projects/portmedia + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * Copyright (c) 2001-2006 Roger B. Dannenberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortMidi license; however, + * the PortMusic community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/* CHANGELOG FOR PORTMIDI + * (see ../CHANGELOG.txt) + * + * NOTES ON HOST ERROR REPORTING: + * + * PortMidi errors (of type PmError) are generic, system-independent errors. + * When an error does not map to one of the more specific PmErrors, the + * catch-all code pmHostError is returned. This means that PortMidi has + * retained a more specific system-dependent error code. The caller can + * get more information by calling Pm_HasHostError() to test if there is + * a pending host error, and Pm_GetHostErrorText() to get a text string + * describing the error. Host errors are reported on a per-device basis + * because only after you open a device does PortMidi have a place to + * record the host error code. I.e. only + * those routines that receive a (PortMidiStream *) argument check and + * report errors. One exception to this is that Pm_OpenInput() and + * Pm_OpenOutput() can report errors even though when an error occurs, + * there is no PortMidiStream* to hold the error. Fortunately, both + * of these functions return any error immediately, so we do not really + * need per-device error memory. Instead, any host error code is stored + * in a global, pmHostError is returned, and the user can call + * Pm_GetHostErrorText() to get the error message (and the invalid stream + * parameter will be ignored.) The functions + * pm_init and pm_term do not fail or raise + * errors. The job of pm_init is to locate all available devices so that + * the caller can get information via PmDeviceInfo(). If an error occurs, + * the device is simply not listed as available. + * + * Host errors come in two flavors: + * a) host error + * b) host error during callback + * These can occur w/midi input or output devices. (b) can only happen + * asynchronously (during callback routines), whereas (a) only occurs while + * synchronously running PortMidi and any resulting system dependent calls. + * Both (a) and (b) are reported by the next read or write call. You can + * also query for asynchronous errors (b) at any time by calling + * Pm_HasHostError(). + * + * NOTES ON COMPILE-TIME SWITCHES + * + * DEBUG assumes stdio and a console. Use this if you want automatic, simple + * error reporting, e.g. for prototyping. If you are using MFC or some + * other graphical interface with no console, DEBUG probably should be + * undefined. + * PM_CHECK_ERRORS more-or-less takes over error checking for return values, + * stopping your program and printing error messages when an error + * occurs. This also uses stdio for console text I/O. + */ + +#ifndef WIN32 +// Linux and OS X have stdint.h +#include <stdint.h> +#else +#ifndef INT32_DEFINED +// rather than having users install a special .h file for windows, +// just put the required definitions inline here. porttime.h uses +// these too, so the definitions are (unfortunately) duplicated there +typedef int int32_t; +typedef unsigned int uint32_t; +#define INT32_DEFINED +#endif +#endif + +//#ifdef _WINDLL +//#define PMEXPORT __declspec(dllexport) +//#else +#define PMEXPORT +//#endif + +#ifndef FALSE + #define FALSE 0 +#endif +#ifndef TRUE + #define TRUE 1 +#endif + +/* default size of buffers for sysex transmission: */ +#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024 + +/** List of portmidi errors.*/ +typedef enum { + pmNoError = 0, + pmNoData = 0, /**< A "no error" return that also indicates no data avail. */ + pmGotData = 1, /**< A "no error" return that also indicates data available */ + pmHostError = -10000, + pmInvalidDeviceId, /** out of range or + * output device when input is requested or + * input device when output is requested or + * device is already opened + */ + pmInsufficientMemory, + pmBufferTooSmall, + pmBufferOverflow, + pmBadPtr, /* PortMidiStream parameter is NULL or + * stream is not opened or + * stream is output when input is required or + * stream is input when output is required */ + pmBadData, /** illegal midi data, e.g. missing EOX */ + pmInternalError, + pmBufferMaxSize /** buffer is already as large as it can be */ + /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */ +} PmError; + +/** + Pm_Initialize() is the library initialisation function - call this before + using the library. +*/ +PMEXPORT PmError Pm_Initialize( void ); + +/** + Pm_Terminate() is the library termination function - call this after + using the library. +*/ +PMEXPORT PmError Pm_Terminate( void ); + +/** A single PortMidiStream is a descriptor for an open MIDI device. +*/ +typedef void PortMidiStream; +#define PmStream PortMidiStream + +/** + Test whether stream has a pending host error. Normally, the client finds + out about errors through returned error codes, but some errors can occur + asynchronously where the client does not + explicitly call a function, and therefore cannot receive an error code. + The client can test for a pending error using Pm_HasHostError(). If true, + the error can be accessed and cleared by calling Pm_GetErrorText(). + Errors are also cleared by calling other functions that can return + errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The + client does not need to call Pm_HasHostError(). Any pending error will be + reported the next time the client performs an explicit function call on + the stream, e.g. an input or output operation. Until the error is cleared, + no new error codes will be obtained, even for a different stream. +*/ +PMEXPORT int Pm_HasHostError( PortMidiStream * stream ); + + +/** Translate portmidi error number into human readable message. + These strings are constants (set at compile time) so client has + no need to allocate storage +*/ +PMEXPORT const char *Pm_GetErrorText( PmError errnum ); + +/** Translate portmidi host error into human readable message. + These strings are computed at run time, so client has to allocate storage. + After this routine executes, the host error is cleared. +*/ +PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len); + +#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; /**< this internal structure version */ + 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; + +/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */ +PMEXPORT int Pm_CountDevices( void ); +/** + Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID() + + Return the default device ID or pmNoDevice if there are no devices. + The result (but not pmNoDevice) can be passed to Pm_OpenMidi(). + + The default device can be specified using a small application + named pmdefaults that is part of the PortMidi distribution. This + program in turn uses the Java Preferences object created by + java.util.prefs.Preferences.userRoot().node("/PortMidi"); the + preference is set by calling + prefs.put("PM_RECOMMENDED_OUTPUT_DEVICE", prefName); + or prefs.put("PM_RECOMMENDED_INPUT_DEVICE", prefName); + + In the statements above, prefName is a string describing the + MIDI device in the form "interf, name" where interf identifies + the underlying software system or API used by PortMdi to access + devices and name is the name of the device. These correspond to + the interf and name fields of a PmDeviceInfo. (Currently supported + interfaces are "MMSystem" for Win32, "ALSA" for Linux, and + "CoreMIDI" for OS X, so in fact, there is no choice of interface.) + In "interf, name", the strings are actually substrings of + the full interface and name strings. For example, the preference + "Core, Sport" will match a device with interface "CoreMIDI" + and name "In USB MidiSport 1x1". It will also match "CoreMIDI" + and "In USB MidiSport 2x2". The devices are enumerated in device + ID order, so the lowest device ID that matches the pattern becomes + the default device. Finally, if the comma-space (", ") separator + between interface and name parts of the preference is not found, + the entire preference string is interpreted as a name, and the + interface part is the empty string, which matches anything. + + On the MAC, preferences are stored in + /Users/$NAME/Library/Preferences/com.apple.java.util.prefs.plist + which is a binary file. In addition to the pmdefaults program, + there are utilities that can read and edit this preference file. + + On the PC, + + On Linux, + +*/ +PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID( void ); +/** see PmDeviceID Pm_GetDefaultInputDeviceID() */ +PMEXPORT 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 int32_t PmTimestamp; +typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); + +/** TRUE if t1 before t2 */ +#define PmBefore(t1,t2) ((t1-t2) < 0) +/** + \defgroup grp_device Input/Output Devices Handling + @{ +*/ +/** + 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(). +*/ +PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); + +/** + Pm_OpenInput() and Pm_OpenOutput() open devices. + + stream is the address of a PortMidiStream pointer which will receive + a pointer to the newly opened stream. + + inputDevice is the id of the device used for input (see PmDeviceID above). + + inputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or handle processing. + inputDriverInfo is never required for correct operation. If not used + inputDriverInfo should be NULL. + + outputDevice is the id of the device used for output (see PmDeviceID above.) + + outputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or handle processing. + outputDriverInfo is never required for correct operation. If not used + outputDriverInfo should be NULL. + + For input, the buffersize specifies the number of input events to be + buffered waiting to be read using Pm_Read(). For output, buffersize + specifies the number of output events to be buffered waiting for output. + (In some cases -- see below -- PortMidi does not buffer output at all + and merely passes data to a lower-level API, in which case buffersize + is ignored.) + + latency is the delay in milliseconds applied to timestamps to determine + when the output should actually occur. (If latency is < 0, 0 is assumed.) + If latency is zero, timestamps are ignored and all output is delivered + immediately. If latency is greater than zero, output is delayed until the + message timestamp plus the latency. (NOTE: the time is measured relative + to the time source indicated by time_proc. Timestamps are absolute, + not relative delays or offsets.) In some cases, PortMidi can obtain + better timing than your application by passing timestamps along to the + device driver or hardware. Latency may also help you to synchronize midi + data to audio data by matching midi latency to the audio buffer latency. + + time_proc is a pointer to a procedure that returns time in milliseconds. It + may be NULL, in which case a default millisecond timebase (PortTime) is + used. If the application wants to use PortTime, it should start the timer + (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the + application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput, + it may get a ptAlreadyStarted error from Pt_Start, and the application's + preferred time resolution and callback function will be ignored. + time_proc result values are appended to incoming MIDI data, and time_proc + times are used to schedule outgoing MIDI data (when latency is non-zero). + + time_info is a pointer passed to time_proc. + + Example: If I provide a timestamp of 5000, latency is 1, and time_proc + returns 4990, then the desired output time will be when time_proc returns + timestamp+latency = 5001. This will be 5001-4990 = 11ms from now. + + return value: + Upon success Pm_Open() returns PmNoError and places a pointer to a + valid PortMidiStream in the stream argument. + If a call to Pm_Open() fails a nonzero error code is returned (see + PMError above) and the value of port is invalid. + + Any stream that is successfully opened should eventually be closed + by calling Pm_Close(). + +*/ +PMEXPORT PmError Pm_OpenInput( PortMidiStream** stream, + PmDeviceID inputDevice, + void *inputDriverInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info ); + +PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream, + PmDeviceID outputDevice, + void *outputDriverInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info, + int32_t latency ); + /** @} */ + +/** + \defgroup grp_events_filters Events and Filters Handling + @{ +*/ + +/* \function PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ) + 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 bit-mask definitions */ +/** filter active sensing messages (0xFE): */ +#define PM_FILT_ACTIVE (1 << 0x0E) +/** filter system exclusive messages (0xF0): */ +#define PM_FILT_SYSEX (1 << 0x00) +/** filter MIDI clock message (0xF8) */ +#define PM_FILT_CLOCK (1 << 0x08) +/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ +#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)) +/** filter tick messages (0xF9) */ +#define PM_FILT_TICK (1 << 0x09) +/** filter undefined FD messages */ +#define PM_FILT_FD (1 << 0x0D) +/** filter undefined real-time messages */ +#define PM_FILT_UNDEFINED PM_FILT_FD +/** filter reset messages (0xFF) */ +#define PM_FILT_RESET (1 << 0x0F) +/** filter all real-time messages */ +#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \ + PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK) +/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ +#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18)) +/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/ +#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D) +/** per-note aftertouch (0xA0-0xAF) */ +#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A) +/** filter both channel and poly aftertouch */ +#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH) +/** Program changes (0xC0-0xCF) */ +#define PM_FILT_PROGRAM (1 << 0x1C) +/** Control Changes (CC's) (0xB0-0xBF)*/ +#define PM_FILT_CONTROL (1 << 0x1B) +/** Pitch Bender (0xE0-0xEF*/ +#define PM_FILT_PITCHBEND (1 << 0x1E) +/** MIDI Time Code (0xF1)*/ +#define PM_FILT_MTC (1 << 0x01) +/** Song Position (0xF2) */ +#define PM_FILT_SONG_POSITION (1 << 0x02) +/** Song Select (0xF3)*/ +#define PM_FILT_SONG_SELECT (1 << 0x03) +/** Tuning request (0xF6)*/ +#define PM_FILT_TUNE (1 << 0x06) +/** All System Common messages (mtc, song position, song select, tune request) */ +#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE) + + +PMEXPORT PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ); + +#define Pm_Channel(channel) (1<<(channel)) +/** + 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)) + + Note that channels are numbered 0 to 15 (not 1 to 16). Most + synthesizer and interfaces number channels starting at 1, but + PortMidi numbers channels starting at 0. + + All channels are allowed by default +*/ +PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); + +/** + 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. + */ +PMEXPORT 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.) +*/ +PMEXPORT PmError Pm_Close( PortMidiStream* stream ); + +/** + Pm_Synchronize() instructs PortMidi to (re)synchronize to the + time_proc passed when the stream was opened. Typically, this + is used when the stream must be opened before the time_proc + reference is actually advancing. In this case, message timing + may be erratic, but since timestamps of zero mean + "send immediately," initialization messages with zero timestamps + can be written without a functioning time reference and without + problems. Before the first MIDI message with a non-zero + timestamp is written to the stream, the time reference must + begin to advance (for example, if the time_proc computes time + based on audio samples, time might begin to advance when an + audio stream becomes active). After time_proc return values + become valid, and BEFORE writing the first non-zero timestamped + MIDI message, call Pm_Synchronize() so that PortMidi can observe + the difference between the current time_proc value and its + MIDI stream time. + + In the more normal case where time_proc + values advance continuously, there is no need to call + Pm_Synchronize. PortMidi will always synchronize at the + first output message and periodically thereafter. +*/ +PmError Pm_Synchronize( PortMidiStream* stream ); + + +/** + Pm_Message() encodes a short Midi message into a 32-bit word. If data1 + and/or data2 are not present, use zero. + + Pm_MessageStatus(), Pm_MessageData1(), and + Pm_MessageData2() extract fields from a 32-bit 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) + +typedef int32_t PmMessage; /**< see PmEvent */ +/** + All midi data comes in the form of PmEvent structures. A sysex + message is encoded as a sequence of PmEvent structures, with each + structure carrying 4 bytes of the message, i.e. only the first + PmEvent carries the status byte. + + Note that MIDI allows nested messages: the so-called "real-time" MIDI + messages can be inserted into the MIDI byte stream at any location, + including within a sysex message. MIDI real-time messages are one-byte + messages used mainly for timing (see the MIDI spec). PortMidi retains + the order of non-real-time MIDI messages on both input and output, but + it does not specify exactly how real-time messages are processed. This + is particulary problematic for MIDI input, because the input parser + must either prepare to buffer an unlimited number of sysex message + bytes or to buffer an unlimited number of real-time messages that + arrive embedded in a long sysex message. To simplify things, the input + parser is allowed to pass real-time MIDI messages embedded within a + sysex message, and it is up to the client to detect, process, and + remove these messages as they arrive. + + When receiving sysex messages, the sysex message is terminated + by either an EOX status byte (anywhere in the 4 byte messages) or + by a non-real-time status byte in the low order byte of the message. + If you get a non-real-time status byte but there was no EOX byte, it + means the sysex message was somehow truncated. This is not + considered an error; e.g., a missing EOX can result from the user + disconnecting a MIDI cable during sysex transmission. + + A real-time message can occur within a sysex message. A real-time + message will always occupy a full PmEvent with the status byte in + the low-order byte of the PmEvent message field. (This implies that + the byte-order of sysex bytes and real-time message bytes may not + be preserved -- for example, if a real-time message arrives after + 3 bytes of a sysex message, the real-time message will be delivered + first. The first word of the sysex message will be delivered only + after the 4th byte arrives, filling the 4-byte PmEvent message field. + + The timestamp field is observed when the output port is opened with + a non-zero latency. A timestamp of zero means "use the current time", + which in turn means to deliver the message with a delay of + latency (the latency parameter used when opening the output port.) + Do not expect PortMidi to sort data according to timestamps -- + messages should be sent in the correct order, and timestamps MUST + be non-decreasing. See also "Example" for Pm_OpenOutput() above. + + A sysex message will generally fill many PmEvent structures. On + output to a PortMidiStream with non-zero latency, the first timestamp + on sysex message data will determine the time to begin sending the + message. PortMidi implementations may ignore timestamps for the + remainder of the sysex message. + + On input, the timestamp ideally denotes the arrival time of the + status byte of the message. The first timestamp on sysex message + data will be valid. Subsequent timestamps may denote + when message bytes were actually received, or they may be simply + copies of the first timestamp. + + Timestamps for nested messages: If a real-time message arrives in + the middle of some other message, it is enqueued immediately with + the timestamp corresponding to its arrival time. The interrupted + non-real-time message or 4-byte packet of sysex data will be enqueued + later. The timestamp of interrupted data will be equal to that of + the interrupting real-time message to insure that timestamps are + non-decreasing. + */ +typedef struct { + PmMessage message; + PmTimestamp timestamp; +} PmEvent; + +/** + @} +*/ +/** \defgroup grp_io Reading and Writing Midi Messages + @{ +*/ +/** + 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. + +*/ +PMEXPORT int Pm_Read( PortMidiStream *stream, PmEvent *buffer, int32_t length ); + +/** + Pm_Poll() tests whether input is available, + returning TRUE, FALSE, or an error value. +*/ +PMEXPORT 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. +*/ +PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t 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.) +*/ +PMEXPORT PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, int32_t msg); + +/** + Pm_WriteSysEx() writes a timestamped system-exclusive midi message. +*/ +PMEXPORT PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg); + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORT_MIDI_H */ diff --git a/src/lib/portmidi/porttime.c b/src/lib/portmidi/porttime.c new file mode 100644 index 00000000000..4a6be96ae17 --- /dev/null +++ b/src/lib/portmidi/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/src/lib/portmidi/porttime.h b/src/lib/portmidi/porttime.h new file mode 100644 index 00000000000..f5ffd5c1f49 --- /dev/null +++ b/src/lib/portmidi/porttime.h @@ -0,0 +1,88 @@ +/* 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 WIN32 +#ifndef INT32_DEFINED +// rather than having users install a special .h file for windows, +// just put the required definitions inline here. portmidi.h uses +// these too, so the definitions are (unfortunately) duplicated there +typedef int int32_t; +typedef unsigned int uint32_t; +#define INT32_DEFINED +#endif +#else +#include <stdint.h> // needed for int32_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PMEXPORT +#define PMEXPORT +#endif + +typedef enum { + ptNoError = 0, /* success */ + ptHostError = -10000, /* a system-specific error occurred */ + ptAlreadyStarted, /* cannot start timer because it is already started */ + ptAlreadyStopped, /* cannot stop timer because it is already stopped */ + ptInsufficientMemory /* memory could not be allocated */ +} PtError; + + +typedef int32_t PtTimestamp; + +typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); + +/* + Pt_Start() starts a real-time service. + + resolution is the timer resolution in ms. The time will advance every + resolution ms. + + callback is a function pointer to be called every resolution ms. + + userData is passed to callback as a parameter. + + return value: + Upon success, returns ptNoError. See PtError for other values. +*/ +PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData); + +/* + Pt_Stop() stops the timer. + + return value: + Upon success, returns ptNoError. See PtError for other values. +*/ +PMEXPORT PtError Pt_Stop(void); + +/* + Pt_Started() returns true iff the timer is running. +*/ +PMEXPORT int Pt_Started(void); + +/* + Pt_Time() returns the current time in ms. +*/ +PMEXPORT PtTimestamp Pt_Time(void); + +/* + Pt_Sleep() pauses, allowing other threads to run. + + duration is the length of the pause in ms. The true duration + of the pause may be rounded to the nearest or next clock tick + as determined by resolution in Pt_Start(). +*/ +PMEXPORT void Pt_Sleep(int32_t duration); + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/portmidi/ptlinux.c b/src/lib/portmidi/ptlinux.c new file mode 100644 index 00000000000..768dd4847f7 --- /dev/null +++ b/src/lib/portmidi/ptlinux.c @@ -0,0 +1,138 @@ +/* ptlinux.c -- portable timer implementation for linux */ + + +/* IMPLEMENTATION NOTES (by Mark Nelson): + +Unlike Windows, Linux has no system call to request a periodic callback, +so if Pt_Start() receives a callback parameter, it must create a thread +that wakes up periodically and calls the provided callback function. +If running as superuser, use setpriority() to renice thread to -20. +One could also set the timer thread to a real-time priority (SCHED_FIFO +and SCHED_RR), but this is dangerous for This is necessary because +if the callback hangs it'll never return. A more serious reason +is that the current scheduler implementation busy-waits instead +of sleeping when realtime threads request a sleep of <=2ms (as a way +to get around the 10ms granularity), which means the thread would never +let anyone else on the CPU. + +CHANGE LOG + +18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer + thread. Simplified implementation notes. + +*/ +/* stdlib, stdio, unistd, and sys/types were added because they appeared + * in a Gentoo patch, but I'm not sure why they are needed. -RBD + */ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include "porttime.h" +#include "sys/time.h" +#include "sys/resource.h" +#include "sys/timeb.h" +#include "pthread.h" + +#define TRUE 1 +#define FALSE 0 + +static int time_started_flag = FALSE; +static struct timeb time_offset = {0, 0, 0, 0}; +static pthread_t pt_thread_pid; +static int pt_thread_created = FALSE; + +/* note that this is static data -- we only need one copy */ +typedef struct { + int id; + int resolution; + PtCallback *callback; + void *userData; +} pt_callback_parameters; + +static int pt_callback_proc_id = 0; + +static void *Pt_CallbackProc(void *p) +{ + pt_callback_parameters *parameters = (pt_callback_parameters *) p; + int mytime = 1; + /* to kill a process, just increment the pt_callback_proc_id */ + /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, + parameters->id); */ + if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); + while (pt_callback_proc_id == parameters->id) { + /* wait for a multiple of resolution ms */ + struct timeval timeout; + int delay = mytime++ * parameters->resolution - Pt_Time(); + if (delay < 0) delay = 0; + timeout.tv_sec = 0; + timeout.tv_usec = delay * 1000; + select(0, NULL, NULL, NULL, &timeout); + (*(parameters->callback))(Pt_Time(), parameters->userData); + } + /* printf("Pt_CallbackProc exiting\n"); */ + // free(parameters); + return NULL; +} + + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + if (time_started_flag) return ptNoError; + 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; + pt_thread_created = TRUE; + } + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop() +{ + /* printf("Pt_Stop called\n"); */ + pt_callback_proc_id++; + if (pt_thread_created) { + pthread_join(pt_thread_pid, NULL); + pt_thread_created = FALSE; + } + time_started_flag = FALSE; + return ptNoError; +} + + +int Pt_Started() +{ + return time_started_flag; +} + + +PtTimestamp Pt_Time() +{ + long seconds, milliseconds; + struct timeb now; + ftime(&now); + seconds = now.time - time_offset.time; + milliseconds = now.millitm - time_offset.millitm; + return seconds * 1000 + milliseconds; +} + + +void Pt_Sleep(int32_t duration) +{ + usleep(duration * 1000); +} + + + diff --git a/src/lib/portmidi/ptmacosx_cf.c b/src/lib/portmidi/ptmacosx_cf.c new file mode 100644 index 00000000000..3eb0de12f82 --- /dev/null +++ b/src/lib/portmidi/ptmacosx_cf.c @@ -0,0 +1,140 @@ +/* ptmacosx.c -- portable timer implementation for mac os x */ + +#include <stdlib.h> +#include <stdio.h> +#include <pthread.h> +#include <CoreFoundation/CoreFoundation.h> + +#import <mach/mach.h> +#import <mach/mach_error.h> +#import <mach/mach_time.h> +#import <mach/clock.h> + +#include "porttime.h" + +#define THREAD_IMPORTANCE 30 +#define LONG_TIME 1000000000.0 + +static int time_started_flag = FALSE; +static CFAbsoluteTime startTime = 0.0; +static CFRunLoopRef timerRunLoop; + +typedef struct { + int resolution; + PtCallback *callback; + void *userData; +} PtThreadParams; + + +void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info) +{ + PtThreadParams *params = (PtThreadParams*)info; + (*params->callback)(Pt_Time(), params->userData); +} + +static void* Pt_Thread(void *p) +{ + CFTimeInterval timerInterval; + CFRunLoopTimerContext timerContext; + CFRunLoopTimerRef timer; + PtThreadParams *params = (PtThreadParams*)p; + //CFTimeInterval timeout; + + /* raise the thread's priority */ + kern_return_t error; + thread_extended_policy_data_t extendedPolicy; + thread_precedence_policy_data_t precedencePolicy; + + extendedPolicy.timeshare = 0; + error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, + (thread_policy_t)&extendedPolicy, + THREAD_EXTENDED_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread timeshare policy", error); + } + + precedencePolicy.importance = THREAD_IMPORTANCE; + error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, + (thread_policy_t)&precedencePolicy, + THREAD_PRECEDENCE_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread precedence policy", error); + } + + /* set up the timer context */ + timerContext.version = 0; + timerContext.info = params; + timerContext.retain = NULL; + timerContext.release = NULL; + timerContext.copyDescription = NULL; + + /* create a new timer */ + timerInterval = (double)params->resolution / 1000.0; + timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval, + 0, 0, Pt_CFTimerCallback, &timerContext); + + timerRunLoop = CFRunLoopGetCurrent(); + CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode")); + + /* run until we're told to stop by Pt_Stop() */ + CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false); + + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode")); + CFRelease(timer); + free(params); + + return NULL; +} + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams)); + pthread_t pthread_id; + + printf("Pt_Start() called\n"); + + // /* make sure we're not already playing */ + if (time_started_flag) return ptAlreadyStarted; + startTime = CFAbsoluteTimeGetCurrent(); + + if (callback) { + + params->resolution = resolution; + params->callback = callback; + params->userData = userData; + + pthread_create(&pthread_id, NULL, Pt_Thread, params); + } + + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop() +{ + 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); +} + + +void Pt_Sleep(int32_t duration) +{ + usleep(duration * 1000); +} diff --git a/src/lib/portmidi/ptmacosx_mach.c b/src/lib/portmidi/ptmacosx_mach.c new file mode 100644 index 00000000000..d477e56e771 --- /dev/null +++ b/src/lib/portmidi/ptmacosx_mach.c @@ -0,0 +1,131 @@ +/* ptmacosx.c -- portable timer implementation for mac os x */ + +#include <stdlib.h> +#include <stdio.h> +#include <CoreAudio/HostTime.h> + +#import <mach/mach.h> +#import <mach/mach_error.h> +#import <mach/mach_time.h> +#import <mach/clock.h> +#include <unistd.h> + +#include "porttime.h" +#include "sys/time.h" +#include "pthread.h" + +//#define NSEC_PER_MSEC 1000000 +#define THREAD_IMPORTANCE 30 + +static int time_started_flag = FALSE; +static UInt64 start_time; +static pthread_t pt_thread_pid; + +/* note that this is static data -- we only need one copy */ +typedef struct { + int id; + int resolution; + PtCallback *callback; + void *userData; +} pt_callback_parameters; + +static int pt_callback_proc_id = 0; + +static void *Pt_CallbackProc(void *p) +{ + pt_callback_parameters *parameters = (pt_callback_parameters *) p; + int mytime = 1; + + kern_return_t error; + thread_extended_policy_data_t extendedPolicy; + thread_precedence_policy_data_t precedencePolicy; + + extendedPolicy.timeshare = 0; + error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, + (thread_policy_t)&extendedPolicy, + THREAD_EXTENDED_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread timeshare policy", error); + } + + precedencePolicy.importance = THREAD_IMPORTANCE; + error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, + (thread_policy_t)&precedencePolicy, + THREAD_PRECEDENCE_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread precedence policy", error); + } + + + /* to kill a process, just increment the pt_callback_proc_id */ + /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, parameters->id); */ + while (pt_callback_proc_id == parameters->id) { + /* wait for a multiple of resolution ms */ + UInt64 wait_time; + int delay = mytime++ * parameters->resolution - Pt_Time(); + PtTimestamp timestamp; + if (delay < 0) delay = 0; + wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); + wait_time += AudioGetCurrentHostTime(); + error = mach_wait_until(wait_time); + timestamp = Pt_Time(); + (*(parameters->callback))(timestamp, parameters->userData); + } + free(parameters); + return NULL; +} + + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + if (time_started_flag) return ptAlreadyStarted; + start_time = AudioGetCurrentHostTime(); + + if (callback) { + int res; + pt_callback_parameters *parms; + + parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters)); + if (!parms) return ptInsufficientMemory; + parms->id = pt_callback_proc_id; + parms->resolution = resolution; + parms->callback = callback; + parms->userData = userData; + res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms); + if (res != 0) return ptHostError; + } + + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop() +{ + /* printf("Pt_Stop called\n"); */ + pt_callback_proc_id++; + pthread_join(pt_thread_pid, NULL); + time_started_flag = FALSE; + return ptNoError; +} + + +int Pt_Started() +{ + return time_started_flag; +} + + +PtTimestamp Pt_Time() +{ + UInt64 clock_time, nsec_time; + clock_time = AudioGetCurrentHostTime() - start_time; + nsec_time = AudioConvertHostTimeToNanos(clock_time); + return (PtTimestamp)(nsec_time / NSEC_PER_MSEC); +} + + +void Pt_Sleep(int32_t duration) +{ + usleep(duration * 1000); +} diff --git a/src/lib/portmidi/ptwinmm.c b/src/lib/portmidi/ptwinmm.c new file mode 100644 index 00000000000..17675bd6c6a --- /dev/null +++ b/src/lib/portmidi/ptwinmm.c @@ -0,0 +1,70 @@ +/* ptwinmm.c -- portable timer implementation for win32 */ + + +#include "porttime.h" +#include "windows.h" +#include "time.h" + + +TIMECAPS caps; + +static long time_offset = 0; +static int time_started_flag = FALSE; +static long time_resolution; +static MMRESULT timer_id; +static PtCallback *time_callback; + +void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, + DWORD_PTR dw1, DWORD_PTR dw2) +{ + (*time_callback)(Pt_Time(), (void *) dwUser); +} + + +PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + if (time_started_flag) return ptAlreadyStarted; + timeBeginPeriod(resolution); + time_resolution = resolution; + time_offset = timeGetTime(); + time_started_flag = TRUE; + time_callback = callback; + if (callback) { + timer_id = timeSetEvent(resolution, 1, winmm_time_callback, + (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); + if (!timer_id) return ptHostError; + } + return ptNoError; +} + + +PMEXPORT PtError Pt_Stop() +{ + if (!time_started_flag) return ptAlreadyStopped; + if (time_callback && timer_id) { + timeKillEvent(timer_id); + time_callback = NULL; + timer_id = 0; + } + time_started_flag = FALSE; + timeEndPeriod(time_resolution); + return ptNoError; +} + + +PMEXPORT int Pt_Started() +{ + return time_started_flag; +} + + +PMEXPORT PtTimestamp Pt_Time() +{ + return timeGetTime() - time_offset; +} + + +PMEXPORT void Pt_Sleep(int32_t duration) +{ + Sleep(duration); +} diff --git a/src/lib/portmidi/readbinaryplist.c b/src/lib/portmidi/readbinaryplist.c new file mode 100644 index 00000000000..9f11c1fbac1 --- /dev/null +++ b/src/lib/portmidi/readbinaryplist.c @@ -0,0 +1,1130 @@ +/* + +readbinaryplist.c -- Roger B. Dannenberg, Jun 2008 +Based on ReadBinaryPList.m by Jens Ayton, 2007 + +Note that this code is intended to read preference files and has an upper +bound on file size (currently 100MB) and assumes in some places that 32 bit +offsets are sufficient. + +Here are his comments: + +Reader for binary property list files (version 00). + +This has been found to work on all 566 binary plists in my ~/Library/Preferences/ +and /Library/Preferences/ directories. This probably does not provide full +test coverage. It has also been found to provide different data to Apple's +implementation when presented with a key-value archive. This is because Apple's +implementation produces undocumented CFKeyArchiverUID objects. My implementation +produces dictionaries instead, matching the in-file representation used in XML +and OpenStep plists. See extract_uid(). + +Full disclosure: in implementing this software, I read one comment and one +struct defintion in CFLite, Apple's implementation, which is under the APSL +license. I also deduced the information about CFKeyArchiverUID from that code. +However, none of the implementation was copied. + +Copyright (C) 2007 Jens Ayton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +/* A note about memory management: +Strings and possibly other values are unique and because the values +associated with IDs are cached, you end up with a directed graph rather +than a tree. It is tricky to free the data because if you do a simple +depth-first search to free nodes, you will free nodes twice. I decided +to allocate memory from blocks of 1024 bytes and keep the blocks in a +list associated with but private to this module. So the user should +access this module by calling: + bplist_read_file() or bplist_read_user_pref() or + bplist_read_system_pref() +which returns a value. When you are done with the value, call + bplist_free_data() +This will of course free the value_ptr returned by bplist_read_*() + +To deal with memory exhaustion (what happens when malloc returns +NULL?), use setjmp/longjmp -- a single setjmp protects the whole +parser, and allocate uses longjmp to abort. After abort, memory +is freed and NULL is returned to caller. There is not much here +in the way of error reporting. + +Memory is obtained by calling allocate which either returns the +memory requested or calls longjmp, so callers don't have to check. + +*/ + +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> +#include <sys/stat.h> +#include "readbinaryplist.h" +#include "osxsupport.h" +#include <Carbon/Carbon.h> + +#define NO 0 +#define YES 1 +#define BOOL int + +//#define MAXPATHLEN 256 + +/* there are 2 levels of error logging/printing: + * BPLIST_LOG and BPLIST_LOG_VERBOSE + * either or both can be set to non-zero to turn on + * If BPLIST_LOG_VERBOSE is true, then BPLIST_LOG + * is also true. + * + * In the code, logging is done by calling either + * bplist_log() or bplist_log_verbose(), which take + * parameters like printf but might be a no-op. + */ + +#define BPLIST_LOG_VERBOSE 0 +#define BPLIST_LOG 0 + +#if BPLIST_LOG_VERBOSE + #ifndef BPLIST_LOG + #define BPLIST_LOG 1 + #endif +#endif + +#if BPLIST_LOG + #define bplist_log printf +#else + #define bplist_log(...) +#endif + +#if BPLIST_LOG_VERBOSE + #define bplist_log_verbose bplist_log +#else + #define bplist_log_verbose(...) +#endif + + +/********* MEMORY MANAGEMENT ********/ +#define BLOCK_SIZE 1024 +// memory is aligned to multiples of this; assume malloc automatically +// aligns to this number and assume this number is > sizeof(void *) +#define ALIGNMENT 8 +static void *block_list = NULL; +static char *free_ptr = NULL; +static char *end_ptr = NULL; +static jmp_buf abort_parsing; + +static void *allocate(size_t size) +{ + void *result; + if (free_ptr + size > end_ptr) { + size_t how_much = BLOCK_SIZE; + // align everything to 8 bytes + if (size > BLOCK_SIZE - ALIGNMENT) { + how_much = size + ALIGNMENT; + } + result = malloc(how_much); + if (result == NULL) { + /* serious problem */ + longjmp(abort_parsing, 1); + } + *((void **)result) = block_list; + block_list = result; + free_ptr = ((char *) result) + ALIGNMENT; + end_ptr = ((char *) result) + how_much; + } + // now, there is enough rooom at free_ptr + result = free_ptr; + free_ptr += size; + return result; +} + +void bplist_free_data() +{ + while (block_list) { + void *next = *(void **)block_list; + free(block_list); + block_list = next; + } + free_ptr = NULL; + end_ptr = NULL; +} + +// layout of trailer -- last 32 bytes in plist data + uint8_t unused[6]; + uint8_t offset_int_size; + uint8_t object_ref_size; + uint64_t object_count; + uint64_t top_level_object; + uint64_t offset_table_offset; + + +enum +{ + kHEADER_SIZE = 8, + kTRAILER_SIZE = 32, //sizeof(bplist_trailer_node), + kMINIMUM_SANE_SIZE = kHEADER_SIZE + kTRAILER_SIZE +}; + + +static const char kHEADER_BYTES[kHEADER_SIZE] = "bplist00"; + +// map from UID key to previously parsed value +typedef struct cache_struct { + uint64_t key; + value_ptr value; + struct cache_struct *next; +} cache_node, *cache_ptr; + + +typedef struct bplist_info +{ + uint64_t object_count; + const uint8_t *data_bytes; + uint64_t length; + uint64_t offset_table_offset; + uint8_t offset_int_size; + uint8_t object_ref_size; + cache_ptr cache; +} bplist_info_node, *bplist_info_ptr; + + +static value_ptr bplist_read_pldata(pldata_ptr data); +static value_ptr bplist_read_pref(char *filename, OSType folder_type); +static uint64_t read_sized_int(bplist_info_ptr bplist, uint64_t offset, uint8_t size); +static uint64_t read_offset(bplist_info_ptr bplist, uint64_t index); +static BOOL read_self_sized_int(bplist_info_ptr bplist, uint64_t offset, uint64_t *outValue, size_t *outSize); + +static value_ptr extract_object(bplist_info_ptr bplist, uint64_t objectRef); +static value_ptr extract_simple(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_int(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_real(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_date(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_data(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_ascii_string(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_unicode_string(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_uid(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset); +static value_ptr extract_dictionary(bplist_info_ptr bplist, uint64_t offset); + + +value_ptr value_create(void) +{ + value_ptr value = (value_ptr) allocate(sizeof(value_node)); + return value; +} + + +void value_set_integer(value_ptr v, int64_t i) { + v->tag = kTAG_INT; v->integer = i; +} + +void value_set_real(value_ptr v, double d) { + v->tag = kTAG_REAL; v->real = d; +} + +// d is seconds since 1 January 2001 +void value_set_date(value_ptr v, double d) { + v->tag = kTAG_DATE; v->real = d; +} + +void value_set_ascii_string(value_ptr v, const uint8_t *s, size_t len) { + v->tag = kTAG_ASCIISTRING; + v->string = (char *) allocate(len + 1); + memcpy(v->string, s, len); + v->string[len] = 0; +} + +void value_set_unicode_string(value_ptr v, const uint8_t *s, size_t len) { + v->tag = kTAG_UNICODESTRING; + v->string = (char *) allocate(len + 1); + memcpy(v->string, s, len); + v->string[len] = 0; +} + +void value_set_uid(value_ptr v, uint64_t uid) +{ + v->tag = kTAG_UID; v->uinteger = uid; +} + +// v->data points to a pldata that points to the actual bytes +// the bytes are copied, so caller must free byte source (*data) +void value_set_data(value_ptr v, const uint8_t *data, size_t len) { + v->tag = kTAG_DATA; + pldata_ptr pldata = (pldata_ptr) allocate(sizeof(pldata_node)); + pldata->data = (uint8_t *) allocate(len); + memcpy(pldata->data, data, len); + pldata->len = len; + v->data = pldata; + printf("value at %p gets data at %p\n", v, pldata); +} + +// caller releases ownership of array to value_ptr v +void value_set_array(value_ptr v, value_ptr *array, size_t length) { + array_ptr a = (array_ptr) allocate(sizeof(array_node)); + a->array = array; + a->length = length; + v->tag = kTAG_ARRAY; + v->array = a; +} + +// caller releases ownership of dict to value_ptr v +void value_set_dict(value_ptr v, dict_ptr dict) { + v->tag = kTAG_DICTIONARY; + v->dict = dict; +} + + +// look up an objectref in the cache, a ref->value_ptr mapping +value_ptr cache_lookup(cache_ptr cache, uint64_t ref) +{ + while (cache) { + if (cache->key == ref) { + return cache->value; + } + cache = cache->next; + } + return NULL; +} + + +// insert an objectref and value in the cache +void cache_insert(cache_ptr *cache, uint64_t ref, value_ptr value) +{ + cache_ptr c = (cache_ptr) allocate(sizeof(cache_node)); + c->key = ref; + c->value = value; + c->next = *cache; + *cache = c; +} + + +// insert an objectref and value in a dictionary +void dict_insert(dict_ptr *dict, value_ptr key, value_ptr value) +{ + dict_ptr d = (dict_ptr) allocate(sizeof(dict_node)); + d->key = key; + d->value = value; + d->next = *dict; + *dict = d; +} + + +BOOL is_binary_plist(pldata_ptr data) +{ + if (data->len < kMINIMUM_SANE_SIZE) return NO; + return memcmp(data->data, kHEADER_BYTES, kHEADER_SIZE) == 0; +} + + +value_ptr bplist_read_file(char *filename) +{ + struct stat stbuf; + pldata_node pldata; + FILE *file; + size_t n; + value_ptr value; + int rslt = stat(filename, &stbuf); + if (rslt) { + #if BPLIST_LOG + perror("in stat"); + #endif + bplist_log("Could not stat %s, error %d\n", filename, rslt); + return NULL; + } + // if file is >100MB, assume it is not a preferences file and give up + if (stbuf.st_size > 100000000) { + bplist_log("Large file %s encountered (%llu bytes) -- not read\n", + filename, stbuf.st_size); + return NULL; + } + pldata.len = (size_t) stbuf.st_size; + // note: this is supposed to be malloc, not allocate. It is separate + // from the graph structure, large, and easy to free right after + // parsing. + pldata.data = (uint8_t *) malloc(pldata.len); + if (!pldata.data) { + bplist_log("Could not allocate %lu bytes for %s\n", + (unsigned long) pldata.len, filename); + return NULL; + } + file = fopen(filename, "rb"); + if (!file) { + bplist_log("Could not open %s\n", filename); + return NULL; + } + n = fread(pldata.data, 1, pldata.len, file); + if (n != pldata.len) { + bplist_log("Error reading from %s\n", filename); + return NULL; + } + value = bplist_read_pldata(&pldata); + free(pldata.data); + return value; +} + + +value_ptr bplist_read_pref(char *filename, OSType folder_type) +{ + char cstr[MAXPATHLEN]; + char *foundstr; + + memset(cstr, 0, MAXPATHLEN); + + // for later OS X, the user preferences folder (~/Library/Preferences) is not available directly from Cocoa, + // Apple documentation suggests just using POSIX APIs like so. + if (folder_type == kPreferencesFolderType) + { + strlcpy(cstr, getenv("HOME"), MAXPATHLEN); + strlcat(cstr, "/Library/Preferences", MAXPATHLEN); + } + else // the system preferences folder (~/Library/PreferencePanes) is accessible from Cocoa however + { + foundstr = FindPrefsDir(); + + if (!foundstr) { + bplist_log("Error finding preferences folder\n"); + return NULL; + } + + strlcat(cstr, foundstr, MAXPATHLEN); + free(foundstr); + foundstr = NULL; + } + + strlcat(cstr, "/", MAXPATHLEN); + strlcat(cstr, filename, MAXPATHLEN); + + return bplist_read_file(cstr); +} + + +value_ptr bplist_read_system_pref(char *filename) { + return bplist_read_pref(filename, kSystemPreferencesFolderType); +} + + +value_ptr bplist_read_user_pref(char *filename) { + return bplist_read_pref(filename, kPreferencesFolderType); +} + + +// data is stored with high-order bytes first. +// read from plist data in a machine-independent fashion +// +uint64_t convert_uint64(uint8_t *ptr) +{ + uint64_t rslt = 0; + int i; + // shift in bytes, high-order first + for (i = 0; i < sizeof(uint64_t); i++) { + rslt <<= 8; + rslt += ptr[i]; + } + return rslt; +} + + +value_ptr bplist_read_pldata(pldata_ptr data) +{ + value_ptr result = NULL; + bplist_info_node bplist; + uint8_t *ptr; + uint64_t top_level_object; + int i; + + if (data == NULL) return NULL; + if (!is_binary_plist(data)) { + bplist_log("Bad binary plist: too short or invalid header.\n"); + return NULL; + } + + // read trailer + ptr = (uint8_t *) (data->data + data->len - kTRAILER_SIZE); + bplist.offset_int_size = ptr[6]; + bplist.object_ref_size = ptr[7]; + bplist.object_count = convert_uint64(ptr + 8); + top_level_object = convert_uint64(ptr + 16); + bplist.offset_table_offset = convert_uint64(ptr + 24); + + // Basic sanity checks + if (bplist.offset_int_size < 1 || bplist.offset_int_size > 8 || + bplist.object_ref_size < 1 || bplist.object_ref_size > 8 || + bplist.offset_table_offset < kHEADER_SIZE) { + bplist_log("Bad binary plist: trailer declared insane.\n"); + return NULL; + } + + // Ensure offset table is inside file + uint64_t offsetTableSize = bplist.offset_int_size * bplist.object_count; + if (offsetTableSize + bplist.offset_table_offset + kTRAILER_SIZE > + data->len) { + bplist_log("Bad binary plist: offset table overlaps end of container.\n"); + return NULL; + } + + bplist.data_bytes = data->data; + bplist.length = data->len; + bplist.cache = NULL; /* dictionary is empty */ + + bplist_log_verbose("Got a sane bplist with %llu items, offset_int_size: %u, object_ref_size: %u\n", + bplist.object_count, bplist.offset_int_size, + bplist.object_ref_size); + /* at this point, we are ready to do some parsing which allocates + memory for the result data structure. If memory allocation (using + allocate fails, a longjmp will return to here and we simply give up + */ + i = setjmp(abort_parsing); + if (i == 0) { + result = extract_object(&bplist, top_level_object); + } else { + bplist_log("allocate() failed to allocate memory. Giving up.\n"); + result = NULL; + } + if (!result) { + bplist_free_data(); + } + return result; +} + + +static value_ptr extract_object(bplist_info_ptr bplist, uint64_t objectRef) +{ + uint64_t offset; + value_ptr result = NULL; + uint8_t objectTag; + + if (objectRef >= bplist->object_count) { + // Out-of-range object reference. + bplist_log("Bad binary plist: object index is out of range.\n"); + return NULL; + } + + // Use cached object if it exists + result = cache_lookup(bplist->cache, objectRef); + if (result != NULL) return result; + + // Otherwise, find object in file. + offset = read_offset(bplist, objectRef); + if (offset > bplist->length) { + // Out-of-range offset. + bplist_log("Bad binary plist: object outside container.\n"); + return NULL; + } + objectTag = *(bplist->data_bytes + offset); + switch (objectTag & 0xF0) { + case kTAG_SIMPLE: + result = extract_simple(bplist, offset); + break; + + case kTAG_INT: + result = extract_int(bplist, offset); + break; + + case kTAG_REAL: + result = extract_real(bplist, offset); + break; + + case kTAG_DATE: + result = extract_date(bplist, offset); + break; + + case kTAG_DATA: + result = extract_data(bplist, offset); + break; + + case kTAG_ASCIISTRING: + result = extract_ascii_string(bplist, offset); + break; + + case kTAG_UNICODESTRING: + result = extract_unicode_string(bplist, offset); + break; + + case kTAG_UID: + result = extract_uid(bplist, offset); + break; + + case kTAG_ARRAY: + result = extract_array(bplist, offset); + break; + + case kTAG_DICTIONARY: + result = extract_dictionary(bplist, offset); + break; + + default: + // Unknown tag. + bplist_log("Bad binary plist: unknown tag 0x%X.\n", + (objectTag & 0x0F) >> 4); + result = NULL; + } + + // Cache and return result. + if (result != NULL) + cache_insert(&bplist->cache, objectRef, result); + return result; +} + + +static uint64_t read_sized_int(bplist_info_ptr bplist, uint64_t offset, + uint8_t size) +{ + assert(bplist->data_bytes != NULL && size >= 1 && size <= 8 && + offset + size <= bplist->length); + + uint64_t result = 0; + const uint8_t *byte = bplist->data_bytes + offset; + + do { + // note that ints seem to be high-order first + result = (result << 8) | *byte++; + } while (--size); + + return result; +} + + +static uint64_t read_offset(bplist_info_ptr bplist, uint64_t index) +{ + assert(index < bplist->object_count); + + return read_sized_int(bplist, + bplist->offset_table_offset + bplist->offset_int_size * index, + bplist->offset_int_size); +} + + +static BOOL read_self_sized_int(bplist_info_ptr bplist, uint64_t offset, + uint64_t *outValue, size_t *outSize) +{ + uint32_t size; + int64_t value; + + assert(bplist->data_bytes != NULL && offset < bplist->length); + + size = 1 << (bplist->data_bytes[offset] & 0x0F); + if (size > 8) { + // Maximum allowable size in this implementation is 1<<3 = 8 bytes. + // This also happens to be the biggest we can handle. + return NO; + } + + if (offset + 1 + size > bplist->length) { + // Out of range. + return NO; + } + + value = read_sized_int(bplist, offset + 1, size); + + if (outValue != NULL) *outValue = value; + if (outSize != NULL) *outSize = size + 1; // +1 for tag byte. + return YES; +} + + +static value_ptr extract_simple(bplist_info_ptr bplist, uint64_t offset) +{ + assert(bplist->data_bytes != NULL && offset < bplist->length); + value_ptr value = value_create(); + + switch (bplist->data_bytes[offset]) { + case kVALUE_NULL: + value->tag = kVALUE_NULL; + return value; + + case kVALUE_TRUE: + value->tag = kVALUE_TRUE; + return value; + + case kVALUE_FALSE: + value->tag = kVALUE_FALSE; + return value; + } + + // Note: kVALUE_FILLER is treated as invalid, because it, er, is. + bplist_log("Bad binary plist: invalid atom.\n"); + free(value); + return NULL; +} + + +static value_ptr extract_int(bplist_info_ptr bplist, uint64_t offset) +{ + value_ptr value = value_create(); + value->tag = kTAG_INT; + + if (!read_self_sized_int(bplist, offset, &value->uinteger, NULL)) { + bplist_log("Bad binary plist: invalid integer object.\n"); + } + + /* NOTE: originally, I sign-extended here. This was the wrong thing; it + turns out that negative ints are always stored as 64-bit, and smaller + ints are unsigned. + */ + return value; +} + + +static value_ptr extract_real(bplist_info_ptr bplist, uint64_t offset) +{ + value_ptr value = value_create(); + uint32_t size; + + assert(bplist->data_bytes != NULL && offset < bplist->length); + + size = 1 << (bplist->data_bytes[offset] & 0x0F); + + // FIXME: what to do if faced with other sizes for float/double? + assert (sizeof (float) == sizeof (uint32_t) && + sizeof (double) == sizeof (uint64_t)); + + if (offset + 1 + size > bplist->length) { + bplist_log("Bad binary plist: %s object overlaps end of container.\n", + "floating-point number"); + free(value); + return NULL; + } + + if (size == sizeof (float)) { + // cast is ok because we know size is 4 bytes + uint32_t i = (uint32_t) read_sized_int(bplist, offset + 1, size); + // Note that this handles byte swapping. + value_set_real(value, *(float *)&i); + return value; + } else if (size == sizeof (double)) { + uint64_t i = read_sized_int(bplist, offset + 1, size); + // Note that this handles byte swapping. + value_set_real(value, *(double *)&i); + return value; + } else { + // Can't handle floats of other sizes. + bplist_log("Bad binary plist: can't handle %u-byte float.\n", size); + free(value); + return NULL; + } +} + + +static value_ptr extract_date(bplist_info_ptr bplist, uint64_t offset) +{ + value_ptr value; + assert(bplist->data_bytes != NULL && offset < bplist->length); + + // Data has size code like int and real, but only 3 (meaning 8 bytes) is valid. + if (bplist->data_bytes[offset] != kVALUE_FULLDATETAG) { + bplist_log("Bad binary plist: invalid size for date object.\n"); + return NULL; + } + + if (offset + 1 + sizeof (double) > bplist->length) { + bplist_log("Bad binary plist: %s object overlaps end of container.\n", + "date"); + return NULL; + } + + // FIXME: what to do if faced with other sizes for double? + assert (sizeof (double) == sizeof (uint64_t)); + + uint64_t date = read_sized_int(bplist, offset + 1, sizeof(double)); + // Note that this handles byte swapping. + value = value_create(); + value_set_date(value, *(double *)&date); + return value; +} + + +uint64_t bplist_get_a_size(bplist_info_ptr bplist, + uint64_t *offset_ptr, char *msg) +{ + uint64_t size = bplist->data_bytes[*offset_ptr] & 0x0F; + (*offset_ptr)++; + if (size == 0x0F) { + // 0x0F means separate int size follows. + // Smaller values are used for short data. + size_t extra; // the length of the data size we are about to read + if ((bplist->data_bytes[*offset_ptr] & 0xF0) != kTAG_INT) { + // Bad data, mistagged size int + bplist_log("Bad binary plist: %s object size is not tagged as int.\n", + msg); + return UINT64_MAX; // error + } + + // read integer data as size, extra tells how many bytes to skip + if (!read_self_sized_int(bplist, *offset_ptr, &size, &extra)) { + bplist_log("Bad binary plist: invalid %s object size tag.\n", + "data"); + return UINT64_MAX; // error + } + (*offset_ptr) += extra; + } + + if (*offset_ptr + size > bplist->length) { + bplist_log("Bad binary plist: %s object overlaps end of container.\n", + "data"); + return UINT64_MAX; // error + } + return size; +} + + +static value_ptr extract_data(bplist_info_ptr bplist, uint64_t offset) +{ + uint64_t size; + value_ptr value; + + assert(bplist->data_bytes != NULL && offset < bplist->length); + + if ((size = bplist_get_a_size(bplist, &offset, (char *)"data")) == UINT64_MAX) + return NULL; + + value = value_create(); + // cast is ok because we only allow files up to 100MB: + value_set_data(value, bplist->data_bytes + (size_t) offset, (size_t) size); + return value; +} + + +static value_ptr extract_ascii_string(bplist_info_ptr bplist, uint64_t offset) +{ + uint64_t size; + value_ptr value; // return value + + assert(bplist->data_bytes != NULL && offset < bplist->length); + + if ((size = bplist_get_a_size(bplist, &offset, (char *)"ascii string")) == + UINT64_MAX) + return NULL; + + value = value_create(); + // cast is ok because we only allow 100MB files + value_set_ascii_string(value, bplist->data_bytes + (size_t) offset, + (size_t) size); + return value; +} + + +static value_ptr extract_unicode_string(bplist_info_ptr bplist, uint64_t offset) +{ + uint64_t size; + value_ptr value; + + assert(bplist->data_bytes != NULL && offset < bplist->length); + + if ((size = bplist_get_a_size(bplist, &offset, (char *)"unicode string")) == + UINT64_MAX) + return NULL; + + value = value_create(); + // cast is ok because we only allow 100MB files + value_set_unicode_string(value, bplist->data_bytes + (size_t) offset, + (size_t) size); + return value; +} + + +static value_ptr extract_uid(bplist_info_ptr bplist, uint64_t offset) +{ + /* UIDs are used by Cocoa's key-value coder. + When writing other plist formats, they are expanded to dictionaries of + the form <dict><key>CF$UID</key><integer>value</integer></dict>, so we + do the same here on reading. This results in plists identical to what + running plutil -convert xml1 gives us. However, this is not the same + result as [Core]Foundation's plist parser, which extracts them as un- + introspectable CF objects. In fact, it even seems to convert the CF$UID + dictionaries from XML plists on the fly. + */ + + value_ptr value; + uint64_t uid; + + if (!read_self_sized_int(bplist, offset, &uid, NULL)) { + bplist_log("Bad binary plist: invalid UID object.\n"); + return NULL; + } + + // assert(NO); // original code suggests using a string for a key + // but our dictionaries all use big ints for keys, so I don't know + // what to do here + + // In practice, I believe this code is never executed by PortMidi. + // I changed it to do something and not raise compiler warnings, but + // not sure what the code should do. + + value = value_create(); + value_set_uid(value, uid); + // return [NSDictionary dictionaryWithObject: + // [NSNumber numberWithUnsignedLongLong:value] + // forKey:"CF$UID"]; + return value; +} + + +static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset) +{ + uint64_t i, count; + uint64_t size; + uint64_t elementID; + value_ptr element = NULL; + value_ptr *array = NULL; + value_ptr value = NULL; + BOOL ok = YES; + + assert(bplist->data_bytes != NULL && offset < bplist->length); + + if ((count = bplist_get_a_size(bplist, &offset, (char *)"array")) == UINT64_MAX) + return NULL; + + if (count > UINT64_MAX / bplist->object_ref_size - offset) { + // Offset overflow. + bplist_log("Bad binary plist: %s object overlaps end of container.\n", + "array"); + return NULL; + } + + size = bplist->object_ref_size * count; + if (size + offset > bplist->length) { + bplist_log("Bad binary plist: %s object overlaps end of container.\n", + "array"); + return NULL; + } + + // got count, the number of array elements + + value = value_create(); + assert(value); + + if (count == 0) { + // count must be size_t or smaller because max file size is 100MB + value_set_array(value, array, (size_t) count); + return value; + } + + array = allocate(sizeof(value_ptr) * (size_t) count); + + for (i = 0; i != count; ++i) { + bplist_log_verbose("[%u]\n", i); + elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size, + bplist->object_ref_size); + element = extract_object(bplist, elementID); + if (element != NULL) { + array[i] = element; + } else { + ok = NO; + break; + } + } + if (ok) { // count is smaller than size_t max because of 100MB file limit + value_set_array(value, array, (size_t) count); + } + + return value; +} + + +static value_ptr extract_dictionary(bplist_info_ptr bplist, uint64_t offset) +{ + uint64_t i, count; + uint64_t size; + uint64_t elementID; + value_ptr value = NULL; + dict_ptr dict = NULL; + BOOL ok = YES; + + assert(bplist->data_bytes != NULL && offset < bplist->length); + + + if ((count = bplist_get_a_size(bplist, &offset, (char *)"array")) == UINT64_MAX) + return NULL; + + if (count > UINT64_MAX / (bplist->object_ref_size * 2) - offset) { + // Offset overflow. + bplist_log("Bad binary plist: %s object overlaps end of container.\n", + "dictionary"); + return NULL; + } + + size = bplist->object_ref_size * count * 2; + if (size + offset > bplist->length) { + bplist_log("Bad binary plist: %s object overlaps end of container.\n", + "dictionary"); + return NULL; + } + + value = value_create(); + if (count == 0) { + value_set_dict(value, NULL); + return value; + } + + for (i = 0; i != count; ++i) { + value_ptr key; + value_ptr val; + elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size, + bplist->object_ref_size); + key = extract_object(bplist, elementID); + if (key != NULL) { + bplist_log_verbose("key: %p\n", key); + } else { + ok = NO; + break; + } + + elementID = read_sized_int(bplist, + offset + (i + count) * bplist->object_ref_size, + bplist->object_ref_size); + val = extract_object(bplist, elementID); + if (val != NULL) { + dict_insert(&dict, key, val); + } else { + ok = NO; + break; + } + } + if (ok) { + value_set_dict(value, dict); + } + + return value; +} + +/*************** functions for accessing values ****************/ + + +char *value_get_asciistring(value_ptr v) +{ + if (v->tag != kTAG_ASCIISTRING) return NULL; + return v->string; +} + + +value_ptr value_dict_lookup_using_string(value_ptr v, char *key) +{ + dict_ptr dict; + if (v->tag != kTAG_DICTIONARY) return NULL; // not a dictionary + dict = v->dict; + /* search for key */ + while (dict) { + if (dict->key && dict->key->tag == kTAG_ASCIISTRING && + strcmp(key, dict->key->string) == 0) { // found it + return dict->value; + } + dict = dict->next; + } + return NULL; /* not found */ +} + +value_ptr value_dict_lookup_using_path(value_ptr v, char *path) +{ + char key[MAX_KEY_SIZE]; + while (*path) { /* more to the path */ + int i = 0; + while (i < MAX_KEY_SIZE - 1) { + key[i] = *path++; + if (key[i] == '/') { /* end of entry in path */ + key[i + 1] = 0; + break; + } + if (!key[i]) { + path--; /* back up to end of string char */ + break; /* this will cause outer loop to exit */ + } + i++; + } + if (!v || v->tag != kTAG_DICTIONARY) return NULL; + /* now, look up the key to get next value */ + v = value_dict_lookup_using_string(v, key); + if (v == NULL) return NULL; + } + return v; +} + + +/*************** functions for debugging ***************/ + +void plist_print(value_ptr v) +{ + size_t i; + int comma_needed; + dict_ptr dict; + if (!v) { + printf("NULL"); + return; + } + switch (v->tag & 0xF0) { + case kTAG_SIMPLE: + switch (v->tag) { + case kVALUE_NULL: + printf("NULL@%p", v); break; + case kVALUE_FALSE: + printf("FALSE@%p", v); break; + case kVALUE_TRUE: + printf("TRUE@%p", v); break; + default: + printf("UNKNOWN tag=%x@%p", v->tag, v); break; + } + break; + case kTAG_INT: + printf("%lld@%p", v->integer, v); break; + case kTAG_REAL: + printf("%g@%p", v->real, v); break; + case kTAG_DATE: + printf("date:%g@%p", v->real, v); break; + case kTAG_DATA: + printf("data@%p->%p:[%p:", v, v->data, v->data->data); + for (i = 0; i < v->data->len; i++) { + printf(" %2x", v->data->data[i]); + } + printf("]"); break; + case kTAG_ASCIISTRING: + printf("%p:\"%s\"@%p", v->string, v->string, v); break; + case kTAG_UNICODESTRING: + printf("unicode:%p:\"%s\"@%p", v->string, v->string, v); break; + case kTAG_UID: + printf("UID:%llu@%p", v->uinteger, v); break; + case kTAG_ARRAY: + comma_needed = FALSE; + printf("%p->%p:[%p:", v, v->array, v->array->array); + for (i = 0; i < v->array->length; i++) { + if (comma_needed) printf(", "); + plist_print(v->array->array[i]); + comma_needed = TRUE; + } + printf("]"); break; + case kTAG_DICTIONARY: + comma_needed = FALSE; + printf("%p:[", v); + dict = v->dict; + while (dict) { + if (comma_needed) printf(", "); + printf("%p:", dict); + plist_print(dict->key); + printf("->"); + plist_print(dict->value); + comma_needed = TRUE; + dict = dict->next; + } + printf("]"); break; + default: + printf("UNKNOWN tag=%x", v->tag); + break; + } +} + + diff --git a/src/lib/portmidi/readbinaryplist.h b/src/lib/portmidi/readbinaryplist.h new file mode 100644 index 00000000000..e82056a0b79 --- /dev/null +++ b/src/lib/portmidi/readbinaryplist.h @@ -0,0 +1,88 @@ +/* readbinaryplist.h -- header to read preference files + + Roger B. Dannenberg, Jun 2008 +*/ + +#include <stdint.h> /* for uint8_t ... */ + +#ifndef TRUE + #define TRUE 1 + #define FALSE 0 +#endif + +#define MAX_KEY_SIZE 256 + +enum +{ + // Object tags (high nybble) + kTAG_SIMPLE = 0x00, // Null, true, false, filler, or invalid + kTAG_INT = 0x10, + kTAG_REAL = 0x20, + kTAG_DATE = 0x30, + kTAG_DATA = 0x40, + kTAG_ASCIISTRING = 0x50, + kTAG_UNICODESTRING = 0x60, + kTAG_UID = 0x80, + kTAG_ARRAY = 0xA0, + kTAG_DICTIONARY = 0xD0, + + // "simple" object values + kVALUE_NULL = 0x00, + kVALUE_FALSE = 0x08, + kVALUE_TRUE = 0x09, + kVALUE_FILLER = 0x0F, + + kVALUE_FULLDATETAG = 0x33 // Dates are tagged with a whole byte. +}; + + +typedef struct pldata_struct { + uint8_t *data; + size_t len; +} pldata_node, *pldata_ptr; + + +typedef struct array_struct { + struct value_struct **array; + uint64_t length; +} array_node, *array_ptr; + + +// a dict_node is a list of <key, value> pairs +typedef struct dict_struct { + struct value_struct *key; + struct value_struct *value; + struct dict_struct *next; +} dict_node, *dict_ptr; + + +// an value_node is a value with a tag telling the type +typedef struct value_struct { + int tag; + union { + int64_t integer; + uint64_t uinteger; + double real; + char *string; + pldata_ptr data; + array_ptr array; + struct dict_struct *dict; + }; +} value_node, *value_ptr; + + +value_ptr bplist_read_file(char *filename); +value_ptr bplist_read_user_pref(char *filename); +value_ptr bplist_read_system_pref(char *filename); +void bplist_free_data(void); + +/*************** functions for accessing values ****************/ + +char *value_get_asciistring(value_ptr v); +value_ptr value_dict_lookup_using_string(value_ptr v, char *key); +value_ptr value_dict_lookup_using_path(value_ptr v, char *path); + +/*************** functions for debugging ***************/ + +void plist_print(value_ptr v); + diff --git a/src/osd/osdcore.h b/src/osd/osdcore.h index 314b17b43b3..039b19aedbf 100644 --- a/src/osd/osdcore.h +++ b/src/osd/osdcore.h @@ -885,6 +885,18 @@ file_error osd_get_full_path(char **dst, const char *path); /*************************************************************************** + MIDI I/O INTERFACES +***************************************************************************/ +struct osd_midi_device; + +void osd_init_midi(void); +void osd_shutdown_midi(void); +void osd_list_midi_devices(void); +osd_midi_device *osd_open_midi_input(const char *devname); +osd_midi_device *osd_open_midi_output(const char *devname); +void osd_close_midi_channel(osd_midi_device *dev); + +/*************************************************************************** UNCATEGORIZED INTERFACES ***************************************************************************/ diff --git a/src/osd/portmedia/pmmidi.c b/src/osd/portmedia/pmmidi.c new file mode 100644 index 00000000000..8a9811a22eb --- /dev/null +++ b/src/osd/portmedia/pmmidi.c @@ -0,0 +1,80 @@ +//============================================================ +// +// pmmidi.c - OSD interface for PortMidi +// +// Copyright (c) 1996-2013, Nicola Salmoria and the MAME Team. +// Visit http://mamedev.org for licensing and usage restrictions. +// +//============================================================ + +#include "emu.h" +#include "osdcore.h" +#include "portmidi/portmidi.h" + +void osd_list_midi_devices(void) +{ + #ifndef DISABLE_MIDI + int num_devs = Pm_CountDevices(); + const PmDeviceInfo *pmInfo; + + printf("\n"); + + if (num_devs == 0) + { + printf("No MIDI ports were found\n"); + return; + } + + printf("MIDI input ports:\n"); + for (int i = 0; i < num_devs; i++) + { + pmInfo = Pm_GetDeviceInfo(i); + + if (pmInfo->input) + { + printf("%s %s\n", pmInfo->name, (i == Pm_GetDefaultInputDeviceID()) ? "(default)" : ""); + } + } + + printf("\nMIDI output ports:\n"); + for (int i = 0; i < num_devs; i++) + { + pmInfo = Pm_GetDeviceInfo(i); + + if (pmInfo->output) + { + printf("%s %s\n", pmInfo->name, (i == Pm_GetDefaultOutputDeviceID()) ? "(default)" : ""); + } + } + #else + printf("\nMIDI is not supported in this build\n"); + #endif +} + +osd_midi_device *osd_open_midi_input(const char *devname) +{ + return NULL; +} + +osd_midi_device *osd_open_midi_output(const char *devname) +{ + return NULL; +} + +void osd_close_midi_channel(osd_midi_device *dev) +{ +} + +void osd_init_midi(void) +{ + #ifndef DISABLE_MIDI + Pm_Initialize(); + #endif +} + +void osd_shutdown_midi(void) +{ + #ifndef DISABLE_MIDI + Pm_Terminate(); + #endif +} diff --git a/src/osd/sdl/sdlmidi.c b/src/osd/sdl/sdlmidi.c new file mode 100644 index 00000000000..e233a992004 --- /dev/null +++ b/src/osd/sdl/sdlmidi.c @@ -0,0 +1 @@ +#include "../portmedia/pmmidi.c" diff --git a/src/osd/windows/winmidi.c b/src/osd/windows/winmidi.c new file mode 100644 index 00000000000..e233a992004 --- /dev/null +++ b/src/osd/windows/winmidi.c @@ -0,0 +1 @@ +#include "../portmedia/pmmidi.c" |