| Previous | 199869 Revisions | Next |
| r19990 Tuesday 1st January, 2013 at 16:11:32 UTC by R. Belmont |
|---|
| 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) |
| [src/lib] | lib.mak |
| [src/lib/portmidi] | finddefault.c* finddefaultlinux.c* osxsupport.h* osxsupport.m* pminternal.h* pmlinux.c* pmlinux.h* pmlinuxalsa.c* pmlinuxalsa.h* pmmac.c* pmmac.h* pmmacosxcm.c* pmmacosxcm.h* pmutil.c* pmutil.h* pmwin.c* pmwinmm.c* pmwinmm.h* portmidi.c* portmidi.h* porttime.c* porttime.h* ptlinux.c* ptmacosx_cf.c* ptmacosx_mach.c* ptwinmm.c* readbinaryplist.c* readbinaryplist.h* |
| [src/osd] | osdcore.h |
| [src/osd/portmedia] | pmmidi.c* |
| [src/osd/sdl] | sdlmidi.c* |
| [src/osd/windows] | winmidi.c* |
| r0 | r19990 | |
|---|---|---|
| 1 | #include "../portmedia/pmmidi.c" |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | #include "../portmedia/pmmidi.c" |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r19989 | r19990 | |
|---|---|---|
| 885 | 885 | |
| 886 | 886 | |
| 887 | 887 | /*************************************************************************** |
| 888 | MIDI I/O INTERFACES | |
| 889 | ***************************************************************************/ | |
| 890 | struct osd_midi_device; | |
| 891 | ||
| 892 | void osd_init_midi(void); | |
| 893 | void osd_shutdown_midi(void); | |
| 894 | void osd_list_midi_devices(void); | |
| 895 | osd_midi_device *osd_open_midi_input(const char *devname); | |
| 896 | osd_midi_device *osd_open_midi_output(const char *devname); | |
| 897 | void osd_close_midi_channel(osd_midi_device *dev); | |
| 898 | ||
| 899 | /*************************************************************************** | |
| 888 | 900 | UNCATEGORIZED INTERFACES |
| 889 | 901 | ***************************************************************************/ |
| 890 | 902 |
| r0 | r19990 | |
|---|---|---|
| 1 | //============================================================ | |
| 2 | // | |
| 3 | // pmmidi.c - OSD interface for PortMidi | |
| 4 | // | |
| 5 | // Copyright (c) 1996-2013, Nicola Salmoria and the MAME Team. | |
| 6 | // Visit http://mamedev.org for licensing and usage restrictions. | |
| 7 | // | |
| 8 | //============================================================ | |
| 9 | ||
| 10 | #include "emu.h" | |
| 11 | #include "osdcore.h" | |
| 12 | #include "portmidi/portmidi.h" | |
| 13 | ||
| 14 | void osd_list_midi_devices(void) | |
| 15 | { | |
| 16 | #ifndef DISABLE_MIDI | |
| 17 | int num_devs = Pm_CountDevices(); | |
| 18 | const PmDeviceInfo *pmInfo; | |
| 19 | ||
| 20 | printf("\n"); | |
| 21 | ||
| 22 | if (num_devs == 0) | |
| 23 | { | |
| 24 | printf("No MIDI ports were found\n"); | |
| 25 | return; | |
| 26 | } | |
| 27 | ||
| 28 | printf("MIDI input ports:\n"); | |
| 29 | for (int i = 0; i < num_devs; i++) | |
| 30 | { | |
| 31 | pmInfo = Pm_GetDeviceInfo(i); | |
| 32 | ||
| 33 | if (pmInfo->input) | |
| 34 | { | |
| 35 | printf("%s %s\n", pmInfo->name, (i == Pm_GetDefaultInputDeviceID()) ? "(default)" : ""); | |
| 36 | } | |
| 37 | } | |
| 38 | ||
| 39 | printf("\nMIDI output ports:\n"); | |
| 40 | for (int i = 0; i < num_devs; i++) | |
| 41 | { | |
| 42 | pmInfo = Pm_GetDeviceInfo(i); | |
| 43 | ||
| 44 | if (pmInfo->output) | |
| 45 | { | |
| 46 | printf("%s %s\n", pmInfo->name, (i == Pm_GetDefaultOutputDeviceID()) ? "(default)" : ""); | |
| 47 | } | |
| 48 | } | |
| 49 | #else | |
| 50 | printf("\nMIDI is not supported in this build\n"); | |
| 51 | #endif | |
| 52 | } | |
| 53 | ||
| 54 | osd_midi_device *osd_open_midi_input(const char *devname) | |
| 55 | { | |
| 56 | return NULL; | |
| 57 | } | |
| 58 | ||
| 59 | osd_midi_device *osd_open_midi_output(const char *devname) | |
| 60 | { | |
| 61 | return NULL; | |
| 62 | } | |
| 63 | ||
| 64 | void osd_close_midi_channel(osd_midi_device *dev) | |
| 65 | { | |
| 66 | } | |
| 67 | ||
| 68 | void osd_init_midi(void) | |
| 69 | { | |
| 70 | #ifndef DISABLE_MIDI | |
| 71 | Pm_Initialize(); | |
| 72 | #endif | |
| 73 | } | |
| 74 | ||
| 75 | void osd_shutdown_midi(void) | |
| 76 | { | |
| 77 | #ifndef DISABLE_MIDI | |
| 78 | Pm_Terminate(); | |
| 79 | #endif | |
| 80 | } |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* readbinaryplist.h -- header to read preference files | |
| 2 | ||
| 3 | Roger B. Dannenberg, Jun 2008 | |
| 4 | */ | |
| 5 | ||
| 6 | #include <stdint.h> /* for uint8_t ... */ | |
| 7 | ||
| 8 | #ifndef TRUE | |
| 9 | #define TRUE 1 | |
| 10 | #define FALSE 0 | |
| 11 | #endif | |
| 12 | ||
| 13 | #define MAX_KEY_SIZE 256 | |
| 14 | ||
| 15 | enum | |
| 16 | { | |
| 17 | // Object tags (high nybble) | |
| 18 | kTAG_SIMPLE = 0x00, // Null, true, false, filler, or invalid | |
| 19 | kTAG_INT = 0x10, | |
| 20 | kTAG_REAL = 0x20, | |
| 21 | kTAG_DATE = 0x30, | |
| 22 | kTAG_DATA = 0x40, | |
| 23 | kTAG_ASCIISTRING = 0x50, | |
| 24 | kTAG_UNICODESTRING = 0x60, | |
| 25 | kTAG_UID = 0x80, | |
| 26 | kTAG_ARRAY = 0xA0, | |
| 27 | kTAG_DICTIONARY = 0xD0, | |
| 28 | ||
| 29 | // "simple" object values | |
| 30 | kVALUE_NULL = 0x00, | |
| 31 | kVALUE_FALSE = 0x08, | |
| 32 | kVALUE_TRUE = 0x09, | |
| 33 | kVALUE_FILLER = 0x0F, | |
| 34 | ||
| 35 | kVALUE_FULLDATETAG = 0x33 // Dates are tagged with a whole byte. | |
| 36 | }; | |
| 37 | ||
| 38 | ||
| 39 | typedef struct pldata_struct { | |
| 40 | uint8_t *data; | |
| 41 | size_t len; | |
| 42 | } pldata_node, *pldata_ptr; | |
| 43 | ||
| 44 | ||
| 45 | typedef struct array_struct { | |
| 46 | struct value_struct **array; | |
| 47 | uint64_t length; | |
| 48 | } array_node, *array_ptr; | |
| 49 | ||
| 50 | ||
| 51 | // a dict_node is a list of <key, value> pairs | |
| 52 | typedef struct dict_struct { | |
| 53 | struct value_struct *key; | |
| 54 | struct value_struct *value; | |
| 55 | struct dict_struct *next; | |
| 56 | } dict_node, *dict_ptr; | |
| 57 | ||
| 58 | ||
| 59 | // an value_node is a value with a tag telling the type | |
| 60 | typedef struct value_struct { | |
| 61 | int tag; | |
| 62 | union { | |
| 63 | int64_t integer; | |
| 64 | uint64_t uinteger; | |
| 65 | double real; | |
| 66 | char *string; | |
| 67 | pldata_ptr data; | |
| 68 | array_ptr array; | |
| 69 | struct dict_struct *dict; | |
| 70 | }; | |
| 71 | } value_node, *value_ptr; | |
| 72 | ||
| 73 | ||
| 74 | value_ptr bplist_read_file(char *filename); | |
| 75 | value_ptr bplist_read_user_pref(char *filename); | |
| 76 | value_ptr bplist_read_system_pref(char *filename); | |
| 77 | void bplist_free_data(void); | |
| 78 | ||
| 79 | /*************** functions for accessing values ****************/ | |
| 80 | ||
| 81 | char *value_get_asciistring(value_ptr v); | |
| 82 | value_ptr value_dict_lookup_using_string(value_ptr v, char *key); | |
| 83 | value_ptr value_dict_lookup_using_path(value_ptr v, char *path); | |
| 84 | ||
| 85 | /*************** functions for debugging ***************/ | |
| 86 | ||
| 87 | void plist_print(value_ptr v); | |
| 88 |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmlinux.h */ | |
| 2 | ||
| 3 | extern PmDeviceID pm_default_input_device_id; | |
| 4 | extern PmDeviceID pm_default_output_device_id; | |
| 5 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmmac.h */ | |
| 2 | ||
| 3 | extern PmDeviceID pm_default_input_device_id; | |
| 4 | extern PmDeviceID pm_default_output_device_id; | |
| No newline at end of file |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmlinuxalsa.h -- system-specific definitions */ | |
| 2 | ||
| 3 | PmError pm_linuxalsa_init(void); | |
| 4 | void pm_linuxalsa_term(void); | |
| 5 | ||
| 6 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* ptlinux.c -- portable timer implementation for linux */ | |
| 2 | ||
| 3 | ||
| 4 | /* IMPLEMENTATION NOTES (by Mark Nelson): | |
| 5 | ||
| 6 | Unlike Windows, Linux has no system call to request a periodic callback, | |
| 7 | so if Pt_Start() receives a callback parameter, it must create a thread | |
| 8 | that wakes up periodically and calls the provided callback function. | |
| 9 | If running as superuser, use setpriority() to renice thread to -20. | |
| 10 | One could also set the timer thread to a real-time priority (SCHED_FIFO | |
| 11 | and SCHED_RR), but this is dangerous for This is necessary because | |
| 12 | if the callback hangs it'll never return. A more serious reason | |
| 13 | is that the current scheduler implementation busy-waits instead | |
| 14 | of sleeping when realtime threads request a sleep of <=2ms (as a way | |
| 15 | to get around the 10ms granularity), which means the thread would never | |
| 16 | let anyone else on the CPU. | |
| 17 | ||
| 18 | CHANGE LOG | |
| 19 | ||
| 20 | 18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer | |
| 21 | thread. Simplified implementation notes. | |
| 22 | ||
| 23 | */ | |
| 24 | /* stdlib, stdio, unistd, and sys/types were added because they appeared | |
| 25 | * in a Gentoo patch, but I'm not sure why they are needed. -RBD | |
| 26 | */ | |
| 27 | #include <stdlib.h> | |
| 28 | #include <stdio.h> | |
| 29 | #include <unistd.h> | |
| 30 | #include <sys/types.h> | |
| 31 | #include "porttime.h" | |
| 32 | #include "sys/time.h" | |
| 33 | #include "sys/resource.h" | |
| 34 | #include "sys/timeb.h" | |
| 35 | #include "pthread.h" | |
| 36 | ||
| 37 | #define TRUE 1 | |
| 38 | #define FALSE 0 | |
| 39 | ||
| 40 | static int time_started_flag = FALSE; | |
| 41 | static struct timeb time_offset = {0, 0, 0, 0}; | |
| 42 | static pthread_t pt_thread_pid; | |
| 43 | static int pt_thread_created = FALSE; | |
| 44 | ||
| 45 | /* note that this is static data -- we only need one copy */ | |
| 46 | typedef struct { | |
| 47 | int id; | |
| 48 | int resolution; | |
| 49 | PtCallback *callback; | |
| 50 | void *userData; | |
| 51 | } pt_callback_parameters; | |
| 52 | ||
| 53 | static int pt_callback_proc_id = 0; | |
| 54 | ||
| 55 | static void *Pt_CallbackProc(void *p) | |
| 56 | { | |
| 57 | pt_callback_parameters *parameters = (pt_callback_parameters *) p; | |
| 58 | int mytime = 1; | |
| 59 | /* to kill a process, just increment the pt_callback_proc_id */ | |
| 60 | /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, | |
| 61 | parameters->id); */ | |
| 62 | if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); | |
| 63 | while (pt_callback_proc_id == parameters->id) { | |
| 64 | /* wait for a multiple of resolution ms */ | |
| 65 | struct timeval timeout; | |
| 66 | int delay = mytime++ * parameters->resolution - Pt_Time(); | |
| 67 | if (delay < 0) delay = 0; | |
| 68 | timeout.tv_sec = 0; | |
| 69 | timeout.tv_usec = delay * 1000; | |
| 70 | select(0, NULL, NULL, NULL, &timeout); | |
| 71 | (*(parameters->callback))(Pt_Time(), parameters->userData); | |
| 72 | } | |
| 73 | /* printf("Pt_CallbackProc exiting\n"); */ | |
| 74 | // free(parameters); | |
| 75 | return NULL; | |
| 76 | } | |
| 77 | ||
| 78 | ||
| 79 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) | |
| 80 | { | |
| 81 | if (time_started_flag) return ptNoError; | |
| 82 | ftime(&time_offset); /* need this set before process runs */ | |
| 83 | if (callback) { | |
| 84 | int res; | |
| 85 | pt_callback_parameters *parms = (pt_callback_parameters *) | |
| 86 | malloc(sizeof(pt_callback_parameters)); | |
| 87 | if (!parms) return ptInsufficientMemory; | |
| 88 | parms->id = pt_callback_proc_id; | |
| 89 | parms->resolution = resolution; | |
| 90 | parms->callback = callback; | |
| 91 | parms->userData = userData; | |
| 92 | res = pthread_create(&pt_thread_pid, NULL, | |
| 93 | Pt_CallbackProc, parms); | |
| 94 | if (res != 0) return ptHostError; | |
| 95 | pt_thread_created = TRUE; | |
| 96 | } | |
| 97 | time_started_flag = TRUE; | |
| 98 | return ptNoError; | |
| 99 | } | |
| 100 | ||
| 101 | ||
| 102 | PtError Pt_Stop() | |
| 103 | { | |
| 104 | /* printf("Pt_Stop called\n"); */ | |
| 105 | pt_callback_proc_id++; | |
| 106 | if (pt_thread_created) { | |
| 107 | pthread_join(pt_thread_pid, NULL); | |
| 108 | pt_thread_created = FALSE; | |
| 109 | } | |
| 110 | time_started_flag = FALSE; | |
| 111 | return ptNoError; | |
| 112 | } | |
| 113 | ||
| 114 | ||
| 115 | int Pt_Started() | |
| 116 | { | |
| 117 | return time_started_flag; | |
| 118 | } | |
| 119 | ||
| 120 | ||
| 121 | PtTimestamp Pt_Time() | |
| 122 | { | |
| 123 | long seconds, milliseconds; | |
| 124 | struct timeb now; | |
| 125 | ftime(&now); | |
| 126 | seconds = now.time - time_offset.time; | |
| 127 | milliseconds = now.millitm - time_offset.millitm; | |
| 128 | return seconds * 1000 + milliseconds; | |
| 129 | } | |
| 130 | ||
| 131 | ||
| 132 | void Pt_Sleep(int32_t duration) | |
| 133 | { | |
| 134 | usleep(duration * 1000); | |
| 135 | } | |
| 136 | ||
| 137 | ||
| 138 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* porttime.c -- portable API for millisecond timer */ | |
| 2 | ||
| 3 | /* There is no machine-independent implementation code to put here */ |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* finddefault.c -- find_default_device() implementation | |
| 2 | Roger Dannenberg, Jan 2009 | |
| 3 | */ | |
| 4 | ||
| 5 | #include <stdlib.h> | |
| 6 | #include <stdio.h> | |
| 7 | #include <string.h> | |
| 8 | #include <ctype.h> | |
| 9 | #include "portmidi.h" | |
| 10 | ||
| 11 | extern int pm_find_default_device(char *pattern, int is_input); | |
| 12 | ||
| 13 | #define STRING_MAX 256 | |
| 14 | ||
| 15 | /* skip over spaces, return first non-space */ | |
| 16 | void skip_spaces(FILE *inf) | |
| 17 | { | |
| 18 | char c; | |
| 19 | while (isspace(c = getc(inf))) ; | |
| 20 | ungetc(c, inf); | |
| 21 | } | |
| 22 | ||
| 23 | /* trim leading spaces and match a string */ | |
| 24 | int match_string(FILE *inf, char *s) | |
| 25 | { | |
| 26 | skip_spaces(inf); | |
| 27 | while (*s && *s == getc(inf)) s++; | |
| 28 | return (*s == 0); | |
| 29 | } | |
| 30 | ||
| 31 | ||
| 32 | /* Parse preference files, find default device, search devices -- | |
| 33 | */ | |
| 34 | PmDeviceID find_default_device(char *path, int input, PmDeviceID id) | |
| 35 | /* path -- the name of the preference we are searching for | |
| 36 | input -- true iff this is an input device | |
| 37 | id -- current default device id | |
| 38 | returns matching device id if found, otherwise id | |
| 39 | */ | |
| 40 | { | |
| 41 | static char *pref_2 = (char *)"/.java/.userPrefs/"; | |
| 42 | static char *pref_3 = (char *)"prefs.xml"; | |
| 43 | char *pref_1 = getenv("HOME"); | |
| 44 | char *full_name, *path_ptr; | |
| 45 | FILE *inf; | |
| 46 | int c, i; | |
| 47 | if (!pref_1) goto nopref; // cannot find preference file | |
| 48 | // full_name will be larger than necessary | |
| 49 | full_name = malloc(strlen(pref_1) + strlen(pref_2) + strlen(pref_3) + | |
| 50 | strlen(path) + 2); | |
| 51 | strcpy(full_name, pref_1); | |
| 52 | strcat(full_name, pref_2); | |
| 53 | // copy all but last path segment to full_name | |
| 54 | if (*path == '/') path++; // skip initial slash in path | |
| 55 | path_ptr = strrchr(path, '/'); | |
| 56 | if (path_ptr) { // copy up to slash after full_name | |
| 57 | path_ptr++; | |
| 58 | int offset = strlen(full_name); | |
| 59 | memcpy(full_name + offset, path, path_ptr - path); | |
| 60 | full_name[offset + path_ptr - path] = 0; // end of string | |
| 61 | } else { | |
| 62 | path_ptr = path; | |
| 63 | } | |
| 64 | strcat(full_name, pref_3); | |
| 65 | inf = fopen(full_name, "r"); | |
| 66 | if (!inf) goto nopref; // cannot open preference file | |
| 67 | // We're not going to build or link in a full XML parser. | |
| 68 | // Instead, find the path string and quoute. Then, look for | |
| 69 | // "value", "=", quote. Then get string up to quote. | |
| 70 | while ((c = getc(inf)) != EOF) { | |
| 71 | char pref_str[STRING_MAX]; | |
| 72 | if (c != '"') continue; // scan up to quote | |
| 73 | // look for quote string quote | |
| 74 | if (!match_string(inf, path_ptr)) continue; // path not found | |
| 75 | if (getc(inf) != '"') continue; // path not found, keep scanning | |
| 76 | if (!match_string(inf, (char *)"value")) goto nopref; // value not found | |
| 77 | if (!match_string(inf, (char *)"=")) goto nopref; // = not found | |
| 78 | if (!match_string(inf, (char *)"\"")) goto nopref; // quote not found | |
| 79 | // now read the value up to the close quote | |
| 80 | for (i = 0; i < STRING_MAX; i++) { | |
| 81 | if ((c = getc(inf)) == '"') break; | |
| 82 | pref_str[i] = c; | |
| 83 | } | |
| 84 | if (i == STRING_MAX) continue; // value too long, ignore | |
| 85 | pref_str[i] = 0; | |
| 86 | i = pm_find_default_device(pref_str, input); | |
| 87 | if (i != pmNoDevice) { | |
| 88 | id = i; | |
| 89 | } | |
| 90 | break; | |
| 91 | } | |
| 92 | nopref: | |
| 93 | return id; | |
| 94 | } |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* ptmacosx.c -- portable timer implementation for mac os x */ | |
| 2 | ||
| 3 | #include <stdlib.h> | |
| 4 | #include <stdio.h> | |
| 5 | #include <pthread.h> | |
| 6 | #include <CoreFoundation/CoreFoundation.h> | |
| 7 | ||
| 8 | #import <mach/mach.h> | |
| 9 | #import <mach/mach_error.h> | |
| 10 | #import <mach/mach_time.h> | |
| 11 | #import <mach/clock.h> | |
| 12 | ||
| 13 | #include "porttime.h" | |
| 14 | ||
| 15 | #define THREAD_IMPORTANCE 30 | |
| 16 | #define LONG_TIME 1000000000.0 | |
| 17 | ||
| 18 | static int time_started_flag = FALSE; | |
| 19 | static CFAbsoluteTime startTime = 0.0; | |
| 20 | static CFRunLoopRef timerRunLoop; | |
| 21 | ||
| 22 | typedef struct { | |
| 23 | int resolution; | |
| 24 | PtCallback *callback; | |
| 25 | void *userData; | |
| 26 | } PtThreadParams; | |
| 27 | ||
| 28 | ||
| 29 | void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info) | |
| 30 | { | |
| 31 | PtThreadParams *params = (PtThreadParams*)info; | |
| 32 | (*params->callback)(Pt_Time(), params->userData); | |
| 33 | } | |
| 34 | ||
| 35 | static void* Pt_Thread(void *p) | |
| 36 | { | |
| 37 | CFTimeInterval timerInterval; | |
| 38 | CFRunLoopTimerContext timerContext; | |
| 39 | CFRunLoopTimerRef timer; | |
| 40 | PtThreadParams *params = (PtThreadParams*)p; | |
| 41 | //CFTimeInterval timeout; | |
| 42 | ||
| 43 | /* raise the thread's priority */ | |
| 44 | kern_return_t error; | |
| 45 | thread_extended_policy_data_t extendedPolicy; | |
| 46 | thread_precedence_policy_data_t precedencePolicy; | |
| 47 | ||
| 48 | extendedPolicy.timeshare = 0; | |
| 49 | error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, | |
| 50 | (thread_policy_t)&extendedPolicy, | |
| 51 | THREAD_EXTENDED_POLICY_COUNT); | |
| 52 | if (error != KERN_SUCCESS) { | |
| 53 | mach_error("Couldn't set thread timeshare policy", error); | |
| 54 | } | |
| 55 | ||
| 56 | precedencePolicy.importance = THREAD_IMPORTANCE; | |
| 57 | error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, | |
| 58 | (thread_policy_t)&precedencePolicy, | |
| 59 | THREAD_PRECEDENCE_POLICY_COUNT); | |
| 60 | if (error != KERN_SUCCESS) { | |
| 61 | mach_error("Couldn't set thread precedence policy", error); | |
| 62 | } | |
| 63 | ||
| 64 | /* set up the timer context */ | |
| 65 | timerContext.version = 0; | |
| 66 | timerContext.info = params; | |
| 67 | timerContext.retain = NULL; | |
| 68 | timerContext.release = NULL; | |
| 69 | timerContext.copyDescription = NULL; | |
| 70 | ||
| 71 | /* create a new timer */ | |
| 72 | timerInterval = (double)params->resolution / 1000.0; | |
| 73 | timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval, | |
| 74 | 0, 0, Pt_CFTimerCallback, &timerContext); | |
| 75 | ||
| 76 | timerRunLoop = CFRunLoopGetCurrent(); | |
| 77 | CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode")); | |
| 78 | ||
| 79 | /* run until we're told to stop by Pt_Stop() */ | |
| 80 | CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false); | |
| 81 | ||
| 82 | CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode")); | |
| 83 | CFRelease(timer); | |
| 84 | free(params); | |
| 85 | ||
| 86 | return NULL; | |
| 87 | } | |
| 88 | ||
| 89 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) | |
| 90 | { | |
| 91 | PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams)); | |
| 92 | pthread_t pthread_id; | |
| 93 | ||
| 94 | printf("Pt_Start() called\n"); | |
| 95 | ||
| 96 | // /* make sure we're not already playing */ | |
| 97 | if (time_started_flag) return ptAlreadyStarted; | |
| 98 | startTime = CFAbsoluteTimeGetCurrent(); | |
| 99 | ||
| 100 | if (callback) { | |
| 101 | ||
| 102 | params->resolution = resolution; | |
| 103 | params->callback = callback; | |
| 104 | params->userData = userData; | |
| 105 | ||
| 106 | pthread_create(&pthread_id, NULL, Pt_Thread, params); | |
| 107 | } | |
| 108 | ||
| 109 | time_started_flag = TRUE; | |
| 110 | return ptNoError; | |
| 111 | } | |
| 112 | ||
| 113 | ||
| 114 | PtError Pt_Stop() | |
| 115 | { | |
| 116 | printf("Pt_Stop called\n"); | |
| 117 | ||
| 118 | CFRunLoopStop(timerRunLoop); | |
| 119 | time_started_flag = FALSE; | |
| 120 | return ptNoError; | |
| 121 | } | |
| 122 | ||
| 123 | ||
| 124 | int Pt_Started() | |
| 125 | { | |
| 126 | return time_started_flag; | |
| 127 | } | |
| 128 | ||
| 129 | ||
| 130 | PtTimestamp Pt_Time() | |
| 131 | { | |
| 132 | CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); | |
| 133 | return (PtTimestamp) ((now - startTime) * 1000.0); | |
| 134 | } | |
| 135 | ||
| 136 | ||
| 137 | void Pt_Sleep(int32_t duration) | |
| 138 | { | |
| 139 | usleep(duration * 1000); | |
| 140 | } |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* | |
| 2 | * Platform interface to the MacOS X CoreMIDI framework | |
| 3 | * | |
| 4 | * Jon Parise <jparise at cmu.edu> | |
| 5 | * and subsequent work by Andrew Zeldis and Zico Kolter | |
| 6 | * and Roger B. Dannenberg | |
| 7 | * | |
| 8 | * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $ | |
| 9 | */ | |
| 10 | ||
| 11 | /* Notes: | |
| 12 | since the input and output streams are represented by MIDIEndpointRef | |
| 13 | values and almost no other state, we store the MIDIEndpointRef on | |
| 14 | descriptors[midi->device_id].descriptor. The only other state we need | |
| 15 | is for errors: we need to know if there is an error and if so, what is | |
| 16 | the error text. We use a structure with two kinds of | |
| 17 | host error: "error" and "callback_error". That way, asynchronous callbacks | |
| 18 | do not interfere with other error information. | |
| 19 | ||
| 20 | OS X does not seem to have an error-code-to-text function, so we will | |
| 21 | just use text messages instead of error codes. | |
| 22 | */ | |
| 23 | ||
| 24 | #include <stdlib.h> | |
| 25 | ||
| 26 | //#define CM_DEBUG 1 | |
| 27 | ||
| 28 | #include "portmidi.h" | |
| 29 | #include "pmutil.h" | |
| 30 | #include "pminternal.h" | |
| 31 | #include "porttime.h" | |
| 32 | #include "pmmac.h" | |
| 33 | #include "pmmacosxcm.h" | |
| 34 | ||
| 35 | #include <stdio.h> | |
| 36 | #include <string.h> | |
| 37 | ||
| 38 | #include <CoreServices/CoreServices.h> | |
| 39 | #include <CoreMIDI/MIDIServices.h> | |
| 40 | #include <CoreAudio/HostTime.h> | |
| 41 | #include <unistd.h> | |
| 42 | ||
| 43 | #define PACKET_BUFFER_SIZE 1024 | |
| 44 | /* maximum overall data rate (OS X limit is 15000 bytes/second) */ | |
| 45 | #define MAX_BYTES_PER_S 14000 | |
| 46 | ||
| 47 | /* Apple reports that packets are dropped when the MIDI bytes/sec | |
| 48 | exceeds 15000. This is computed by "tracking the number of MIDI | |
| 49 | bytes scheduled into 1-second buckets over the last six seconds | |
| 50 | and averaging these counts." | |
| 51 | ||
| 52 | This is apparently based on timestamps, not on real time, so | |
| 53 | we have to avoid constructing packets that schedule high speed | |
| 54 | output even if the actual writes are delayed (which was my first | |
| 55 | solution). | |
| 56 | ||
| 57 | The LIMIT_RATE symbol, if defined, enables code to modify | |
| 58 | timestamps as follows: | |
| 59 | After each packet is formed, the next allowable timestamp is | |
| 60 | computed as this_packet_time + this_packet_len * delay_per_byte | |
| 61 | ||
| 62 | This is the minimum timestamp allowed in the next packet. | |
| 63 | ||
| 64 | Note that this distorts accurate timestamps somewhat. | |
| 65 | */ | |
| 66 | #define LIMIT_RATE 1 | |
| 67 | ||
| 68 | #define SYSEX_BUFFER_SIZE 128 | |
| 69 | ||
| 70 | #define VERBOSE_ON 1 | |
| 71 | #define VERBOSE if (VERBOSE_ON) | |
| 72 | ||
| 73 | #define MIDI_SYSEX 0xf0 | |
| 74 | #define MIDI_EOX 0xf7 | |
| 75 | #define MIDI_STATUS_MASK 0x80 | |
| 76 | ||
| 77 | // "Ref"s are pointers on 32-bit machines and ints on 64 bit machines | |
| 78 | // NULL_REF is our representation of either 0 or NULL | |
| 79 | #ifdef __LP64__ | |
| 80 | #define NULL_REF 0 | |
| 81 | #else | |
| 82 | #define NULL_REF NULL | |
| 83 | #endif | |
| 84 | ||
| 85 | static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */ | |
| 86 | static MIDIPortRef portIn = NULL_REF; /* Input port handle */ | |
| 87 | static MIDIPortRef portOut = NULL_REF; /* Output port handle */ | |
| 88 | ||
| 89 | extern pm_fns_node pm_macosx_in_dictionary; | |
| 90 | extern pm_fns_node pm_macosx_out_dictionary; | |
| 91 | ||
| 92 | typedef struct midi_macosxcm_struct { | |
| 93 | PmTimestamp sync_time; /* when did we last determine delta? */ | |
| 94 | UInt64 delta; /* difference between stream time and real time in ns */ | |
| 95 | UInt64 last_time; /* last output time in host units*/ | |
| 96 | int first_message; /* tells midi_write to sychronize timestamps */ | |
| 97 | int sysex_mode; /* middle of sending sysex */ | |
| 98 | uint32_t sysex_word; /* accumulate data when receiving sysex */ | |
| 99 | uint32_t sysex_byte_count; /* count how many received */ | |
| 100 | char error[PM_HOST_ERROR_MSG_LEN]; | |
| 101 | char callback_error[PM_HOST_ERROR_MSG_LEN]; | |
| 102 | Byte packetBuffer[PACKET_BUFFER_SIZE]; | |
| 103 | MIDIPacketList *packetList; /* a pointer to packetBuffer */ | |
| 104 | MIDIPacket *packet; | |
| 105 | Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */ | |
| 106 | MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */ | |
| 107 | /* allow for running status (is running status possible here? -rbd): -cpr */ | |
| 108 | unsigned char last_command; | |
| 109 | int32_t last_msg_length; | |
| 110 | /* limit midi data rate (a CoreMidi requirement): */ | |
| 111 | UInt64 min_next_time; /* when can the next send take place? */ | |
| 112 | int byte_count; /* how many bytes in the next packet list? */ | |
| 113 | Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */ | |
| 114 | UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */ | |
| 115 | } midi_macosxcm_node, *midi_macosxcm_type; | |
| 116 | ||
| 117 | /* private function declarations */ | |
| 118 | MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); | |
| 119 | PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); | |
| 120 | ||
| 121 | char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint); | |
| 122 | ||
| 123 | ||
| 124 | static int | |
| 125 | midi_length(int32_t msg) | |
| 126 | { | |
| 127 | int status, high, low; | |
| 128 | static int high_lengths[] = { | |
| 129 | 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */ | |
| 130 | 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */ | |
| 131 | }; | |
| 132 | static int low_lengths[] = { | |
| 133 | 1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */ | |
| 134 | 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */ | |
| 135 | }; | |
| 136 | ||
| 137 | status = msg & 0xFF; | |
| 138 | high = status >> 4; | |
| 139 | low = status & 15; | |
| 140 | ||
| 141 | return (high != 0xF) ? high_lengths[high] : low_lengths[low]; | |
| 142 | } | |
| 143 | ||
| 144 | static PmTimestamp midi_synchronize(PmInternal *midi) | |
| 145 | { | |
| 146 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 147 | UInt64 pm_stream_time_2 = | |
| 148 | AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); | |
| 149 | PmTimestamp real_time; | |
| 150 | UInt64 pm_stream_time; | |
| 151 | /* if latency is zero and this is an output, there is no | |
| 152 | time reference and midi_synchronize should never be called */ | |
| 153 | assert(midi->time_proc); | |
| 154 | assert(!(midi->write_flag && midi->latency == 0)); | |
| 155 | do { | |
| 156 | /* read real_time between two reads of stream time */ | |
| 157 | pm_stream_time = pm_stream_time_2; | |
| 158 | real_time = (*midi->time_proc)(midi->time_info); | |
| 159 | pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); | |
| 160 | /* repeat if more than 0.5 ms has elapsed */ | |
| 161 | } while (pm_stream_time_2 > pm_stream_time + 500000); | |
| 162 | m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000); | |
| 163 | m->sync_time = real_time; | |
| 164 | return real_time; | |
| 165 | } | |
| 166 | ||
| 167 | ||
| 168 | static void | |
| 169 | process_packet(MIDIPacket *packet, PmEvent *event, | |
| 170 | PmInternal *midi, midi_macosxcm_type m) | |
| 171 | { | |
| 172 | /* handle a packet of MIDI messages from CoreMIDI */ | |
| 173 | /* there may be multiple short messages in one packet (!) */ | |
| 174 | unsigned int remaining_length = packet->length; | |
| 175 | unsigned char *cur_packet_data = packet->data; | |
| 176 | while (remaining_length > 0) { | |
| 177 | if (cur_packet_data[0] == MIDI_SYSEX || | |
| 178 | /* are we in the middle of a sysex message? */ | |
| 179 | (m->last_command == 0 && | |
| 180 | !(cur_packet_data[0] & MIDI_STATUS_MASK))) { | |
| 181 | m->last_command = 0; /* no running status */ | |
| 182 | unsigned int amt = pm_read_bytes(midi, cur_packet_data, | |
| 183 | remaining_length, | |
| 184 | event->timestamp); | |
| 185 | remaining_length -= amt; | |
| 186 | cur_packet_data += amt; | |
| 187 | } else if (cur_packet_data[0] == MIDI_EOX) { | |
| 188 | /* this should never happen, because pm_read_bytes should | |
| 189 | * get and read all EOX bytes*/ | |
| 190 | midi->sysex_in_progress = FALSE; | |
| 191 | m->last_command = 0; | |
| 192 | } else if (cur_packet_data[0] & MIDI_STATUS_MASK) { | |
| 193 | /* compute the length of the next (short) msg in packet */ | |
| 194 | unsigned int cur_message_length = midi_length(cur_packet_data[0]); | |
| 195 | if (cur_message_length > remaining_length) { | |
| 196 | #ifdef DEBUG | |
| 197 | printf("PortMidi debug msg: not enough data"); | |
| 198 | #endif | |
| 199 | /* since there's no more data, we're done */ | |
| 200 | return; | |
| 201 | } | |
| 202 | m->last_msg_length = cur_message_length; | |
| 203 | m->last_command = cur_packet_data[0]; | |
| 204 | switch (cur_message_length) { | |
| 205 | case 1: | |
| 206 | event->message = Pm_Message(cur_packet_data[0], 0, 0); | |
| 207 | break; | |
| 208 | case 2: | |
| 209 | event->message = Pm_Message(cur_packet_data[0], | |
| 210 | cur_packet_data[1], 0); | |
| 211 | break; | |
| 212 | case 3: | |
| 213 | event->message = Pm_Message(cur_packet_data[0], | |
| 214 | cur_packet_data[1], | |
| 215 | cur_packet_data[2]); | |
| 216 | break; | |
| 217 | default: | |
| 218 | /* PortMIDI internal error; should never happen */ | |
| 219 | assert(cur_message_length == 1); | |
| 220 | return; /* give up on packet if continued after assert */ | |
| 221 | } | |
| 222 | pm_read_short(midi, event); | |
| 223 | remaining_length -= m->last_msg_length; | |
| 224 | cur_packet_data += m->last_msg_length; | |
| 225 | } else if (m->last_msg_length > remaining_length + 1) { | |
| 226 | /* we have running status, but not enough data */ | |
| 227 | #ifdef DEBUG | |
| 228 | printf("PortMidi debug msg: not enough data in CoreMIDI packet"); | |
| 229 | #endif | |
| 230 | /* since there's no more data, we're done */ | |
| 231 | return; | |
| 232 | } else { /* output message using running status */ | |
| 233 | switch (m->last_msg_length) { | |
| 234 | case 1: | |
| 235 | event->message = Pm_Message(m->last_command, 0, 0); | |
| 236 | break; | |
| 237 | case 2: | |
| 238 | event->message = Pm_Message(m->last_command, | |
| 239 | cur_packet_data[0], 0); | |
| 240 | break; | |
| 241 | case 3: | |
| 242 | event->message = Pm_Message(m->last_command, | |
| 243 | cur_packet_data[0], | |
| 244 | cur_packet_data[1]); | |
| 245 | break; | |
| 246 | default: | |
| 247 | /* last_msg_length is invalid -- internal PortMIDI error */ | |
| 248 | assert(m->last_msg_length == 1); | |
| 249 | } | |
| 250 | pm_read_short(midi, event); | |
| 251 | remaining_length -= (m->last_msg_length - 1); | |
| 252 | cur_packet_data += (m->last_msg_length - 1); | |
| 253 | } | |
| 254 | } | |
| 255 | } | |
| 256 | ||
| 257 | ||
| 258 | ||
| 259 | /* called when MIDI packets are received */ | |
| 260 | static void | |
| 261 | readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon) | |
| 262 | { | |
| 263 | PmInternal *midi; | |
| 264 | midi_macosxcm_type m; | |
| 265 | PmEvent event; | |
| 266 | MIDIPacket *packet; | |
| 267 | unsigned int packetIndex; | |
| 268 | uint32_t now; | |
| 269 | unsigned int status; | |
| 270 | ||
| 271 | #ifdef CM_DEBUG | |
| 272 | printf("readProc: numPackets %d: ", newPackets->numPackets); | |
| 273 | #endif | |
| 274 | ||
| 275 | /* Retrieve the context for this connection */ | |
| 276 | midi = (PmInternal *) connRefCon; | |
| 277 | m = (midi_macosxcm_type) midi->descriptor; | |
| 278 | assert(m); | |
| 279 | ||
| 280 | /* synchronize time references every 100ms */ | |
| 281 | now = (*midi->time_proc)(midi->time_info); | |
| 282 | if (m->first_message || m->sync_time + 100 /*ms*/ < now) { | |
| 283 | /* time to resync */ | |
| 284 | now = midi_synchronize(midi); | |
| 285 | m->first_message = FALSE; | |
| 286 | } | |
| 287 | ||
| 288 | packet = (MIDIPacket *) &newPackets->packet[0]; | |
| 289 | /* printf("readproc packet status %x length %d\n", packet->data[0], | |
| 290 | packet->length); */ | |
| 291 | for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) { | |
| 292 | /* Set the timestamp and dispatch this message */ | |
| 293 | event.timestamp = (PmTimestamp) /* explicit conversion */ ( | |
| 294 | (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) / | |
| 295 | (UInt64) 1000000); | |
| 296 | status = packet->data[0]; | |
| 297 | /* process packet as sysex data if it begins with MIDI_SYSEX, or | |
| 298 | MIDI_EOX or non-status byte with no running status */ | |
| 299 | #ifdef CM_DEBUG | |
| 300 | printf(" %d", packet->length); | |
| 301 | #endif | |
| 302 | if (status == MIDI_SYSEX || status == MIDI_EOX || | |
| 303 | ((!(status & MIDI_STATUS_MASK)) && !m->last_command)) { | |
| 304 | /* previously was: !(status & MIDI_STATUS_MASK)) { | |
| 305 | * but this could mistake running status for sysex data | |
| 306 | */ | |
| 307 | /* reset running status data -cpr */ | |
| 308 | m->last_command = 0; | |
| 309 | m->last_msg_length = 0; | |
| 310 | /* printf("sysex packet length: %d\n", packet->length); */ | |
| 311 | pm_read_bytes(midi, packet->data, packet->length, event.timestamp); | |
| 312 | } else { | |
| 313 | process_packet(packet, &event, midi, m); | |
| 314 | } | |
| 315 | packet = MIDIPacketNext(packet); | |
| 316 | } | |
| 317 | #ifdef CM_DEBUG | |
| 318 | printf("\n"); | |
| 319 | #endif | |
| 320 | } | |
| 321 | ||
| 322 | static PmError | |
| 323 | midi_in_open(PmInternal *midi, void *driverInfo) | |
| 324 | { | |
| 325 | MIDIEndpointRef endpoint; | |
| 326 | midi_macosxcm_type m; | |
| 327 | OSStatus macHostError; | |
| 328 | ||
| 329 | /* insure that we have a time_proc for timing */ | |
| 330 | if (midi->time_proc == NULL) { | |
| 331 | if (!Pt_Started()) | |
| 332 | Pt_Start(1, 0, 0); | |
| 333 | /* time_get does not take a parameter, so coerce */ | |
| 334 | midi->time_proc = (PmTimeProcPtr) Pt_Time; | |
| 335 | } | |
| 336 | endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; | |
| 337 | if (endpoint == NULL_REF) { | |
| 338 | return pmInvalidDeviceId; | |
| 339 | } | |
| 340 | ||
| 341 | m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ | |
| 342 | midi->descriptor = m; | |
| 343 | if (!m) { | |
| 344 | return pmInsufficientMemory; | |
| 345 | } | |
| 346 | m->error[0] = 0; | |
| 347 | m->callback_error[0] = 0; | |
| 348 | m->sync_time = 0; | |
| 349 | m->delta = 0; | |
| 350 | m->last_time = 0; | |
| 351 | m->first_message = TRUE; | |
| 352 | m->sysex_mode = FALSE; | |
| 353 | m->sysex_word = 0; | |
| 354 | m->sysex_byte_count = 0; | |
| 355 | m->packetList = NULL; | |
| 356 | m->packet = NULL; | |
| 357 | m->last_command = 0; | |
| 358 | m->last_msg_length = 0; | |
| 359 | ||
| 360 | macHostError = MIDIPortConnectSource(portIn, endpoint, midi); | |
| 361 | if (macHostError != noErr) { | |
| 362 | pm_hosterror = macHostError; | |
| 363 | sprintf(pm_hosterror_text, | |
| 364 | "Host error %ld: MIDIPortConnectSource() in midi_in_open()", | |
| 365 | (long) macHostError); | |
| 366 | midi->descriptor = NULL; | |
| 367 | pm_free(m); | |
| 368 | return pmHostError; | |
| 369 | } | |
| 370 | ||
| 371 | return pmNoError; | |
| 372 | } | |
| 373 | ||
| 374 | static PmError | |
| 375 | midi_in_close(PmInternal *midi) | |
| 376 | { | |
| 377 | MIDIEndpointRef endpoint; | |
| 378 | OSStatus macHostError; | |
| 379 | PmError err = pmNoError; | |
| 380 | ||
| 381 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 382 | ||
| 383 | if (!m) return pmBadPtr; | |
| 384 | ||
| 385 | endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; | |
| 386 | if (endpoint == NULL_REF) { | |
| 387 | pm_hosterror = pmBadPtr; | |
| 388 | } | |
| 389 | ||
| 390 | /* shut off the incoming messages before freeing data structures */ | |
| 391 | macHostError = MIDIPortDisconnectSource(portIn, endpoint); | |
| 392 | if (macHostError != noErr) { | |
| 393 | pm_hosterror = macHostError; | |
| 394 | sprintf(pm_hosterror_text, | |
| 395 | "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()", | |
| 396 | (long) macHostError); | |
| 397 | err = pmHostError; | |
| 398 | } | |
| 399 | ||
| 400 | midi->descriptor = NULL; | |
| 401 | pm_free(midi->descriptor); | |
| 402 | ||
| 403 | return err; | |
| 404 | } | |
| 405 | ||
| 406 | ||
| 407 | static PmError | |
| 408 | midi_out_open(PmInternal *midi, void *driverInfo) | |
| 409 | { | |
| 410 | midi_macosxcm_type m; | |
| 411 | ||
| 412 | m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ | |
| 413 | midi->descriptor = m; | |
| 414 | if (!m) { | |
| 415 | return pmInsufficientMemory; | |
| 416 | } | |
| 417 | m->error[0] = 0; | |
| 418 | m->callback_error[0] = 0; | |
| 419 | m->sync_time = 0; | |
| 420 | m->delta = 0; | |
| 421 | m->last_time = 0; | |
| 422 | m->first_message = TRUE; | |
| 423 | m->sysex_mode = FALSE; | |
| 424 | m->sysex_word = 0; | |
| 425 | m->sysex_byte_count = 0; | |
| 426 | m->packetList = (MIDIPacketList *) m->packetBuffer; | |
| 427 | m->packet = NULL; | |
| 428 | m->last_command = 0; | |
| 429 | m->last_msg_length = 0; | |
| 430 | m->min_next_time = 0; | |
| 431 | m->byte_count = 0; | |
| 432 | m->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency(); | |
| 433 | m->host_ticks_per_byte = (UInt64) (1000000.0 / | |
| 434 | (m->us_per_host_tick * MAX_BYTES_PER_S)); | |
| 435 | return pmNoError; | |
| 436 | } | |
| 437 | ||
| 438 | ||
| 439 | static PmError | |
| 440 | midi_out_close(PmInternal *midi) | |
| 441 | { | |
| 442 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 443 | if (!m) return pmBadPtr; | |
| 444 | ||
| 445 | midi->descriptor = NULL; | |
| 446 | pm_free(midi->descriptor); | |
| 447 | ||
| 448 | return pmNoError; | |
| 449 | } | |
| 450 | ||
| 451 | static PmError | |
| 452 | midi_abort(PmInternal *midi) | |
| 453 | { | |
| 454 | PmError err = pmNoError; | |
| 455 | OSStatus macHostError; | |
| 456 | MIDIEndpointRef endpoint = | |
| 457 | (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; | |
| 458 | macHostError = MIDIFlushOutput(endpoint); | |
| 459 | if (macHostError != noErr) { | |
| 460 | pm_hosterror = macHostError; | |
| 461 | sprintf(pm_hosterror_text, | |
| 462 | "Host error %ld: MIDIFlushOutput()", (long) macHostError); | |
| 463 | err = pmHostError; | |
| 464 | } | |
| 465 | return err; | |
| 466 | } | |
| 467 | ||
| 468 | ||
| 469 | static PmError | |
| 470 | midi_write_flush(PmInternal *midi, PmTimestamp timestamp) | |
| 471 | { | |
| 472 | OSStatus macHostError; | |
| 473 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 474 | MIDIEndpointRef endpoint = | |
| 475 | (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; | |
| 476 | assert(m); | |
| 477 | assert(endpoint); | |
| 478 | if (m->packet != NULL) { | |
| 479 | /* out of space, send the buffer and start refilling it */ | |
| 480 | /* before we can send, maybe delay to limit data rate. OS X allows | |
| 481 | * 15KB/s. */ | |
| 482 | UInt64 now = AudioGetCurrentHostTime(); | |
| 483 | if (now < m->min_next_time) { | |
| 484 | usleep((useconds_t) | |
| 485 | ((m->min_next_time - now) * m->us_per_host_tick)); | |
| 486 | } | |
| 487 | macHostError = MIDISend(portOut, endpoint, m->packetList); | |
| 488 | m->packet = NULL; /* indicate no data in packetList now */ | |
| 489 | m->min_next_time = now + m->byte_count * m->host_ticks_per_byte; | |
| 490 | m->byte_count = 0; | |
| 491 | if (macHostError != noErr) goto send_packet_error; | |
| 492 | } | |
| 493 | return pmNoError; | |
| 494 | ||
| 495 | send_packet_error: | |
| 496 | pm_hosterror = macHostError; | |
| 497 | sprintf(pm_hosterror_text, | |
| 498 | "Host error %ld: MIDISend() in midi_write()", | |
| 499 | (long) macHostError); | |
| 500 | return pmHostError; | |
| 501 | ||
| 502 | } | |
| 503 | ||
| 504 | ||
| 505 | static PmError | |
| 506 | send_packet(PmInternal *midi, Byte *message, unsigned int messageLength, | |
| 507 | MIDITimeStamp timestamp) | |
| 508 | { | |
| 509 | PmError err; | |
| 510 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 511 | assert(m); | |
| 512 | ||
| 513 | /* printf("add %d to packet %p len %d\n", message[0], m->packet, messageLength); */ | |
| 514 | m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), | |
| 515 | m->packet, timestamp, messageLength, | |
| 516 | message); | |
| 517 | m->byte_count += messageLength; | |
| 518 | if (m->packet == NULL) { | |
| 519 | /* out of space, send the buffer and start refilling it */ | |
| 520 | /* make midi->packet non-null to fool midi_write_flush into sending */ | |
| 521 | m->packet = (MIDIPacket *) 4; | |
| 522 | /* timestamp is 0 because midi_write_flush ignores timestamp since | |
| 523 | * timestamps are already in packets. The timestamp parameter is here | |
| 524 | * because other API's need it. midi_write_flush can be called | |
| 525 | * from system-independent code that must be cross-API. | |
| 526 | */ | |
| 527 | if ((err = midi_write_flush(midi, 0)) != pmNoError) return err; | |
| 528 | m->packet = MIDIPacketListInit(m->packetList); | |
| 529 | assert(m->packet); /* if this fails, it's a programming error */ | |
| 530 | m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), | |
| 531 | m->packet, timestamp, messageLength, | |
| 532 | message); | |
| 533 | assert(m->packet); /* can't run out of space on first message */ | |
| 534 | } | |
| 535 | return pmNoError; | |
| 536 | } | |
| 537 | ||
| 538 | ||
| 539 | static PmError | |
| 540 | midi_write_short(PmInternal *midi, PmEvent *event) | |
| 541 | { | |
| 542 | PmTimestamp when = event->timestamp; | |
| 543 | PmMessage what = event->message; | |
| 544 | MIDITimeStamp timestamp; | |
| 545 | UInt64 when_ns; | |
| 546 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 547 | Byte message[4]; | |
| 548 | unsigned int messageLength; | |
| 549 | ||
| 550 | if (m->packet == NULL) { | |
| 551 | m->packet = MIDIPacketListInit(m->packetList); | |
| 552 | /* this can never fail, right? failure would indicate something | |
| 553 | unrecoverable */ | |
| 554 | assert(m->packet); | |
| 555 | } | |
| 556 | ||
| 557 | /* compute timestamp */ | |
| 558 | if (when == 0) when = midi->now; | |
| 559 | /* if latency == 0, midi->now is not valid. We will just set it to zero */ | |
| 560 | if (midi->latency == 0) when = 0; | |
| 561 | when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta; | |
| 562 | timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); | |
| 563 | ||
| 564 | message[0] = Pm_MessageStatus(what); | |
| 565 | message[1] = Pm_MessageData1(what); | |
| 566 | message[2] = Pm_MessageData2(what); | |
| 567 | messageLength = midi_length(what); | |
| 568 | ||
| 569 | /* make sure we go foreward in time */ | |
| 570 | if (timestamp < m->min_next_time) timestamp = m->min_next_time; | |
| 571 | ||
| 572 | #ifdef LIMIT_RATE | |
| 573 | if (timestamp < m->last_time) | |
| 574 | timestamp = m->last_time; | |
| 575 | m->last_time = timestamp + messageLength * m->host_ticks_per_byte; | |
| 576 | #endif | |
| 577 | ||
| 578 | /* Add this message to the packet list */ | |
| 579 | return send_packet(midi, message, messageLength, timestamp); | |
| 580 | } | |
| 581 | ||
| 582 | ||
| 583 | static PmError | |
| 584 | midi_begin_sysex(PmInternal *midi, PmTimestamp when) | |
| 585 | { | |
| 586 | UInt64 when_ns; | |
| 587 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 588 | assert(m); | |
| 589 | m->sysex_byte_count = 0; | |
| 590 | ||
| 591 | /* compute timestamp */ | |
| 592 | if (when == 0) when = midi->now; | |
| 593 | /* if latency == 0, midi->now is not valid. We will just set it to zero */ | |
| 594 | if (midi->latency == 0) when = 0; | |
| 595 | when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta; | |
| 596 | m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); | |
| 597 | ||
| 598 | if (m->packet == NULL) { | |
| 599 | m->packet = MIDIPacketListInit(m->packetList); | |
| 600 | /* this can never fail, right? failure would indicate something | |
| 601 | unrecoverable */ | |
| 602 | assert(m->packet); | |
| 603 | } | |
| 604 | return pmNoError; | |
| 605 | } | |
| 606 | ||
| 607 | ||
| 608 | static PmError | |
| 609 | midi_end_sysex(PmInternal *midi, PmTimestamp when) | |
| 610 | { | |
| 611 | PmError err; | |
| 612 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 613 | assert(m); | |
| 614 | ||
| 615 | /* make sure we go foreward in time */ | |
| 616 | if (m->sysex_timestamp < m->min_next_time) | |
| 617 | m->sysex_timestamp = m->min_next_time; | |
| 618 | ||
| 619 | #ifdef LIMIT_RATE | |
| 620 | if (m->sysex_timestamp < m->last_time) | |
| 621 | m->sysex_timestamp = m->last_time; | |
| 622 | m->last_time = m->sysex_timestamp + m->sysex_byte_count * | |
| 623 | m->host_ticks_per_byte; | |
| 624 | #endif | |
| 625 | ||
| 626 | /* now send what's in the buffer */ | |
| 627 | err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count, | |
| 628 | m->sysex_timestamp); | |
| 629 | m->sysex_byte_count = 0; | |
| 630 | if (err != pmNoError) { | |
| 631 | m->packet = NULL; /* flush everything in the packet list */ | |
| 632 | return err; | |
| 633 | } | |
| 634 | return pmNoError; | |
| 635 | } | |
| 636 | ||
| 637 | ||
| 638 | static PmError | |
| 639 | midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp) | |
| 640 | { | |
| 641 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 642 | assert(m); | |
| 643 | if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) { | |
| 644 | PmError err = midi_end_sysex(midi, timestamp); | |
| 645 | if (err != pmNoError) return err; | |
| 646 | } | |
| 647 | m->sysex_buffer[m->sysex_byte_count++] = byte; | |
| 648 | return pmNoError; | |
| 649 | } | |
| 650 | ||
| 651 | ||
| 652 | static PmError | |
| 653 | midi_write_realtime(PmInternal *midi, PmEvent *event) | |
| 654 | { | |
| 655 | /* to send a realtime message during a sysex message, first | |
| 656 | flush all pending sysex bytes into packet list */ | |
| 657 | PmError err = midi_end_sysex(midi, 0); | |
| 658 | if (err != pmNoError) return err; | |
| 659 | /* then we can just do a normal midi_write_short */ | |
| 660 | return midi_write_short(midi, event); | |
| 661 | } | |
| 662 | ||
| 663 | static unsigned int midi_has_host_error(PmInternal *midi) | |
| 664 | { | |
| 665 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 666 | return (m->callback_error[0] != 0) || (m->error[0] != 0); | |
| 667 | } | |
| 668 | ||
| 669 | ||
| 670 | static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len) | |
| 671 | { | |
| 672 | midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; | |
| 673 | msg[0] = 0; /* initialize to empty string */ | |
| 674 | if (m) { /* make sure there is an open device to examine */ | |
| 675 | if (m->error[0]) { | |
| 676 | strncpy(msg, m->error, len); | |
| 677 | m->error[0] = 0; /* clear the error */ | |
| 678 | } else if (m->callback_error[0]) { | |
| 679 | strncpy(msg, m->callback_error, len); | |
| 680 | m->callback_error[0] = 0; /* clear the error */ | |
| 681 | } | |
| 682 | msg[len - 1] = 0; /* make sure string is terminated */ | |
| 683 | } | |
| 684 | } | |
| 685 | ||
| 686 | ||
| 687 | MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp) | |
| 688 | { | |
| 689 | UInt64 nanos; | |
| 690 | if (timestamp <= 0) { | |
| 691 | return (MIDITimeStamp)0; | |
| 692 | } else { | |
| 693 | nanos = (UInt64)timestamp * (UInt64)1000000; | |
| 694 | return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos); | |
| 695 | } | |
| 696 | } | |
| 697 | ||
| 698 | PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp) | |
| 699 | { | |
| 700 | UInt64 nanos; | |
| 701 | nanos = AudioConvertHostTimeToNanos(timestamp); | |
| 702 | return (PmTimestamp)(nanos / (UInt64)1000000); | |
| 703 | } | |
| 704 | ||
| 705 | ||
| 706 | // | |
| 707 | // Code taken from http://developer.apple.com/qa/qa2004/qa1374.html | |
| 708 | ////////////////////////////////////// | |
| 709 | // Obtain the name of an endpoint without regard for whether it has connections. | |
| 710 | // The result should be released by the caller. | |
| 711 | CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) | |
| 712 | { | |
| 713 | CFMutableStringRef result = CFStringCreateMutable(NULL, 0); | |
| 714 | CFStringRef str; | |
| 715 | ||
| 716 | // begin with the endpoint's name | |
| 717 | str = NULL; | |
| 718 | MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str); | |
| 719 | if (str != NULL) { | |
| 720 | CFStringAppend(result, str); | |
| 721 | CFRelease(str); | |
| 722 | } | |
| 723 | ||
| 724 | MIDIEntityRef entity = NULL_REF; | |
| 725 | MIDIEndpointGetEntity(endpoint, &entity); | |
| 726 | if (entity == NULL_REF) | |
| 727 | // probably virtual | |
| 728 | return result; | |
| 729 | ||
| 730 | if (CFStringGetLength(result) == 0) { | |
| 731 | // endpoint name has zero length -- try the entity | |
| 732 | str = NULL; | |
| 733 | MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str); | |
| 734 | if (str != NULL) { | |
| 735 | CFStringAppend(result, str); | |
| 736 | CFRelease(str); | |
| 737 | } | |
| 738 | } | |
| 739 | // now consider the device's name | |
| 740 | MIDIDeviceRef device = NULL_REF; | |
| 741 | MIDIEntityGetDevice(entity, &device); | |
| 742 | if (device == NULL_REF) | |
| 743 | return result; | |
| 744 | ||
| 745 | str = NULL; | |
| 746 | MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str); | |
| 747 | if (CFStringGetLength(result) == 0) { | |
| 748 | CFRelease(result); | |
| 749 | return str; | |
| 750 | } | |
| 751 | if (str != NULL) { | |
| 752 | // if an external device has only one entity, throw away | |
| 753 | // the endpoint name and just use the device name | |
| 754 | if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) { | |
| 755 | CFRelease(result); | |
| 756 | return str; | |
| 757 | } else { | |
| 758 | if (CFStringGetLength(str) == 0) { | |
| 759 | CFRelease(str); | |
| 760 | return result; | |
| 761 | } | |
| 762 | // does the entity name already start with the device name? | |
| 763 | // (some drivers do this though they shouldn't) | |
| 764 | // if so, do not prepend | |
| 765 | if (CFStringCompareWithOptions( result, /* endpoint name */ | |
| 766 | str /* device name */, | |
| 767 | CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) { | |
| 768 | // prepend the device name to the entity name | |
| 769 | if (CFStringGetLength(result) > 0) | |
| 770 | CFStringInsert(result, 0, CFSTR(" ")); | |
| 771 | CFStringInsert(result, 0, str); | |
| 772 | } | |
| 773 | CFRelease(str); | |
| 774 | } | |
| 775 | } | |
| 776 | return result; | |
| 777 | } | |
| 778 | ||
| 779 | ||
| 780 | // Obtain the name of an endpoint, following connections. | |
| 781 | // The result should be released by the caller. | |
| 782 | static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint) | |
| 783 | { | |
| 784 | CFMutableStringRef result = CFStringCreateMutable(NULL, 0); | |
| 785 | CFStringRef str; | |
| 786 | OSStatus err; | |
| 787 | long i; | |
| 788 | ||
| 789 | // Does the endpoint have connections? | |
| 790 | CFDataRef connections = NULL; | |
| 791 | long nConnected = 0; | |
| 792 | bool anyStrings = false; | |
| 793 | err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections); | |
| 794 | if (connections != NULL) { | |
| 795 | // It has connections, follow them | |
| 796 | // Concatenate the names of all connected devices | |
| 797 | nConnected = CFDataGetLength(connections) / (int32_t) sizeof(MIDIUniqueID); | |
| 798 | if (nConnected) { | |
| 799 | const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); | |
| 800 | for (i = 0; i < nConnected; ++i, ++pid) { | |
| 801 | MIDIUniqueID id = EndianS32_BtoN(*pid); | |
| 802 | MIDIObjectRef connObject; | |
| 803 | MIDIObjectType connObjectType; | |
| 804 | err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); | |
| 805 | if (err == noErr) { | |
| 806 | if (connObjectType == kMIDIObjectType_ExternalSource || | |
| 807 | connObjectType == kMIDIObjectType_ExternalDestination) { | |
| 808 | // Connected to an external device's endpoint (10.3 and later). | |
| 809 | str = EndpointName((MIDIEndpointRef)(connObject), true); | |
| 810 | } else { | |
| 811 | // Connected to an external device (10.2) (or something else, catch-all) | |
| 812 | str = NULL; | |
| 813 | MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str); | |
| 814 | } | |
| 815 | if (str != NULL) { | |
| 816 | if (anyStrings) | |
| 817 | CFStringAppend(result, CFSTR(", ")); | |
| 818 | else anyStrings = true; | |
| 819 | CFStringAppend(result, str); | |
| 820 | CFRelease(str); | |
| 821 | } | |
| 822 | } | |
| 823 | } | |
| 824 | } | |
| 825 | CFRelease(connections); | |
| 826 | } | |
| 827 | if (anyStrings) | |
| 828 | return result; | |
| 829 | ||
| 830 | // Here, either the endpoint had no connections, or we failed to obtain names for any of them. | |
| 831 | return EndpointName(endpoint, false); | |
| 832 | } | |
| 833 | ||
| 834 | ||
| 835 | char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint) | |
| 836 | { | |
| 837 | #ifdef OLDCODE | |
| 838 | MIDIEntityRef entity; | |
| 839 | MIDIDeviceRef device; | |
| 840 | ||
| 841 | CFStringRef endpointName = NULL; | |
| 842 | CFStringRef deviceName = NULL; | |
| 843 | #endif | |
| 844 | CFStringRef fullName = NULL; | |
| 845 | CFStringEncoding defaultEncoding; | |
| 846 | char* newName; | |
| 847 | ||
| 848 | /* get the default string encoding */ | |
| 849 | defaultEncoding = CFStringGetSystemEncoding(); | |
| 850 | ||
| 851 | fullName = ConnectedEndpointName(endpoint); | |
| 852 | ||
| 853 | #ifdef OLDCODE | |
| 854 | /* get the entity and device info */ | |
| 855 | MIDIEndpointGetEntity(endpoint, &entity); | |
| 856 | MIDIEntityGetDevice(entity, &device); | |
| 857 | ||
| 858 | /* create the nicely formated name */ | |
| 859 | MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName); | |
| 860 | MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName); | |
| 861 | if (deviceName != NULL) { | |
| 862 | fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"), | |
| 863 | deviceName, endpointName); | |
| 864 | } else { | |
| 865 | fullName = endpointName; | |
| 866 | } | |
| 867 | #endif | |
| 868 | /* copy the string into our buffer */ | |
| 869 | newName = (char *) malloc(CFStringGetLength(fullName) + 1); | |
| 870 | CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1, | |
| 871 | defaultEncoding); | |
| 872 | ||
| 873 | /* clean up */ | |
| 874 | #ifdef OLDCODE | |
| 875 | if (endpointName) CFRelease(endpointName); | |
| 876 | if (deviceName) CFRelease(deviceName); | |
| 877 | #endif | |
| 878 | if (fullName) CFRelease(fullName); | |
| 879 | ||
| 880 | return newName; | |
| 881 | } | |
| 882 | ||
| 883 | ||
| 884 | ||
| 885 | pm_fns_node pm_macosx_in_dictionary = { | |
| 886 | none_write_short, | |
| 887 | none_sysex, | |
| 888 | none_sysex, | |
| 889 | none_write_byte, | |
| 890 | none_write_short, | |
| 891 | none_write_flush, | |
| 892 | none_synchronize, | |
| 893 | midi_in_open, | |
| 894 | midi_abort, | |
| 895 | midi_in_close, | |
| 896 | success_poll, | |
| 897 | midi_has_host_error, | |
| 898 | midi_get_host_error, | |
| 899 | }; | |
| 900 | ||
| 901 | pm_fns_node pm_macosx_out_dictionary = { | |
| 902 | midi_write_short, | |
| 903 | midi_begin_sysex, | |
| 904 | midi_end_sysex, | |
| 905 | midi_write_byte, | |
| 906 | midi_write_realtime, | |
| 907 | midi_write_flush, | |
| 908 | midi_synchronize, | |
| 909 | midi_out_open, | |
| 910 | midi_abort, | |
| 911 | midi_out_close, | |
| 912 | success_poll, | |
| 913 | midi_has_host_error, | |
| 914 | midi_get_host_error, | |
| 915 | }; | |
| 916 | ||
| 917 | ||
| 918 | PmError pm_macosxcm_init(void) | |
| 919 | { | |
| 920 | ItemCount numInputs, numOutputs, numDevices; | |
| 921 | MIDIEndpointRef endpoint; | |
| 922 | int i; | |
| 923 | OSStatus macHostError; | |
| 924 | char *error_text; | |
| 925 | ||
| 926 | /* Determine the number of MIDI devices on the system */ | |
| 927 | numDevices = MIDIGetNumberOfDevices(); | |
| 928 | numInputs = MIDIGetNumberOfSources(); | |
| 929 | numOutputs = MIDIGetNumberOfDestinations(); | |
| 930 | ||
| 931 | /* Return prematurely if no devices exist on the system | |
| 932 | Note that this is not an error. There may be no devices. | |
| 933 | Pm_CountDevices() will return zero, which is correct and | |
| 934 | useful information | |
| 935 | */ | |
| 936 | if (numDevices <= 0) { | |
| 937 | return pmNoError; | |
| 938 | } | |
| 939 | ||
| 940 | ||
| 941 | /* Initialize the client handle */ | |
| 942 | macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client); | |
| 943 | if (macHostError != noErr) { | |
| 944 | error_text = (char *)"MIDIClientCreate() in pm_macosxcm_init()"; | |
| 945 | goto error_return; | |
| 946 | } | |
| 947 | ||
| 948 | /* Create the input port */ | |
| 949 | macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc, | |
| 950 | NULL, &portIn); | |
| 951 | if (macHostError != noErr) { | |
| 952 | error_text = (char *)"MIDIInputPortCreate() in pm_macosxcm_init()"; | |
| 953 | goto error_return; | |
| 954 | } | |
| 955 | ||
| 956 | /* Create the output port */ | |
| 957 | macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut); | |
| 958 | if (macHostError != noErr) { | |
| 959 | error_text = (char *)"MIDIOutputPortCreate() in pm_macosxcm_init()"; | |
| 960 | goto error_return; | |
| 961 | } | |
| 962 | ||
| 963 | /* Iterate over the MIDI input devices */ | |
| 964 | for (i = 0; i < numInputs; i++) { | |
| 965 | endpoint = MIDIGetSource(i); | |
| 966 | if (endpoint == NULL_REF) { | |
| 967 | continue; | |
| 968 | } | |
| 969 | ||
| 970 | /* set the first input we see to the default */ | |
| 971 | if (pm_default_input_device_id == -1) | |
| 972 | pm_default_input_device_id = pm_descriptor_index; | |
| 973 | ||
| 974 | /* Register this device with PortMidi */ | |
| 975 | pm_add_device((char *)"CoreMIDI", cm_get_full_endpoint_name(endpoint), | |
| 976 | TRUE, (void *) (long) endpoint, &pm_macosx_in_dictionary); | |
| 977 | } | |
| 978 | ||
| 979 | /* Iterate over the MIDI output devices */ | |
| 980 | for (i = 0; i < numOutputs; i++) { | |
| 981 | endpoint = MIDIGetDestination(i); | |
| 982 | if (endpoint == NULL_REF) { | |
| 983 | continue; | |
| 984 | } | |
| 985 | ||
| 986 | /* set the first output we see to the default */ | |
| 987 | if (pm_default_output_device_id == -1) | |
| 988 | pm_default_output_device_id = pm_descriptor_index; | |
| 989 | ||
| 990 | /* Register this device with PortMidi */ | |
| 991 | pm_add_device((char *)"CoreMIDI", cm_get_full_endpoint_name(endpoint), | |
| 992 | FALSE, (void *) (long) endpoint, | |
| 993 | &pm_macosx_out_dictionary); | |
| 994 | } | |
| 995 | return pmNoError; | |
| 996 | ||
| 997 | error_return: | |
| 998 | pm_hosterror = macHostError; | |
| 999 | sprintf(pm_hosterror_text, "Host error %ld: %s\n", (long) macHostError, | |
| 1000 | error_text); | |
| 1001 | pm_macosxcm_term(); /* clear out any opened ports */ | |
| 1002 | return pmHostError; | |
| 1003 | } | |
| 1004 | ||
| 1005 | void pm_macosxcm_term(void) | |
| 1006 | { | |
| 1007 | if (client != NULL_REF) MIDIClientDispose(client); | |
| 1008 | if (portIn != NULL_REF) MIDIPortDispose(portIn); | |
| 1009 | if (portOut != NULL_REF) MIDIPortDispose(portOut); | |
| 1010 | } |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* porttime.h -- portable interface to millisecond timer */ | |
| 2 | ||
| 3 | /* CHANGE LOG FOR PORTTIME | |
| 4 | 10-Jun-03 Mark Nelson & RBD | |
| 5 | boost priority of timer thread in ptlinux.c implementation | |
| 6 | */ | |
| 7 | ||
| 8 | /* Should there be a way to choose the source of time here? */ | |
| 9 | ||
| 10 | #ifdef WIN32 | |
| 11 | #ifndef INT32_DEFINED | |
| 12 | // rather than having users install a special .h file for windows, | |
| 13 | // just put the required definitions inline here. portmidi.h uses | |
| 14 | // these too, so the definitions are (unfortunately) duplicated there | |
| 15 | typedef int int32_t; | |
| 16 | typedef unsigned int uint32_t; | |
| 17 | #define INT32_DEFINED | |
| 18 | #endif | |
| 19 | #else | |
| 20 | #include <stdint.h> // needed for int32_t | |
| 21 | #endif | |
| 22 | ||
| 23 | #ifdef __cplusplus | |
| 24 | extern "C" { | |
| 25 | #endif | |
| 26 | ||
| 27 | #ifndef PMEXPORT | |
| 28 | #define PMEXPORT | |
| 29 | #endif | |
| 30 | ||
| 31 | typedef enum { | |
| 32 | ptNoError = 0, /* success */ | |
| 33 | ptHostError = -10000, /* a system-specific error occurred */ | |
| 34 | ptAlreadyStarted, /* cannot start timer because it is already started */ | |
| 35 | ptAlreadyStopped, /* cannot stop timer because it is already stopped */ | |
| 36 | ptInsufficientMemory /* memory could not be allocated */ | |
| 37 | } PtError; | |
| 38 | ||
| 39 | ||
| 40 | typedef int32_t PtTimestamp; | |
| 41 | ||
| 42 | typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); | |
| 43 | ||
| 44 | /* | |
| 45 | Pt_Start() starts a real-time service. | |
| 46 | ||
| 47 | resolution is the timer resolution in ms. The time will advance every | |
| 48 | resolution ms. | |
| 49 | ||
| 50 | callback is a function pointer to be called every resolution ms. | |
| 51 | ||
| 52 | userData is passed to callback as a parameter. | |
| 53 | ||
| 54 | return value: | |
| 55 | Upon success, returns ptNoError. See PtError for other values. | |
| 56 | */ | |
| 57 | PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData); | |
| 58 | ||
| 59 | /* | |
| 60 | Pt_Stop() stops the timer. | |
| 61 | ||
| 62 | return value: | |
| 63 | Upon success, returns ptNoError. See PtError for other values. | |
| 64 | */ | |
| 65 | PMEXPORT PtError Pt_Stop(void); | |
| 66 | ||
| 67 | /* | |
| 68 | Pt_Started() returns true iff the timer is running. | |
| 69 | */ | |
| 70 | PMEXPORT int Pt_Started(void); | |
| 71 | ||
| 72 | /* | |
| 73 | Pt_Time() returns the current time in ms. | |
| 74 | */ | |
| 75 | PMEXPORT PtTimestamp Pt_Time(void); | |
| 76 | ||
| 77 | /* | |
| 78 | Pt_Sleep() pauses, allowing other threads to run. | |
| 79 | ||
| 80 | duration is the length of the pause in ms. The true duration | |
| 81 | of the pause may be rounded to the nearest or next clock tick | |
| 82 | as determined by resolution in Pt_Start(). | |
| 83 | */ | |
| 84 | PMEXPORT void Pt_Sleep(int32_t duration); | |
| 85 | ||
| 86 | #ifdef __cplusplus | |
| 87 | } | |
| 88 | #endif |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmutil.c -- some helpful utilities for building midi | |
| 2 | applications that use PortMidi | |
| 3 | */ | |
| 4 | #include <stdlib.h> | |
| 5 | #include <assert.h> | |
| 6 | #include <string.h> | |
| 7 | #include "portmidi.h" | |
| 8 | #include "pmutil.h" | |
| 9 | #include "pminternal.h" | |
| 10 | ||
| 11 | #ifdef WIN32 | |
| 12 | #define bzero(addr, siz) memset(addr, 0, siz) | |
| 13 | #endif | |
| 14 | ||
| 15 | // #define QUEUE_DEBUG 1 | |
| 16 | #ifdef QUEUE_DEBUG | |
| 17 | #include "stdio.h" | |
| 18 | #endif | |
| 19 | ||
| 20 | typedef struct { | |
| 21 | long head; | |
| 22 | long tail; | |
| 23 | long len; | |
| 24 | long overflow; | |
| 25 | int32_t msg_size; /* number of int32_t in a message including extra word */ | |
| 26 | int32_t peek_overflow; | |
| 27 | int32_t *buffer; | |
| 28 | int32_t *peek; | |
| 29 | int32_t peek_flag; | |
| 30 | } PmQueueRep; | |
| 31 | ||
| 32 | ||
| 33 | PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg) | |
| 34 | { | |
| 35 | int32_t int32s_per_msg = | |
| 36 | (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) & | |
| 37 | ~(sizeof(int32_t) - 1)) / sizeof(int32_t)); | |
| 38 | PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); | |
| 39 | if (!queue) /* memory allocation failed */ | |
| 40 | return NULL; | |
| 41 | ||
| 42 | /* need extra word per message for non-zero encoding */ | |
| 43 | queue->len = num_msgs * (int32s_per_msg + 1); | |
| 44 | queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t)); | |
| 45 | bzero(queue->buffer, queue->len * sizeof(int32_t)); | |
| 46 | if (!queue->buffer) { | |
| 47 | pm_free(queue); | |
| 48 | return NULL; | |
| 49 | } else { /* allocate the "peek" buffer */ | |
| 50 | queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t)); | |
| 51 | if (!queue->peek) { | |
| 52 | /* free everything allocated so far and return */ | |
| 53 | pm_free(queue->buffer); | |
| 54 | pm_free(queue); | |
| 55 | return NULL; | |
| 56 | } | |
| 57 | } | |
| 58 | bzero(queue->buffer, queue->len * sizeof(int32_t)); | |
| 59 | queue->head = 0; | |
| 60 | queue->tail = 0; | |
| 61 | /* msg_size is in words */ | |
| 62 | queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */ | |
| 63 | queue->overflow = FALSE; | |
| 64 | queue->peek_overflow = FALSE; | |
| 65 | queue->peek_flag = FALSE; | |
| 66 | return queue; | |
| 67 | } | |
| 68 | ||
| 69 | ||
| 70 | PMEXPORT PmError Pm_QueueDestroy(PmQueue *q) | |
| 71 | { | |
| 72 | PmQueueRep *queue = (PmQueueRep *) q; | |
| 73 | ||
| 74 | /* arg checking */ | |
| 75 | if (!queue || !queue->buffer || !queue->peek) | |
| 76 | return pmBadPtr; | |
| 77 | ||
| 78 | pm_free(queue->peek); | |
| 79 | pm_free(queue->buffer); | |
| 80 | pm_free(queue); | |
| 81 | return pmNoError; | |
| 82 | } | |
| 83 | ||
| 84 | ||
| 85 | PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg) | |
| 86 | { | |
| 87 | long head; | |
| 88 | PmQueueRep *queue = (PmQueueRep *) q; | |
| 89 | int i; | |
| 90 | int32_t *msg_as_int32 = (int32_t *) msg; | |
| 91 | ||
| 92 | /* arg checking */ | |
| 93 | if (!queue) | |
| 94 | return pmBadPtr; | |
| 95 | /* a previous peek operation encountered an overflow, but the overflow | |
| 96 | * has not yet been reported to client, so do it now. No message is | |
| 97 | * returned, but on the next call, we will return the peek buffer. | |
| 98 | */ | |
| 99 | if (queue->peek_overflow) { | |
| 100 | queue->peek_overflow = FALSE; | |
| 101 | return pmBufferOverflow; | |
| 102 | } | |
| 103 | if (queue->peek_flag) { | |
| 104 | memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t)); | |
| 105 | queue->peek_flag = FALSE; | |
| 106 | return pmGotData; | |
| 107 | } | |
| 108 | ||
| 109 | head = queue->head; | |
| 110 | /* if writer overflows, it writes queue->overflow = tail+1 so that | |
| 111 | * when the reader gets to that position in the buffer, it can | |
| 112 | * return the overflow condition to the reader. The problem is that | |
| 113 | * at overflow, things have wrapped around, so tail == head, and the | |
| 114 | * reader will detect overflow immediately instead of waiting until | |
| 115 | * it reads everything in the buffer, wrapping around again to the | |
| 116 | * point where tail == head. So the condition also checks that | |
| 117 | * queue->buffer[head] is zero -- if so, then the buffer is now | |
| 118 | * empty, and we're at the point in the msg stream where overflow | |
| 119 | * occurred. It's time to signal overflow to the reader. If | |
| 120 | * queue->buffer[head] is non-zero, there's a message there and we | |
| 121 | * should read all the way around the buffer before signalling overflow. | |
| 122 | * There is a write-order dependency here, but to fail, the overflow | |
| 123 | * field would have to be written while an entire buffer full of | |
| 124 | * writes are still pending. I'm assuming out-of-order writes are | |
| 125 | * possible, but not that many. | |
| 126 | */ | |
| 127 | if (queue->overflow == head + 1 && !queue->buffer[head]) { | |
| 128 | queue->overflow = 0; /* non-overflow condition */ | |
| 129 | return pmBufferOverflow; | |
| 130 | } | |
| 131 | ||
| 132 | /* test to see if there is data in the queue -- test from back | |
| 133 | * to front so if writer is simultaneously writing, we don't | |
| 134 | * waste time discovering the write is not finished | |
| 135 | */ | |
| 136 | for (i = queue->msg_size - 1; i >= 0; i--) { | |
| 137 | if (!queue->buffer[head + i]) { | |
| 138 | return pmNoData; | |
| 139 | } | |
| 140 | } | |
| 141 | memcpy(msg, (char *) &queue->buffer[head + 1], | |
| 142 | sizeof(int32_t) * (queue->msg_size - 1)); | |
| 143 | /* fix up zeros */ | |
| 144 | i = queue->buffer[head]; | |
| 145 | while (i < queue->msg_size) { | |
| 146 | int32_t j; | |
| 147 | i--; /* msg does not have extra word so shift down */ | |
| 148 | j = msg_as_int32[i]; | |
| 149 | msg_as_int32[i] = 0; | |
| 150 | i = j; | |
| 151 | } | |
| 152 | /* signal that data has been removed by zeroing: */ | |
| 153 | bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size); | |
| 154 | ||
| 155 | /* update head */ | |
| 156 | head += queue->msg_size; | |
| 157 | if (head == queue->len) head = 0; | |
| 158 | queue->head = head; | |
| 159 | return pmGotData; /* success */ | |
| 160 | } | |
| 161 | ||
| 162 | ||
| 163 | ||
| 164 | PMEXPORT PmError Pm_SetOverflow(PmQueue *q) | |
| 165 | { | |
| 166 | PmQueueRep *queue = (PmQueueRep *) q; | |
| 167 | long tail; | |
| 168 | /* arg checking */ | |
| 169 | if (!queue) | |
| 170 | return pmBadPtr; | |
| 171 | /* no more enqueue until receiver acknowledges overflow */ | |
| 172 | if (queue->overflow) return pmBufferOverflow; | |
| 173 | tail = queue->tail; | |
| 174 | queue->overflow = tail + 1; | |
| 175 | return pmBufferOverflow; | |
| 176 | } | |
| 177 | ||
| 178 | ||
| 179 | PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg) | |
| 180 | { | |
| 181 | PmQueueRep *queue = (PmQueueRep *) q; | |
| 182 | long tail; | |
| 183 | int i; | |
| 184 | int32_t *src = (int32_t *) msg; | |
| 185 | int32_t *ptr; | |
| 186 | int32_t *dest; | |
| 187 | int rslt; | |
| 188 | if (!queue) | |
| 189 | return pmBadPtr; | |
| 190 | /* no more enqueue until receiver acknowledges overflow */ | |
| 191 | if (queue->overflow) return pmBufferOverflow; | |
| 192 | rslt = Pm_QueueFull(q); | |
| 193 | /* already checked above: if (rslt == pmBadPtr) return rslt; */ | |
| 194 | tail = queue->tail; | |
| 195 | if (rslt) { | |
| 196 | queue->overflow = tail + 1; | |
| 197 | return pmBufferOverflow; | |
| 198 | } | |
| 199 | ||
| 200 | /* queue is has room for message, and overflow flag is cleared */ | |
| 201 | ptr = &queue->buffer[tail]; | |
| 202 | dest = ptr + 1; | |
| 203 | for (i = 1; i < queue->msg_size; i++) { | |
| 204 | int32_t j = src[i - 1]; | |
| 205 | if (!j) { | |
| 206 | *ptr = i; | |
| 207 | ptr = dest; | |
| 208 | } else { | |
| 209 | *dest = j; | |
| 210 | } | |
| 211 | dest++; | |
| 212 | } | |
| 213 | *ptr = i; | |
| 214 | tail += queue->msg_size; | |
| 215 | if (tail == queue->len) tail = 0; | |
| 216 | queue->tail = tail; | |
| 217 | return pmNoError; | |
| 218 | } | |
| 219 | ||
| 220 | ||
| 221 | PMEXPORT int Pm_QueueEmpty(PmQueue *q) | |
| 222 | { | |
| 223 | PmQueueRep *queue = (PmQueueRep *) q; | |
| 224 | return (!queue) || /* null pointer -> return "empty" */ | |
| 225 | (queue->buffer[queue->head] == 0 && !queue->peek_flag); | |
| 226 | } | |
| 227 | ||
| 228 | ||
| 229 | PMEXPORT int Pm_QueueFull(PmQueue *q) | |
| 230 | { | |
| 231 | long tail; | |
| 232 | int i; | |
| 233 | PmQueueRep *queue = (PmQueueRep *) q; | |
| 234 | /* arg checking */ | |
| 235 | if (!queue) | |
| 236 | return pmBadPtr; | |
| 237 | tail = queue->tail; | |
| 238 | /* test to see if there is space in the queue */ | |
| 239 | for (i = 0; i < queue->msg_size; i++) { | |
| 240 | if (queue->buffer[tail + i]) { | |
| 241 | return TRUE; | |
| 242 | } | |
| 243 | } | |
| 244 | return FALSE; | |
| 245 | } | |
| 246 | ||
| 247 | ||
| 248 | PMEXPORT void *Pm_QueuePeek(PmQueue *q) | |
| 249 | { | |
| 250 | PmError rslt; | |
| 251 | int32_t temp; | |
| 252 | PmQueueRep *queue = (PmQueueRep *) q; | |
| 253 | /* arg checking */ | |
| 254 | if (!queue) | |
| 255 | return NULL; | |
| 256 | ||
| 257 | if (queue->peek_flag) { | |
| 258 | return queue->peek; | |
| 259 | } | |
| 260 | /* this is ugly: if peek_overflow is set, then Pm_Dequeue() | |
| 261 | * returns immediately with pmBufferOverflow, but here, we | |
| 262 | * want Pm_Dequeue() to really check for data. If data is | |
| 263 | * there, we can return it | |
| 264 | */ | |
| 265 | temp = queue->peek_overflow; | |
| 266 | queue->peek_overflow = FALSE; | |
| 267 | rslt = Pm_Dequeue(q, queue->peek); | |
| 268 | queue->peek_overflow = temp; | |
| 269 | ||
| 270 | if (rslt == 1) { | |
| 271 | queue->peek_flag = TRUE; | |
| 272 | return queue->peek; | |
| 273 | } else if (rslt == pmBufferOverflow) { | |
| 274 | /* when overflow is indicated, the queue is empty and the | |
| 275 | * first message that was dropped by Enqueue (signalling | |
| 276 | * pmBufferOverflow to its caller) would have been the next | |
| 277 | * message in the queue. Pm_QueuePeek will return NULL, but | |
| 278 | * remember that an overflow occurred. (see Pm_Dequeue) | |
| 279 | */ | |
| 280 | queue->peek_overflow = TRUE; | |
| 281 | } | |
| 282 | return NULL; | |
| 283 | } | |
| 284 |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* | |
| 2 | osxsupport.h - Cocoa glue to emulated deprecated old Carbon path finder functions | |
| 3 | */ | |
| 4 | ||
| 5 | #ifndef _OSXSUPPORT_H_ | |
| 6 | #define _OSXSUPPORT_H_ | |
| 7 | ||
| 8 | #ifdef __cplusplus | |
| 9 | extern "C" { | |
| 10 | #endif | |
| 11 | ||
| 12 | char *FindPrefsDir(void); | |
| 13 | ||
| 14 | #ifdef __cplusplus | |
| 15 | } | |
| 16 | #endif | |
| 17 | ||
| 18 | #endif | |
| 19 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* system-specific definitions */ | |
| 2 | ||
| 3 | PmError pm_macosxcm_init(void); | |
| 4 | void pm_macosxcm_term(void); | |
| 5 | ||
| 6 | PmDeviceID find_default_device(char *path, int input, PmDeviceID id); |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pminternal.h -- header for interface implementations */ | |
| 2 | ||
| 3 | /* this file is included by files that implement library internals */ | |
| 4 | /* Here is a guide to implementers: | |
| 5 | provide an initialization function similar to pm_winmm_init() | |
| 6 | add your initialization function to pm_init() | |
| 7 | Note that your init function should never require not-standard | |
| 8 | libraries or fail in any way. If the interface is not available, | |
| 9 | simply do not call pm_add_device. This means that non-standard | |
| 10 | libraries should try to do dynamic linking at runtime using a DLL | |
| 11 | and return without error if the DLL cannot be found or if there | |
| 12 | is any other failure. | |
| 13 | implement functions as indicated in pm_fns_type to open, read, write, | |
| 14 | close, etc. | |
| 15 | call pm_add_device() for each input and output device, passing it a | |
| 16 | pm_fns_type structure. | |
| 17 | assumptions about pm_fns_type functions are given below. | |
| 18 | */ | |
| 19 | ||
| 20 | #ifdef __cplusplus | |
| 21 | extern "C" { | |
| 22 | #endif | |
| 23 | ||
| 24 | extern int pm_initialized; /* see note in portmidi.c */ | |
| 25 | ||
| 26 | /* these are defined in system-specific file */ | |
| 27 | void *pm_alloc(size_t s); | |
| 28 | void pm_free(void *ptr); | |
| 29 | ||
| 30 | /* if an error occurs while opening or closing a midi stream, set these: */ | |
| 31 | extern int pm_hosterror; | |
| 32 | extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; | |
| 33 | ||
| 34 | struct pm_internal_struct; | |
| 35 | ||
| 36 | /* these do not use PmInternal because it is not defined yet... */ | |
| 37 | typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, | |
| 38 | PmEvent *buffer); | |
| 39 | typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi, | |
| 40 | PmTimestamp timestamp); | |
| 41 | typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi, | |
| 42 | PmTimestamp timestamp); | |
| 43 | typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi, | |
| 44 | unsigned char byte, PmTimestamp timestamp); | |
| 45 | typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi, | |
| 46 | PmEvent *buffer); | |
| 47 | typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi, | |
| 48 | PmTimestamp timestamp); | |
| 49 | typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi); | |
| 50 | /* pm_open_fn should clean up all memory and close the device if any part | |
| 51 | of the open fails */ | |
| 52 | typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi, | |
| 53 | void *driverInfo); | |
| 54 | typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi); | |
| 55 | /* pm_close_fn should clean up all memory and close the device if any | |
| 56 | part of the close fails. */ | |
| 57 | typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi); | |
| 58 | typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi); | |
| 59 | typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg, | |
| 60 | unsigned int len); | |
| 61 | typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi); | |
| 62 | ||
| 63 | typedef struct { | |
| 64 | pm_write_short_fn write_short; /* output short MIDI msg */ | |
| 65 | pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */ | |
| 66 | pm_end_sysex_fn end_sysex; /* marks end of sysex message */ | |
| 67 | pm_write_byte_fn write_byte; /* accumulate one more sysex byte */ | |
| 68 | pm_write_realtime_fn write_realtime; /* send real-time message within sysex */ | |
| 69 | pm_write_flush_fn write_flush; /* send any accumulated but unsent data */ | |
| 70 | pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */ | |
| 71 | pm_open_fn open; /* open MIDI device */ | |
| 72 | pm_abort_fn abort; /* abort */ | |
| 73 | pm_close_fn close; /* close device */ | |
| 74 | pm_poll_fn poll; /* read pending midi events into portmidi buffer */ | |
| 75 | pm_has_host_error_fn has_host_error; /* true when device has had host | |
| 76 | error message */ | |
| 77 | pm_host_error_fn host_error; /* provide text readable host error message | |
| 78 | for device (clears and resets) */ | |
| 79 | } pm_fns_node, *pm_fns_type; | |
| 80 | ||
| 81 | ||
| 82 | /* when open fails, the dictionary gets this set of functions: */ | |
| 83 | extern pm_fns_node pm_none_dictionary; | |
| 84 | ||
| 85 | typedef struct { | |
| 86 | PmDeviceInfo pub; /* some portmidi state also saved in here (for autmatic | |
| 87 | device closing (see PmDeviceInfo struct) */ | |
| 88 | void *descriptor; /* ID number passed to win32 multimedia API open */ | |
| 89 | void *internalDescriptor; /* points to PmInternal device, allows automatic | |
| 90 | device closing */ | |
| 91 | pm_fns_type dictionary; | |
| 92 | } descriptor_node, *descriptor_type; | |
| 93 | ||
| 94 | extern int pm_descriptor_max; | |
| 95 | extern descriptor_type descriptors; | |
| 96 | extern int pm_descriptor_index; | |
| 97 | ||
| 98 | typedef uint32_t (*time_get_proc_type)(void *time_info); | |
| 99 | ||
| 100 | typedef struct pm_internal_struct { | |
| 101 | int device_id; /* which device is open (index to descriptors) */ | |
| 102 | short write_flag; /* MIDI_IN, or MIDI_OUT */ | |
| 103 | ||
| 104 | PmTimeProcPtr time_proc; /* where to get the time */ | |
| 105 | void *time_info; /* pass this to get_time() */ | |
| 106 | int32_t buffer_len; /* how big is the buffer or queue? */ | |
| 107 | PmQueue *queue; | |
| 108 | ||
| 109 | int32_t latency; /* time delay in ms between timestamps and actual output */ | |
| 110 | /* set to zero to get immediate, simple blocking output */ | |
| 111 | /* if latency is zero, timestamps will be ignored; */ | |
| 112 | /* if midi input device, this field ignored */ | |
| 113 | ||
| 114 | int sysex_in_progress; /* when sysex status is seen, this flag becomes | |
| 115 | * true until EOX is seen. When true, new data is appended to the | |
| 116 | * stream of outgoing bytes. When overflow occurs, sysex data is | |
| 117 | * dropped (until an EOX or non-real-timei status byte is seen) so | |
| 118 | * that, if the overflow condition is cleared, we don't start | |
| 119 | * sending data from the middle of a sysex message. If a sysex | |
| 120 | * message is filtered, sysex_in_progress is false, causing the | |
| 121 | * message to be dropped. */ | |
| 122 | PmMessage sysex_message; /* buffer for 4 bytes of sysex data */ | |
| 123 | int sysex_message_count; /* how many bytes in sysex_message so far */ | |
| 124 | ||
| 125 | int32_t filters; /* flags that filter incoming message classes */ | |
| 126 | int32_t channel_mask; /* filter incoming messages based on channel */ | |
| 127 | PmTimestamp last_msg_time; /* timestamp of last message */ | |
| 128 | PmTimestamp sync_time; /* time of last synchronization */ | |
| 129 | PmTimestamp now; /* set by PmWrite to current time */ | |
| 130 | int first_message; /* initially true, used to run first synchronization */ | |
| 131 | pm_fns_type dictionary; /* implementation functions */ | |
| 132 | void *descriptor; /* system-dependent state */ | |
| 133 | /* the following are used to expedite sysex data */ | |
| 134 | /* on windows, in debug mode, based on some profiling, these optimizations | |
| 135 | * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte, | |
| 136 | * but this does not count time in the driver, so I don't know if it is | |
| 137 | * important | |
| 138 | */ | |
| 139 | unsigned char *fill_base; /* addr of ptr to sysex data */ | |
| 140 | uint32_t *fill_offset_ptr; /* offset of next sysex byte */ | |
| 141 | int32_t fill_length; /* how many sysex bytes to write */ | |
| 142 | } PmInternal; | |
| 143 | ||
| 144 | ||
| 145 | /* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */ | |
| 146 | void pm_init(void); | |
| 147 | void pm_term(void); | |
| 148 | ||
| 149 | /* defined by portMidi, used by pmwinmm */ | |
| 150 | PmError none_write_short(PmInternal *midi, PmEvent *buffer); | |
| 151 | PmError none_write_byte(PmInternal *midi, unsigned char byte, | |
| 152 | PmTimestamp timestamp); | |
| 153 | PmTimestamp none_synchronize(PmInternal *midi); | |
| 154 | ||
| 155 | PmError pm_fail_fn(PmInternal *midi); | |
| 156 | PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); | |
| 157 | PmError pm_success_fn(PmInternal *midi); | |
| 158 | PmError pm_add_device(char *interf, char *name, int input, void *descriptor, | |
| 159 | pm_fns_type dictionary); | |
| 160 | uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, | |
| 161 | PmTimestamp timestamp); | |
| 162 | void pm_read_short(PmInternal *midi, PmEvent *event); | |
| 163 | ||
| 164 | #define none_write_flush pm_fail_timestamp_fn | |
| 165 | #define none_sysex pm_fail_timestamp_fn | |
| 166 | #define none_poll pm_fail_fn | |
| 167 | #define success_poll pm_success_fn | |
| 168 | ||
| 169 | #define MIDI_REALTIME_MASK 0xf8 | |
| 170 | #define is_real_time(msg) \ | |
| 171 | ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK) | |
| 172 | ||
| 173 | int pm_find_default_device(char *pattern, int is_input); | |
| 174 | ||
| 175 | #ifdef __cplusplus | |
| 176 | } | |
| 177 | #endif | |
| 178 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmutil.h -- some helpful utilities for building midi | |
| 2 | applications that use PortMidi | |
| 3 | */ | |
| 4 | ||
| 5 | #ifdef __cplusplus | |
| 6 | extern "C" { | |
| 7 | #endif /* __cplusplus */ | |
| 8 | ||
| 9 | typedef void PmQueue; | |
| 10 | ||
| 11 | /* | |
| 12 | A single-reader, single-writer queue is created by | |
| 13 | Pm_QueueCreate(), which takes the number of messages and | |
| 14 | the message size as parameters. The queue only accepts | |
| 15 | fixed sized messages. Returns NULL if memory cannot be allocated. | |
| 16 | ||
| 17 | This queue implementation uses the "light pipe" algorithm which | |
| 18 | operates correctly even with multi-processors and out-of-order | |
| 19 | memory writes. (see Alexander Dokumentov, "Lock-free Interprocess | |
| 20 | Communication," Dr. Dobbs Portal, http://www.ddj.com/, | |
| 21 | articleID=189401457, June 15, 2006. This algorithm requires | |
| 22 | that messages be translated to a form where no words contain | |
| 23 | zeros. Each word becomes its own "data valid" tag. Because of | |
| 24 | this translation, we cannot return a pointer to data still in | |
| 25 | the queue when the "peek" method is called. Instead, a buffer | |
| 26 | is preallocated so that data can be copied there. Pm_QueuePeek() | |
| 27 | dequeues a message into this buffer and returns a pointer to | |
| 28 | it. A subsequent Pm_Dequeue() will copy from this buffer. | |
| 29 | ||
| 30 | This implementation does not try to keep reader/writer data in | |
| 31 | separate cache lines or prevent thrashing on cache lines. | |
| 32 | However, this algorithm differs by doing inserts/removals in | |
| 33 | units of messages rather than units of machine words. Some | |
| 34 | performance improvement might be obtained by not clearing data | |
| 35 | immediately after a read, but instead by waiting for the end | |
| 36 | of the cache line, especially if messages are smaller than | |
| 37 | cache lines. See the Dokumentov article for explanation. | |
| 38 | ||
| 39 | The algorithm is extended to handle "overflow" reporting. To report | |
| 40 | an overflow, the sender writes the current tail position to a field. | |
| 41 | The receiver must acknowlege receipt by zeroing the field. The sender | |
| 42 | will not send more until the field is zeroed. | |
| 43 | ||
| 44 | Pm_QueueDestroy() destroys the queue and frees its storage. | |
| 45 | */ | |
| 46 | ||
| 47 | PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); | |
| 48 | PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); | |
| 49 | ||
| 50 | /* | |
| 51 | Pm_Dequeue() removes one item from the queue, copying it into msg. | |
| 52 | Returns 1 if successful, and 0 if the queue is empty. | |
| 53 | Returns pmBufferOverflow if what would have been the next thing | |
| 54 | in the queue was dropped due to overflow. (So when overflow occurs, | |
| 55 | the receiver can receive a queue full of messages before getting the | |
| 56 | overflow report. This protocol ensures that the reader will be | |
| 57 | notified when data is lost due to overflow. | |
| 58 | */ | |
| 59 | PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg); | |
| 60 | ||
| 61 | ||
| 62 | /* | |
| 63 | Pm_Enqueue() inserts one item into the queue, copying it from msg. | |
| 64 | Returns pmNoError if successful and pmBufferOverflow if the queue was | |
| 65 | already full. If pmBufferOverflow is returned, the overflow flag is set. | |
| 66 | */ | |
| 67 | PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); | |
| 68 | ||
| 69 | ||
| 70 | /* | |
| 71 | Pm_QueueFull() returns non-zero if the queue is full | |
| 72 | Pm_QueueEmpty() returns non-zero if the queue is empty | |
| 73 | ||
| 74 | Either condition may change immediately because a parallel | |
| 75 | enqueue or dequeue operation could be in progress. Furthermore, | |
| 76 | Pm_QueueEmpty() is optimistic: it may say false, when due to | |
| 77 | out-of-order writes, the full message has not arrived. Therefore, | |
| 78 | Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns | |
| 79 | false. On the other hand, Pm_QueueFull() is pessimistic: if it | |
| 80 | returns false, then Pm_Enqueue() is guaranteed to succeed. | |
| 81 | ||
| 82 | Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. | |
| 83 | Pm_QueueEmpty() returns FALSE if queue is NULL. | |
| 84 | */ | |
| 85 | PMEXPORT int Pm_QueueFull(PmQueue *queue); | |
| 86 | PMEXPORT int Pm_QueueEmpty(PmQueue *queue); | |
| 87 | ||
| 88 | ||
| 89 | /* | |
| 90 | Pm_QueuePeek() returns a pointer to the item at the head of the queue, | |
| 91 | or NULL if the queue is empty. The item is not removed from the queue. | |
| 92 | Pm_QueuePeek() will not indicate when an overflow occurs. If you want | |
| 93 | to get and check pmBufferOverflow messages, use the return value of | |
| 94 | Pm_QueuePeek() *only* as an indication that you should call | |
| 95 | Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would | |
| 96 | return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally | |
| 97 | clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume | |
| 98 | enqueuing messages. A subsequent call to Pm_QueuePeek() | |
| 99 | will return a pointer to the first message *after* the overflow. | |
| 100 | Using this as an indication to call Pm_Dequeue(), the first call | |
| 101 | to Pm_Dequeue() will return pmBufferOverflow. The second call will | |
| 102 | return success, copying the same message pointed to by the previous | |
| 103 | Pm_QueuePeek(). | |
| 104 | ||
| 105 | When to use Pm_QueuePeek(): (1) when you need to look at the message | |
| 106 | data to decide who should be called to receive it. (2) when you need | |
| 107 | to know a message is ready but cannot accept the message. | |
| 108 | ||
| 109 | Note that Pm_QueuePeek() is not a fast check, so if possible, you | |
| 110 | might as well just call Pm_Dequeue() and accept the data if it is there. | |
| 111 | */ | |
| 112 | PMEXPORT void *Pm_QueuePeek(PmQueue *queue); | |
| 113 | ||
| 114 | /* | |
| 115 | Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow | |
| 116 | condition to the reader (dequeuer). E.g. when transfering data from | |
| 117 | the OS to an application, if the OS indicates a buffer overrun, | |
| 118 | Pm_SetOverflow() can be used to insure that the reader receives a | |
| 119 | pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue | |
| 120 | is NULL, returns pmBufferOverflow if buffer is already in an overflow | |
| 121 | state, returns pmNoError if successfully set overflow state. | |
| 122 | */ | |
| 123 | PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); | |
| 124 | ||
| 125 | #ifdef __cplusplus | |
| 126 | } | |
| 127 | #endif /* __cplusplus */ |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* | |
| 2 | osxsupport.m - Cocoa glue to emulated deprecated old Carbon path finder functions | |
| 3 | */ | |
| 4 | ||
| 5 | #import <Cocoa/Cocoa.h> | |
| 6 | #import <AvailabilityMacros.h> | |
| 7 | #include "osxsupport.h" | |
| 8 | ||
| 9 | // convert an NSString to a C string | |
| 10 | static char *StringToChar(NSString *str) | |
| 11 | { | |
| 12 | const char *charstr = [str UTF8String]; | |
| 13 | char *resstr = (char *)malloc(strlen(charstr)+1); | |
| 14 | ||
| 15 | strcpy(resstr, charstr); | |
| 16 | return resstr; | |
| 17 | } | |
| 18 | ||
| 19 | char *FindPrefsDir(void) | |
| 20 | { | |
| 21 | char *resstr = NULL; | |
| 22 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSPreferencePanesDirectory, NSUserDomainMask, YES); | |
| 23 | ||
| 24 | if ([paths count] > 0) | |
| 25 | { | |
| 26 | resstr = StringToChar([paths objectAtIndex:0]) ; | |
| 27 | } | |
| 28 | ||
| 29 | return resstr; | |
| 30 | } |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmwinmm.c -- system specific definitions */ | |
| 2 | ||
| 3 | #ifdef _MSC_VER | |
| 4 | #pragma warning(disable: 4133) // stop warnings about implicit typecasts | |
| 5 | #endif | |
| 6 | ||
| 7 | #ifndef _WIN32_WINNT | |
| 8 | /* without this define, InitializeCriticalSectionAndSpinCount is | |
| 9 | * undefined. This version level means "Windows 2000 and higher" | |
| 10 | */ | |
| 11 | #define _WIN32_WINNT 0x0500 | |
| 12 | #endif | |
| 13 | #undef UNICODE | |
| 14 | #include "windows.h" | |
| 15 | #include "mmsystem.h" | |
| 16 | #include "portmidi.h" | |
| 17 | #include "pmutil.h" | |
| 18 | #include "pminternal.h" | |
| 19 | #include "pmwinmm.h" | |
| 20 | #include <string.h> | |
| 21 | #include "porttime.h" | |
| 22 | #include "osdcomm.h" | |
| 23 | ||
| 24 | #ifdef PTR64 | |
| 25 | typedef UINT64 FPTR; | |
| 26 | #else | |
| 27 | typedef UINT32 FPTR; | |
| 28 | #endif | |
| 29 | ||
| 30 | /* asserts used to verify portMidi code logic is sound; later may want | |
| 31 | something more graceful */ | |
| 32 | #include <assert.h> | |
| 33 | #ifdef DEBUG | |
| 34 | /* this printf stuff really important for debugging client app w/host errors. | |
| 35 | probably want to do something else besides read/write from/to console | |
| 36 | for portability, however */ | |
| 37 | #define STRING_MAX 80 | |
| 38 | #include "stdio.h" | |
| 39 | #endif | |
| 40 | ||
| 41 | #define streql(x, y) (strcmp(x, y) == 0) | |
| 42 | ||
| 43 | #define MIDI_SYSEX 0xf0 | |
| 44 | #define MIDI_EOX 0xf7 | |
| 45 | ||
| 46 | /* callback routines */ | |
| 47 | static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn, | |
| 48 | WORD wMsg, DWORD dwInstance, | |
| 49 | DWORD dwParam1, DWORD dwParam2); | |
| 50 | static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, | |
| 51 | DWORD dwInstance, DWORD dwParam1, | |
| 52 | DWORD dwParam2); | |
| 53 | #ifdef USE_SYSEX_BUFFERS | |
| 54 | static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, | |
| 55 | DWORD dwInstance, DWORD dwParam1, | |
| 56 | DWORD dwParam2); | |
| 57 | #endif | |
| 58 | ||
| 59 | extern pm_fns_node pm_winmm_in_dictionary; | |
| 60 | extern pm_fns_node pm_winmm_out_dictionary; | |
| 61 | ||
| 62 | static void winmm_out_delete(PmInternal *midi); /* forward reference */ | |
| 63 | ||
| 64 | /* | |
| 65 | A note about buffers: WinMM seems to hold onto buffers longer than | |
| 66 | one would expect, e.g. when I tried using 2 small buffers to send | |
| 67 | long sysex messages, at some point WinMM held both buffers. This problem | |
| 68 | was fixed by making buffers bigger. Therefore, it seems that there should | |
| 69 | be enough buffer space to hold a whole sysex message. | |
| 70 | ||
| 71 | The bufferSize passed into Pm_OpenInput (passed into here as buffer_len) | |
| 72 | will be used to estimate the largest sysex message (= buffer_len * 4 bytes). | |
| 73 | Call that the max_sysex_len = buffer_len * 4. | |
| 74 | ||
| 75 | For simple midi output (latency == 0), allocate 3 buffers, each with half | |
| 76 | the size of max_sysex_len, but each at least 256 bytes. | |
| 77 | ||
| 78 | For stream output, there will already be enough space in very short | |
| 79 | buffers, so use them, but make sure there are at least 16. | |
| 80 | ||
| 81 | For input, use many small buffers rather than 2 large ones so that when | |
| 82 | there are short sysex messages arriving frequently (as in control surfaces) | |
| 83 | there will be more free buffers to fill. Use max_sysex_len / 64 buffers, | |
| 84 | but at least 16, of size 64 bytes each. | |
| 85 | ||
| 86 | The following constants help to represent these design parameters: | |
| 87 | */ | |
| 88 | #define NUM_SIMPLE_SYSEX_BUFFERS 3 | |
| 89 | #define MIN_SIMPLE_SYSEX_LEN 256 | |
| 90 | ||
| 91 | #define MIN_STREAM_BUFFERS 16 | |
| 92 | #define STREAM_BUFFER_LEN 24 | |
| 93 | ||
| 94 | #define INPUT_SYSEX_LEN 64 | |
| 95 | #define MIN_INPUT_BUFFERS 16 | |
| 96 | ||
| 97 | /* if we run out of space for output (assume this is due to a sysex msg, | |
| 98 | expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN | |
| 99 | */ | |
| 100 | #define NUM_EXPANSION_BUFFERS 128 | |
| 101 | #define EXPANSION_BUFFER_LEN 1024 | |
| 102 | ||
| 103 | /* A sysex buffer has 3 DWORDS as a header plus the actual message size */ | |
| 104 | #define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3) | |
| 105 | /* A MIDIHDR with a sysex message is the buffer length plus the header size */ | |
| 106 | #define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) | |
| 107 | #ifdef USE_SYSEX_BUFFERS | |
| 108 | /* Size of a MIDIHDR with a buffer contaning multiple MIDIEVENT structures */ | |
| 109 | #define MIDIHDR_SIZE(x) ((x) + sizeof(MIDIHDR)) | |
| 110 | #endif | |
| 111 | ||
| 112 | /* | |
| 113 | ============================================================================== | |
| 114 | win32 mmedia system specific structure passed to midi callbacks | |
| 115 | ============================================================================== | |
| 116 | */ | |
| 117 | ||
| 118 | /* global winmm device info */ | |
| 119 | MIDIINCAPS *midi_in_caps = NULL; | |
| 120 | MIDIINCAPS midi_in_mapper_caps; | |
| 121 | UINT midi_num_inputs = 0; | |
| 122 | MIDIOUTCAPS *midi_out_caps = NULL; | |
| 123 | MIDIOUTCAPS midi_out_mapper_caps; | |
| 124 | UINT midi_num_outputs = 0; | |
| 125 | ||
| 126 | /* per device info */ | |
| 127 | typedef struct midiwinmm_struct { | |
| 128 | union { | |
| 129 | HMIDISTRM stream; /* windows handle for stream */ | |
| 130 | HMIDIOUT out; /* windows handle for out calls */ | |
| 131 | HMIDIIN in; /* windows handle for in calls */ | |
| 132 | } handle; | |
| 133 | ||
| 134 | /* midi output messages are sent in these buffers, which are allocated | |
| 135 | * in a round-robin fashion, using next_buffer as an index | |
| 136 | */ | |
| 137 | LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */ | |
| 138 | int max_buffers; /* length of buffers array */ | |
| 139 | int buffers_expanded; /* buffers array expanded for extra msgs? */ | |
| 140 | int num_buffers; /* how many buffers allocated in buffers array */ | |
| 141 | int next_buffer; /* index of next buffer to send */ | |
| 142 | HANDLE buffer_signal; /* used to wait for buffer to become free */ | |
| 143 | #ifdef USE_SYSEX_BUFFERS | |
| 144 | /* sysex buffers will be allocated only when | |
| 145 | * a sysex message is sent. The size of the buffer is fixed. | |
| 146 | */ | |
| 147 | LPMIDIHDR sysex_buffers[NUM_SYSEX_BUFFERS]; /* pool of buffers for sysex data */ | |
| 148 | int next_sysex_buffer; /* index of next sysexbuffer to send */ | |
| 149 | #endif | |
| 150 | unsigned long last_time; /* last output time */ | |
| 151 | int first_message; /* flag: treat first message differently */ | |
| 152 | int sysex_mode; /* middle of sending sysex */ | |
| 153 | unsigned long sysex_word; /* accumulate data when receiving sysex */ | |
| 154 | unsigned int sysex_byte_count; /* count how many received */ | |
| 155 | LPMIDIHDR hdr; /* the message accumulating sysex to send */ | |
| 156 | unsigned long sync_time; /* when did we last determine delta? */ | |
| 157 | long delta; /* difference between stream time and | |
| 158 | real time */ | |
| 159 | int error; /* host error from doing port midi call */ | |
| 160 | CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */ | |
| 161 | } midiwinmm_node, *midiwinmm_type; | |
| 162 | ||
| 163 | ||
| 164 | /* | |
| 165 | ============================================================================= | |
| 166 | general MIDI device queries | |
| 167 | ============================================================================= | |
| 168 | */ | |
| 169 | static void pm_winmm_general_inputs(void) | |
| 170 | { | |
| 171 | UINT i; | |
| 172 | WORD wRtn; | |
| 173 | midi_num_inputs = midiInGetNumDevs(); | |
| 174 | midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) * | |
| 175 | midi_num_inputs); | |
| 176 | if (midi_in_caps == NULL) { | |
| 177 | /* if you can't open a particular system-level midi interface | |
| 178 | * (such as winmm), we just consider that system or API to be | |
| 179 | * unavailable and move on without reporting an error. | |
| 180 | */ | |
| 181 | return; | |
| 182 | } | |
| 183 | ||
| 184 | for (i = 0; i < midi_num_inputs; i++) { | |
| 185 | wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i], | |
| 186 | sizeof(MIDIINCAPS)); | |
| 187 | if (wRtn == MMSYSERR_NOERROR) { | |
| 188 | /* ignore errors here -- if pm_descriptor_max is exceeded, some | |
| 189 | devices will not be accessible. */ | |
| 190 | pm_add_device((char *)"MMSystem", midi_in_caps[i].szPname, TRUE, | |
| 191 | (void *)(FPTR)i, | |
| 192 | &pm_winmm_in_dictionary); | |
| 193 | } | |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | ||
| 198 | static void pm_winmm_mapper_input(void) | |
| 199 | { | |
| 200 | WORD wRtn; | |
| 201 | /* Note: if MIDIMAPPER opened as input (documentation implies you | |
| 202 | can, but current system fails to retrieve input mapper | |
| 203 | capabilities) then you still should retrieve some formof | |
| 204 | setup info. */ | |
| 205 | wRtn = midiInGetDevCaps((UINT) MIDIMAPPER, | |
| 206 | (LPMIDIINCAPS) & midi_in_mapper_caps, | |
| 207 | sizeof(MIDIINCAPS)); | |
| 208 | if (wRtn == MMSYSERR_NOERROR) { | |
| 209 | pm_add_device((char *)"MMSystem", midi_in_mapper_caps.szPname, TRUE, | |
| 210 | (void *) MIDIMAPPER, &pm_winmm_in_dictionary); | |
| 211 | } | |
| 212 | } | |
| 213 | ||
| 214 | ||
| 215 | static void pm_winmm_general_outputs(void) | |
| 216 | { | |
| 217 | UINT i; | |
| 218 | DWORD wRtn; | |
| 219 | midi_num_outputs = midiOutGetNumDevs(); | |
| 220 | midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs ); | |
| 221 | ||
| 222 | if (midi_out_caps == NULL) { | |
| 223 | /* no error is reported -- see pm_winmm_general_inputs */ | |
| 224 | return ; | |
| 225 | } | |
| 226 | ||
| 227 | for (i = 0; i < midi_num_outputs; i++) { | |
| 228 | wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i], | |
| 229 | sizeof(MIDIOUTCAPS)); | |
| 230 | if (wRtn == MMSYSERR_NOERROR) { | |
| 231 | pm_add_device((char *)"MMSystem", midi_out_caps[i].szPname, FALSE, | |
| 232 | (void *)(FPTR)i, | |
| 233 | &pm_winmm_out_dictionary); | |
| 234 | } | |
| 235 | } | |
| 236 | } | |
| 237 | ||
| 238 | ||
| 239 | static void pm_winmm_mapper_output(void) | |
| 240 | { | |
| 241 | WORD wRtn; | |
| 242 | /* Note: if MIDIMAPPER opened as output (pseudo MIDI device | |
| 243 | maps device independent messages into device dependant ones, | |
| 244 | via NT midimapper program) you still should get some setup info */ | |
| 245 | wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS) | |
| 246 | & midi_out_mapper_caps, sizeof(MIDIOUTCAPS)); | |
| 247 | if (wRtn == MMSYSERR_NOERROR) { | |
| 248 | pm_add_device((char *)"MMSystem", midi_out_mapper_caps.szPname, FALSE, | |
| 249 | (void *) MIDIMAPPER, &pm_winmm_out_dictionary); | |
| 250 | } | |
| 251 | } | |
| 252 | ||
| 253 | ||
| 254 | /* | |
| 255 | ========================================================================================= | |
| 256 | host error handling | |
| 257 | ========================================================================================= | |
| 258 | */ | |
| 259 | static unsigned int winmm_has_host_error(PmInternal * midi) | |
| 260 | { | |
| 261 | midiwinmm_type m = (midiwinmm_type)midi->descriptor; | |
| 262 | return m->error; | |
| 263 | } | |
| 264 | ||
| 265 | ||
| 266 | /* str_copy_len -- like strcat, but won't overrun the destination string */ | |
| 267 | /* | |
| 268 | * returns length of resulting string | |
| 269 | */ | |
| 270 | static int str_copy_len(char *dst, char *src, int len) | |
| 271 | { | |
| 272 | strncpy(dst, src, len); | |
| 273 | /* just in case suffex is greater then len, terminate with zero */ | |
| 274 | dst[len - 1] = 0; | |
| 275 | return strlen(dst); | |
| 276 | } | |
| 277 | ||
| 278 | ||
| 279 | static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) | |
| 280 | { | |
| 281 | /* precondition: midi != NULL */ | |
| 282 | midiwinmm_node * m = (midiwinmm_node *) midi->descriptor; | |
| 283 | char *hdr1 = (char *)"Host error: "; | |
| 284 | //char *hdr2 = (char *)"Host callback error: "; | |
| 285 | ||
| 286 | msg[0] = 0; /* initialize result string to empty */ | |
| 287 | ||
| 288 | if (descriptors[midi->device_id].pub.input) { | |
| 289 | /* input and output use different winmm API calls */ | |
| 290 | if (m) { /* make sure there is an open device to examine */ | |
| 291 | if (m->error != MMSYSERR_NOERROR) { | |
| 292 | int n = str_copy_len(msg, hdr1, len); | |
| 293 | /* read and record host error */ | |
| 294 | midiInGetErrorText(m->error, msg + n, len - n); | |
| 295 | //assert(err == MMSYSERR_NOERROR); | |
| 296 | m->error = MMSYSERR_NOERROR; | |
| 297 | } | |
| 298 | } | |
| 299 | } else { /* output port */ | |
| 300 | if (m) { | |
| 301 | if (m->error != MMSYSERR_NOERROR) { | |
| 302 | int n = str_copy_len(msg, hdr1, len); | |
| 303 | midiOutGetErrorText(m->error, msg + n, len - n); | |
| 304 | //assert(err == MMSYSERR_NOERROR); | |
| 305 | m->error = MMSYSERR_NOERROR; | |
| 306 | } | |
| 307 | } | |
| 308 | } | |
| 309 | } | |
| 310 | ||
| 311 | ||
| 312 | /* | |
| 313 | ============================================================================= | |
| 314 | buffer handling | |
| 315 | ============================================================================= | |
| 316 | */ | |
| 317 | static MIDIHDR *allocate_buffer(long data_size) | |
| 318 | { | |
| 319 | LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); | |
| 320 | MIDIEVENT *evt; | |
| 321 | if (!hdr) return NULL; | |
| 322 | evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ | |
| 323 | hdr->lpData = (LPSTR) evt; | |
| 324 | hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); | |
| 325 | hdr->dwBytesRecorded = 0; | |
| 326 | hdr->dwFlags = 0; | |
| 327 | hdr->dwUser = hdr->dwBufferLength; | |
| 328 | return hdr; | |
| 329 | } | |
| 330 | ||
| 331 | #ifdef USE_SYSEX_BUFFERS | |
| 332 | static MIDIHDR *allocate_sysex_buffer(long data_size) | |
| 333 | { | |
| 334 | /* we're actually allocating more than data_size because the buffer | |
| 335 | * will include the MIDIEVENT header in addition to the data | |
| 336 | */ | |
| 337 | LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); | |
| 338 | MIDIEVENT *evt; | |
| 339 | if (!hdr) return NULL; | |
| 340 | evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ | |
| 341 | hdr->lpData = (LPSTR) evt; | |
| 342 | hdr->dwFlags = 0; | |
| 343 | hdr->dwUser = 0; | |
| 344 | return hdr; | |
| 345 | } | |
| 346 | #endif | |
| 347 | ||
| 348 | static PmError allocate_buffers(midiwinmm_type m, long data_size, long count) | |
| 349 | { | |
| 350 | int i; | |
| 351 | /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ | |
| 352 | m->num_buffers = 0; /* in case no memory can be allocated */ | |
| 353 | m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); | |
| 354 | if (!m->buffers) return pmInsufficientMemory; | |
| 355 | m->max_buffers = count; | |
| 356 | for (i = 0; i < count; i++) { | |
| 357 | LPMIDIHDR hdr = allocate_buffer(data_size); | |
| 358 | if (!hdr) { /* free everything allocated so far and return */ | |
| 359 | for (i = i - 1; i >= 0; i--) pm_free(m->buffers[i]); | |
| 360 | pm_free(m->buffers); | |
| 361 | m->max_buffers = 0; | |
| 362 | return pmInsufficientMemory; | |
| 363 | } | |
| 364 | m->buffers[i] = hdr; /* this may be NULL if allocation fails */ | |
| 365 | } | |
| 366 | m->num_buffers = count; | |
| 367 | return pmNoError; | |
| 368 | } | |
| 369 | ||
| 370 | #ifdef USE_SYSEX_BUFFERS | |
| 371 | static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size) | |
| 372 | { | |
| 373 | PmError rslt = pmNoError; | |
| 374 | /* sysex_buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ | |
| 375 | int i; | |
| 376 | for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { | |
| 377 | LPMIDIHDR hdr = allocate_sysex_buffer(data_size); | |
| 378 | ||
| 379 | if (!hdr) rslt = pmInsufficientMemory; | |
| 380 | m->sysex_buffers[i] = hdr; /* this may be NULL if allocation fails */ | |
| 381 | hdr->dwFlags = 0; /* mark as free */ | |
| 382 | } | |
| 383 | return rslt; | |
| 384 | } | |
| 385 | #endif | |
| 386 | ||
| 387 | #ifdef USE_SYSEX_BUFFERS | |
| 388 | static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi) | |
| 389 | { | |
| 390 | LPMIDIHDR r = NULL; | |
| 391 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 392 | if (!m->sysex_buffers[0]) { | |
| 393 | if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER)) { | |
| 394 | return NULL; | |
| 395 | } | |
| 396 | } | |
| 397 | /* busy wait until we find a free buffer */ | |
| 398 | while (TRUE) { | |
| 399 | int i; | |
| 400 | for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { | |
| 401 | /* cycle through buffers, modulo NUM_SYSEX_BUFFERS */ | |
| 402 | m->next_sysex_buffer++; | |
| 403 | if (m->next_sysex_buffer >= NUM_SYSEX_BUFFERS) m->next_sysex_buffer = 0; | |
| 404 | r = m->sysex_buffers[m->next_sysex_buffer]; | |
| 405 | if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer; | |
| 406 | } | |
| 407 | /* after scanning every buffer and not finding anything, block */ | |
| 408 | if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { | |
| 409 | #ifdef DEBUG | |
| 410 | printf("PortMidi warning: get_free_sysex_buffer() wait timed out after 1000ms\n"); | |
| 411 | #endif | |
| 412 | } | |
| 413 | } | |
| 414 | found_sysex_buffer: | |
| 415 | r->dwBytesRecorded = 0; | |
| 416 | r->dwBufferLength = 0; /* changed to correct value later */ | |
| 417 | return r; | |
| 418 | } | |
| 419 | #endif | |
| 420 | ||
| 421 | static LPMIDIHDR get_free_output_buffer(PmInternal *midi) | |
| 422 | { | |
| 423 | LPMIDIHDR r = NULL; | |
| 424 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 425 | while (TRUE) { | |
| 426 | int i; | |
| 427 | for (i = 0; i < m->num_buffers; i++) { | |
| 428 | /* cycle through buffers, modulo m->num_buffers */ | |
| 429 | m->next_buffer++; | |
| 430 | if (m->next_buffer >= m->num_buffers) m->next_buffer = 0; | |
| 431 | r = m->buffers[m->next_buffer]; | |
| 432 | if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer; | |
| 433 | } | |
| 434 | /* after scanning every buffer and not finding anything, block */ | |
| 435 | if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { | |
| 436 | #ifdef DEBUG | |
| 437 | printf("PortMidi warning: get_free_output_buffer() wait timed out after 1000ms\n"); | |
| 438 | #endif | |
| 439 | /* if we're trying to send a sysex message, maybe the | |
| 440 | * message is too big and we need more message buffers. | |
| 441 | * Expand the buffer pool by 128KB using 1024-byte buffers. | |
| 442 | */ | |
| 443 | /* first, expand the buffers array if necessary */ | |
| 444 | if (!m->buffers_expanded) { | |
| 445 | LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc( | |
| 446 | (m->num_buffers + NUM_EXPANSION_BUFFERS) * | |
| 447 | sizeof(LPMIDIHDR)); | |
| 448 | /* if no memory, we could return a no-memory error, but user | |
| 449 | * probably will be unprepared to deal with it. Maybe the | |
| 450 | * MIDI driver is temporarily hung so we should just wait. | |
| 451 | * I don't know the right answer, but waiting is easier. | |
| 452 | */ | |
| 453 | if (!new_buffers) continue; | |
| 454 | /* copy buffers to new_buffers and replace buffers */ | |
| 455 | memcpy(new_buffers, m->buffers, | |
| 456 | m->num_buffers * sizeof(LPMIDIHDR)); | |
| 457 | pm_free(m->buffers); | |
| 458 | m->buffers = new_buffers; | |
| 459 | m->max_buffers = m->num_buffers + NUM_EXPANSION_BUFFERS; | |
| 460 | m->buffers_expanded = TRUE; | |
| 461 | } | |
| 462 | /* next, add one buffer and return it */ | |
| 463 | if (m->num_buffers < m->max_buffers) { | |
| 464 | r = allocate_buffer(EXPANSION_BUFFER_LEN); | |
| 465 | /* again, if there's no memory, we may not really be | |
| 466 | * dead -- maybe the system is temporarily hung and | |
| 467 | * we can just wait longer for a message buffer */ | |
| 468 | if (!r) continue; | |
| 469 | m->buffers[m->num_buffers++] = r; | |
| 470 | goto found_buffer; /* break out of 2 loops */ | |
| 471 | } | |
| 472 | /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers, | |
| 473 | * and we have no free buffers to send. We'll just keep | |
| 474 | * polling to see if any buffers show up. | |
| 475 | */ | |
| 476 | } | |
| 477 | } | |
| 478 | found_buffer: | |
| 479 | r->dwBytesRecorded = 0; | |
| 480 | /* actual buffer length is saved in dwUser field */ | |
| 481 | r->dwBufferLength = (DWORD) r->dwUser; | |
| 482 | return r; | |
| 483 | } | |
| 484 | ||
| 485 | #ifdef EXPANDING_SYSEX_BUFFERS | |
| 486 | note: this is not working code, but might be useful if you want | |
| 487 | to grow sysex buffers. | |
| 488 | static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size) | |
| 489 | { | |
| 490 | LPMIDIHDR big; | |
| 491 | int i; | |
| 492 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 493 | /* buffer must be smaller than 64k, but be also a multiple of 4 */ | |
| 494 | if (new_size > 65520) { | |
| 495 | if (old_size >= 65520) | |
| 496 | return pmBufferMaxSize; | |
| 497 | else | |
| 498 | new_size = 65520; | |
| 499 | } | |
| 500 | /* allocate a bigger message */ | |
| 501 | big = allocate_sysex_buffer(new_size); | |
| 502 | /* printf("expand to %d bytes\n", new_size);*/ | |
| 503 | if (!big) return pmInsufficientMemory; | |
| 504 | m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR)); | |
| 505 | if (m->error) { | |
| 506 | pm_free(big); | |
| 507 | return pmHostError; | |
| 508 | } | |
| 509 | /* make sure we're not going to overwrite any memory */ | |
| 510 | assert(old_size <= new_size); | |
| 511 | memcpy(big->lpData, m->hdr->lpData, old_size); | |
| 512 | /* keep track of how many sysex bytes are in message so far */ | |
| 513 | big->dwBytesRecorded = m->hdr->dwBytesRecorded; | |
| 514 | big->dwBufferLength = new_size; | |
| 515 | /* find which buffer this was, and replace it */ | |
| 516 | for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { | |
| 517 | if (m->sysex_buffers[i] == m->hdr) { | |
| 518 | m->sysex_buffers[i] = big; | |
| 519 | m->sysex_buffer_size[i] = new_size; | |
| 520 | pm_free(m->hdr); | |
| 521 | m->hdr = big; | |
| 522 | break; | |
| 523 | } | |
| 524 | } | |
| 525 | assert(i != NUM_SYSEX_BUFFERS); | |
| 526 | ||
| 527 | return pmNoError; | |
| 528 | } | |
| 529 | #endif | |
| 530 | ||
| 531 | /* | |
| 532 | ========================================================================================= | |
| 533 | begin midi input implementation | |
| 534 | ========================================================================================= | |
| 535 | */ | |
| 536 | ||
| 537 | ||
| 538 | static PmError allocate_input_buffer(HMIDIIN h, long buffer_len) | |
| 539 | { | |
| 540 | LPMIDIHDR hdr = allocate_buffer(buffer_len); | |
| 541 | if (!hdr) return pmInsufficientMemory; | |
| 542 | pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR)); | |
| 543 | if (pm_hosterror) { | |
| 544 | pm_free(hdr); | |
| 545 | return pm_hosterror; | |
| 546 | } | |
| 547 | pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR)); | |
| 548 | return pm_hosterror; | |
| 549 | } | |
| 550 | ||
| 551 | ||
| 552 | static PmError winmm_in_open(PmInternal *midi, void *driverInfo) | |
| 553 | { | |
| 554 | DWORD dwDevice; | |
| 555 | int i = midi->device_id; | |
| 556 | int max_sysex_len = midi->buffer_len * 4; | |
| 557 | int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN; | |
| 558 | midiwinmm_type m; | |
| 559 | ||
| 560 | dwDevice = (DWORD)(FPTR)descriptors[i].descriptor; | |
| 561 | ||
| 562 | /* create system dependent device data */ | |
| 563 | m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ | |
| 564 | midi->descriptor = m; | |
| 565 | if (!m) goto no_memory; | |
| 566 | m->handle.in = NULL; | |
| 567 | m->buffers = NULL; /* not used for input */ | |
| 568 | m->num_buffers = 0; /* not used for input */ | |
| 569 | m->max_buffers = FALSE; /* not used for input */ | |
| 570 | m->buffers_expanded = 0; /* not used for input */ | |
| 571 | m->next_buffer = 0; /* not used for input */ | |
| 572 | m->buffer_signal = 0; /* not used for input */ | |
| 573 | #ifdef USE_SYSEX_BUFFERS | |
| 574 | for (i = 0; i < NUM_SYSEX_BUFFERS; i++) | |
| 575 | m->sysex_buffers[i] = NULL; /* not used for input */ | |
| 576 | m->next_sysex_buffer = 0; /* not used for input */ | |
| 577 | #endif | |
| 578 | m->last_time = 0; | |
| 579 | m->first_message = TRUE; /* not used for input */ | |
| 580 | m->sysex_mode = FALSE; | |
| 581 | m->sysex_word = 0; | |
| 582 | m->sysex_byte_count = 0; | |
| 583 | m->hdr = NULL; /* not used for input */ | |
| 584 | m->sync_time = 0; | |
| 585 | m->delta = 0; | |
| 586 | m->error = MMSYSERR_NOERROR; | |
| 587 | /* 4000 is based on Windows documentation -- that's the value used in the | |
| 588 | memory manager. It's small enough that it should not hurt performance even | |
| 589 | if it's not optimal. | |
| 590 | */ | |
| 591 | InitializeCriticalSectionAndSpinCount(&m->lock, 4000); | |
| 592 | /* open device */ | |
| 593 | pm_hosterror = midiInOpen( | |
| 594 | &(m->handle.in), /* input device handle */ | |
| 595 | dwDevice, /* device ID */ | |
| 596 | (DWORD_PTR) winmm_in_callback, /* callback address */ | |
| 597 | (DWORD_PTR) midi, /* callback instance data */ | |
| 598 | CALLBACK_FUNCTION); /* callback is a procedure */ | |
| 599 | if (pm_hosterror) goto free_descriptor; | |
| 600 | ||
| 601 | if (num_input_buffers < MIN_INPUT_BUFFERS) | |
| 602 | num_input_buffers = MIN_INPUT_BUFFERS; | |
| 603 | for (i = 0; i < num_input_buffers; i++) { | |
| 604 | if (allocate_input_buffer(m->handle.in, INPUT_SYSEX_LEN)) { | |
| 605 | /* either pm_hosterror was set, or the proper return code | |
| 606 | is pmInsufficientMemory */ | |
| 607 | goto close_device; | |
| 608 | } | |
| 609 | } | |
| 610 | /* start device */ | |
| 611 | pm_hosterror = midiInStart(m->handle.in); | |
| 612 | if (pm_hosterror) goto reset_device; | |
| 613 | return pmNoError; | |
| 614 | ||
| 615 | /* undo steps leading up to the detected error */ | |
| 616 | reset_device: | |
| 617 | /* ignore return code (we already have an error to report) */ | |
| 618 | midiInReset(m->handle.in); | |
| 619 | close_device: | |
| 620 | midiInClose(m->handle.in); /* ignore return code */ | |
| 621 | free_descriptor: | |
| 622 | midi->descriptor = NULL; | |
| 623 | pm_free(m); | |
| 624 | no_memory: | |
| 625 | if (pm_hosterror) { | |
| 626 | midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, | |
| 627 | PM_HOST_ERROR_MSG_LEN); | |
| 628 | //assert(err == MMSYSERR_NOERROR); | |
| 629 | return pmHostError; | |
| 630 | } | |
| 631 | /* if !pm_hosterror, then the error must be pmInsufficientMemory */ | |
| 632 | return pmInsufficientMemory; | |
| 633 | /* note: if we return an error code, the device will be | |
| 634 | closed and memory will be freed. It's up to the caller | |
| 635 | to free the parameter midi */ | |
| 636 | } | |
| 637 | ||
| 638 | static PmError winmm_in_poll(PmInternal *midi) { | |
| 639 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 640 | return m->error; | |
| 641 | } | |
| 642 | ||
| 643 | ||
| 644 | ||
| 645 | /* winmm_in_close -- close an open midi input device */ | |
| 646 | /* | |
| 647 | * assume midi is non-null (checked by caller) | |
| 648 | */ | |
| 649 | static PmError winmm_in_close(PmInternal *midi) | |
| 650 | { | |
| 651 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 652 | if (!m) return pmBadPtr; | |
| 653 | /* device to close */ | |
| 654 | if ((pm_hosterror = midiInStop(m->handle.in))) { | |
| 655 | midiInReset(m->handle.in); /* try to reset and close port */ | |
| 656 | midiInClose(m->handle.in); | |
| 657 | } else if ((pm_hosterror = midiInReset(m->handle.in))) { | |
| 658 | midiInClose(m->handle.in); /* best effort to close midi port */ | |
| 659 | } else { | |
| 660 | pm_hosterror = midiInClose(m->handle.in); | |
| 661 | } | |
| 662 | midi->descriptor = NULL; | |
| 663 | DeleteCriticalSection(&m->lock); | |
| 664 | pm_free(m); /* delete */ | |
| 665 | if (pm_hosterror) { | |
| 666 | midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, | |
| 667 | PM_HOST_ERROR_MSG_LEN); | |
| 668 | //assert(err == MMSYSERR_NOERROR); | |
| 669 | return pmHostError; | |
| 670 | } | |
| 671 | return pmNoError; | |
| 672 | } | |
| 673 | ||
| 674 | ||
| 675 | /* Callback function executed via midiInput SW interrupt (via midiInOpen). */ | |
| 676 | static void FAR PASCAL winmm_in_callback( | |
| 677 | HMIDIIN hMidiIn, /* midiInput device Handle */ | |
| 678 | WORD wMsg, /* midi msg */ | |
| 679 | DWORD dwInstance, /* application data */ | |
| 680 | DWORD dwParam1, /* MIDI data */ | |
| 681 | DWORD dwParam2) /* device timestamp (wrt most recent midiInStart) */ | |
| 682 | { | |
| 683 | //static int entry = 0; | |
| 684 | PmInternal *midi = (PmInternal *)(FPTR) dwInstance; | |
| 685 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 686 | ||
| 687 | /* NOTE: we do not just EnterCriticalSection() here because an | |
| 688 | * MIM_CLOSE message arrives when the port is closed, but then | |
| 689 | * the m->lock has been destroyed. | |
| 690 | */ | |
| 691 | ||
| 692 | switch (wMsg) { | |
| 693 | case MIM_DATA: { | |
| 694 | /* if this callback is reentered with data, we're in trouble. | |
| 695 | * It's hard to imagine that Microsoft would allow callbacks | |
| 696 | * to be reentrant -- isn't the model that this is like a | |
| 697 | * hardware interrupt? -- but I've seen reentrant behavior | |
| 698 | * using a debugger, so it happens. | |
| 699 | */ | |
| 700 | //long new_driver_time; | |
| 701 | EnterCriticalSection(&m->lock); | |
| 702 | ||
| 703 | /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of | |
| 704 | message LOB; | |
| 705 | dwParam2 is time message received by input device driver, specified | |
| 706 | in [ms] from when midiInStart called. | |
| 707 | each message is expanded to include the status byte */ | |
| 708 | ||
| 709 | //new_driver_time = dwParam2; | |
| 710 | ||
| 711 | if ((dwParam1 & 0x80) == 0) { | |
| 712 | /* not a status byte -- ignore it. This happened running the | |
| 713 | sysex.c test under Win2K with MidiMan USB 1x1 interface, | |
| 714 | but I can't reproduce it. -RBD | |
| 715 | */ | |
| 716 | /* printf("non-status byte found\n"); */ | |
| 717 | } else { /* data to process */ | |
| 718 | PmEvent event; | |
| 719 | if (midi->time_proc) | |
| 720 | dwParam2 = (*midi->time_proc)(midi->time_info); | |
| 721 | event.timestamp = dwParam2; | |
| 722 | event.message = dwParam1; | |
| 723 | pm_read_short(midi, &event); | |
| 724 | } | |
| 725 | LeaveCriticalSection(&m->lock); | |
| 726 | break; | |
| 727 | } | |
| 728 | case MIM_LONGDATA: { | |
| 729 | MIDIHDR *lpMidiHdr = (MIDIHDR *)(FPTR)dwParam1; | |
| 730 | unsigned char *data = (unsigned char *) lpMidiHdr->lpData; | |
| 731 | unsigned int processed = 0; | |
| 732 | int remaining = lpMidiHdr->dwBytesRecorded; | |
| 733 | ||
| 734 | EnterCriticalSection(&m->lock); | |
| 735 | /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", | |
| 736 | lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */ | |
| 737 | if (midi->time_proc) | |
| 738 | dwParam2 = (*midi->time_proc)(midi->time_info); | |
| 739 | /* can there be more than one message in one buffer? */ | |
| 740 | /* assume yes and iterate through them */ | |
| 741 | while (remaining > 0) { | |
| 742 | unsigned int amt = pm_read_bytes(midi, data + processed, | |
| 743 | remaining, dwParam2); | |
| 744 | remaining -= amt; | |
| 745 | processed += amt; | |
| 746 | } | |
| 747 | ||
| 748 | /* when a device is closed, the pending MIM_LONGDATA buffers are | |
| 749 | returned to this callback with dwBytesRecorded == 0. In this | |
| 750 | case, we do not want to send them back to the interface (if | |
| 751 | we do, the interface will not close, and Windows OS may hang). */ | |
| 752 | if (lpMidiHdr->dwBytesRecorded > 0) { | |
| 753 | //MMRESULT rslt; | |
| 754 | lpMidiHdr->dwBytesRecorded = 0; | |
| 755 | lpMidiHdr->dwFlags = 0; | |
| 756 | ||
| 757 | /* note: no error checking -- can this actually fail? */ | |
| 758 | midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); | |
| 759 | //assert(rslt == MMSYSERR_NOERROR); | |
| 760 | /* note: I don't think this can fail except possibly for | |
| 761 | * MMSYSERR_NOMEM, but the pain of reporting this | |
| 762 | * unlikely but probably catastrophic error does not seem | |
| 763 | * worth it. | |
| 764 | */ | |
| 765 | midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); | |
| 766 | //assert(rslt == MMSYSERR_NOERROR); | |
| 767 | LeaveCriticalSection(&m->lock); | |
| 768 | } else { | |
| 769 | midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR)); | |
| 770 | LeaveCriticalSection(&m->lock); | |
| 771 | pm_free(lpMidiHdr); | |
| 772 | } | |
| 773 | break; | |
| 774 | } | |
| 775 | case MIM_OPEN: | |
| 776 | break; | |
| 777 | case MIM_CLOSE: | |
| 778 | break; | |
| 779 | case MIM_ERROR: | |
| 780 | /* printf("MIM_ERROR\n"); */ | |
| 781 | break; | |
| 782 | case MIM_LONGERROR: | |
| 783 | /* printf("MIM_LONGERROR\n"); */ | |
| 784 | break; | |
| 785 | default: | |
| 786 | break; | |
| 787 | } | |
| 788 | } | |
| 789 | ||
| 790 | /* | |
| 791 | ========================================================================================= | |
| 792 | begin midi output implementation | |
| 793 | ========================================================================================= | |
| 794 | */ | |
| 795 | ||
| 796 | /* begin helper routines used by midiOutStream interface */ | |
| 797 | ||
| 798 | /* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */ | |
| 799 | static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, | |
| 800 | unsigned long delta, unsigned long msg) | |
| 801 | { | |
| 802 | unsigned long *ptr = (unsigned long *) | |
| 803 | (hdr->lpData + hdr->dwBytesRecorded); | |
| 804 | *ptr++ = delta; /* dwDeltaTime */ | |
| 805 | *ptr++ = 0; /* dwStream */ | |
| 806 | *ptr++ = msg; /* dwEvent */ | |
| 807 | hdr->dwBytesRecorded += 3 * sizeof(long); | |
| 808 | /* if the addition of three more words (a message) would extend beyond | |
| 809 | the buffer length, then return TRUE (full) | |
| 810 | */ | |
| 811 | return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength; | |
| 812 | } | |
| 813 | ||
| 814 | ||
| 815 | static PmTimestamp pm_time_get(midiwinmm_type m) | |
| 816 | { | |
| 817 | MMTIME mmtime; | |
| 818 | //MMRESULT wRtn; | |
| 819 | mmtime.wType = TIME_TICKS; | |
| 820 | mmtime.u.ticks = 0; | |
| 821 | midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime)); | |
| 822 | //assert(wRtn == MMSYSERR_NOERROR); | |
| 823 | return mmtime.u.ticks; | |
| 824 | } | |
| 825 | ||
| 826 | ||
| 827 | /* end helper routines used by midiOutStream interface */ | |
| 828 | ||
| 829 | ||
| 830 | static PmError winmm_out_open(PmInternal *midi, void *driverInfo) | |
| 831 | { | |
| 832 | DWORD dwDevice; | |
| 833 | int i = midi->device_id; | |
| 834 | midiwinmm_type m; | |
| 835 | MIDIPROPTEMPO propdata; | |
| 836 | MIDIPROPTIMEDIV divdata; | |
| 837 | int max_sysex_len = midi->buffer_len * 4; | |
| 838 | int output_buffer_len; | |
| 839 | int num_buffers; | |
| 840 | dwDevice = (DWORD)(FPTR) descriptors[i].descriptor; | |
| 841 | /* create system dependent device data */ | |
| 842 | m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ | |
| 843 | midi->descriptor = m; | |
| 844 | if (!m) goto no_memory; | |
| 845 | m->handle.out = NULL; | |
| 846 | m->buffers = NULL; | |
| 847 | m->num_buffers = 0; | |
| 848 | m->max_buffers = 0; | |
| 849 | m->buffers_expanded = FALSE; | |
| 850 | m->next_buffer = 0; | |
| 851 | #ifdef USE_SYSEX_BUFFERS | |
| 852 | m->sysex_buffers[0] = NULL; | |
| 853 | m->sysex_buffers[1] = NULL; | |
| 854 | m->next_sysex_buffer = 0; | |
| 855 | #endif | |
| 856 | m->last_time = 0; | |
| 857 | m->first_message = TRUE; /* we treat first message as special case */ | |
| 858 | m->sysex_mode = FALSE; | |
| 859 | m->sysex_word = 0; | |
| 860 | m->sysex_byte_count = 0; | |
| 861 | m->hdr = NULL; | |
| 862 | m->sync_time = 0; | |
| 863 | m->delta = 0; | |
| 864 | m->error = MMSYSERR_NOERROR; | |
| 865 | ||
| 866 | /* create a signal */ | |
| 867 | m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); | |
| 868 | ||
| 869 | /* this should only fail when there are very serious problems */ | |
| 870 | assert(m->buffer_signal); | |
| 871 | ||
| 872 | /* open device */ | |
| 873 | if (midi->latency == 0) { | |
| 874 | /* use simple midi out calls */ | |
| 875 | pm_hosterror = midiOutOpen( | |
| 876 | (LPHMIDIOUT) & m->handle.out, /* device Handle */ | |
| 877 | dwDevice, /* device ID */ | |
| 878 | /* note: same callback fn as for StreamOpen: */ | |
| 879 | (DWORD_PTR) winmm_streamout_callback, /* callback fn */ | |
| 880 | (DWORD_PTR) midi, /* callback instance data */ | |
| 881 | CALLBACK_FUNCTION); /* callback type */ | |
| 882 | } else { | |
| 883 | /* use stream-based midi output (schedulable in future) */ | |
| 884 | pm_hosterror = midiStreamOpen( | |
| 885 | &m->handle.stream, /* device Handle */ | |
| 886 | (LPUINT) & dwDevice, /* device ID pointer */ | |
| 887 | 1, /* reserved, must be 1 */ | |
| 888 | (DWORD_PTR) winmm_streamout_callback, | |
| 889 | (DWORD_PTR) midi, /* callback instance data */ | |
| 890 | CALLBACK_FUNCTION); | |
| 891 | } | |
| 892 | if (pm_hosterror != MMSYSERR_NOERROR) { | |
| 893 | goto free_descriptor; | |
| 894 | } | |
| 895 | ||
| 896 | if (midi->latency == 0) { | |
| 897 | num_buffers = NUM_SIMPLE_SYSEX_BUFFERS; | |
| 898 | output_buffer_len = max_sysex_len / num_buffers; | |
| 899 | if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN) | |
| 900 | output_buffer_len = MIN_SIMPLE_SYSEX_LEN; | |
| 901 | } else { | |
| 902 | //long dur = 0; | |
| 903 | //num_buffers = (int)(double)max((double)midi->buffer_len, (double)midi->latency / 2); | |
| 904 | if (midi->buffer_len > (midi->latency / 2)) | |
| 905 | { | |
| 906 | num_buffers = midi->buffer_len; | |
| 907 | } | |
| 908 | else | |
| 909 | { | |
| 910 | num_buffers = (midi->latency / 2); | |
| 911 | } | |
| 912 | ||
| 913 | if (num_buffers < MIN_STREAM_BUFFERS) | |
| 914 | num_buffers = MIN_STREAM_BUFFERS; | |
| 915 | output_buffer_len = STREAM_BUFFER_LEN; | |
| 916 | ||
| 917 | propdata.cbStruct = sizeof(MIDIPROPTEMPO); | |
| 918 | propdata.dwTempo = 480000; /* microseconds per quarter */ | |
| 919 | pm_hosterror = midiStreamProperty(m->handle.stream, | |
| 920 | (LPBYTE) & propdata, | |
| 921 | MIDIPROP_SET | MIDIPROP_TEMPO); | |
| 922 | if (pm_hosterror) goto close_device; | |
| 923 | ||
| 924 | divdata.cbStruct = sizeof(MIDIPROPTEMPO); | |
| 925 | divdata.dwTimeDiv = 480; /* divisions per quarter */ | |
| 926 | pm_hosterror = midiStreamProperty(m->handle.stream, | |
| 927 | (LPBYTE) & divdata, | |
| 928 | MIDIPROP_SET | MIDIPROP_TIMEDIV); | |
| 929 | if (pm_hosterror) goto close_device; | |
| 930 | } | |
| 931 | /* allocate buffers */ | |
| 932 | if (allocate_buffers(m, output_buffer_len, num_buffers)) | |
| 933 | goto free_buffers; | |
| 934 | /* start device */ | |
| 935 | if (midi->latency != 0) { | |
| 936 | pm_hosterror = midiStreamRestart(m->handle.stream); | |
| 937 | if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers; | |
| 938 | } | |
| 939 | return pmNoError; | |
| 940 | ||
| 941 | free_buffers: | |
| 942 | /* buffers are freed below by winmm_out_delete */ | |
| 943 | close_device: | |
| 944 | midiOutClose(m->handle.out); | |
| 945 | free_descriptor: | |
| 946 | midi->descriptor = NULL; | |
| 947 | winmm_out_delete(midi); /* frees buffers and m */ | |
| 948 | no_memory: | |
| 949 | if (pm_hosterror) { | |
| 950 | midiOutGetErrorText(pm_hosterror, (char *) pm_hosterror_text, | |
| 951 | PM_HOST_ERROR_MSG_LEN); | |
| 952 | //assert(err == MMSYSERR_NOERROR); | |
| 953 | return pmHostError; | |
| 954 | } | |
| 955 | return pmInsufficientMemory; | |
| 956 | } | |
| 957 | ||
| 958 | ||
| 959 | /* winmm_out_delete -- carefully free data associated with midi */ | |
| 960 | /**/ | |
| 961 | static void winmm_out_delete(PmInternal *midi) | |
| 962 | { | |
| 963 | int i; | |
| 964 | /* delete system dependent device data */ | |
| 965 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 966 | if (m) { | |
| 967 | if (m->buffer_signal) { | |
| 968 | /* don't report errors -- better not to stop cleanup */ | |
| 969 | CloseHandle(m->buffer_signal); | |
| 970 | } | |
| 971 | /* if using stream output, free buffers */ | |
| 972 | for (i = 0; i < m->num_buffers; i++) { | |
| 973 | if (m->buffers[i]) pm_free(m->buffers[i]); | |
| 974 | } | |
| 975 | m->num_buffers = 0; | |
| 976 | pm_free(m->buffers); | |
| 977 | m->max_buffers = 0; | |
| 978 | #ifdef USE_SYSEX_BUFFERS | |
| 979 | /* free sysex buffers */ | |
| 980 | for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { | |
| 981 | if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); | |
| 982 | } | |
| 983 | #endif | |
| 984 | } | |
| 985 | midi->descriptor = NULL; | |
| 986 | pm_free(m); /* delete */ | |
| 987 | } | |
| 988 | ||
| 989 | ||
| 990 | /* see comments for winmm_in_close */ | |
| 991 | static PmError winmm_out_close(PmInternal *midi) | |
| 992 | { | |
| 993 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 994 | if (m->handle.out) { | |
| 995 | /* device to close */ | |
| 996 | if (midi->latency == 0) { | |
| 997 | pm_hosterror = midiOutClose(m->handle.out); | |
| 998 | } else { | |
| 999 | pm_hosterror = midiStreamClose(m->handle.stream); | |
| 1000 | } | |
| 1001 | /* regardless of outcome, free memory */ | |
| 1002 | winmm_out_delete(midi); | |
| 1003 | } | |
| 1004 | if (pm_hosterror) { | |
| 1005 | midiOutGetErrorText(pm_hosterror, | |
| 1006 | (char *) pm_hosterror_text, | |
| 1007 | PM_HOST_ERROR_MSG_LEN); | |
| 1008 | //assert(err == MMSYSERR_NOERROR); | |
| 1009 | return pmHostError; | |
| 1010 | } | |
| 1011 | return pmNoError; | |
| 1012 | } | |
| 1013 | ||
| 1014 | ||
| 1015 | static PmError winmm_out_abort(PmInternal *midi) | |
| 1016 | { | |
| 1017 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1018 | m->error = MMSYSERR_NOERROR; | |
| 1019 | ||
| 1020 | /* only stop output streams */ | |
| 1021 | if (midi->latency > 0) { | |
| 1022 | m->error = midiStreamStop(m->handle.stream); | |
| 1023 | } | |
| 1024 | return m->error ? pmHostError : pmNoError; | |
| 1025 | } | |
| 1026 | ||
| 1027 | ||
| 1028 | static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp) | |
| 1029 | { | |
| 1030 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1031 | assert(m); | |
| 1032 | if (m->hdr) { | |
| 1033 | m->error = midiOutPrepareHeader(m->handle.out, m->hdr, | |
| 1034 | sizeof(MIDIHDR)); | |
| 1035 | if (m->error) { | |
| 1036 | /* do not send message */ | |
| 1037 | } else if (midi->latency == 0) { | |
| 1038 | /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded | |
| 1039 | * should be zero. This is set in get_free_sysex_buffer(). | |
| 1040 | * The msg length goes in dwBufferLength in spite of what | |
| 1041 | * Microsoft documentation says (or doesn't say). */ | |
| 1042 | m->hdr->dwBufferLength = m->hdr->dwBytesRecorded; | |
| 1043 | m->hdr->dwBytesRecorded = 0; | |
| 1044 | m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); | |
| 1045 | } else { | |
| 1046 | m->error = midiStreamOut(m->handle.stream, m->hdr, | |
| 1047 | sizeof(MIDIHDR)); | |
| 1048 | } | |
| 1049 | midi->fill_base = NULL; | |
| 1050 | m->hdr = NULL; | |
| 1051 | if (m->error) { | |
| 1052 | m->hdr->dwFlags = 0; /* release the buffer */ | |
| 1053 | return pmHostError; | |
| 1054 | } | |
| 1055 | } | |
| 1056 | return pmNoError; | |
| 1057 | } | |
| 1058 | ||
| 1059 | ||
| 1060 | ||
| 1061 | #ifdef GARBAGE | |
| 1062 | static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte) | |
| 1063 | { | |
| 1064 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1065 | unsigned char *msg_buffer; | |
| 1066 | ||
| 1067 | /* at the beginning of sysex, m->hdr is NULL */ | |
| 1068 | if (!m->hdr) { /* allocate a buffer if none allocated yet */ | |
| 1069 | m->hdr = get_free_output_buffer(midi); | |
| 1070 | if (!m->hdr) return pmInsufficientMemory; | |
| 1071 | m->sysex_byte_count = 0; | |
| 1072 | } | |
| 1073 | /* figure out where to write byte */ | |
| 1074 | msg_buffer = (unsigned char *) (m->hdr->lpData); | |
| 1075 | assert(m->hdr->lpData == (char *) (m->hdr + 1)); | |
| 1076 | ||
| 1077 | /* check for overflow */ | |
| 1078 | if (m->sysex_byte_count >= m->hdr->dwBufferLength) { | |
| 1079 | /* allocate a bigger message -- double it every time */ | |
| 1080 | LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2); | |
| 1081 | /* printf("expand to %d bytes\n", m->sysex_byte_count * 2); */ | |
| 1082 | if (!big) return pmInsufficientMemory; | |
| 1083 | m->error = midiOutPrepareHeader(m->handle.out, big, | |
| 1084 | sizeof(MIDIHDR)); | |
| 1085 | if (m->error) { | |
| 1086 | m->hdr = NULL; | |
| 1087 | return pmHostError; | |
| 1088 | } | |
| 1089 | memcpy(big->lpData, msg_buffer, m->sysex_byte_count); | |
| 1090 | msg_buffer = (unsigned char *) (big->lpData); | |
| 1091 | if (m->buffers[0] == m->hdr) { | |
| 1092 | m->buffers[0] = big; | |
| 1093 | pm_free(m->hdr); | |
| 1094 | /* printf("freed m->hdr\n"); */ | |
| 1095 | } else if (m->buffers[1] == m->hdr) { | |
| 1096 | m->buffers[1] = big; | |
| 1097 | pm_free(m->hdr); | |
| 1098 | /* printf("freed m->hdr\n"); */ | |
| 1099 | } | |
| 1100 | m->hdr = big; | |
| 1101 | } | |
| 1102 | ||
| 1103 | /* append byte to message */ | |
| 1104 | msg_buffer[m->sysex_byte_count++] = byte; | |
| 1105 | ||
| 1106 | /* see if we have a complete message */ | |
| 1107 | if (byte == MIDI_EOX) { | |
| 1108 | m->hdr->dwBytesRecorded = m->sysex_byte_count; | |
| 1109 | /* | |
| 1110 | { int i; int len = m->hdr->dwBytesRecorded; | |
| 1111 | printf("OutLongMsg %d ", len); | |
| 1112 | for (i = 0; i < len; i++) { | |
| 1113 | printf("%2x ", msg_buffer[i]); | |
| 1114 | } | |
| 1115 | } | |
| 1116 | */ | |
| 1117 | m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); | |
| 1118 | m->hdr = NULL; /* stop using this message buffer */ | |
| 1119 | if (m->error) return pmHostError; | |
| 1120 | } | |
| 1121 | return pmNoError; | |
| 1122 | } | |
| 1123 | #endif | |
| 1124 | ||
| 1125 | ||
| 1126 | static PmError winmm_write_short(PmInternal *midi, PmEvent *event) | |
| 1127 | { | |
| 1128 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1129 | PmError rslt = pmNoError; | |
| 1130 | assert(m); | |
| 1131 | ||
| 1132 | if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ | |
| 1133 | m->error = midiOutShortMsg(m->handle.out, event->message); | |
| 1134 | if (m->error) rslt = pmHostError; | |
| 1135 | } else { /* use midiStream interface -- pass data through buffers */ | |
| 1136 | unsigned long when = event->timestamp; | |
| 1137 | unsigned long delta; | |
| 1138 | int full; | |
| 1139 | if (when == 0) when = midi->now; | |
| 1140 | /* when is in real_time; translate to intended stream time */ | |
| 1141 | when = when + m->delta + midi->latency; | |
| 1142 | /* make sure we don't go backward in time */ | |
| 1143 | if (when < m->last_time) when = m->last_time; | |
| 1144 | delta = when - m->last_time; | |
| 1145 | m->last_time = when; | |
| 1146 | /* before we insert any data, we must have a buffer */ | |
| 1147 | if (m->hdr == NULL) { | |
| 1148 | /* stream interface: buffers allocated when stream is opened */ | |
| 1149 | m->hdr = get_free_output_buffer(midi); | |
| 1150 | } | |
| 1151 | full = add_to_buffer(m, m->hdr, delta, event->message); | |
| 1152 | if (full) rslt = winmm_write_flush(midi, when); | |
| 1153 | } | |
| 1154 | return rslt; | |
| 1155 | } | |
| 1156 | ||
| 1157 | #define winmm_begin_sysex winmm_write_flush | |
| 1158 | #ifndef winmm_begin_sysex | |
| 1159 | static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp) | |
| 1160 | { | |
| 1161 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1162 | PmError rslt = pmNoError; | |
| 1163 | ||
| 1164 | if (midi->latency == 0) { | |
| 1165 | /* do nothing -- it's handled in winmm_write_byte */ | |
| 1166 | } else { | |
| 1167 | /* sysex expects an empty sysex buffer, so send whatever is here */ | |
| 1168 | rslt = winmm_write_flush(midi); | |
| 1169 | } | |
| 1170 | return rslt; | |
| 1171 | } | |
| 1172 | #endif | |
| 1173 | ||
| 1174 | static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) | |
| 1175 | { | |
| 1176 | /* could check for callback_error here, but I haven't checked | |
| 1177 | * what happens if we exit early and don't finish the sysex msg | |
| 1178 | * and clean up | |
| 1179 | */ | |
| 1180 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1181 | PmError rslt = pmNoError; | |
| 1182 | LPMIDIHDR hdr = m->hdr; | |
| 1183 | if (!hdr) return rslt; /* something bad happened earlier, | |
| 1184 | do not report an error because it would have been | |
| 1185 | reported (at least) once already */ | |
| 1186 | /* a(n old) version of MIDI YOKE requires a zero byte after | |
| 1187 | * the sysex message, but do not increment dwBytesRecorded: */ | |
| 1188 | hdr->lpData[hdr->dwBytesRecorded] = 0; | |
| 1189 | if (midi->latency == 0) { | |
| 1190 | #ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX | |
| 1191 | /* DEBUG CODE: */ | |
| 1192 | { int i; int len = m->hdr->dwBufferLength; | |
| 1193 | printf("OutLongMsg %d ", len); | |
| 1194 | for (i = 0; i < len; i++) { | |
| 1195 | printf("%2x ", (unsigned char) (m->hdr->lpData[i])); | |
| 1196 | } | |
| 1197 | } | |
| 1198 | #endif | |
| 1199 | } else { | |
| 1200 | /* Using stream interface. There are accumulated bytes in m->hdr | |
| 1201 | to send using midiStreamOut | |
| 1202 | */ | |
| 1203 | /* add bytes recorded to MIDIEVENT length, but don't | |
| 1204 | count the MIDIEVENT data (3 longs) */ | |
| 1205 | MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData); | |
| 1206 | evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); | |
| 1207 | /* round up BytesRecorded to multiple of 4 */ | |
| 1208 | hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; | |
| 1209 | } | |
| 1210 | rslt = winmm_write_flush(midi, timestamp); | |
| 1211 | return rslt; | |
| 1212 | } | |
| 1213 | ||
| 1214 | ||
| 1215 | static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, | |
| 1216 | PmTimestamp timestamp) | |
| 1217 | { | |
| 1218 | /* write a sysex byte */ | |
| 1219 | PmError rslt = pmNoError; | |
| 1220 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1221 | LPMIDIHDR hdr = m->hdr; | |
| 1222 | unsigned char *msg_buffer; | |
| 1223 | assert(m); | |
| 1224 | if (!hdr) { | |
| 1225 | m->hdr = hdr = get_free_output_buffer(midi); | |
| 1226 | assert(hdr); | |
| 1227 | midi->fill_base = (unsigned char *)(FPTR) m->hdr->lpData; | |
| 1228 | midi->fill_offset_ptr = (uint32_t *)&(hdr->dwBytesRecorded); | |
| 1229 | ||
| 1230 | /* when buffer fills, Pm_WriteSysEx will revert to calling | |
| 1231 | * pmwin_write_byte, which expect to have space, so leave | |
| 1232 | * one byte free for pmwin_write_byte. Leave another byte | |
| 1233 | * of space for zero after message to make early version of | |
| 1234 | * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */ | |
| 1235 | midi->fill_length = hdr->dwBufferLength - 2; | |
| 1236 | if (midi->latency != 0) { | |
| 1237 | unsigned long when = (unsigned long) timestamp; | |
| 1238 | unsigned long delta; | |
| 1239 | unsigned long *ptr; | |
| 1240 | if (when == 0) when = midi->now; | |
| 1241 | /* when is in real_time; translate to intended stream time */ | |
| 1242 | when = when + m->delta + midi->latency; | |
| 1243 | /* make sure we don't go backward in time */ | |
| 1244 | if (when < m->last_time) when = m->last_time; | |
| 1245 | delta = when - m->last_time; | |
| 1246 | m->last_time = when; | |
| 1247 | ||
| 1248 | ptr = (unsigned long *) hdr->lpData; | |
| 1249 | *ptr++ = delta; | |
| 1250 | *ptr++ = 0; | |
| 1251 | *ptr = MEVT_F_LONG; | |
| 1252 | hdr->dwBytesRecorded = 3 * sizeof(long); | |
| 1253 | /* data will be added at an offset of dwBytesRecorded ... */ | |
| 1254 | } | |
| 1255 | } | |
| 1256 | /* add the data byte */ | |
| 1257 | msg_buffer = (unsigned char *) (hdr->lpData); | |
| 1258 | msg_buffer[hdr->dwBytesRecorded++] = byte; | |
| 1259 | ||
| 1260 | /* see if buffer is full, leave one byte extra for pad */ | |
| 1261 | if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) { | |
| 1262 | /* write what we've got and continue */ | |
| 1263 | rslt = winmm_end_sysex(midi, timestamp); | |
| 1264 | } | |
| 1265 | return rslt; | |
| 1266 | } | |
| 1267 | ||
| 1268 | #ifdef EXPANDING_SYSEX_BUFFERS | |
| 1269 | note: this code is here as an aid in case you want sysex buffers | |
| 1270 | to expand to hold large messages completely. If so, you | |
| 1271 | will want to change SYSEX_BYTES_PER_BUFFER above to some | |
| 1272 | variable that remembers the buffer size. A good place to | |
| 1273 | put this value would be in the hdr->dwUser field. | |
| 1274 | ||
| 1275 | rslt = resize_sysex_buffer(midi, m->sysex_byte_count, | |
| 1276 | m->sysex_byte_count * 2); | |
| 1277 | ||
| 1278 | if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */ | |
| 1279 | #endif | |
| 1280 | #ifdef EXPANDING_SYSEX_BUFFERS | |
| 1281 | int bytesRecorded = hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ | |
| 1282 | rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); | |
| 1283 | hdr->dwBytesRecorded = bytesRecorded; | |
| 1284 | ||
| 1285 | if (rslt == pmBufferMaxSize) /* if buffer can't be resized */ | |
| 1286 | #endif | |
| 1287 | ||
| 1288 | ||
| 1289 | ||
| 1290 | static PmTimestamp winmm_synchronize(PmInternal *midi) | |
| 1291 | { | |
| 1292 | midiwinmm_type m; | |
| 1293 | unsigned long pm_stream_time_2; | |
| 1294 | unsigned long real_time; | |
| 1295 | unsigned long pm_stream_time; | |
| 1296 | ||
| 1297 | /* only synchronize if we are using stream interface */ | |
| 1298 | if (midi->latency == 0) return 0; | |
| 1299 | ||
| 1300 | /* figure out the time */ | |
| 1301 | m = (midiwinmm_type) midi->descriptor; | |
| 1302 | pm_stream_time_2 = pm_time_get(m); | |
| 1303 | ||
| 1304 | do { | |
| 1305 | /* read real_time between two reads of stream time */ | |
| 1306 | pm_stream_time = pm_stream_time_2; | |
| 1307 | real_time = (*midi->time_proc)(midi->time_info); | |
| 1308 | pm_stream_time_2 = pm_time_get(m); | |
| 1309 | /* repeat if more than 1ms elapsed */ | |
| 1310 | } while (pm_stream_time_2 > pm_stream_time + 1); | |
| 1311 | m->delta = pm_stream_time - real_time; | |
| 1312 | m->sync_time = real_time; | |
| 1313 | return real_time; | |
| 1314 | } | |
| 1315 | ||
| 1316 | #ifdef USE_SYSEX_BUFFERS | |
| 1317 | /* winmm_out_callback -- recycle sysex buffers */ | |
| 1318 | static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, | |
| 1319 | DWORD dwInstance, DWORD dwParam1, | |
| 1320 | DWORD dwParam2) | |
| 1321 | { | |
| 1322 | PmInternal *midi = (PmInternal *) dwInstance; | |
| 1323 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1324 | LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; | |
| 1325 | int err = 0; /* set to 0 so that no buffer match will also be an error */ | |
| 1326 | ||
| 1327 | /* Future optimization: eliminate UnprepareHeader calls -- they aren't | |
| 1328 | necessary; however, this code uses the prepared-flag to indicate which | |
| 1329 | buffers are free, so we need to do something to flag empty buffers if | |
| 1330 | we leave them prepared | |
| 1331 | */ | |
| 1332 | /* | |
| 1333 | printf("out_callback: hdr %x, wMsg %x, MOM_DONE %x\n", | |
| 1334 | hdr, wMsg, MOM_DONE); | |
| 1335 | */ | |
| 1336 | if (wMsg == MOM_DONE) { | |
| 1337 | MMRESULT ret = midiOutUnprepareHeader(m->handle.out, hdr, | |
| 1338 | sizeof(MIDIHDR)); | |
| 1339 | assert(ret == MMSYSERR_NOERROR); | |
| 1340 | } | |
| 1341 | /* notify waiting sender that a buffer is available */ | |
| 1342 | err = SetEvent(m->buffer_signal); | |
| 1343 | assert(err); /* false -> error */ | |
| 1344 | } | |
| 1345 | #endif | |
| 1346 | ||
| 1347 | /* winmm_streamout_callback -- unprepare (free) buffer header */ | |
| 1348 | static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, | |
| 1349 | DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) | |
| 1350 | { | |
| 1351 | PmInternal *midi = (PmInternal *)(FPTR) dwInstance; | |
| 1352 | LPMIDIHDR hdr = (LPMIDIHDR)(FPTR) dwParam1; | |
| 1353 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1354 | //int err; | |
| 1355 | ||
| 1356 | /* Even if an error is pending, I think we should unprepare msgs and | |
| 1357 | signal their arrival | |
| 1358 | */ | |
| 1359 | /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", | |
| 1360 | hdr, wMsg, MOM_DONE); */ | |
| 1361 | if (wMsg == MOM_DONE) { | |
| 1362 | midiOutUnprepareHeader(m->handle.out, hdr, | |
| 1363 | sizeof(MIDIHDR)); | |
| 1364 | //assert(ret == MMSYSERR_NOERROR); | |
| 1365 | } | |
| 1366 | /* signal client in case it is blocked waiting for buffer */ | |
| 1367 | SetEvent(m->buffer_signal); | |
| 1368 | //assert(err); /* false -> error */ | |
| 1369 | } | |
| 1370 | ||
| 1371 | ||
| 1372 | /* | |
| 1373 | ========================================================================================= | |
| 1374 | begin exported functions | |
| 1375 | ========================================================================================= | |
| 1376 | */ | |
| 1377 | ||
| 1378 | #define winmm_in_abort pm_fail_fn | |
| 1379 | pm_fns_node pm_winmm_in_dictionary = { | |
| 1380 | none_write_short, | |
| 1381 | none_sysex, | |
| 1382 | none_sysex, | |
| 1383 | none_write_byte, | |
| 1384 | none_write_short, | |
| 1385 | none_write_flush, | |
| 1386 | winmm_synchronize, | |
| 1387 | winmm_in_open, | |
| 1388 | winmm_in_abort, | |
| 1389 | winmm_in_close, | |
| 1390 | winmm_in_poll, | |
| 1391 | winmm_has_host_error, | |
| 1392 | winmm_get_host_error | |
| 1393 | }; | |
| 1394 | ||
| 1395 | pm_fns_node pm_winmm_out_dictionary = { | |
| 1396 | winmm_write_short, | |
| 1397 | winmm_begin_sysex, | |
| 1398 | winmm_end_sysex, | |
| 1399 | winmm_write_byte, | |
| 1400 | winmm_write_short, /* short realtime message */ | |
| 1401 | winmm_write_flush, | |
| 1402 | winmm_synchronize, | |
| 1403 | winmm_out_open, | |
| 1404 | winmm_out_abort, | |
| 1405 | winmm_out_close, | |
| 1406 | none_poll, | |
| 1407 | winmm_has_host_error, | |
| 1408 | winmm_get_host_error | |
| 1409 | }; | |
| 1410 | ||
| 1411 | ||
| 1412 | /* initialize winmm interface. Note that if there is something wrong | |
| 1413 | with winmm (e.g. it is not supported or installed), it is not an | |
| 1414 | error. We should simply return without having added any devices to | |
| 1415 | the table. Hence, no error code is returned. Furthermore, this init | |
| 1416 | code is called along with every other supported interface, so the | |
| 1417 | user would have a very hard time figuring out what hardware and API | |
| 1418 | generated the error. Finally, it would add complexity to pmwin.c to | |
| 1419 | remember where the error code came from in order to convert to text. | |
| 1420 | */ | |
| 1421 | void pm_winmm_init( void ) | |
| 1422 | { | |
| 1423 | pm_winmm_mapper_input(); | |
| 1424 | pm_winmm_mapper_output(); | |
| 1425 | pm_winmm_general_inputs(); | |
| 1426 | pm_winmm_general_outputs(); | |
| 1427 | } | |
| 1428 | ||
| 1429 | ||
| 1430 | /* no error codes are returned, even if errors are encountered, because | |
| 1431 | there is probably nothing the user could do (e.g. it would be an error | |
| 1432 | to retry. | |
| 1433 | */ | |
| 1434 | void pm_winmm_term( void ) | |
| 1435 | { | |
| 1436 | int i; | |
| 1437 | #ifdef DEBUG | |
| 1438 | char msg[PM_HOST_ERROR_MSG_LEN]; | |
| 1439 | #endif | |
| 1440 | //int doneAny = 0; | |
| 1441 | #ifdef DEBUG | |
| 1442 | printf("pm_winmm_term called\n"); | |
| 1443 | #endif | |
| 1444 | for (i = 0; i < pm_descriptor_index; i++) { | |
| 1445 | PmInternal * midi = descriptors[i].internalDescriptor; | |
| 1446 | if (midi) { | |
| 1447 | midiwinmm_type m = (midiwinmm_type) midi->descriptor; | |
| 1448 | if (m->handle.out) { | |
| 1449 | /* close next open device*/ | |
| 1450 | #ifdef DEBUG | |
| 1451 | if (doneAny == 0) { | |
| 1452 | printf("begin closing open devices...\n"); | |
| 1453 | doneAny = 1; | |
| 1454 | } | |
| 1455 | /* report any host errors; this EXTEREMELY useful when | |
| 1456 | trying to debug client app */ | |
| 1457 | if (winmm_has_host_error(midi)) { | |
| 1458 | winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN); | |
| 1459 | printf("%s\n", msg); | |
| 1460 | } | |
| 1461 | #endif | |
| 1462 | /* close all open ports */ | |
| 1463 | (*midi->dictionary->close)(midi); | |
| 1464 | } | |
| 1465 | } | |
| 1466 | } | |
| 1467 | if (midi_in_caps) { | |
| 1468 | pm_free(midi_in_caps); | |
| 1469 | midi_in_caps = NULL; | |
| 1470 | } | |
| 1471 | if (midi_out_caps) { | |
| 1472 | pm_free(midi_out_caps); | |
| 1473 | midi_out_caps = NULL; | |
| 1474 | } | |
| 1475 | #ifdef DEBUG | |
| 1476 | if (doneAny) { | |
| 1477 | printf("warning: devices were left open. They have been closed.\n"); | |
| 1478 | } | |
| 1479 | printf("pm_winmm_term exiting\n"); | |
| 1480 | #endif | |
| 1481 | pm_descriptor_index = 0; | |
| 1482 | } |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* finddefault.c -- find_default_device() implementation | |
| 2 | Roger Dannenberg, June 2008 | |
| 3 | */ | |
| 4 | ||
| 5 | #include <stdlib.h> | |
| 6 | #include <string.h> | |
| 7 | #include "portmidi.h" | |
| 8 | #include "pmutil.h" | |
| 9 | #include "pminternal.h" | |
| 10 | #include "pmmacosxcm.h" | |
| 11 | #include "readbinaryplist.h" | |
| 12 | ||
| 13 | /* Parse preference files, find default device, search devices -- | |
| 14 | This parses the preference file(s) once for input and once for | |
| 15 | output, which is inefficient but much simpler to manage. Note | |
| 16 | that using the readbinaryplist.c module, you cannot keep two | |
| 17 | plist files (user and system) open at once (due to a simple | |
| 18 | memory management scheme). | |
| 19 | */ | |
| 20 | PmDeviceID find_default_device(char *path, int input, PmDeviceID id) | |
| 21 | /* path -- the name of the preference we are searching for | |
| 22 | input -- true iff this is an input device | |
| 23 | id -- current default device id | |
| 24 | returns matching device id if found, otherwise id | |
| 25 | */ | |
| 26 | { | |
| 27 | static char *pref_file = (char *)"com.apple.java.util.prefs.plist"; | |
| 28 | char *pref_str = NULL; | |
| 29 | // read device preferences | |
| 30 | value_ptr prefs = bplist_read_user_pref(pref_file); | |
| 31 | if (prefs) { | |
| 32 | value_ptr pref_val = value_dict_lookup_using_path(prefs, path); | |
| 33 | if (pref_val) { | |
| 34 | pref_str = value_get_asciistring(pref_val); | |
| 35 | } | |
| 36 | } | |
| 37 | if (!pref_str) { | |
| 38 | bplist_free_data(); /* look elsewhere */ | |
| 39 | prefs = bplist_read_system_pref(pref_file); | |
| 40 | if (prefs) { | |
| 41 | value_ptr pref_val = value_dict_lookup_using_path(prefs, path); | |
| 42 | if (pref_val) { | |
| 43 | pref_str = value_get_asciistring(pref_val); | |
| 44 | } | |
| 45 | } | |
| 46 | } | |
| 47 | if (pref_str) { /* search devices for match */ | |
| 48 | int i = pm_find_default_device(pref_str, input); | |
| 49 | if (i != pmNoDevice) { | |
| 50 | id = i; | |
| 51 | } | |
| 52 | } | |
| 53 | if (prefs) { | |
| 54 | bplist_free_data(); | |
| 55 | } | |
| 56 | return id; | |
| 57 | } |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* ptmacosx.c -- portable timer implementation for mac os x */ | |
| 2 | ||
| 3 | #include <stdlib.h> | |
| 4 | #include <stdio.h> | |
| 5 | #include <CoreAudio/HostTime.h> | |
| 6 | ||
| 7 | #import <mach/mach.h> | |
| 8 | #import <mach/mach_error.h> | |
| 9 | #import <mach/mach_time.h> | |
| 10 | #import <mach/clock.h> | |
| 11 | #include <unistd.h> | |
| 12 | ||
| 13 | #include "porttime.h" | |
| 14 | #include "sys/time.h" | |
| 15 | #include "pthread.h" | |
| 16 | ||
| 17 | //#define NSEC_PER_MSEC 1000000 | |
| 18 | #define THREAD_IMPORTANCE 30 | |
| 19 | ||
| 20 | static int time_started_flag = FALSE; | |
| 21 | static UInt64 start_time; | |
| 22 | static pthread_t pt_thread_pid; | |
| 23 | ||
| 24 | /* note that this is static data -- we only need one copy */ | |
| 25 | typedef struct { | |
| 26 | int id; | |
| 27 | int resolution; | |
| 28 | PtCallback *callback; | |
| 29 | void *userData; | |
| 30 | } pt_callback_parameters; | |
| 31 | ||
| 32 | static int pt_callback_proc_id = 0; | |
| 33 | ||
| 34 | static void *Pt_CallbackProc(void *p) | |
| 35 | { | |
| 36 | pt_callback_parameters *parameters = (pt_callback_parameters *) p; | |
| 37 | int mytime = 1; | |
| 38 | ||
| 39 | kern_return_t error; | |
| 40 | thread_extended_policy_data_t extendedPolicy; | |
| 41 | thread_precedence_policy_data_t precedencePolicy; | |
| 42 | ||
| 43 | extendedPolicy.timeshare = 0; | |
| 44 | error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, | |
| 45 | (thread_policy_t)&extendedPolicy, | |
| 46 | THREAD_EXTENDED_POLICY_COUNT); | |
| 47 | if (error != KERN_SUCCESS) { | |
| 48 | mach_error("Couldn't set thread timeshare policy", error); | |
| 49 | } | |
| 50 | ||
| 51 | precedencePolicy.importance = THREAD_IMPORTANCE; | |
| 52 | error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, | |
| 53 | (thread_policy_t)&precedencePolicy, | |
| 54 | THREAD_PRECEDENCE_POLICY_COUNT); | |
| 55 | if (error != KERN_SUCCESS) { | |
| 56 | mach_error("Couldn't set thread precedence policy", error); | |
| 57 | } | |
| 58 | ||
| 59 | ||
| 60 | /* to kill a process, just increment the pt_callback_proc_id */ | |
| 61 | /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, parameters->id); */ | |
| 62 | while (pt_callback_proc_id == parameters->id) { | |
| 63 | /* wait for a multiple of resolution ms */ | |
| 64 | UInt64 wait_time; | |
| 65 | int delay = mytime++ * parameters->resolution - Pt_Time(); | |
| 66 | PtTimestamp timestamp; | |
| 67 | if (delay < 0) delay = 0; | |
| 68 | wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); | |
| 69 | wait_time += AudioGetCurrentHostTime(); | |
| 70 | error = mach_wait_until(wait_time); | |
| 71 | timestamp = Pt_Time(); | |
| 72 | (*(parameters->callback))(timestamp, parameters->userData); | |
| 73 | } | |
| 74 | free(parameters); | |
| 75 | return NULL; | |
| 76 | } | |
| 77 | ||
| 78 | ||
| 79 | PtError Pt_Start(int resolution, PtCallback *callback, void *userData) | |
| 80 | { | |
| 81 | if (time_started_flag) return ptAlreadyStarted; | |
| 82 | start_time = AudioGetCurrentHostTime(); | |
| 83 | ||
| 84 | if (callback) { | |
| 85 | int res; | |
| 86 | pt_callback_parameters *parms; | |
| 87 | ||
| 88 | parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters)); | |
| 89 | if (!parms) return ptInsufficientMemory; | |
| 90 | parms->id = pt_callback_proc_id; | |
| 91 | parms->resolution = resolution; | |
| 92 | parms->callback = callback; | |
| 93 | parms->userData = userData; | |
| 94 | res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms); | |
| 95 | if (res != 0) return ptHostError; | |
| 96 | } | |
| 97 | ||
| 98 | time_started_flag = TRUE; | |
| 99 | return ptNoError; | |
| 100 | } | |
| 101 | ||
| 102 | ||
| 103 | PtError Pt_Stop() | |
| 104 | { | |
| 105 | /* printf("Pt_Stop called\n"); */ | |
| 106 | pt_callback_proc_id++; | |
| 107 | pthread_join(pt_thread_pid, NULL); | |
| 108 | time_started_flag = FALSE; | |
| 109 | return ptNoError; | |
| 110 | } | |
| 111 | ||
| 112 | ||
| 113 | int Pt_Started() | |
| 114 | { | |
| 115 | return time_started_flag; | |
| 116 | } | |
| 117 | ||
| 118 | ||
| 119 | PtTimestamp Pt_Time() | |
| 120 | { | |
| 121 | UInt64 clock_time, nsec_time; | |
| 122 | clock_time = AudioGetCurrentHostTime() - start_time; | |
| 123 | nsec_time = AudioConvertHostTimeToNanos(clock_time); | |
| 124 | return (PtTimestamp)(nsec_time / NSEC_PER_MSEC); | |
| 125 | } | |
| 126 | ||
| 127 | ||
| 128 | void Pt_Sleep(int32_t duration) | |
| 129 | { | |
| 130 | usleep(duration * 1000); | |
| 131 | } |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | #ifdef _MSC_VER | |
| 2 | #pragma warning(disable: 4244) // stop warnings about downsize typecasts | |
| 3 | #pragma warning(disable: 4018) // stop warnings about signed/unsigned | |
| 4 | #endif | |
| 5 | ||
| 6 | #include "stdlib.h" | |
| 7 | #include "string.h" | |
| 8 | #include "portmidi.h" | |
| 9 | #include "porttime.h" | |
| 10 | #include "pmutil.h" | |
| 11 | #include "pminternal.h" | |
| 12 | #include <assert.h> | |
| 13 | ||
| 14 | #define MIDI_CLOCK 0xf8 | |
| 15 | #define MIDI_ACTIVE 0xfe | |
| 16 | #define MIDI_STATUS_MASK 0x80 | |
| 17 | #define MIDI_SYSEX 0xf0 | |
| 18 | #define MIDI_EOX 0xf7 | |
| 19 | #define MIDI_START 0xFA | |
| 20 | #define MIDI_STOP 0xFC | |
| 21 | #define MIDI_CONTINUE 0xFB | |
| 22 | #define MIDI_F9 0xF9 | |
| 23 | #define MIDI_FD 0xFD | |
| 24 | #define MIDI_RESET 0xFF | |
| 25 | #define MIDI_NOTE_ON 0x90 | |
| 26 | #define MIDI_NOTE_OFF 0x80 | |
| 27 | #define MIDI_CHANNEL_AT 0xD0 | |
| 28 | #define MIDI_POLY_AT 0xA0 | |
| 29 | #define MIDI_PROGRAM 0xC0 | |
| 30 | #define MIDI_CONTROL 0xB0 | |
| 31 | #define MIDI_PITCHBEND 0xE0 | |
| 32 | #define MIDI_MTC 0xF1 | |
| 33 | #define MIDI_SONGPOS 0xF2 | |
| 34 | #define MIDI_SONGSEL 0xF3 | |
| 35 | #define MIDI_TUNE 0xF6 | |
| 36 | ||
| 37 | #define is_empty(midi) ((midi)->tail == (midi)->head) | |
| 38 | ||
| 39 | /* this is not static so that pm_init can set it directly if | |
| 40 | * (see pmmac.c:pm_init()) | |
| 41 | */ | |
| 42 | int pm_initialized = FALSE; | |
| 43 | ||
| 44 | int pm_hosterror; | |
| 45 | char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; | |
| 46 | ||
| 47 | #ifdef PM_CHECK_ERRORS | |
| 48 | ||
| 49 | #include <stdio.h> | |
| 50 | ||
| 51 | #define STRING_MAX 80 | |
| 52 | ||
| 53 | static void prompt_and_exit(void) | |
| 54 | { | |
| 55 | char line[STRING_MAX]; | |
| 56 | printf("type ENTER..."); | |
| 57 | fgets(line, STRING_MAX, stdin); | |
| 58 | /* this will clean up open ports: */ | |
| 59 | exit(-1); | |
| 60 | } | |
| 61 | ||
| 62 | ||
| 63 | static PmError pm_errmsg(PmError err) | |
| 64 | { | |
| 65 | if (err == pmHostError) { | |
| 66 | /* it seems pointless to allocate memory and copy the string, | |
| 67 | * so I will do the work of Pm_GetHostErrorText directly | |
| 68 | */ | |
| 69 | printf("PortMidi found host error...\n %s\n", pm_hosterror_text); | |
| 70 | pm_hosterror = FALSE; | |
| 71 | pm_hosterror_text[0] = 0; /* clear the message */ | |
| 72 | prompt_and_exit(); | |
| 73 | } else if (err < 0) { | |
| 74 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); | |
| 75 | prompt_and_exit(); | |
| 76 | } | |
| 77 | return err; | |
| 78 | } | |
| 79 | #else | |
| 80 | #define pm_errmsg(err) err | |
| 81 | #endif | |
| 82 | ||
| 83 | /* | |
| 84 | ==================================================================== | |
| 85 | system implementation of portmidi interface | |
| 86 | ==================================================================== | |
| 87 | */ | |
| 88 | ||
| 89 | int pm_descriptor_max = 0; | |
| 90 | int pm_descriptor_index = 0; | |
| 91 | descriptor_type descriptors = NULL; | |
| 92 | ||
| 93 | /* pm_add_device -- describe interface/device pair to library | |
| 94 | * | |
| 95 | * This is called at intialization time, once for each | |
| 96 | * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1) | |
| 97 | * The strings are retained but NOT COPIED, so do not destroy them! | |
| 98 | * | |
| 99 | * returns pmInvalidDeviceId if device memory is exceeded | |
| 100 | * otherwise returns pmNoError | |
| 101 | */ | |
| 102 | PmError pm_add_device(char *interf, char *name, int input, | |
| 103 | void *descriptor, pm_fns_type dictionary) { | |
| 104 | if (pm_descriptor_index >= pm_descriptor_max) { | |
| 105 | // expand descriptors | |
| 106 | descriptor_type new_descriptors = (descriptor_type) | |
| 107 | pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32)); | |
| 108 | if (!new_descriptors) return pmInsufficientMemory; | |
| 109 | if (descriptors) { | |
| 110 | memcpy(new_descriptors, descriptors, | |
| 111 | sizeof(descriptor_node) * pm_descriptor_max); | |
| 112 | free(descriptors); | |
| 113 | } | |
| 114 | pm_descriptor_max += 32; | |
| 115 | descriptors = new_descriptors; | |
| 116 | } | |
| 117 | descriptors[pm_descriptor_index].pub.interf = interf; | |
| 118 | descriptors[pm_descriptor_index].pub.name = name; | |
| 119 | descriptors[pm_descriptor_index].pub.input = input; | |
| 120 | descriptors[pm_descriptor_index].pub.output = !input; | |
| 121 | ||
| 122 | /* default state: nothing to close (for automatic device closing) */ | |
| 123 | descriptors[pm_descriptor_index].pub.opened = FALSE; | |
| 124 | ||
| 125 | /* ID number passed to win32 multimedia API open */ | |
| 126 | descriptors[pm_descriptor_index].descriptor = descriptor; | |
| 127 | ||
| 128 | /* points to PmInternal, allows automatic device closing */ | |
| 129 | descriptors[pm_descriptor_index].internalDescriptor = NULL; | |
| 130 | ||
| 131 | descriptors[pm_descriptor_index].dictionary = dictionary; | |
| 132 | ||
| 133 | pm_descriptor_index++; | |
| 134 | ||
| 135 | return pmNoError; | |
| 136 | } | |
| 137 | ||
| 138 | ||
| 139 | /* utility to look up device, given a pattern, | |
| 140 | note: pattern is modified | |
| 141 | */ | |
| 142 | int pm_find_default_device(char *pattern, int is_input) | |
| 143 | { | |
| 144 | int id = pmNoDevice; | |
| 145 | int i; | |
| 146 | /* first parse pattern into name, interf parts */ | |
| 147 | char *interf_pref = (char *)""; /* initially assume it is not there */ | |
| 148 | char *name_pref = strstr(pattern, ", "); | |
| 149 | ||
| 150 | if (name_pref) { /* found separator, adjust the pointer */ | |
| 151 | interf_pref = pattern; | |
| 152 | name_pref[0] = 0; | |
| 153 | name_pref += 2; | |
| 154 | } else { | |
| 155 | name_pref = pattern; /* whole string is the name pattern */ | |
| 156 | } | |
| 157 | for (i = 0; i < pm_descriptor_index; i++) { | |
| 158 | const PmDeviceInfo *info = Pm_GetDeviceInfo(i); | |
| 159 | if (info->input == is_input && | |
| 160 | strstr(info->name, name_pref) && | |
| 161 | strstr(info->interf, interf_pref)) { | |
| 162 | id = i; | |
| 163 | break; | |
| 164 | } | |
| 165 | } | |
| 166 | return id; | |
| 167 | } | |
| 168 | ||
| 169 | ||
| 170 | /* | |
| 171 | ==================================================================== | |
| 172 | portmidi implementation | |
| 173 | ==================================================================== | |
| 174 | */ | |
| 175 | ||
| 176 | PMEXPORT int Pm_CountDevices( void ) { | |
| 177 | Pm_Initialize(); | |
| 178 | /* no error checking -- Pm_Initialize() does not fail */ | |
| 179 | return pm_descriptor_index; | |
| 180 | } | |
| 181 | ||
| 182 | ||
| 183 | PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) { | |
| 184 | Pm_Initialize(); /* no error check needed */ | |
| 185 | if (id >= 0 && id < pm_descriptor_index) { | |
| 186 | return &descriptors[id].pub; | |
| 187 | } | |
| 188 | return NULL; | |
| 189 | } | |
| 190 | ||
| 191 | /* pm_success_fn -- "noop" function pointer */ | |
| 192 | PmError pm_success_fn(PmInternal *midi) { | |
| 193 | return pmNoError; | |
| 194 | } | |
| 195 | ||
| 196 | /* none_write -- returns an error if called */ | |
| 197 | PmError none_write_short(PmInternal *midi, PmEvent *buffer) { | |
| 198 | return pmBadPtr; | |
| 199 | } | |
| 200 | ||
| 201 | /* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */ | |
| 202 | PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp) { | |
| 203 | return pmBadPtr; | |
| 204 | } | |
| 205 | ||
| 206 | PmError none_write_byte(PmInternal *midi, unsigned char byte, | |
| 207 | PmTimestamp timestamp) { | |
| 208 | return pmBadPtr; | |
| 209 | } | |
| 210 | ||
| 211 | /* pm_fail_fn -- generic function, returns error if called */ | |
| 212 | PmError pm_fail_fn(PmInternal *midi) { | |
| 213 | return pmBadPtr; | |
| 214 | } | |
| 215 | ||
| 216 | static PmError none_open(PmInternal *midi, void *driverInfo) { | |
| 217 | return pmBadPtr; | |
| 218 | } | |
| 219 | static void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) { | |
| 220 | *msg = 0; // empty string | |
| 221 | } | |
| 222 | static unsigned int none_has_host_error(PmInternal * midi) { | |
| 223 | return FALSE; | |
| 224 | } | |
| 225 | PmTimestamp none_synchronize(PmInternal *midi) { | |
| 226 | return 0; | |
| 227 | } | |
| 228 | ||
| 229 | #define none_abort pm_fail_fn | |
| 230 | #define none_close pm_fail_fn | |
| 231 | ||
| 232 | pm_fns_node pm_none_dictionary = { | |
| 233 | none_write_short, | |
| 234 | none_sysex, | |
| 235 | none_sysex, | |
| 236 | none_write_byte, | |
| 237 | none_write_short, | |
| 238 | none_write_flush, | |
| 239 | none_synchronize, | |
| 240 | none_open, | |
| 241 | none_abort, | |
| 242 | none_close, | |
| 243 | none_poll, | |
| 244 | none_has_host_error, | |
| 245 | none_get_host_error | |
| 246 | }; | |
| 247 | ||
| 248 | ||
| 249 | PMEXPORT const char *Pm_GetErrorText( PmError errnum ) { | |
| 250 | const char *msg; | |
| 251 | ||
| 252 | switch(errnum) | |
| 253 | { | |
| 254 | case pmNoError: | |
| 255 | msg = ""; | |
| 256 | break; | |
| 257 | case pmHostError: | |
| 258 | msg = "PortMidi: `Host error'"; | |
| 259 | break; | |
| 260 | case pmInvalidDeviceId: | |
| 261 | msg = "PortMidi: `Invalid device ID'"; | |
| 262 | break; | |
| 263 | case pmInsufficientMemory: | |
| 264 | msg = "PortMidi: `Insufficient memory'"; | |
| 265 | break; | |
| 266 | case pmBufferTooSmall: | |
| 267 | msg = "PortMidi: `Buffer too small'"; | |
| 268 | break; | |
| 269 | case pmBadPtr: | |
| 270 | msg = "PortMidi: `Bad pointer'"; | |
| 271 | break; | |
| 272 | case pmInternalError: | |
| 273 | msg = "PortMidi: `Internal PortMidi Error'"; | |
| 274 | break; | |
| 275 | case pmBufferOverflow: | |
| 276 | msg = "PortMidi: `Buffer overflow'"; | |
| 277 | break; | |
| 278 | case pmBadData: | |
| 279 | msg = "PortMidi: `Invalid MIDI message Data'"; | |
| 280 | break; | |
| 281 | case pmBufferMaxSize: | |
| 282 | msg = "PortMidi: `Buffer cannot be made larger'"; | |
| 283 | break; | |
| 284 | default: | |
| 285 | msg = "PortMidi: `Illegal error number'"; | |
| 286 | break; | |
| 287 | } | |
| 288 | return msg; | |
| 289 | } | |
| 290 | ||
| 291 | ||
| 292 | /* This can be called whenever you get a pmHostError return value. | |
| 293 | * The error will always be in the global pm_hosterror_text. | |
| 294 | */ | |
| 295 | PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len) { | |
| 296 | assert(msg); | |
| 297 | assert(len > 0); | |
| 298 | if (pm_hosterror) { | |
| 299 | strncpy(msg, (char *) pm_hosterror_text, len); | |
| 300 | pm_hosterror = FALSE; | |
| 301 | pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it | |
| 302 | might help with debugging */ | |
| 303 | msg[len - 1] = 0; /* make sure string is terminated */ | |
| 304 | } else { | |
| 305 | msg[0] = 0; /* no string to return */ | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | ||
| 310 | PMEXPORT int Pm_HasHostError(PortMidiStream * stream) { | |
| 311 | if (pm_hosterror) return TRUE; | |
| 312 | if (stream) { | |
| 313 | PmInternal * midi = (PmInternal *) stream; | |
| 314 | pm_hosterror = (*midi->dictionary->has_host_error)(midi); | |
| 315 | if (pm_hosterror) { | |
| 316 | midi->dictionary->host_error(midi, pm_hosterror_text, | |
| 317 | PM_HOST_ERROR_MSG_LEN); | |
| 318 | /* now error message is global */ | |
| 319 | return TRUE; | |
| 320 | } | |
| 321 | } | |
| 322 | return FALSE; | |
| 323 | } | |
| 324 | ||
| 325 | ||
| 326 | PMEXPORT PmError Pm_Initialize( void ) { | |
| 327 | if (!pm_initialized) { | |
| 328 | pm_hosterror = FALSE; | |
| 329 | pm_hosterror_text[0] = 0; /* the null string */ | |
| 330 | pm_init(); | |
| 331 | pm_initialized = TRUE; | |
| 332 | } | |
| 333 | return pmNoError; | |
| 334 | } | |
| 335 | ||
| 336 | ||
| 337 | PMEXPORT PmError Pm_Terminate( void ) { | |
| 338 | if (pm_initialized) { | |
| 339 | pm_term(); | |
| 340 | // if there are no devices, descriptors might still be NULL | |
| 341 | if (descriptors != NULL) { | |
| 342 | free(descriptors); | |
| 343 | descriptors = NULL; | |
| 344 | } | |
| 345 | pm_descriptor_index = 0; | |
| 346 | pm_descriptor_max = 0; | |
| 347 | pm_initialized = FALSE; | |
| 348 | } | |
| 349 | return pmNoError; | |
| 350 | } | |
| 351 | ||
| 352 | ||
| 353 | /* Pm_Read -- read up to length messages from source into buffer */ | |
| 354 | /* | |
| 355 | * returns number of messages actually read, or error code | |
| 356 | */ | |
| 357 | PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) { | |
| 358 | PmInternal *midi = (PmInternal *) stream; | |
| 359 | int n = 0; | |
| 360 | PmError err = pmNoError; | |
| 361 | pm_hosterror = FALSE; | |
| 362 | /* arg checking */ | |
| 363 | if(midi == NULL) | |
| 364 | err = pmBadPtr; | |
| 365 | else if(!descriptors[midi->device_id].pub.opened) | |
| 366 | err = pmBadPtr; | |
| 367 | else if(!descriptors[midi->device_id].pub.input) | |
| 368 | err = pmBadPtr; | |
| 369 | /* First poll for data in the buffer... | |
| 370 | * This either simply checks for data, or attempts first to fill the buffer | |
| 371 | * with data from the MIDI hardware; this depends on the implementation. | |
| 372 | * We could call Pm_Poll here, but that would redo a lot of redundant | |
| 373 | * parameter checking, so I copied some code from Pm_Poll to here: */ | |
| 374 | else err = (*(midi->dictionary->poll))(midi); | |
| 375 | ||
| 376 | if (err != pmNoError) { | |
| 377 | if (err == pmHostError) { | |
| 378 | midi->dictionary->host_error(midi, pm_hosterror_text, | |
| 379 | PM_HOST_ERROR_MSG_LEN); | |
| 380 | pm_hosterror = TRUE; | |
| 381 | } | |
| 382 | return pm_errmsg(err); | |
| 383 | } | |
| 384 | ||
| 385 | while (n < length) { | |
| 386 | PmError err = Pm_Dequeue(midi->queue, buffer++); | |
| 387 | if (err == pmBufferOverflow) { | |
| 388 | /* ignore the data we have retreived so far */ | |
| 389 | return pm_errmsg(pmBufferOverflow); | |
| 390 | } else if (err == 0) { /* empty queue */ | |
| 391 | break; | |
| 392 | } | |
| 393 | n++; | |
| 394 | } | |
| 395 | return n; | |
| 396 | } | |
| 397 | ||
| 398 | PMEXPORT PmError Pm_Poll( PortMidiStream *stream ) | |
| 399 | { | |
| 400 | PmInternal *midi = (PmInternal *) stream; | |
| 401 | PmError err; | |
| 402 | ||
| 403 | pm_hosterror = FALSE; | |
| 404 | /* arg checking */ | |
| 405 | if(midi == NULL) | |
| 406 | err = pmBadPtr; | |
| 407 | else if (!descriptors[midi->device_id].pub.opened) | |
| 408 | err = pmBadPtr; | |
| 409 | else if (!descriptors[midi->device_id].pub.input) | |
| 410 | err = pmBadPtr; | |
| 411 | else | |
| 412 | err = (*(midi->dictionary->poll))(midi); | |
| 413 | ||
| 414 | if (err != pmNoError) { | |
| 415 | if (err == pmHostError) { | |
| 416 | midi->dictionary->host_error(midi, pm_hosterror_text, | |
| 417 | PM_HOST_ERROR_MSG_LEN); | |
| 418 | pm_hosterror = TRUE; | |
| 419 | } | |
| 420 | return pm_errmsg(err); | |
| 421 | } | |
| 422 | ||
| 423 | return !Pm_QueueEmpty(midi->queue); | |
| 424 | } | |
| 425 | ||
| 426 | ||
| 427 | /* this is called from Pm_Write and Pm_WriteSysEx to issue a | |
| 428 | * call to the system-dependent end_sysex function and handle | |
| 429 | * the error return | |
| 430 | */ | |
| 431 | static PmError pm_end_sysex(PmInternal *midi) | |
| 432 | { | |
| 433 | PmError err = (*midi->dictionary->end_sysex)(midi, 0); | |
| 434 | midi->sysex_in_progress = FALSE; | |
| 435 | if (err == pmHostError) { | |
| 436 | midi->dictionary->host_error(midi, pm_hosterror_text, | |
| 437 | PM_HOST_ERROR_MSG_LEN); | |
| 438 | pm_hosterror = TRUE; | |
| 439 | } | |
| 440 | return err; | |
| 441 | } | |
| 442 | ||
| 443 | ||
| 444 | /* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and | |
| 445 | Pm_WriteSysEx all operate a state machine that "outputs" calls to | |
| 446 | write_short, begin_sysex, write_byte, end_sysex, and write_realtime */ | |
| 447 | ||
| 448 | PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length) | |
| 449 | { | |
| 450 | PmInternal *midi = (PmInternal *) stream; | |
| 451 | PmError err = pmNoError; | |
| 452 | int i; | |
| 453 | int bits; | |
| 454 | ||
| 455 | pm_hosterror = FALSE; | |
| 456 | /* arg checking */ | |
| 457 | if(midi == NULL) | |
| 458 | err = pmBadPtr; | |
| 459 | else if(!descriptors[midi->device_id].pub.opened) | |
| 460 | err = pmBadPtr; | |
| 461 | else if(!descriptors[midi->device_id].pub.output) | |
| 462 | err = pmBadPtr; | |
| 463 | else | |
| 464 | err = pmNoError; | |
| 465 | ||
| 466 | if (err != pmNoError) goto pm_write_error; | |
| 467 | ||
| 468 | if (midi->latency == 0) { | |
| 469 | midi->now = 0; | |
| 470 | } else { | |
| 471 | midi->now = (*(midi->time_proc))(midi->time_info); | |
| 472 | if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) { | |
| 473 | /* time to resync */ | |
| 474 | midi->now = (*midi->dictionary->synchronize)(midi); | |
| 475 | midi->first_message = FALSE; | |
| 476 | } | |
| 477 | } | |
| 478 | /* error recovery: when a sysex is detected, we call | |
| 479 | * dictionary->begin_sysex() followed by calls to | |
| 480 | * dictionary->write_byte() and dictionary->write_realtime() | |
| 481 | * until an end-of-sysex is detected, when we call | |
| 482 | * dictionary->end_sysex(). After an error occurs, | |
| 483 | * Pm_Write() continues to call functions. For example, | |
| 484 | * it will continue to call write_byte() even after | |
| 485 | * an error sending a sysex message, and end_sysex() will be | |
| 486 | * called when an EOX or non-real-time status is found. | |
| 487 | * When errors are detected, Pm_Write() returns immediately, | |
| 488 | * so it is possible that this will drop data and leave | |
| 489 | * sysex messages in a partially transmitted state. | |
| 490 | */ | |
| 491 | for (i = 0; i < length; i++) { | |
| 492 | uint32_t msg = buffer[i].message; | |
| 493 | bits = 0; | |
| 494 | /* is this a sysex message? */ | |
| 495 | if (Pm_MessageStatus(msg) == MIDI_SYSEX) { | |
| 496 | if (midi->sysex_in_progress) { | |
| 497 | /* error: previous sysex was not terminated by EOX */ | |
| 498 | midi->sysex_in_progress = FALSE; | |
| 499 | err = pmBadData; | |
| 500 | goto pm_write_error; | |
| 501 | } | |
| 502 | midi->sysex_in_progress = TRUE; | |
| 503 | if ((err = (*midi->dictionary->begin_sysex)(midi, | |
| 504 | buffer[i].timestamp)) != pmNoError) | |
| 505 | goto pm_write_error; | |
| 506 | if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX, | |
| 507 | buffer[i].timestamp)) != pmNoError) | |
| 508 | goto pm_write_error; | |
| 509 | bits = 8; | |
| 510 | /* fall through to continue sysex processing */ | |
| 511 | } else if ((msg & MIDI_STATUS_MASK) && | |
| 512 | (Pm_MessageStatus(msg) != MIDI_EOX)) { | |
| 513 | /* a non-sysex message */ | |
| 514 | if (midi->sysex_in_progress) { | |
| 515 | /* this should be a realtime message */ | |
| 516 | if (is_real_time(msg)) { | |
| 517 | if ((err = (*midi->dictionary->write_realtime)(midi, | |
| 518 | &(buffer[i]))) != pmNoError) | |
| 519 | goto pm_write_error; | |
| 520 | } else { | |
| 521 | midi->sysex_in_progress = FALSE; | |
| 522 | err = pmBadData; | |
| 523 | /* ignore any error from this, because we already have one */ | |
| 524 | /* pass 0 as timestamp -- it's ignored */ | |
| 525 | (*midi->dictionary->end_sysex)(midi, 0); | |
| 526 | goto pm_write_error; | |
| 527 | } | |
| 528 | } else { /* regular short midi message */ | |
| 529 | if ((err = (*midi->dictionary->write_short)(midi, | |
| 530 | &(buffer[i]))) != pmNoError) | |
| 531 | goto pm_write_error; | |
| 532 | continue; | |
| 533 | } | |
| 534 | } | |
| 535 | if (midi->sysex_in_progress) { /* send sysex bytes until EOX */ | |
| 536 | /* see if we can accelerate data transfer */ | |
| 537 | if (bits == 0 && midi->fill_base && /* 4 bytes to copy */ | |
| 538 | (*midi->fill_offset_ptr) + 4 <= midi->fill_length && | |
| 539 | (msg & 0x80808080) == 0) { /* all data */ | |
| 540 | /* copy 4 bytes from msg to fill_base + fill_offset */ | |
| 541 | unsigned char *ptr = midi->fill_base + | |
| 542 | *(midi->fill_offset_ptr); | |
| 543 | ptr[0] = msg; ptr[1] = msg >> 8; | |
| 544 | ptr[2] = msg >> 16; ptr[3] = msg >> 24; | |
| 545 | (*midi->fill_offset_ptr) += 4; | |
| 546 | continue; | |
| 547 | } | |
| 548 | /* no acceleration, so do byte-by-byte copying */ | |
| 549 | while (bits < 32) { | |
| 550 | unsigned char midi_byte = (unsigned char) (msg >> bits); | |
| 551 | if ((err = (*midi->dictionary->write_byte)(midi, midi_byte, | |
| 552 | buffer[i].timestamp)) != pmNoError) | |
| 553 | goto pm_write_error; | |
| 554 | if (midi_byte == MIDI_EOX) { | |
| 555 | err = pm_end_sysex(midi); | |
| 556 | if (err != pmNoError) goto error_exit; | |
| 557 | break; /* from while loop */ | |
| 558 | } | |
| 559 | bits += 8; | |
| 560 | } | |
| 561 | } else { | |
| 562 | /* not in sysex mode, but message did not start with status */ | |
| 563 | err = pmBadData; | |
| 564 | goto pm_write_error; | |
| 565 | } | |
| 566 | } | |
| 567 | /* after all messages are processed, send the data */ | |
| 568 | if (!midi->sysex_in_progress) | |
| 569 | err = (*midi->dictionary->write_flush)(midi, 0); | |
| 570 | pm_write_error: | |
| 571 | if (err == pmHostError) { | |
| 572 | midi->dictionary->host_error(midi, pm_hosterror_text, | |
| 573 | PM_HOST_ERROR_MSG_LEN); | |
| 574 | pm_hosterror = TRUE; | |
| 575 | } | |
| 576 | error_exit: | |
| 577 | return pm_errmsg(err); | |
| 578 | } | |
| 579 | ||
| 580 | ||
| 581 | PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when, PmMessage msg) | |
| 582 | { | |
| 583 | PmEvent event; | |
| 584 | ||
| 585 | event.timestamp = when; | |
| 586 | event.message = msg; | |
| 587 | return Pm_Write(stream, &event, 1); | |
| 588 | } | |
| 589 | ||
| 590 | ||
| 591 | PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, | |
| 592 | unsigned char *msg) | |
| 593 | { | |
| 594 | /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */ | |
| 595 | /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */ | |
| 596 | #define BUFLEN ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage))) | |
| 597 | PmEvent buffer[BUFLEN]; | |
| 598 | int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */ | |
| 599 | PmInternal *midi = (PmInternal *) stream; | |
| 600 | /* the next byte in the buffer is represented by an index, bufx, and | |
| 601 | a shift in bits */ | |
| 602 | int shift = 0; | |
| 603 | int bufx = 0; | |
| 604 | buffer[0].message = 0; | |
| 605 | buffer[0].timestamp = when; | |
| 606 | ||
| 607 | while (1) { | |
| 608 | /* insert next byte into buffer */ | |
| 609 | buffer[bufx].message |= ((*msg) << shift); | |
| 610 | shift += 8; | |
| 611 | if (*msg++ == MIDI_EOX) break; | |
| 612 | if (shift == 32) { | |
| 613 | shift = 0; | |
| 614 | bufx++; | |
| 615 | if (bufx == buffer_size) { | |
| 616 | PmError err = Pm_Write(stream, buffer, buffer_size); | |
| 617 | /* note: Pm_Write has already called errmsg() */ | |
| 618 | if (err) return err; | |
| 619 | /* prepare to fill another buffer */ | |
| 620 | bufx = 0; | |
| 621 | buffer_size = BUFLEN; | |
| 622 | /* optimization: maybe we can just copy bytes */ | |
| 623 | if (midi->fill_base) { | |
| 624 | PmError err; | |
| 625 | while (*(midi->fill_offset_ptr) < midi->fill_length) { | |
| 626 | midi->fill_base[(*midi->fill_offset_ptr)++] = *msg; | |
| 627 | if (*msg++ == MIDI_EOX) { | |
| 628 | err = pm_end_sysex(midi); | |
| 629 | if (err != pmNoError) return pm_errmsg(err); | |
| 630 | goto end_of_sysex; | |
| 631 | } | |
| 632 | } | |
| 633 | /* I thought that I could do a pm_Write here and | |
| 634 | * change this if to a loop, avoiding calls in Pm_Write | |
| 635 | * to the slower write_byte, but since | |
| 636 | * sysex_in_progress is true, this will not flush | |
| 637 | * the buffer, and we'll infinite loop: */ | |
| 638 | /* err = Pm_Write(stream, buffer, 0); | |
| 639 | if (err) return err; */ | |
| 640 | /* instead, the way this works is that Pm_Write calls | |
| 641 | * write_byte on 4 bytes. The first, since the buffer | |
| 642 | * is full, will flush the buffer and allocate a new | |
| 643 | * one. This primes the buffer so | |
| 644 | * that we can return to the loop above and fill it | |
| 645 | * efficiently without a lot of function calls. | |
| 646 | */ | |
| 647 | buffer_size = 1; /* get another message started */ | |
| 648 | } | |
| 649 | } | |
| 650 | buffer[bufx].message = 0; | |
| 651 | buffer[bufx].timestamp = when; | |
| 652 | } | |
| 653 | /* keep inserting bytes until you find MIDI_EOX */ | |
| 654 | } | |
| 655 | end_of_sysex: | |
| 656 | /* we're finished sending full buffers, but there may | |
| 657 | * be a partial one left. | |
| 658 | */ | |
| 659 | if (shift != 0) bufx++; /* add partial message to buffer len */ | |
| 660 | if (bufx) { /* bufx is number of PmEvents to send from buffer */ | |
| 661 | PmError err = Pm_Write(stream, buffer, bufx); | |
| 662 | if (err) return err; | |
| 663 | } | |
| 664 | return pmNoError; | |
| 665 | } | |
| 666 | ||
| 667 | ||
| 668 | ||
| 669 | PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream, | |
| 670 | PmDeviceID inputDevice, | |
| 671 | void *inputDriverInfo, | |
| 672 | int32_t bufferSize, | |
| 673 | PmTimeProcPtr time_proc, | |
| 674 | void *time_info) | |
| 675 | { | |
| 676 | PmInternal *midi; | |
| 677 | PmError err = pmNoError; | |
| 678 | pm_hosterror = FALSE; | |
| 679 | *stream = NULL; | |
| 680 | ||
| 681 | /* arg checking */ | |
| 682 | if (inputDevice < 0 || inputDevice >= pm_descriptor_index) | |
| 683 | err = pmInvalidDeviceId; | |
| 684 | else if (!descriptors[inputDevice].pub.input) | |
| 685 | err = pmInvalidDeviceId; | |
| 686 | else if(descriptors[inputDevice].pub.opened) | |
| 687 | err = pmInvalidDeviceId; | |
| 688 | ||
| 689 | if (err != pmNoError) | |
| 690 | goto error_return; | |
| 691 | ||
| 692 | /* create portMidi internal data */ | |
| 693 | midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); | |
| 694 | *stream = midi; | |
| 695 | if (!midi) { | |
| 696 | err = pmInsufficientMemory; | |
| 697 | goto error_return; | |
| 698 | } | |
| 699 | midi->device_id = inputDevice; | |
| 700 | midi->write_flag = FALSE; | |
| 701 | midi->time_proc = time_proc; | |
| 702 | midi->time_info = time_info; | |
| 703 | /* windows adds timestamps in the driver and these are more accurate than | |
| 704 | using a time_proc, so do not automatically provide a time proc. Non-win | |
| 705 | implementations may want to provide a default time_proc in their | |
| 706 | system-specific midi_out_open() method. | |
| 707 | */ | |
| 708 | if (bufferSize <= 0) bufferSize = 256; /* default buffer size */ | |
| 709 | midi->queue = Pm_QueueCreate(bufferSize, (int32_t) sizeof(PmEvent)); | |
| 710 | if (!midi->queue) { | |
| 711 | /* free portMidi data */ | |
| 712 | *stream = NULL; | |
| 713 | pm_free(midi); | |
| 714 | err = pmInsufficientMemory; | |
| 715 | goto error_return; | |
| 716 | } | |
| 717 | midi->buffer_len = bufferSize; /* portMidi input storage */ | |
| 718 | midi->latency = 0; /* not used */ | |
| 719 | midi->sysex_in_progress = FALSE; | |
| 720 | midi->sysex_message = 0; | |
| 721 | midi->sysex_message_count = 0; | |
| 722 | midi->filters = PM_FILT_ACTIVE; | |
| 723 | midi->channel_mask = 0xFFFF; | |
| 724 | midi->sync_time = 0; | |
| 725 | midi->first_message = TRUE; | |
| 726 | midi->dictionary = descriptors[inputDevice].dictionary; | |
| 727 | midi->fill_base = NULL; | |
| 728 | midi->fill_offset_ptr = NULL; | |
| 729 | midi->fill_length = 0; | |
| 730 | descriptors[inputDevice].internalDescriptor = midi; | |
| 731 | /* open system dependent input device */ | |
| 732 | err = (*midi->dictionary->open)(midi, inputDriverInfo); | |
| 733 | if (err) { | |
| 734 | *stream = NULL; | |
| 735 | descriptors[inputDevice].internalDescriptor = NULL; | |
| 736 | /* free portMidi data */ | |
| 737 | Pm_QueueDestroy(midi->queue); | |
| 738 | pm_free(midi); | |
| 739 | } else { | |
| 740 | /* portMidi input open successful */ | |
| 741 | descriptors[inputDevice].pub.opened = TRUE; | |
| 742 | } | |
| 743 | error_return: | |
| 744 | /* note: if there is a pmHostError, it is the responsibility | |
| 745 | * of the system-dependent code (*midi->dictionary->open)() | |
| 746 | * to set pm_hosterror and pm_hosterror_text | |
| 747 | */ | |
| 748 | return pm_errmsg(err); | |
| 749 | } | |
| 750 | ||
| 751 | ||
| 752 | PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream, | |
| 753 | PmDeviceID outputDevice, | |
| 754 | void *outputDriverInfo, | |
| 755 | int32_t bufferSize, | |
| 756 | PmTimeProcPtr time_proc, | |
| 757 | void *time_info, | |
| 758 | int32_t latency ) | |
| 759 | { | |
| 760 | PmInternal *midi; | |
| 761 | PmError err = pmNoError; | |
| 762 | pm_hosterror = FALSE; | |
| 763 | *stream = NULL; | |
| 764 | ||
| 765 | /* arg checking */ | |
| 766 | if (outputDevice < 0 || outputDevice >= pm_descriptor_index) | |
| 767 | err = pmInvalidDeviceId; | |
| 768 | else if (!descriptors[outputDevice].pub.output) | |
| 769 | err = pmInvalidDeviceId; | |
| 770 | else if (descriptors[outputDevice].pub.opened) | |
| 771 | err = pmInvalidDeviceId; | |
| 772 | if (err != pmNoError) | |
| 773 | goto error_return; | |
| 774 | ||
| 775 | /* create portMidi internal data */ | |
| 776 | midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); | |
| 777 | *stream = midi; | |
| 778 | if (!midi) { | |
| 779 | err = pmInsufficientMemory; | |
| 780 | goto error_return; | |
| 781 | } | |
| 782 | midi->device_id = outputDevice; | |
| 783 | midi->write_flag = TRUE; | |
| 784 | midi->time_proc = time_proc; | |
| 785 | /* if latency > 0, we need a time reference. If none is provided, | |
| 786 | use PortTime library */ | |
| 787 | if (time_proc == NULL && latency != 0) { | |
| 788 | if (!Pt_Started()) | |
| 789 | Pt_Start(1, 0, 0); | |
| 790 | /* time_get does not take a parameter, so coerce */ | |
| 791 | midi->time_proc = (PmTimeProcPtr) Pt_Time; | |
| 792 | } | |
| 793 | midi->time_info = time_info; | |
| 794 | midi->buffer_len = bufferSize; | |
| 795 | midi->queue = NULL; /* unused by output */ | |
| 796 | /* if latency zero, output immediate (timestamps ignored) */ | |
| 797 | /* if latency < 0, use 0 but don't return an error */ | |
| 798 | if (latency < 0) latency = 0; | |
| 799 | midi->latency = latency; | |
| 800 | midi->sysex_in_progress = FALSE; | |
| 801 | midi->sysex_message = 0; /* unused by output */ | |
| 802 | midi->sysex_message_count = 0; /* unused by output */ | |
| 803 | midi->filters = 0; /* not used for output */ | |
| 804 | midi->channel_mask = 0xFFFF; | |
| 805 | midi->sync_time = 0; | |
| 806 | midi->first_message = TRUE; | |
| 807 | midi->dictionary = descriptors[outputDevice].dictionary; | |
| 808 | midi->fill_base = NULL; | |
| 809 | midi->fill_offset_ptr = NULL; | |
| 810 | midi->fill_length = 0; | |
| 811 | descriptors[outputDevice].internalDescriptor = midi; | |
| 812 | /* open system dependent output device */ | |
| 813 | err = (*midi->dictionary->open)(midi, outputDriverInfo); | |
| 814 | if (err) { | |
| 815 | *stream = NULL; | |
| 816 | descriptors[outputDevice].internalDescriptor = NULL; | |
| 817 | /* free portMidi data */ | |
| 818 | pm_free(midi); | |
| 819 | } else { | |
| 820 | /* portMidi input open successful */ | |
| 821 | descriptors[outputDevice].pub.opened = TRUE; | |
| 822 | } | |
| 823 | error_return: | |
| 824 | /* note: system-dependent code must set pm_hosterror and | |
| 825 | * pm_hosterror_text if a pmHostError occurs | |
| 826 | */ | |
| 827 | return pm_errmsg(err); | |
| 828 | } | |
| 829 | ||
| 830 | ||
| 831 | PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) | |
| 832 | { | |
| 833 | PmInternal *midi = (PmInternal *) stream; | |
| 834 | PmError err = pmNoError; | |
| 835 | ||
| 836 | if (midi == NULL) | |
| 837 | err = pmBadPtr; | |
| 838 | else | |
| 839 | midi->channel_mask = mask; | |
| 840 | ||
| 841 | return pm_errmsg(err); | |
| 842 | } | |
| 843 | ||
| 844 | ||
| 845 | PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters) { | |
| 846 | PmInternal *midi = (PmInternal *) stream; | |
| 847 | PmError err = pmNoError; | |
| 848 | ||
| 849 | /* arg checking */ | |
| 850 | if (midi == NULL) | |
| 851 | err = pmBadPtr; | |
| 852 | else if (!descriptors[midi->device_id].pub.opened) | |
| 853 | err = pmBadPtr; | |
| 854 | else | |
| 855 | midi->filters = filters; | |
| 856 | return pm_errmsg(err); | |
| 857 | } | |
| 858 | ||
| 859 | ||
| 860 | PMEXPORT PmError Pm_Close( PortMidiStream *stream ) { | |
| 861 | PmInternal *midi = (PmInternal *) stream; | |
| 862 | PmError err = pmNoError; | |
| 863 | ||
| 864 | pm_hosterror = FALSE; | |
| 865 | /* arg checking */ | |
| 866 | if (midi == NULL) /* midi must point to something */ | |
| 867 | err = pmBadPtr; | |
| 868 | /* if it is an open device, the device_id will be valid */ | |
| 869 | else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_index) | |
| 870 | err = pmBadPtr; | |
| 871 | /* and the device should be in the opened state */ | |
| 872 | else if (!descriptors[midi->device_id].pub.opened) | |
| 873 | err = pmBadPtr; | |
| 874 | ||
| 875 | if (err != pmNoError) | |
| 876 | goto error_return; | |
| 877 | ||
| 878 | /* close the device */ | |
| 879 | err = (*midi->dictionary->close)(midi); | |
| 880 | /* even if an error occurred, continue with cleanup */ | |
| 881 | descriptors[midi->device_id].internalDescriptor = NULL; | |
| 882 | descriptors[midi->device_id].pub.opened = FALSE; | |
| 883 | if (midi->queue) Pm_QueueDestroy(midi->queue); | |
| 884 | pm_free(midi); | |
| 885 | error_return: | |
| 886 | /* system dependent code must set pm_hosterror and | |
| 887 | * pm_hosterror_text if a pmHostError occurs. | |
| 888 | */ | |
| 889 | return pm_errmsg(err); | |
| 890 | } | |
| 891 | ||
| 892 | PmError Pm_Synchronize( PortMidiStream* stream ) { | |
| 893 | PmInternal *midi = (PmInternal *) stream; | |
| 894 | PmError err = pmNoError; | |
| 895 | if (midi == NULL) | |
| 896 | err = pmBadPtr; | |
| 897 | else if (!descriptors[midi->device_id].pub.output) | |
| 898 | err = pmBadPtr; | |
| 899 | else if (!descriptors[midi->device_id].pub.opened) | |
| 900 | err = pmBadPtr; | |
| 901 | else | |
| 902 | midi->first_message = TRUE; | |
| 903 | return err; | |
| 904 | } | |
| 905 | ||
| 906 | PMEXPORT PmError Pm_Abort( PortMidiStream* stream ) { | |
| 907 | PmInternal *midi = (PmInternal *) stream; | |
| 908 | PmError err; | |
| 909 | /* arg checking */ | |
| 910 | if (midi == NULL) | |
| 911 | err = pmBadPtr; | |
| 912 | else if (!descriptors[midi->device_id].pub.output) | |
| 913 | err = pmBadPtr; | |
| 914 | else if (!descriptors[midi->device_id].pub.opened) | |
| 915 | err = pmBadPtr; | |
| 916 | else | |
| 917 | err = (*midi->dictionary->abort)(midi); | |
| 918 | ||
| 919 | if (err == pmHostError) { | |
| 920 | midi->dictionary->host_error(midi, pm_hosterror_text, | |
| 921 | PM_HOST_ERROR_MSG_LEN); | |
| 922 | pm_hosterror = TRUE; | |
| 923 | } | |
| 924 | return pm_errmsg(err); | |
| 925 | } | |
| 926 | ||
| 927 | ||
| 928 | ||
| 929 | /* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */ | |
| 930 | #define pm_channel_filtered(status, mask) \ | |
| 931 | ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask)))) | |
| 932 | ||
| 933 | ||
| 934 | /* The following two functions will checks to see if a MIDI message matches | |
| 935 | the filtering criteria. Since the sysex routines only want to filter realtime messages, | |
| 936 | we need to have separate routines. | |
| 937 | */ | |
| 938 | ||
| 939 | ||
| 940 | /* pm_realtime_filtered returns non-zero if the filter will kill the current message. | |
| 941 | Note that only realtime messages are checked here. | |
| 942 | */ | |
| 943 | #define pm_realtime_filtered(status, filters) \ | |
| 944 | ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters))) | |
| 945 | ||
| 946 | /* | |
| 947 | return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE)) | |
| 948 | || ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK)) | |
| 949 | || ((status == MIDI_START) && (filters & PM_FILT_PLAY)) | |
| 950 | || ((status == MIDI_STOP) && (filters & PM_FILT_PLAY)) | |
| 951 | || ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY)) | |
| 952 | || ((status == MIDI_F9) && (filters & PM_FILT_F9)) | |
| 953 | || ((status == MIDI_FD) && (filters & PM_FILT_FD)) | |
| 954 | || ((status == MIDI_RESET) && (filters & PM_FILT_RESET)) | |
| 955 | || ((status == MIDI_MTC) && (filters & PM_FILT_MTC)) | |
| 956 | || ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION)) | |
| 957 | || ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT)) | |
| 958 | || ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE)); | |
| 959 | }*/ | |
| 960 | ||
| 961 | ||
| 962 | /* pm_status_filtered returns non-zero if a filter will kill the current message, based on status. | |
| 963 | Note that sysex and real time are not checked. It is up to the subsystem (winmm, core midi, alsa) | |
| 964 | to filter sysex, as it is handled more easily and efficiently at that level. | |
| 965 | Realtime message are filtered in pm_realtime_filtered. | |
| 966 | */ | |
| 967 | #define pm_status_filtered(status, filters) ((1 << (16 + ((status) >> 4))) & (filters)) | |
| 968 | ||
| 969 | ||
| 970 | /* | |
| 971 | return ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE)) | |
| 972 | || ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE)) | |
| 973 | || ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH)) | |
| 974 | || ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH)) | |
| 975 | || ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM)) | |
| 976 | || ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL)) | |
| 977 | || ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND)); | |
| 978 | ||
| 979 | } | |
| 980 | */ | |
| 981 | ||
| 982 | static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp) | |
| 983 | { | |
| 984 | PmEvent event; | |
| 985 | ||
| 986 | /* there may be nothing in the buffer */ | |
| 987 | if (midi->sysex_message_count == 0) return; /* nothing to flush */ | |
| 988 | ||
| 989 | event.message = midi->sysex_message; | |
| 990 | event.timestamp = timestamp; | |
| 991 | /* copied from pm_read_short, avoids filtering */ | |
| 992 | if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { | |
| 993 | midi->sysex_in_progress = FALSE; | |
| 994 | } | |
| 995 | midi->sysex_message_count = 0; | |
| 996 | midi->sysex_message = 0; | |
| 997 | } | |
| 998 | ||
| 999 | ||
| 1000 | /* pm_read_short and pm_read_bytes | |
| 1001 | are the interface between system-dependent MIDI input handlers | |
| 1002 | and the system-independent PortMIDI code. | |
| 1003 | The input handler MUST obey these rules: | |
| 1004 | 1) all short input messages must be sent to pm_read_short, which | |
| 1005 | enqueues them to a FIFO for the application. | |
| 1006 | 2) each buffer of sysex bytes should be reported by calling pm_read_bytes | |
| 1007 | (which sets midi->sysex_in_progress). After the eox byte, | |
| 1008 | pm_read_bytes will clear sysex_in_progress | |
| 1009 | */ | |
| 1010 | ||
| 1011 | /* pm_read_short is the place where all input messages arrive from | |
| 1012 | system-dependent code such as pmwinmm.c. Here, the messages | |
| 1013 | are entered into the PortMidi input buffer. | |
| 1014 | */ | |
| 1015 | void pm_read_short(PmInternal *midi, PmEvent *event) | |
| 1016 | { | |
| 1017 | int status; | |
| 1018 | /* arg checking */ | |
| 1019 | assert(midi != NULL); | |
| 1020 | /* midi filtering is applied here */ | |
| 1021 | status = Pm_MessageStatus(event->message); | |
| 1022 | if (!pm_status_filtered(status, midi->filters) | |
| 1023 | && (!is_real_time(status) || | |
| 1024 | !pm_realtime_filtered(status, midi->filters)) | |
| 1025 | && !pm_channel_filtered(status, midi->channel_mask)) { | |
| 1026 | /* if sysex is in progress and we get a status byte, it had | |
| 1027 | better be a realtime message or the starting SYSEX byte; | |
| 1028 | otherwise, we exit the sysex_in_progress state | |
| 1029 | */ | |
| 1030 | if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) { | |
| 1031 | /* two choices: real-time or not. If it's real-time, then | |
| 1032 | * this should be delivered as a sysex byte because it is | |
| 1033 | * embedded in a sysex message | |
| 1034 | */ | |
| 1035 | if (is_real_time(status)) { | |
| 1036 | midi->sysex_message |= | |
| 1037 | (status << (8 * midi->sysex_message_count++)); | |
| 1038 | if (midi->sysex_message_count == 4) { | |
| 1039 | pm_flush_sysex(midi, event->timestamp); | |
| 1040 | } | |
| 1041 | } else { /* otherwise, it's not real-time. This interrupts | |
| 1042 | * a sysex message in progress */ | |
| 1043 | midi->sysex_in_progress = FALSE; | |
| 1044 | } | |
| 1045 | } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) { | |
| 1046 | midi->sysex_in_progress = FALSE; | |
| 1047 | } | |
| 1048 | } | |
| 1049 | } | |
| 1050 | ||
| 1051 | /* pm_read_bytes -- read one (partial) sysex msg from MIDI data */ | |
| 1052 | /* | |
| 1053 | * returns how many bytes processed | |
| 1054 | */ | |
| 1055 | unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, | |
| 1056 | int len, PmTimestamp timestamp) | |
| 1057 | { | |
| 1058 | int i = 0; /* index into data, must not be unsigned (!) */ | |
| 1059 | PmEvent event; | |
| 1060 | event.timestamp = timestamp; | |
| 1061 | assert(midi); | |
| 1062 | /* note that since buffers may not have multiples of 4 bytes, | |
| 1063 | * pm_read_bytes may be called in the middle of an outgoing | |
| 1064 | * 4-byte PortMidi message. sysex_in_progress indicates that | |
| 1065 | * a sysex has been sent but no eox. | |
| 1066 | */ | |
| 1067 | if (len == 0) return 0; /* sanity check */ | |
| 1068 | if (!midi->sysex_in_progress) { | |
| 1069 | while (i < len) { /* process all data */ | |
| 1070 | unsigned char byte = data[i++]; | |
| 1071 | if (byte == MIDI_SYSEX && | |
| 1072 | !pm_realtime_filtered(byte, midi->filters)) { | |
| 1073 | midi->sysex_in_progress = TRUE; | |
| 1074 | i--; /* back up so code below will get SYSEX byte */ | |
| 1075 | break; /* continue looping below to process msg */ | |
| 1076 | } else if (byte == MIDI_EOX) { | |
| 1077 | midi->sysex_in_progress = FALSE; | |
| 1078 | return i; /* done with one message */ | |
| 1079 | } else if (byte & MIDI_STATUS_MASK) { | |
| 1080 | /* We're getting MIDI but no sysex in progress. | |
| 1081 | * Either the SYSEX status byte was dropped or | |
| 1082 | * the message was filtered. Drop the data, but | |
| 1083 | * send any embedded realtime bytes. | |
| 1084 | */ | |
| 1085 | /* assume that this is a real-time message: | |
| 1086 | * it is an error to pass non-real-time messages | |
| 1087 | * to pm_read_bytes | |
| 1088 | */ | |
| 1089 | event.message = byte; | |
| 1090 | pm_read_short(midi, &event); | |
| 1091 | } | |
| 1092 | } /* all bytes in the buffer are processed */ | |
| 1093 | } | |
| 1094 | /* Now, i<len implies sysex_in_progress. If sysex_in_progress | |
| 1095 | * becomes false in the loop, there must have been an overflow | |
| 1096 | * and we can just drop all remaining bytes | |
| 1097 | */ | |
| 1098 | while (i < len && midi->sysex_in_progress) { | |
| 1099 | if (midi->sysex_message_count == 0 && i <= len - 4 && | |
| 1100 | ((event.message = (((PmMessage) data[i]) | | |
| 1101 | (((PmMessage) data[i+1]) << 8) | | |
| 1102 | (((PmMessage) data[i+2]) << 16) | | |
| 1103 | (((PmMessage) data[i+3]) << 24))) & | |
| 1104 | 0x80808080) == 0) { /* all data, no status */ | |
| 1105 | if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { | |
| 1106 | midi->sysex_in_progress = FALSE; | |
| 1107 | } | |
| 1108 | i += 4; | |
| 1109 | } else { | |
| 1110 | while (i < len) { | |
| 1111 | /* send one byte at a time */ | |
| 1112 | unsigned char byte = data[i++]; | |
| 1113 | if (is_real_time(byte) && | |
| 1114 | pm_realtime_filtered(byte, midi->filters)) { | |
| 1115 | continue; /* real-time data is filtered, so omit */ | |
| 1116 | } | |
| 1117 | midi->sysex_message |= | |
| 1118 | (byte << (8 * midi->sysex_message_count++)); | |
| 1119 | if (byte == MIDI_EOX) { | |
| 1120 | midi->sysex_in_progress = FALSE; | |
| 1121 | pm_flush_sysex(midi, event.timestamp); | |
| 1122 | return i; | |
| 1123 | } else if (midi->sysex_message_count == 4) { | |
| 1124 | pm_flush_sysex(midi, event.timestamp); | |
| 1125 | /* after handling at least one non-data byte | |
| 1126 | * and reaching a 4-byte message boundary, | |
| 1127 | * resume trying to send 4 at a time in outer loop | |
| 1128 | */ | |
| 1129 | break; | |
| 1130 | } | |
| 1131 | } | |
| 1132 | } | |
| 1133 | } | |
| 1134 | return i; | |
| 1135 | } | |
| 1136 | ||
| 1137 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* midiwin32.h -- system-specific definitions */ | |
| 2 | ||
| 3 | void pm_winmm_init( void ); | |
| 4 | void pm_winmm_term( void ); | |
| 5 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmwin.c -- PortMidi os-dependent code */ | |
| 2 | ||
| 3 | /* This file only needs to implement: | |
| 4 | pm_init(), which calls various routines to register the | |
| 5 | available midi devices, | |
| 6 | Pm_GetDefaultInputDeviceID(), and | |
| 7 | Pm_GetDefaultOutputDeviceID(). | |
| 8 | This file must | |
| 9 | be separate from the main portmidi.c file because it is system | |
| 10 | dependent, and it is separate from, say, pmwinmm.c, because it | |
| 11 | might need to register devices for winmm, directx, and others. | |
| 12 | ||
| 13 | */ | |
| 14 | ||
| 15 | #include "stdlib.h" | |
| 16 | #include "portmidi.h" | |
| 17 | #include "pmutil.h" | |
| 18 | #include "pminternal.h" | |
| 19 | #include "pmwinmm.h" | |
| 20 | #ifdef DEBUG | |
| 21 | #include "stdio.h" | |
| 22 | #endif | |
| 23 | #undef UNICODE | |
| 24 | #include <windows.h> | |
| 25 | ||
| 26 | /* pm_exit is called when the program exits. | |
| 27 | It calls pm_term to make sure PortMidi is properly closed. | |
| 28 | If DEBUG is on, we prompt for input to avoid losing error messages. | |
| 29 | */ | |
| 30 | static void pm_exit(void) { | |
| 31 | pm_term(); | |
| 32 | #ifdef DEBUG | |
| 33 | #define STRING_MAX 80 | |
| 34 | { | |
| 35 | char line[STRING_MAX]; | |
| 36 | printf("Type ENTER...\n"); | |
| 37 | /* note, w/o this prompting, client console application can not see one | |
| 38 | of its errors before closing. */ | |
| 39 | fgets(line, STRING_MAX, stdin); | |
| 40 | } | |
| 41 | #endif | |
| 42 | } | |
| 43 | ||
| 44 | ||
| 45 | /* pm_init is the windows-dependent initialization.*/ | |
| 46 | void pm_init(void) | |
| 47 | { | |
| 48 | atexit(pm_exit); | |
| 49 | #ifdef DEBUG | |
| 50 | printf("registered pm_exit with atexit()\n"); | |
| 51 | #endif | |
| 52 | pm_winmm_init(); | |
| 53 | /* initialize other APIs (DirectX?) here */ | |
| 54 | } | |
| 55 | ||
| 56 | ||
| 57 | void pm_term(void) { | |
| 58 | pm_winmm_term(); | |
| 59 | } | |
| 60 | ||
| 61 | ||
| 62 | static PmDeviceID pm_get_default_device_id(int is_input, char *key) { | |
| 63 | HKEY hkey; | |
| 64 | #define PATTERN_MAX 256 | |
| 65 | char pattern[PATTERN_MAX]; | |
| 66 | DWORD pattern_max = PATTERN_MAX; | |
| 67 | DWORD dwType; | |
| 68 | /* Find first input or device -- this is the default. */ | |
| 69 | PmDeviceID id = pmNoDevice; | |
| 70 | int i, j; | |
| 71 | Pm_Initialize(); /* make sure descriptors exist! */ | |
| 72 | for (i = 0; i < pm_descriptor_index; i++) { | |
| 73 | if (descriptors[i].pub.input == is_input) { | |
| 74 | id = i; | |
| 75 | break; | |
| 76 | } | |
| 77 | } | |
| 78 | /* Look in registry for a default device name pattern. */ | |
| 79 | if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software", 0, KEY_READ, &hkey) != | |
| 80 | ERROR_SUCCESS) { | |
| 81 | return id; | |
| 82 | } | |
| 83 | if (RegOpenKeyExA(hkey, "JavaSoft", 0, KEY_READ, &hkey) != | |
| 84 | ERROR_SUCCESS) { | |
| 85 | return id; | |
| 86 | } | |
| 87 | if (RegOpenKeyExA(hkey, "Prefs", 0, KEY_READ, &hkey) != | |
| 88 | ERROR_SUCCESS) { | |
| 89 | return id; | |
| 90 | } | |
| 91 | if (RegOpenKeyExA(hkey, "/Port/Midi", 0, KEY_READ, &hkey) != | |
| 92 | ERROR_SUCCESS) { | |
| 93 | return id; | |
| 94 | } | |
| 95 | if (RegQueryValueExA(hkey, key, NULL, &dwType, (BYTE *)pattern, &pattern_max) != | |
| 96 | ERROR_SUCCESS) { | |
| 97 | return id; | |
| 98 | } | |
| 99 | ||
| 100 | /* decode pattern: upper case encoded with "/" prefix */ | |
| 101 | i = j = 0; | |
| 102 | while (pattern[i]) { | |
| 103 | if (pattern[i] == '/' && pattern[i + 1]) { | |
| 104 | pattern[j++] = toupper(pattern[++i]); | |
| 105 | } else { | |
| 106 | pattern[j++] = tolower(pattern[i]); | |
| 107 | } | |
| 108 | i++; | |
| 109 | } | |
| 110 | pattern[j] = 0; /* end of string */ | |
| 111 | ||
| 112 | /* now pattern is the string from the registry; search for match */ | |
| 113 | i = pm_find_default_device(pattern, is_input); | |
| 114 | if (i != pmNoDevice) { | |
| 115 | id = i; | |
| 116 | } | |
| 117 | return id; | |
| 118 | } | |
| 119 | ||
| 120 | ||
| 121 | PmDeviceID Pm_GetDefaultInputDeviceID() { | |
| 122 | return pm_get_default_device_id(TRUE, | |
| 123 | (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"); | |
| 124 | } | |
| 125 | ||
| 126 | ||
| 127 | PmDeviceID Pm_GetDefaultOutputDeviceID() { | |
| 128 | return pm_get_default_device_id(FALSE, | |
| 129 | (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"); | |
| 130 | } | |
| 131 | ||
| 132 | ||
| 133 | #include "stdio.h" | |
| 134 | ||
| 135 | void *pm_alloc(size_t s) { | |
| 136 | return malloc(s); | |
| 137 | } | |
| 138 | ||
| 139 | ||
| 140 | void pm_free(void *ptr) { | |
| 141 | free(ptr); | |
| 142 | } | |
| 143 | ||
| 144 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | /* ptwinmm.c -- portable timer implementation for win32 */ | |
| 2 | ||
| 3 | ||
| 4 | #include "porttime.h" | |
| 5 | #include "windows.h" | |
| 6 | #include "time.h" | |
| 7 | ||
| 8 | ||
| 9 | TIMECAPS caps; | |
| 10 | ||
| 11 | static long time_offset = 0; | |
| 12 | static int time_started_flag = FALSE; | |
| 13 | static long time_resolution; | |
| 14 | static MMRESULT timer_id; | |
| 15 | static PtCallback *time_callback; | |
| 16 | ||
| 17 | void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, | |
| 18 | DWORD_PTR dw1, DWORD_PTR dw2) | |
| 19 | { | |
| 20 | (*time_callback)(Pt_Time(), (void *) dwUser); | |
| 21 | } | |
| 22 | ||
| 23 | ||
| 24 | PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData) | |
| 25 | { | |
| 26 | if (time_started_flag) return ptAlreadyStarted; | |
| 27 | timeBeginPeriod(resolution); | |
| 28 | time_resolution = resolution; | |
| 29 | time_offset = timeGetTime(); | |
| 30 | time_started_flag = TRUE; | |
| 31 | time_callback = callback; | |
| 32 | if (callback) { | |
| 33 | timer_id = timeSetEvent(resolution, 1, winmm_time_callback, | |
| 34 | (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); | |
| 35 | if (!timer_id) return ptHostError; | |
| 36 | } | |
| 37 | return ptNoError; | |
| 38 | } | |
| 39 | ||
| 40 | ||
| 41 | PMEXPORT PtError Pt_Stop() | |
| 42 | { | |
| 43 | if (!time_started_flag) return ptAlreadyStopped; | |
| 44 | if (time_callback && timer_id) { | |
| 45 | timeKillEvent(timer_id); | |
| 46 | time_callback = NULL; | |
| 47 | timer_id = 0; | |
| 48 | } | |
| 49 | time_started_flag = FALSE; | |
| 50 | timeEndPeriod(time_resolution); | |
| 51 | return ptNoError; | |
| 52 | } | |
| 53 | ||
| 54 | ||
| 55 | PMEXPORT int Pt_Started() | |
| 56 | { | |
| 57 | return time_started_flag; | |
| 58 | } | |
| 59 | ||
| 60 | ||
| 61 | PMEXPORT PtTimestamp Pt_Time() | |
| 62 | { | |
| 63 | return timeGetTime() - time_offset; | |
| 64 | } | |
| 65 | ||
| 66 | ||
| 67 | PMEXPORT void Pt_Sleep(int32_t duration) | |
| 68 | { | |
| 69 | Sleep(duration); | |
| 70 | } |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* | |
| 2 | ||
| 3 | readbinaryplist.c -- Roger B. Dannenberg, Jun 2008 | |
| 4 | Based on ReadBinaryPList.m by Jens Ayton, 2007 | |
| 5 | ||
| 6 | Note that this code is intended to read preference files and has an upper | |
| 7 | bound on file size (currently 100MB) and assumes in some places that 32 bit | |
| 8 | offsets are sufficient. | |
| 9 | ||
| 10 | Here are his comments: | |
| 11 | ||
| 12 | Reader for binary property list files (version 00). | |
| 13 | ||
| 14 | This has been found to work on all 566 binary plists in my ~/Library/Preferences/ | |
| 15 | and /Library/Preferences/ directories. This probably does not provide full | |
| 16 | test coverage. It has also been found to provide different data to Apple's | |
| 17 | implementation when presented with a key-value archive. This is because Apple's | |
| 18 | implementation produces undocumented CFKeyArchiverUID objects. My implementation | |
| 19 | produces dictionaries instead, matching the in-file representation used in XML | |
| 20 | and OpenStep plists. See extract_uid(). | |
| 21 | ||
| 22 | Full disclosure: in implementing this software, I read one comment and one | |
| 23 | struct defintion in CFLite, Apple's implementation, which is under the APSL | |
| 24 | license. I also deduced the information about CFKeyArchiverUID from that code. | |
| 25 | However, none of the implementation was copied. | |
| 26 | ||
| 27 | Copyright (C) 2007 Jens Ayton | |
| 28 | ||
| 29 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 30 | of this software and associated documentation files (the "Software"), to deal | |
| 31 | in the Software without restriction, including without limitation the rights | |
| 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 33 | copies of the Software, and to permit persons to whom the Software is | |
| 34 | furnished to do so, subject to the following conditions: | |
| 35 | ||
| 36 | The above copyright notice and this permission notice shall be included in all | |
| 37 | copies or substantial portions of the Software. | |
| 38 | ||
| 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 45 | SOFTWARE. | |
| 46 | ||
| 47 | */ | |
| 48 | ||
| 49 | /* A note about memory management: | |
| 50 | Strings and possibly other values are unique and because the values | |
| 51 | associated with IDs are cached, you end up with a directed graph rather | |
| 52 | than a tree. It is tricky to free the data because if you do a simple | |
| 53 | depth-first search to free nodes, you will free nodes twice. I decided | |
| 54 | to allocate memory from blocks of 1024 bytes and keep the blocks in a | |
| 55 | list associated with but private to this module. So the user should | |
| 56 | access this module by calling: | |
| 57 | bplist_read_file() or bplist_read_user_pref() or | |
| 58 | bplist_read_system_pref() | |
| 59 | which returns a value. When you are done with the value, call | |
| 60 | bplist_free_data() | |
| 61 | This will of course free the value_ptr returned by bplist_read_*() | |
| 62 | ||
| 63 | To deal with memory exhaustion (what happens when malloc returns | |
| 64 | NULL?), use setjmp/longjmp -- a single setjmp protects the whole | |
| 65 | parser, and allocate uses longjmp to abort. After abort, memory | |
| 66 | is freed and NULL is returned to caller. There is not much here | |
| 67 | in the way of error reporting. | |
| 68 | ||
| 69 | Memory is obtained by calling allocate which either returns the | |
| 70 | memory requested or calls longjmp, so callers don't have to check. | |
| 71 | ||
| 72 | */ | |
| 73 | ||
| 74 | #include <sys/types.h> | |
| 75 | #include <stdlib.h> | |
| 76 | #include <string.h> | |
| 77 | #include <assert.h> | |
| 78 | #include <stdio.h> | |
| 79 | #include <sys/stat.h> | |
| 80 | #include "readbinaryplist.h" | |
| 81 | #include "osxsupport.h" | |
| 82 | #include <Carbon/Carbon.h> | |
| 83 | ||
| 84 | #define NO 0 | |
| 85 | #define YES 1 | |
| 86 | #define BOOL int | |
| 87 | ||
| 88 | //#define MAXPATHLEN 256 | |
| 89 | ||
| 90 | /* there are 2 levels of error logging/printing: | |
| 91 | * BPLIST_LOG and BPLIST_LOG_VERBOSE | |
| 92 | * either or both can be set to non-zero to turn on | |
| 93 | * If BPLIST_LOG_VERBOSE is true, then BPLIST_LOG | |
| 94 | * is also true. | |
| 95 | * | |
| 96 | * In the code, logging is done by calling either | |
| 97 | * bplist_log() or bplist_log_verbose(), which take | |
| 98 | * parameters like printf but might be a no-op. | |
| 99 | */ | |
| 100 | ||
| 101 | #define BPLIST_LOG_VERBOSE 0 | |
| 102 | #define BPLIST_LOG 0 | |
| 103 | ||
| 104 | #if BPLIST_LOG_VERBOSE | |
| 105 | #ifndef BPLIST_LOG | |
| 106 | #define BPLIST_LOG 1 | |
| 107 | #endif | |
| 108 | #endif | |
| 109 | ||
| 110 | #if BPLIST_LOG | |
| 111 | #define bplist_log printf | |
| 112 | #else | |
| 113 | #define bplist_log(...) | |
| 114 | #endif | |
| 115 | ||
| 116 | #if BPLIST_LOG_VERBOSE | |
| 117 | #define bplist_log_verbose bplist_log | |
| 118 | #else | |
| 119 | #define bplist_log_verbose(...) | |
| 120 | #endif | |
| 121 | ||
| 122 | ||
| 123 | /********* MEMORY MANAGEMENT ********/ | |
| 124 | #define BLOCK_SIZE 1024 | |
| 125 | // memory is aligned to multiples of this; assume malloc automatically | |
| 126 | // aligns to this number and assume this number is > sizeof(void *) | |
| 127 | #define ALIGNMENT 8 | |
| 128 | static void *block_list = NULL; | |
| 129 | static char *free_ptr = NULL; | |
| 130 | static char *end_ptr = NULL; | |
| 131 | static jmp_buf abort_parsing; | |
| 132 | ||
| 133 | static void *allocate(size_t size) | |
| 134 | { | |
| 135 | void *result; | |
| 136 | if (free_ptr + size > end_ptr) { | |
| 137 | size_t how_much = BLOCK_SIZE; | |
| 138 | // align everything to 8 bytes | |
| 139 | if (size > BLOCK_SIZE - ALIGNMENT) { | |
| 140 | how_much = size + ALIGNMENT; | |
| 141 | } | |
| 142 | result = malloc(how_much); | |
| 143 | if (result == NULL) { | |
| 144 | /* serious problem */ | |
| 145 | longjmp(abort_parsing, 1); | |
| 146 | } | |
| 147 | *((void **)result) = block_list; | |
| 148 | block_list = result; | |
| 149 | free_ptr = ((char *) result) + ALIGNMENT; | |
| 150 | end_ptr = ((char *) result) + how_much; | |
| 151 | } | |
| 152 | // now, there is enough rooom at free_ptr | |
| 153 | result = free_ptr; | |
| 154 | free_ptr += size; | |
| 155 | return result; | |
| 156 | } | |
| 157 | ||
| 158 | void bplist_free_data() | |
| 159 | { | |
| 160 | while (block_list) { | |
| 161 | void *next = *(void **)block_list; | |
| 162 | free(block_list); | |
| 163 | block_list = next; | |
| 164 | } | |
| 165 | free_ptr = NULL; | |
| 166 | end_ptr = NULL; | |
| 167 | } | |
| 168 | ||
| 169 | // layout of trailer -- last 32 bytes in plist data | |
| 170 | uint8_t unused[6]; | |
| 171 | uint8_t offset_int_size; | |
| 172 | uint8_t object_ref_size; | |
| 173 | uint64_t object_count; | |
| 174 | uint64_t top_level_object; | |
| 175 | uint64_t offset_table_offset; | |
| 176 | ||
| 177 | ||
| 178 | enum | |
| 179 | { | |
| 180 | kHEADER_SIZE = 8, | |
| 181 | kTRAILER_SIZE = 32, //sizeof(bplist_trailer_node), | |
| 182 | kMINIMUM_SANE_SIZE = kHEADER_SIZE + kTRAILER_SIZE | |
| 183 | }; | |
| 184 | ||
| 185 | ||
| 186 | static const char kHEADER_BYTES[kHEADER_SIZE] = "bplist00"; | |
| 187 | ||
| 188 | // map from UID key to previously parsed value | |
| 189 | typedef struct cache_struct { | |
| 190 | uint64_t key; | |
| 191 | value_ptr value; | |
| 192 | struct cache_struct *next; | |
| 193 | } cache_node, *cache_ptr; | |
| 194 | ||
| 195 | ||
| 196 | typedef struct bplist_info | |
| 197 | { | |
| 198 | uint64_t object_count; | |
| 199 | const uint8_t *data_bytes; | |
| 200 | uint64_t length; | |
| 201 | uint64_t offset_table_offset; | |
| 202 | uint8_t offset_int_size; | |
| 203 | uint8_t object_ref_size; | |
| 204 | cache_ptr cache; | |
| 205 | } bplist_info_node, *bplist_info_ptr; | |
| 206 | ||
| 207 | ||
| 208 | static value_ptr bplist_read_pldata(pldata_ptr data); | |
| 209 | static value_ptr bplist_read_pref(char *filename, OSType folder_type); | |
| 210 | static uint64_t read_sized_int(bplist_info_ptr bplist, uint64_t offset, uint8_t size); | |
| 211 | static uint64_t read_offset(bplist_info_ptr bplist, uint64_t index); | |
| 212 | static BOOL read_self_sized_int(bplist_info_ptr bplist, uint64_t offset, uint64_t *outValue, size_t *outSize); | |
| 213 | ||
| 214 | static value_ptr extract_object(bplist_info_ptr bplist, uint64_t objectRef); | |
| 215 | static value_ptr extract_simple(bplist_info_ptr bplist, uint64_t offset); | |
| 216 | static value_ptr extract_int(bplist_info_ptr bplist, uint64_t offset); | |
| 217 | static value_ptr extract_real(bplist_info_ptr bplist, uint64_t offset); | |
| 218 | static value_ptr extract_date(bplist_info_ptr bplist, uint64_t offset); | |
| 219 | static value_ptr extract_data(bplist_info_ptr bplist, uint64_t offset); | |
| 220 | static value_ptr extract_ascii_string(bplist_info_ptr bplist, uint64_t offset); | |
| 221 | static value_ptr extract_unicode_string(bplist_info_ptr bplist, uint64_t offset); | |
| 222 | static value_ptr extract_uid(bplist_info_ptr bplist, uint64_t offset); | |
| 223 | static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset); | |
| 224 | static value_ptr extract_dictionary(bplist_info_ptr bplist, uint64_t offset); | |
| 225 | ||
| 226 | ||
| 227 | value_ptr value_create(void) | |
| 228 | { | |
| 229 | value_ptr value = (value_ptr) allocate(sizeof(value_node)); | |
| 230 | return value; | |
| 231 | } | |
| 232 | ||
| 233 | ||
| 234 | void value_set_integer(value_ptr v, int64_t i) { | |
| 235 | v->tag = kTAG_INT; v->integer = i; | |
| 236 | } | |
| 237 | ||
| 238 | void value_set_real(value_ptr v, double d) { | |
| 239 | v->tag = kTAG_REAL; v->real = d; | |
| 240 | } | |
| 241 | ||
| 242 | // d is seconds since 1 January 2001 | |
| 243 | void value_set_date(value_ptr v, double d) { | |
| 244 | v->tag = kTAG_DATE; v->real = d; | |
| 245 | } | |
| 246 | ||
| 247 | void value_set_ascii_string(value_ptr v, const uint8_t *s, size_t len) { | |
| 248 | v->tag = kTAG_ASCIISTRING; | |
| 249 | v->string = (char *) allocate(len + 1); | |
| 250 | memcpy(v->string, s, len); | |
| 251 | v->string[len] = 0; | |
| 252 | } | |
| 253 | ||
| 254 | void value_set_unicode_string(value_ptr v, const uint8_t *s, size_t len) { | |
| 255 | v->tag = kTAG_UNICODESTRING; | |
| 256 | v->string = (char *) allocate(len + 1); | |
| 257 | memcpy(v->string, s, len); | |
| 258 | v->string[len] = 0; | |
| 259 | } | |
| 260 | ||
| 261 | void value_set_uid(value_ptr v, uint64_t uid) | |
| 262 | { | |
| 263 | v->tag = kTAG_UID; v->uinteger = uid; | |
| 264 | } | |
| 265 | ||
| 266 | // v->data points to a pldata that points to the actual bytes | |
| 267 | // the bytes are copied, so caller must free byte source (*data) | |
| 268 | void value_set_data(value_ptr v, const uint8_t *data, size_t len) { | |
| 269 | v->tag = kTAG_DATA; | |
| 270 | pldata_ptr pldata = (pldata_ptr) allocate(sizeof(pldata_node)); | |
| 271 | pldata->data = (uint8_t *) allocate(len); | |
| 272 | memcpy(pldata->data, data, len); | |
| 273 | pldata->len = len; | |
| 274 | v->data = pldata; | |
| 275 | printf("value at %p gets data at %p\n", v, pldata); | |
| 276 | } | |
| 277 | ||
| 278 | // caller releases ownership of array to value_ptr v | |
| 279 | void value_set_array(value_ptr v, value_ptr *array, size_t length) { | |
| 280 | array_ptr a = (array_ptr) allocate(sizeof(array_node)); | |
| 281 | a->array = array; | |
| 282 | a->length = length; | |
| 283 | v->tag = kTAG_ARRAY; | |
| 284 | v->array = a; | |
| 285 | } | |
| 286 | ||
| 287 | // caller releases ownership of dict to value_ptr v | |
| 288 | void value_set_dict(value_ptr v, dict_ptr dict) { | |
| 289 | v->tag = kTAG_DICTIONARY; | |
| 290 | v->dict = dict; | |
| 291 | } | |
| 292 | ||
| 293 | ||
| 294 | // look up an objectref in the cache, a ref->value_ptr mapping | |
| 295 | value_ptr cache_lookup(cache_ptr cache, uint64_t ref) | |
| 296 | { | |
| 297 | while (cache) { | |
| 298 | if (cache->key == ref) { | |
| 299 | return cache->value; | |
| 300 | } | |
| 301 | cache = cache->next; | |
| 302 | } | |
| 303 | return NULL; | |
| 304 | } | |
| 305 | ||
| 306 | ||
| 307 | // insert an objectref and value in the cache | |
| 308 | void cache_insert(cache_ptr *cache, uint64_t ref, value_ptr value) | |
| 309 | { | |
| 310 | cache_ptr c = (cache_ptr) allocate(sizeof(cache_node)); | |
| 311 | c->key = ref; | |
| 312 | c->value = value; | |
| 313 | c->next = *cache; | |
| 314 | *cache = c; | |
| 315 | } | |
| 316 | ||
| 317 | ||
| 318 | // insert an objectref and value in a dictionary | |
| 319 | void dict_insert(dict_ptr *dict, value_ptr key, value_ptr value) | |
| 320 | { | |
| 321 | dict_ptr d = (dict_ptr) allocate(sizeof(dict_node)); | |
| 322 | d->key = key; | |
| 323 | d->value = value; | |
| 324 | d->next = *dict; | |
| 325 | *dict = d; | |
| 326 | } | |
| 327 | ||
| 328 | ||
| 329 | BOOL is_binary_plist(pldata_ptr data) | |
| 330 | { | |
| 331 | if (data->len < kMINIMUM_SANE_SIZE) return NO; | |
| 332 | return memcmp(data->data, kHEADER_BYTES, kHEADER_SIZE) == 0; | |
| 333 | } | |
| 334 | ||
| 335 | ||
| 336 | value_ptr bplist_read_file(char *filename) | |
| 337 | { | |
| 338 | struct stat stbuf; | |
| 339 | pldata_node pldata; | |
| 340 | FILE *file; | |
| 341 | size_t n; | |
| 342 | value_ptr value; | |
| 343 | int rslt = stat(filename, &stbuf); | |
| 344 | if (rslt) { | |
| 345 | #if BPLIST_LOG | |
| 346 | perror("in stat"); | |
| 347 | #endif | |
| 348 | bplist_log("Could not stat %s, error %d\n", filename, rslt); | |
| 349 | return NULL; | |
| 350 | } | |
| 351 | // if file is >100MB, assume it is not a preferences file and give up | |
| 352 | if (stbuf.st_size > 100000000) { | |
| 353 | bplist_log("Large file %s encountered (%llu bytes) -- not read\n", | |
| 354 | filename, stbuf.st_size); | |
| 355 | return NULL; | |
| 356 | } | |
| 357 | pldata.len = (size_t) stbuf.st_size; | |
| 358 | // note: this is supposed to be malloc, not allocate. It is separate | |
| 359 | // from the graph structure, large, and easy to free right after | |
| 360 | // parsing. | |
| 361 | pldata.data = (uint8_t *) malloc(pldata.len); | |
| 362 | if (!pldata.data) { | |
| 363 | bplist_log("Could not allocate %lu bytes for %s\n", | |
| 364 | (unsigned long) pldata.len, filename); | |
| 365 | return NULL; | |
| 366 | } | |
| 367 | file = fopen(filename, "rb"); | |
| 368 | if (!file) { | |
| 369 | bplist_log("Could not open %s\n", filename); | |
| 370 | return NULL; | |
| 371 | } | |
| 372 | n = fread(pldata.data, 1, pldata.len, file); | |
| 373 | if (n != pldata.len) { | |
| 374 | bplist_log("Error reading from %s\n", filename); | |
| 375 | return NULL; | |
| 376 | } | |
| 377 | value = bplist_read_pldata(&pldata); | |
| 378 | free(pldata.data); | |
| 379 | return value; | |
| 380 | } | |
| 381 | ||
| 382 | ||
| 383 | value_ptr bplist_read_pref(char *filename, OSType folder_type) | |
| 384 | { | |
| 385 | char cstr[MAXPATHLEN]; | |
| 386 | char *foundstr; | |
| 387 | ||
| 388 | memset(cstr, 0, MAXPATHLEN); | |
| 389 | ||
| 390 | // for later OS X, the user preferences folder (~/Library/Preferences) is not available directly from Cocoa, | |
| 391 | // Apple documentation suggests just using POSIX APIs like so. | |
| 392 | if (folder_type == kPreferencesFolderType) | |
| 393 | { | |
| 394 | strlcpy(cstr, getenv("HOME"), MAXPATHLEN); | |
| 395 | strlcat(cstr, "/Library/Preferences", MAXPATHLEN); | |
| 396 | } | |
| 397 | else // the system preferences folder (~/Library/PreferencePanes) is accessible from Cocoa however | |
| 398 | { | |
| 399 | foundstr = FindPrefsDir(); | |
| 400 | ||
| 401 | if (!foundstr) { | |
| 402 | bplist_log("Error finding preferences folder\n"); | |
| 403 | return NULL; | |
| 404 | } | |
| 405 | ||
| 406 | strlcat(cstr, foundstr, MAXPATHLEN); | |
| 407 | free(foundstr); | |
| 408 | foundstr = NULL; | |
| 409 | } | |
| 410 | ||
| 411 | strlcat(cstr, "/", MAXPATHLEN); | |
| 412 | strlcat(cstr, filename, MAXPATHLEN); | |
| 413 | ||
| 414 | return bplist_read_file(cstr); | |
| 415 | } | |
| 416 | ||
| 417 | ||
| 418 | value_ptr bplist_read_system_pref(char *filename) { | |
| 419 | return bplist_read_pref(filename, kSystemPreferencesFolderType); | |
| 420 | } | |
| 421 | ||
| 422 | ||
| 423 | value_ptr bplist_read_user_pref(char *filename) { | |
| 424 | return bplist_read_pref(filename, kPreferencesFolderType); | |
| 425 | } | |
| 426 | ||
| 427 | ||
| 428 | // data is stored with high-order bytes first. | |
| 429 | // read from plist data in a machine-independent fashion | |
| 430 | // | |
| 431 | uint64_t convert_uint64(uint8_t *ptr) | |
| 432 | { | |
| 433 | uint64_t rslt = 0; | |
| 434 | int i; | |
| 435 | // shift in bytes, high-order first | |
| 436 | for (i = 0; i < sizeof(uint64_t); i++) { | |
| 437 | rslt <<= 8; | |
| 438 | rslt += ptr[i]; | |
| 439 | } | |
| 440 | return rslt; | |
| 441 | } | |
| 442 | ||
| 443 | ||
| 444 | value_ptr bplist_read_pldata(pldata_ptr data) | |
| 445 | { | |
| 446 | value_ptr result = NULL; | |
| 447 | bplist_info_node bplist; | |
| 448 | uint8_t *ptr; | |
| 449 | uint64_t top_level_object; | |
| 450 | int i; | |
| 451 | ||
| 452 | if (data == NULL) return NULL; | |
| 453 | if (!is_binary_plist(data)) { | |
| 454 | bplist_log("Bad binary plist: too short or invalid header.\n"); | |
| 455 | return NULL; | |
| 456 | } | |
| 457 | ||
| 458 | // read trailer | |
| 459 | ptr = (uint8_t *) (data->data + data->len - kTRAILER_SIZE); | |
| 460 | bplist.offset_int_size = ptr[6]; | |
| 461 | bplist.object_ref_size = ptr[7]; | |
| 462 | bplist.object_count = convert_uint64(ptr + 8); | |
| 463 | top_level_object = convert_uint64(ptr + 16); | |
| 464 | bplist.offset_table_offset = convert_uint64(ptr + 24); | |
| 465 | ||
| 466 | // Basic sanity checks | |
| 467 | if (bplist.offset_int_size < 1 || bplist.offset_int_size > 8 || | |
| 468 | bplist.object_ref_size < 1 || bplist.object_ref_size > 8 || | |
| 469 | bplist.offset_table_offset < kHEADER_SIZE) { | |
| 470 | bplist_log("Bad binary plist: trailer declared insane.\n"); | |
| 471 | return NULL; | |
| 472 | } | |
| 473 | ||
| 474 | // Ensure offset table is inside file | |
| 475 | uint64_t offsetTableSize = bplist.offset_int_size * bplist.object_count; | |
| 476 | if (offsetTableSize + bplist.offset_table_offset + kTRAILER_SIZE > | |
| 477 | data->len) { | |
| 478 | bplist_log("Bad binary plist: offset table overlaps end of container.\n"); | |
| 479 | return NULL; | |
| 480 | } | |
| 481 | ||
| 482 | bplist.data_bytes = data->data; | |
| 483 | bplist.length = data->len; | |
| 484 | bplist.cache = NULL; /* dictionary is empty */ | |
| 485 | ||
| 486 | bplist_log_verbose("Got a sane bplist with %llu items, offset_int_size: %u, object_ref_size: %u\n", | |
| 487 | bplist.object_count, bplist.offset_int_size, | |
| 488 | bplist.object_ref_size); | |
| 489 | /* at this point, we are ready to do some parsing which allocates | |
| 490 | memory for the result data structure. If memory allocation (using | |
| 491 | allocate fails, a longjmp will return to here and we simply give up | |
| 492 | */ | |
| 493 | i = setjmp(abort_parsing); | |
| 494 | if (i == 0) { | |
| 495 | result = extract_object(&bplist, top_level_object); | |
| 496 | } else { | |
| 497 | bplist_log("allocate() failed to allocate memory. Giving up.\n"); | |
| 498 | result = NULL; | |
| 499 | } | |
| 500 | if (!result) { | |
| 501 | bplist_free_data(); | |
| 502 | } | |
| 503 | return result; | |
| 504 | } | |
| 505 | ||
| 506 | ||
| 507 | static value_ptr extract_object(bplist_info_ptr bplist, uint64_t objectRef) | |
| 508 | { | |
| 509 | uint64_t offset; | |
| 510 | value_ptr result = NULL; | |
| 511 | uint8_t objectTag; | |
| 512 | ||
| 513 | if (objectRef >= bplist->object_count) { | |
| 514 | // Out-of-range object reference. | |
| 515 | bplist_log("Bad binary plist: object index is out of range.\n"); | |
| 516 | return NULL; | |
| 517 | } | |
| 518 | ||
| 519 | // Use cached object if it exists | |
| 520 | result = cache_lookup(bplist->cache, objectRef); | |
| 521 | if (result != NULL) return result; | |
| 522 | ||
| 523 | // Otherwise, find object in file. | |
| 524 | offset = read_offset(bplist, objectRef); | |
| 525 | if (offset > bplist->length) { | |
| 526 | // Out-of-range offset. | |
| 527 | bplist_log("Bad binary plist: object outside container.\n"); | |
| 528 | return NULL; | |
| 529 | } | |
| 530 | objectTag = *(bplist->data_bytes + offset); | |
| 531 | switch (objectTag & 0xF0) { | |
| 532 | case kTAG_SIMPLE: | |
| 533 | result = extract_simple(bplist, offset); | |
| 534 | break; | |
| 535 | ||
| 536 | case kTAG_INT: | |
| 537 | result = extract_int(bplist, offset); | |
| 538 | break; | |
| 539 | ||
| 540 | case kTAG_REAL: | |
| 541 | result = extract_real(bplist, offset); | |
| 542 | break; | |
| 543 | ||
| 544 | case kTAG_DATE: | |
| 545 | result = extract_date(bplist, offset); | |
| 546 | break; | |
| 547 | ||
| 548 | case kTAG_DATA: | |
| 549 | result = extract_data(bplist, offset); | |
| 550 | break; | |
| 551 | ||
| 552 | case kTAG_ASCIISTRING: | |
| 553 | result = extract_ascii_string(bplist, offset); | |
| 554 | break; | |
| 555 | ||
| 556 | case kTAG_UNICODESTRING: | |
| 557 | result = extract_unicode_string(bplist, offset); | |
| 558 | break; | |
| 559 | ||
| 560 | case kTAG_UID: | |
| 561 | result = extract_uid(bplist, offset); | |
| 562 | break; | |
| 563 | ||
| 564 | case kTAG_ARRAY: | |
| 565 | result = extract_array(bplist, offset); | |
| 566 | break; | |
| 567 | ||
| 568 | case kTAG_DICTIONARY: | |
| 569 | result = extract_dictionary(bplist, offset); | |
| 570 | break; | |
| 571 | ||
| 572 | default: | |
| 573 | // Unknown tag. | |
| 574 | bplist_log("Bad binary plist: unknown tag 0x%X.\n", | |
| 575 | (objectTag & 0x0F) >> 4); | |
| 576 | result = NULL; | |
| 577 | } | |
| 578 | ||
| 579 | // Cache and return result. | |
| 580 | if (result != NULL) | |
| 581 | cache_insert(&bplist->cache, objectRef, result); | |
| 582 | return result; | |
| 583 | } | |
| 584 | ||
| 585 | ||
| 586 | static uint64_t read_sized_int(bplist_info_ptr bplist, uint64_t offset, | |
| 587 | uint8_t size) | |
| 588 | { | |
| 589 | assert(bplist->data_bytes != NULL && size >= 1 && size <= 8 && | |
| 590 | offset + size <= bplist->length); | |
| 591 | ||
| 592 | uint64_t result = 0; | |
| 593 | const uint8_t *byte = bplist->data_bytes + offset; | |
| 594 | ||
| 595 | do { | |
| 596 | // note that ints seem to be high-order first | |
| 597 | result = (result << 8) | *byte++; | |
| 598 | } while (--size); | |
| 599 | ||
| 600 | return result; | |
| 601 | } | |
| 602 | ||
| 603 | ||
| 604 | static uint64_t read_offset(bplist_info_ptr bplist, uint64_t index) | |
| 605 | { | |
| 606 | assert(index < bplist->object_count); | |
| 607 | ||
| 608 | return read_sized_int(bplist, | |
| 609 | bplist->offset_table_offset + bplist->offset_int_size * index, | |
| 610 | bplist->offset_int_size); | |
| 611 | } | |
| 612 | ||
| 613 | ||
| 614 | static BOOL read_self_sized_int(bplist_info_ptr bplist, uint64_t offset, | |
| 615 | uint64_t *outValue, size_t *outSize) | |
| 616 | { | |
| 617 | uint32_t size; | |
| 618 | int64_t value; | |
| 619 | ||
| 620 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 621 | ||
| 622 | size = 1 << (bplist->data_bytes[offset] & 0x0F); | |
| 623 | if (size > 8) { | |
| 624 | // Maximum allowable size in this implementation is 1<<3 = 8 bytes. | |
| 625 | // This also happens to be the biggest we can handle. | |
| 626 | return NO; | |
| 627 | } | |
| 628 | ||
| 629 | if (offset + 1 + size > bplist->length) { | |
| 630 | // Out of range. | |
| 631 | return NO; | |
| 632 | } | |
| 633 | ||
| 634 | value = read_sized_int(bplist, offset + 1, size); | |
| 635 | ||
| 636 | if (outValue != NULL) *outValue = value; | |
| 637 | if (outSize != NULL) *outSize = size + 1; // +1 for tag byte. | |
| 638 | return YES; | |
| 639 | } | |
| 640 | ||
| 641 | ||
| 642 | static value_ptr extract_simple(bplist_info_ptr bplist, uint64_t offset) | |
| 643 | { | |
| 644 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 645 | value_ptr value = value_create(); | |
| 646 | ||
| 647 | switch (bplist->data_bytes[offset]) { | |
| 648 | case kVALUE_NULL: | |
| 649 | value->tag = kVALUE_NULL; | |
| 650 | return value; | |
| 651 | ||
| 652 | case kVALUE_TRUE: | |
| 653 | value->tag = kVALUE_TRUE; | |
| 654 | return value; | |
| 655 | ||
| 656 | case kVALUE_FALSE: | |
| 657 | value->tag = kVALUE_FALSE; | |
| 658 | return value; | |
| 659 | } | |
| 660 | ||
| 661 | // Note: kVALUE_FILLER is treated as invalid, because it, er, is. | |
| 662 | bplist_log("Bad binary plist: invalid atom.\n"); | |
| 663 | free(value); | |
| 664 | return NULL; | |
| 665 | } | |
| 666 | ||
| 667 | ||
| 668 | static value_ptr extract_int(bplist_info_ptr bplist, uint64_t offset) | |
| 669 | { | |
| 670 | value_ptr value = value_create(); | |
| 671 | value->tag = kTAG_INT; | |
| 672 | ||
| 673 | if (!read_self_sized_int(bplist, offset, &value->uinteger, NULL)) { | |
| 674 | bplist_log("Bad binary plist: invalid integer object.\n"); | |
| 675 | } | |
| 676 | ||
| 677 | /* NOTE: originally, I sign-extended here. This was the wrong thing; it | |
| 678 | turns out that negative ints are always stored as 64-bit, and smaller | |
| 679 | ints are unsigned. | |
| 680 | */ | |
| 681 | return value; | |
| 682 | } | |
| 683 | ||
| 684 | ||
| 685 | static value_ptr extract_real(bplist_info_ptr bplist, uint64_t offset) | |
| 686 | { | |
| 687 | value_ptr value = value_create(); | |
| 688 | uint32_t size; | |
| 689 | ||
| 690 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 691 | ||
| 692 | size = 1 << (bplist->data_bytes[offset] & 0x0F); | |
| 693 | ||
| 694 | // FIXME: what to do if faced with other sizes for float/double? | |
| 695 | assert (sizeof (float) == sizeof (uint32_t) && | |
| 696 | sizeof (double) == sizeof (uint64_t)); | |
| 697 | ||
| 698 | if (offset + 1 + size > bplist->length) { | |
| 699 | bplist_log("Bad binary plist: %s object overlaps end of container.\n", | |
| 700 | "floating-point number"); | |
| 701 | free(value); | |
| 702 | return NULL; | |
| 703 | } | |
| 704 | ||
| 705 | if (size == sizeof (float)) { | |
| 706 | // cast is ok because we know size is 4 bytes | |
| 707 | uint32_t i = (uint32_t) read_sized_int(bplist, offset + 1, size); | |
| 708 | // Note that this handles byte swapping. | |
| 709 | value_set_real(value, *(float *)&i); | |
| 710 | return value; | |
| 711 | } else if (size == sizeof (double)) { | |
| 712 | uint64_t i = read_sized_int(bplist, offset + 1, size); | |
| 713 | // Note that this handles byte swapping. | |
| 714 | value_set_real(value, *(double *)&i); | |
| 715 | return value; | |
| 716 | } else { | |
| 717 | // Can't handle floats of other sizes. | |
| 718 | bplist_log("Bad binary plist: can't handle %u-byte float.\n", size); | |
| 719 | free(value); | |
| 720 | return NULL; | |
| 721 | } | |
| 722 | } | |
| 723 | ||
| 724 | ||
| 725 | static value_ptr extract_date(bplist_info_ptr bplist, uint64_t offset) | |
| 726 | { | |
| 727 | value_ptr value; | |
| 728 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 729 | ||
| 730 | // Data has size code like int and real, but only 3 (meaning 8 bytes) is valid. | |
| 731 | if (bplist->data_bytes[offset] != kVALUE_FULLDATETAG) { | |
| 732 | bplist_log("Bad binary plist: invalid size for date object.\n"); | |
| 733 | return NULL; | |
| 734 | } | |
| 735 | ||
| 736 | if (offset + 1 + sizeof (double) > bplist->length) { | |
| 737 | bplist_log("Bad binary plist: %s object overlaps end of container.\n", | |
| 738 | "date"); | |
| 739 | return NULL; | |
| 740 | } | |
| 741 | ||
| 742 | // FIXME: what to do if faced with other sizes for double? | |
| 743 | assert (sizeof (double) == sizeof (uint64_t)); | |
| 744 | ||
| 745 | uint64_t date = read_sized_int(bplist, offset + 1, sizeof(double)); | |
| 746 | // Note that this handles byte swapping. | |
| 747 | value = value_create(); | |
| 748 | value_set_date(value, *(double *)&date); | |
| 749 | return value; | |
| 750 | } | |
| 751 | ||
| 752 | ||
| 753 | uint64_t bplist_get_a_size(bplist_info_ptr bplist, | |
| 754 | uint64_t *offset_ptr, char *msg) | |
| 755 | { | |
| 756 | uint64_t size = bplist->data_bytes[*offset_ptr] & 0x0F; | |
| 757 | (*offset_ptr)++; | |
| 758 | if (size == 0x0F) { | |
| 759 | // 0x0F means separate int size follows. | |
| 760 | // Smaller values are used for short data. | |
| 761 | size_t extra; // the length of the data size we are about to read | |
| 762 | if ((bplist->data_bytes[*offset_ptr] & 0xF0) != kTAG_INT) { | |
| 763 | // Bad data, mistagged size int | |
| 764 | bplist_log("Bad binary plist: %s object size is not tagged as int.\n", | |
| 765 | msg); | |
| 766 | return UINT64_MAX; // error | |
| 767 | } | |
| 768 | ||
| 769 | // read integer data as size, extra tells how many bytes to skip | |
| 770 | if (!read_self_sized_int(bplist, *offset_ptr, &size, &extra)) { | |
| 771 | bplist_log("Bad binary plist: invalid %s object size tag.\n", | |
| 772 | "data"); | |
| 773 | return UINT64_MAX; // error | |
| 774 | } | |
| 775 | (*offset_ptr) += extra; | |
| 776 | } | |
| 777 | ||
| 778 | if (*offset_ptr + size > bplist->length) { | |
| 779 | bplist_log("Bad binary plist: %s object overlaps end of container.\n", | |
| 780 | "data"); | |
| 781 | return UINT64_MAX; // error | |
| 782 | } | |
| 783 | return size; | |
| 784 | } | |
| 785 | ||
| 786 | ||
| 787 | static value_ptr extract_data(bplist_info_ptr bplist, uint64_t offset) | |
| 788 | { | |
| 789 | uint64_t size; | |
| 790 | value_ptr value; | |
| 791 | ||
| 792 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 793 | ||
| 794 | if ((size = bplist_get_a_size(bplist, &offset, (char *)"data")) == UINT64_MAX) | |
| 795 | return NULL; | |
| 796 | ||
| 797 | value = value_create(); | |
| 798 | // cast is ok because we only allow files up to 100MB: | |
| 799 | value_set_data(value, bplist->data_bytes + (size_t) offset, (size_t) size); | |
| 800 | return value; | |
| 801 | } | |
| 802 | ||
| 803 | ||
| 804 | static value_ptr extract_ascii_string(bplist_info_ptr bplist, uint64_t offset) | |
| 805 | { | |
| 806 | uint64_t size; | |
| 807 | value_ptr value; // return value | |
| 808 | ||
| 809 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 810 | ||
| 811 | if ((size = bplist_get_a_size(bplist, &offset, (char *)"ascii string")) == | |
| 812 | UINT64_MAX) | |
| 813 | return NULL; | |
| 814 | ||
| 815 | value = value_create(); | |
| 816 | // cast is ok because we only allow 100MB files | |
| 817 | value_set_ascii_string(value, bplist->data_bytes + (size_t) offset, | |
| 818 | (size_t) size); | |
| 819 | return value; | |
| 820 | } | |
| 821 | ||
| 822 | ||
| 823 | static value_ptr extract_unicode_string(bplist_info_ptr bplist, uint64_t offset) | |
| 824 | { | |
| 825 | uint64_t size; | |
| 826 | value_ptr value; | |
| 827 | ||
| 828 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 829 | ||
| 830 | if ((size = bplist_get_a_size(bplist, &offset, (char *)"unicode string")) == | |
| 831 | UINT64_MAX) | |
| 832 | return NULL; | |
| 833 | ||
| 834 | value = value_create(); | |
| 835 | // cast is ok because we only allow 100MB files | |
| 836 | value_set_unicode_string(value, bplist->data_bytes + (size_t) offset, | |
| 837 | (size_t) size); | |
| 838 | return value; | |
| 839 | } | |
| 840 | ||
| 841 | ||
| 842 | static value_ptr extract_uid(bplist_info_ptr bplist, uint64_t offset) | |
| 843 | { | |
| 844 | /* UIDs are used by Cocoa's key-value coder. | |
| 845 | When writing other plist formats, they are expanded to dictionaries of | |
| 846 | the form <dict><key>CF$UID</key><integer>value</integer></dict>, so we | |
| 847 | do the same here on reading. This results in plists identical to what | |
| 848 | running plutil -convert xml1 gives us. However, this is not the same | |
| 849 | result as [Core]Foundation's plist parser, which extracts them as un- | |
| 850 | introspectable CF objects. In fact, it even seems to convert the CF$UID | |
| 851 | dictionaries from XML plists on the fly. | |
| 852 | */ | |
| 853 | ||
| 854 | value_ptr value; | |
| 855 | uint64_t uid; | |
| 856 | ||
| 857 | if (!read_self_sized_int(bplist, offset, &uid, NULL)) { | |
| 858 | bplist_log("Bad binary plist: invalid UID object.\n"); | |
| 859 | return NULL; | |
| 860 | } | |
| 861 | ||
| 862 | // assert(NO); // original code suggests using a string for a key | |
| 863 | // but our dictionaries all use big ints for keys, so I don't know | |
| 864 | // what to do here | |
| 865 | ||
| 866 | // In practice, I believe this code is never executed by PortMidi. | |
| 867 | // I changed it to do something and not raise compiler warnings, but | |
| 868 | // not sure what the code should do. | |
| 869 | ||
| 870 | value = value_create(); | |
| 871 | value_set_uid(value, uid); | |
| 872 | // return [NSDictionary dictionaryWithObject: | |
| 873 | // [NSNumber numberWithUnsignedLongLong:value] | |
| 874 | // forKey:"CF$UID"]; | |
| 875 | return value; | |
| 876 | } | |
| 877 | ||
| 878 | ||
| 879 | static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset) | |
| 880 | { | |
| 881 | uint64_t i, count; | |
| 882 | uint64_t size; | |
| 883 | uint64_t elementID; | |
| 884 | value_ptr element = NULL; | |
| 885 | value_ptr *array = NULL; | |
| 886 | value_ptr value = NULL; | |
| 887 | BOOL ok = YES; | |
| 888 | ||
| 889 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 890 | ||
| 891 | if ((count = bplist_get_a_size(bplist, &offset, (char *)"array")) == UINT64_MAX) | |
| 892 | return NULL; | |
| 893 | ||
| 894 | if (count > UINT64_MAX / bplist->object_ref_size - offset) { | |
| 895 | // Offset overflow. | |
| 896 | bplist_log("Bad binary plist: %s object overlaps end of container.\n", | |
| 897 | "array"); | |
| 898 | return NULL; | |
| 899 | } | |
| 900 | ||
| 901 | size = bplist->object_ref_size * count; | |
| 902 | if (size + offset > bplist->length) { | |
| 903 | bplist_log("Bad binary plist: %s object overlaps end of container.\n", | |
| 904 | "array"); | |
| 905 | return NULL; | |
| 906 | } | |
| 907 | ||
| 908 | // got count, the number of array elements | |
| 909 | ||
| 910 | value = value_create(); | |
| 911 | assert(value); | |
| 912 | ||
| 913 | if (count == 0) { | |
| 914 | // count must be size_t or smaller because max file size is 100MB | |
| 915 | value_set_array(value, array, (size_t) count); | |
| 916 | return value; | |
| 917 | } | |
| 918 | ||
| 919 | array = allocate(sizeof(value_ptr) * (size_t) count); | |
| 920 | ||
| 921 | for (i = 0; i != count; ++i) { | |
| 922 | bplist_log_verbose("[%u]\n", i); | |
| 923 | elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size, | |
| 924 | bplist->object_ref_size); | |
| 925 | element = extract_object(bplist, elementID); | |
| 926 | if (element != NULL) { | |
| 927 | array[i] = element; | |
| 928 | } else { | |
| 929 | ok = NO; | |
| 930 | break; | |
| 931 | } | |
| 932 | } | |
| 933 | if (ok) { // count is smaller than size_t max because of 100MB file limit | |
| 934 | value_set_array(value, array, (size_t) count); | |
| 935 | } | |
| 936 | ||
| 937 | return value; | |
| 938 | } | |
| 939 | ||
| 940 | ||
| 941 | static value_ptr extract_dictionary(bplist_info_ptr bplist, uint64_t offset) | |
| 942 | { | |
| 943 | uint64_t i, count; | |
| 944 | uint64_t size; | |
| 945 | uint64_t elementID; | |
| 946 | value_ptr value = NULL; | |
| 947 | dict_ptr dict = NULL; | |
| 948 | BOOL ok = YES; | |
| 949 | ||
| 950 | assert(bplist->data_bytes != NULL && offset < bplist->length); | |
| 951 | ||
| 952 | ||
| 953 | if ((count = bplist_get_a_size(bplist, &offset, (char *)"array")) == UINT64_MAX) | |
| 954 | return NULL; | |
| 955 | ||
| 956 | if (count > UINT64_MAX / (bplist->object_ref_size * 2) - offset) { | |
| 957 | // Offset overflow. | |
| 958 | bplist_log("Bad binary plist: %s object overlaps end of container.\n", | |
| 959 | "dictionary"); | |
| 960 | return NULL; | |
| 961 | } | |
| 962 | ||
| 963 | size = bplist->object_ref_size * count * 2; | |
| 964 | if (size + offset > bplist->length) { | |
| 965 | bplist_log("Bad binary plist: %s object overlaps end of container.\n", | |
| 966 | "dictionary"); | |
| 967 | return NULL; | |
| 968 | } | |
| 969 | ||
| 970 | value = value_create(); | |
| 971 | if (count == 0) { | |
| 972 | value_set_dict(value, NULL); | |
| 973 | return value; | |
| 974 | } | |
| 975 | ||
| 976 | for (i = 0; i != count; ++i) { | |
| 977 | value_ptr key; | |
| 978 | value_ptr val; | |
| 979 | elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size, | |
| 980 | bplist->object_ref_size); | |
| 981 | key = extract_object(bplist, elementID); | |
| 982 | if (key != NULL) { | |
| 983 | bplist_log_verbose("key: %p\n", key); | |
| 984 | } else { | |
| 985 | ok = NO; | |
| 986 | break; | |
| 987 | } | |
| 988 | ||
| 989 | elementID = read_sized_int(bplist, | |
| 990 | offset + (i + count) * bplist->object_ref_size, | |
| 991 | bplist->object_ref_size); | |
| 992 | val = extract_object(bplist, elementID); | |
| 993 | if (val != NULL) { | |
| 994 | dict_insert(&dict, key, val); | |
| 995 | } else { | |
| 996 | ok = NO; | |
| 997 | break; | |
| 998 | } | |
| 999 | } | |
| 1000 | if (ok) { | |
| 1001 | value_set_dict(value, dict); | |
| 1002 | } | |
| 1003 | ||
| 1004 | return value; | |
| 1005 | } | |
| 1006 | ||
| 1007 | /*************** functions for accessing values ****************/ | |
| 1008 | ||
| 1009 | ||
| 1010 | char *value_get_asciistring(value_ptr v) | |
| 1011 | { | |
| 1012 | if (v->tag != kTAG_ASCIISTRING) return NULL; | |
| 1013 | return v->string; | |
| 1014 | } | |
| 1015 | ||
| 1016 | ||
| 1017 | value_ptr value_dict_lookup_using_string(value_ptr v, char *key) | |
| 1018 | { | |
| 1019 | dict_ptr dict; | |
| 1020 | if (v->tag != kTAG_DICTIONARY) return NULL; // not a dictionary | |
| 1021 | dict = v->dict; | |
| 1022 | /* search for key */ | |
| 1023 | while (dict) { | |
| 1024 | if (dict->key && dict->key->tag == kTAG_ASCIISTRING && | |
| 1025 | strcmp(key, dict->key->string) == 0) { // found it | |
| 1026 | return dict->value; | |
| 1027 | } | |
| 1028 | dict = dict->next; | |
| 1029 | } | |
| 1030 | return NULL; /* not found */ | |
| 1031 | } | |
| 1032 | ||
| 1033 | value_ptr value_dict_lookup_using_path(value_ptr v, char *path) | |
| 1034 | { | |
| 1035 | char key[MAX_KEY_SIZE]; | |
| 1036 | while (*path) { /* more to the path */ | |
| 1037 | int i = 0; | |
| 1038 | while (i < MAX_KEY_SIZE - 1) { | |
| 1039 | key[i] = *path++; | |
| 1040 | if (key[i] == '/') { /* end of entry in path */ | |
| 1041 | key[i + 1] = 0; | |
| 1042 | break; | |
| 1043 | } | |
| 1044 | if (!key[i]) { | |
| 1045 | path--; /* back up to end of string char */ | |
| 1046 | break; /* this will cause outer loop to exit */ | |
| 1047 | } | |
| 1048 | i++; | |
| 1049 | } | |
| 1050 | if (!v || v->tag != kTAG_DICTIONARY) return NULL; | |
| 1051 | /* now, look up the key to get next value */ | |
| 1052 | v = value_dict_lookup_using_string(v, key); | |
| 1053 | if (v == NULL) return NULL; | |
| 1054 | } | |
| 1055 | return v; | |
| 1056 | } | |
| 1057 | ||
| 1058 | ||
| 1059 | /*************** functions for debugging ***************/ | |
| 1060 | ||
| 1061 | void plist_print(value_ptr v) | |
| 1062 | { | |
| 1063 | size_t i; | |
| 1064 | int comma_needed; | |
| 1065 | dict_ptr dict; | |
| 1066 | if (!v) { | |
| 1067 | printf("NULL"); | |
| 1068 | return; | |
| 1069 | } | |
| 1070 | switch (v->tag & 0xF0) { | |
| 1071 | case kTAG_SIMPLE: | |
| 1072 | switch (v->tag) { | |
| 1073 | case kVALUE_NULL: | |
| 1074 | printf("NULL@%p", v); break; | |
| 1075 | case kVALUE_FALSE: | |
| 1076 | printf("FALSE@%p", v); break; | |
| 1077 | case kVALUE_TRUE: | |
| 1078 | printf("TRUE@%p", v); break; | |
| 1079 | default: | |
| 1080 | printf("UNKNOWN tag=%x@%p", v->tag, v); break; | |
| 1081 | } | |
| 1082 | break; | |
| 1083 | case kTAG_INT: | |
| 1084 | printf("%lld@%p", v->integer, v); break; | |
| 1085 | case kTAG_REAL: | |
| 1086 | printf("%g@%p", v->real, v); break; | |
| 1087 | case kTAG_DATE: | |
| 1088 | printf("date:%g@%p", v->real, v); break; | |
| 1089 | case kTAG_DATA: | |
| 1090 | printf("data@%p->%p:[%p:", v, v->data, v->data->data); | |
| 1091 | for (i = 0; i < v->data->len; i++) { | |
| 1092 | printf(" %2x", v->data->data[i]); | |
| 1093 | } | |
| 1094 | printf("]"); break; | |
| 1095 | case kTAG_ASCIISTRING: | |
| 1096 | printf("%p:\"%s\"@%p", v->string, v->string, v); break; | |
| 1097 | case kTAG_UNICODESTRING: | |
| 1098 | printf("unicode:%p:\"%s\"@%p", v->string, v->string, v); break; | |
| 1099 | case kTAG_UID: | |
| 1100 | printf("UID:%llu@%p", v->uinteger, v); break; | |
| 1101 | case kTAG_ARRAY: | |
| 1102 | comma_needed = FALSE; | |
| 1103 | printf("%p->%p:[%p:", v, v->array, v->array->array); | |
| 1104 | for (i = 0; i < v->array->length; i++) { | |
| 1105 | if (comma_needed) printf(", "); | |
| 1106 | plist_print(v->array->array[i]); | |
| 1107 | comma_needed = TRUE; | |
| 1108 | } | |
| 1109 | printf("]"); break; | |
| 1110 | case kTAG_DICTIONARY: | |
| 1111 | comma_needed = FALSE; | |
| 1112 | printf("%p:[", v); | |
| 1113 | dict = v->dict; | |
| 1114 | while (dict) { | |
| 1115 | if (comma_needed) printf(", "); | |
| 1116 | printf("%p:", dict); | |
| 1117 | plist_print(dict->key); | |
| 1118 | printf("->"); | |
| 1119 | plist_print(dict->value); | |
| 1120 | comma_needed = TRUE; | |
| 1121 | dict = dict->next; | |
| 1122 | } | |
| 1123 | printf("]"); break; | |
| 1124 | default: | |
| 1125 | printf("UNKNOWN tag=%x", v->tag); | |
| 1126 | break; | |
| 1127 | } | |
| 1128 | } | |
| 1129 | ||
| 1130 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r0 | r19990 | |
|---|---|---|
| 1 | #ifndef PORT_MIDI_H | |
| 2 | #define PORT_MIDI_H | |
| 3 | #ifdef __cplusplus | |
| 4 | extern "C" { | |
| 5 | #endif /* __cplusplus */ | |
| 6 | ||
| 7 | /* | |
| 8 | * PortMidi Portable Real-Time MIDI Library | |
| 9 | * PortMidi API Header File | |
| 10 | * Latest version available at: http://sourceforge.net/projects/portmedia | |
| 11 | * | |
| 12 | * Copyright (c) 1999-2000 Ross Bencina and Phil Burk | |
| 13 | * Copyright (c) 2001-2006 Roger B. Dannenberg | |
| 14 | * | |
| 15 | * Permission is hereby granted, free of charge, to any person obtaining | |
| 16 | * a copy of this software and associated documentation files | |
| 17 | * (the "Software"), to deal in the Software without restriction, | |
| 18 | * including without limitation the rights to use, copy, modify, merge, | |
| 19 | * publish, distribute, sublicense, and/or sell copies of the Software, | |
| 20 | * and to permit persons to whom the Software is furnished to do so, | |
| 21 | * subject to the following conditions: | |
| 22 | * | |
| 23 | * The above copyright notice and this permission notice shall be | |
| 24 | * included in all copies or substantial portions of the Software. | |
| 25 | * | |
| 26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 27 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 28 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 29 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR | |
| 30 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
| 31 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 32 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 33 | */ | |
| 34 | ||
| 35 | /* | |
| 36 | * The text above constitutes the entire PortMidi license; however, | |
| 37 | * the PortMusic community also makes the following non-binding requests: | |
| 38 | * | |
| 39 | * Any person wishing to distribute modifications to the Software is | |
| 40 | * requested to send the modifications to the original developer so that | |
| 41 | * they can be incorporated into the canonical version. It is also | |
| 42 | * requested that these non-binding requests be included along with the | |
| 43 | * license above. | |
| 44 | */ | |
| 45 | ||
| 46 | /* CHANGELOG FOR PORTMIDI | |
| 47 | * (see ../CHANGELOG.txt) | |
| 48 | * | |
| 49 | * NOTES ON HOST ERROR REPORTING: | |
| 50 | * | |
| 51 | * PortMidi errors (of type PmError) are generic, system-independent errors. | |
| 52 | * When an error does not map to one of the more specific PmErrors, the | |
| 53 | * catch-all code pmHostError is returned. This means that PortMidi has | |
| 54 | * retained a more specific system-dependent error code. The caller can | |
| 55 | * get more information by calling Pm_HasHostError() to test if there is | |
| 56 | * a pending host error, and Pm_GetHostErrorText() to get a text string | |
| 57 | * describing the error. Host errors are reported on a per-device basis | |
| 58 | * because only after you open a device does PortMidi have a place to | |
| 59 | * record the host error code. I.e. only | |
| 60 | * those routines that receive a (PortMidiStream *) argument check and | |
| 61 | * report errors. One exception to this is that Pm_OpenInput() and | |
| 62 | * Pm_OpenOutput() can report errors even though when an error occurs, | |
| 63 | * there is no PortMidiStream* to hold the error. Fortunately, both | |
| 64 | * of these functions return any error immediately, so we do not really | |
| 65 | * need per-device error memory. Instead, any host error code is stored | |
| 66 | * in a global, pmHostError is returned, and the user can call | |
| 67 | * Pm_GetHostErrorText() to get the error message (and the invalid stream | |
| 68 | * parameter will be ignored.) The functions | |
| 69 | * pm_init and pm_term do not fail or raise | |
| 70 | * errors. The job of pm_init is to locate all available devices so that | |
| 71 | * the caller can get information via PmDeviceInfo(). If an error occurs, | |
| 72 | * the device is simply not listed as available. | |
| 73 | * | |
| 74 | * Host errors come in two flavors: | |
| 75 | * a) host error | |
| 76 | * b) host error during callback | |
| 77 | * These can occur w/midi input or output devices. (b) can only happen | |
| 78 | * asynchronously (during callback routines), whereas (a) only occurs while | |
| 79 | * synchronously running PortMidi and any resulting system dependent calls. | |
| 80 | * Both (a) and (b) are reported by the next read or write call. You can | |
| 81 | * also query for asynchronous errors (b) at any time by calling | |
| 82 | * Pm_HasHostError(). | |
| 83 | * | |
| 84 | * NOTES ON COMPILE-TIME SWITCHES | |
| 85 | * | |
| 86 | * DEBUG assumes stdio and a console. Use this if you want automatic, simple | |
| 87 | * error reporting, e.g. for prototyping. If you are using MFC or some | |
| 88 | * other graphical interface with no console, DEBUG probably should be | |
| 89 | * undefined. | |
| 90 | * PM_CHECK_ERRORS more-or-less takes over error checking for return values, | |
| 91 | * stopping your program and printing error messages when an error | |
| 92 | * occurs. This also uses stdio for console text I/O. | |
| 93 | */ | |
| 94 | ||
| 95 | #ifndef WIN32 | |
| 96 | // Linux and OS X have stdint.h | |
| 97 | #include <stdint.h> | |
| 98 | #else | |
| 99 | #ifndef INT32_DEFINED | |
| 100 | // rather than having users install a special .h file for windows, | |
| 101 | // just put the required definitions inline here. porttime.h uses | |
| 102 | // these too, so the definitions are (unfortunately) duplicated there | |
| 103 | typedef int int32_t; | |
| 104 | typedef unsigned int uint32_t; | |
| 105 | #define INT32_DEFINED | |
| 106 | #endif | |
| 107 | #endif | |
| 108 | ||
| 109 | //#ifdef _WINDLL | |
| 110 | //#define PMEXPORT __declspec(dllexport) | |
| 111 | //#else | |
| 112 | #define PMEXPORT | |
| 113 | //#endif | |
| 114 | ||
| 115 | #ifndef FALSE | |
| 116 | #define FALSE 0 | |
| 117 | #endif | |
| 118 | #ifndef TRUE | |
| 119 | #define TRUE 1 | |
| 120 | #endif | |
| 121 | ||
| 122 | /* default size of buffers for sysex transmission: */ | |
| 123 | #define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024 | |
| 124 | ||
| 125 | /** List of portmidi errors.*/ | |
| 126 | typedef enum { | |
| 127 | pmNoError = 0, | |
| 128 | pmNoData = 0, /**< A "no error" return that also indicates no data avail. */ | |
| 129 | pmGotData = 1, /**< A "no error" return that also indicates data available */ | |
| 130 | pmHostError = -10000, | |
| 131 | pmInvalidDeviceId, /** out of range or | |
| 132 | * output device when input is requested or | |
| 133 | * input device when output is requested or | |
| 134 | * device is already opened | |
| 135 | */ | |
| 136 | pmInsufficientMemory, | |
| 137 | pmBufferTooSmall, | |
| 138 | pmBufferOverflow, | |
| 139 | pmBadPtr, /* PortMidiStream parameter is NULL or | |
| 140 | * stream is not opened or | |
| 141 | * stream is output when input is required or | |
| 142 | * stream is input when output is required */ | |
| 143 | pmBadData, /** illegal midi data, e.g. missing EOX */ | |
| 144 | pmInternalError, | |
| 145 | pmBufferMaxSize /** buffer is already as large as it can be */ | |
| 146 | /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */ | |
| 147 | } PmError; | |
| 148 | ||
| 149 | /** | |
| 150 | Pm_Initialize() is the library initialisation function - call this before | |
| 151 | using the library. | |
| 152 | */ | |
| 153 | PMEXPORT PmError Pm_Initialize( void ); | |
| 154 | ||
| 155 | /** | |
| 156 | Pm_Terminate() is the library termination function - call this after | |
| 157 | using the library. | |
| 158 | */ | |
| 159 | PMEXPORT PmError Pm_Terminate( void ); | |
| 160 | ||
| 161 | /** A single PortMidiStream is a descriptor for an open MIDI device. | |
| 162 | */ | |
| 163 | typedef void PortMidiStream; | |
| 164 | #define PmStream PortMidiStream | |
| 165 | ||
| 166 | /** | |
| 167 | Test whether stream has a pending host error. Normally, the client finds | |
| 168 | out about errors through returned error codes, but some errors can occur | |
| 169 | asynchronously where the client does not | |
| 170 | explicitly call a function, and therefore cannot receive an error code. | |
| 171 | The client can test for a pending error using Pm_HasHostError(). If true, | |
| 172 | the error can be accessed and cleared by calling Pm_GetErrorText(). | |
| 173 | Errors are also cleared by calling other functions that can return | |
| 174 | errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The | |
| 175 | client does not need to call Pm_HasHostError(). Any pending error will be | |
| 176 | reported the next time the client performs an explicit function call on | |
| 177 | the stream, e.g. an input or output operation. Until the error is cleared, | |
| 178 | no new error codes will be obtained, even for a different stream. | |
| 179 | */ | |
| 180 | PMEXPORT int Pm_HasHostError( PortMidiStream * stream ); | |
| 181 | ||
| 182 | ||
| 183 | /** Translate portmidi error number into human readable message. | |
| 184 | These strings are constants (set at compile time) so client has | |
| 185 | no need to allocate storage | |
| 186 | */ | |
| 187 | PMEXPORT const char *Pm_GetErrorText( PmError errnum ); | |
| 188 | ||
| 189 | /** Translate portmidi host error into human readable message. | |
| 190 | These strings are computed at run time, so client has to allocate storage. | |
| 191 | After this routine executes, the host error is cleared. | |
| 192 | */ | |
| 193 | PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len); | |
| 194 | ||
| 195 | #define HDRLENGTH 50 | |
| 196 | #define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less | |
| 197 | than this number of characters */ | |
| 198 | ||
| 199 | /** | |
| 200 | Device enumeration mechanism. | |
| 201 | ||
| 202 | Device ids range from 0 to Pm_CountDevices()-1. | |
| 203 | ||
| 204 | */ | |
| 205 | typedef int PmDeviceID; | |
| 206 | #define pmNoDevice -1 | |
| 207 | typedef struct { | |
| 208 | int structVersion; /**< this internal structure version */ | |
| 209 | const char *interf; /**< underlying MIDI API, e.g. MMSystem or DirectX */ | |
| 210 | const char *name; /**< device name, e.g. USB MidiSport 1x1 */ | |
| 211 | int input; /**< true iff input is available */ | |
| 212 | int output; /**< true iff output is available */ | |
| 213 | int opened; /**< used by generic PortMidi code to do error checking on arguments */ | |
| 214 | ||
| 215 | } PmDeviceInfo; | |
| 216 | ||
| 217 | /** Get devices count, ids range from 0 to Pm_CountDevices()-1. */ | |
| 218 | PMEXPORT int Pm_CountDevices( void ); | |
| 219 | /** | |
| 220 | Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID() | |
| 221 | ||
| 222 | Return the default device ID or pmNoDevice if there are no devices. | |
| 223 | The result (but not pmNoDevice) can be passed to Pm_OpenMidi(). | |
| 224 | ||
| 225 | The default device can be specified using a small application | |
| 226 | named pmdefaults that is part of the PortMidi distribution. This | |
| 227 | program in turn uses the Java Preferences object created by | |
| 228 | java.util.prefs.Preferences.userRoot().node("/PortMidi"); the | |
| 229 | preference is set by calling | |
| 230 | prefs.put("PM_RECOMMENDED_OUTPUT_DEVICE", prefName); | |
| 231 | or prefs.put("PM_RECOMMENDED_INPUT_DEVICE", prefName); | |
| 232 | ||
| 233 | In the statements above, prefName is a string describing the | |
| 234 | MIDI device in the form "interf, name" where interf identifies | |
| 235 | the underlying software system or API used by PortMdi to access | |
| 236 | devices and name is the name of the device. These correspond to | |
| 237 | the interf and name fields of a PmDeviceInfo. (Currently supported | |
| 238 | interfaces are "MMSystem" for Win32, "ALSA" for Linux, and | |
| 239 | "CoreMIDI" for OS X, so in fact, there is no choice of interface.) | |
| 240 | In "interf, name", the strings are actually substrings of | |
| 241 | the full interface and name strings. For example, the preference | |
| 242 | "Core, Sport" will match a device with interface "CoreMIDI" | |
| 243 | and name "In USB MidiSport 1x1". It will also match "CoreMIDI" | |
| 244 | and "In USB MidiSport 2x2". The devices are enumerated in device | |
| 245 | ID order, so the lowest device ID that matches the pattern becomes | |
| 246 | the default device. Finally, if the comma-space (", ") separator | |
| 247 | between interface and name parts of the preference is not found, | |
| 248 | the entire preference string is interpreted as a name, and the | |
| 249 | interface part is the empty string, which matches anything. | |
| 250 | ||
| 251 | On the MAC, preferences are stored in | |
| 252 | /Users/$NAME/Library/Preferences/com.apple.java.util.prefs.plist | |
| 253 | which is a binary file. In addition to the pmdefaults program, | |
| 254 | there are utilities that can read and edit this preference file. | |
| 255 | ||
| 256 | On the PC, | |
| 257 | ||
| 258 | On Linux, | |
| 259 | ||
| 260 | */ | |
| 261 | PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID( void ); | |
| 262 | /** see PmDeviceID Pm_GetDefaultInputDeviceID() */ | |
| 263 | PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID( void ); | |
| 264 | ||
| 265 | /** | |
| 266 | PmTimestamp is used to represent a millisecond clock with arbitrary | |
| 267 | start time. The type is used for all MIDI timestampes and clocks. | |
| 268 | */ | |
| 269 | typedef int32_t PmTimestamp; | |
| 270 | typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); | |
| 271 | ||
| 272 | /** TRUE if t1 before t2 */ | |
| 273 | #define PmBefore(t1,t2) ((t1-t2) < 0) | |
| 274 | /** | |
| 275 | \defgroup grp_device Input/Output Devices Handling | |
| 276 | @{ | |
| 277 | */ | |
| 278 | /** | |
| 279 | Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure | |
| 280 | referring to the device specified by id. | |
| 281 | If id is out of range the function returns NULL. | |
| 282 | ||
| 283 | The returned structure is owned by the PortMidi implementation and must | |
| 284 | not be manipulated or freed. The pointer is guaranteed to be valid | |
| 285 | between calls to Pm_Initialize() and Pm_Terminate(). | |
| 286 | */ | |
| 287 | PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); | |
| 288 | ||
| 289 | /** | |
| 290 | Pm_OpenInput() and Pm_OpenOutput() open devices. | |
| 291 | ||
| 292 | stream is the address of a PortMidiStream pointer which will receive | |
| 293 | a pointer to the newly opened stream. | |
| 294 | ||
| 295 | inputDevice is the id of the device used for input (see PmDeviceID above). | |
| 296 | ||
| 297 | inputDriverInfo is a pointer to an optional driver specific data structure | |
| 298 | containing additional information for device setup or handle processing. | |
| 299 | inputDriverInfo is never required for correct operation. If not used | |
| 300 | inputDriverInfo should be NULL. | |
| 301 | ||
| 302 | outputDevice is the id of the device used for output (see PmDeviceID above.) | |
| 303 | ||
| 304 | outputDriverInfo is a pointer to an optional driver specific data structure | |
| 305 | containing additional information for device setup or handle processing. | |
| 306 | outputDriverInfo is never required for correct operation. If not used | |
| 307 | outputDriverInfo should be NULL. | |
| 308 | ||
| 309 | For input, the buffersize specifies the number of input events to be | |
| 310 | buffered waiting to be read using Pm_Read(). For output, buffersize | |
| 311 | specifies the number of output events to be buffered waiting for output. | |
| 312 | (In some cases -- see below -- PortMidi does not buffer output at all | |
| 313 | and merely passes data to a lower-level API, in which case buffersize | |
| 314 | is ignored.) | |
| 315 | ||
| 316 | latency is the delay in milliseconds applied to timestamps to determine | |
| 317 | when the output should actually occur. (If latency is < 0, 0 is assumed.) | |
| 318 | If latency is zero, timestamps are ignored and all output is delivered | |
| 319 | immediately. If latency is greater than zero, output is delayed until the | |
| 320 | message timestamp plus the latency. (NOTE: the time is measured relative | |
| 321 | to the time source indicated by time_proc. Timestamps are absolute, | |
| 322 | not relative delays or offsets.) In some cases, PortMidi can obtain | |
| 323 | better timing than your application by passing timestamps along to the | |
| 324 | device driver or hardware. Latency may also help you to synchronize midi | |
| 325 | data to audio data by matching midi latency to the audio buffer latency. | |
| 326 | ||
| 327 | time_proc is a pointer to a procedure that returns time in milliseconds. It | |
| 328 | may be NULL, in which case a default millisecond timebase (PortTime) is | |
| 329 | used. If the application wants to use PortTime, it should start the timer | |
| 330 | (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the | |
| 331 | application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput, | |
| 332 | it may get a ptAlreadyStarted error from Pt_Start, and the application's | |
| 333 | preferred time resolution and callback function will be ignored. | |
| 334 | time_proc result values are appended to incoming MIDI data, and time_proc | |
| 335 | times are used to schedule outgoing MIDI data (when latency is non-zero). | |
| 336 | ||
| 337 | time_info is a pointer passed to time_proc. | |
| 338 | ||
| 339 | Example: If I provide a timestamp of 5000, latency is 1, and time_proc | |
| 340 | returns 4990, then the desired output time will be when time_proc returns | |
| 341 | timestamp+latency = 5001. This will be 5001-4990 = 11ms from now. | |
| 342 | ||
| 343 | return value: | |
| 344 | Upon success Pm_Open() returns PmNoError and places a pointer to a | |
| 345 | valid PortMidiStream in the stream argument. | |
| 346 | If a call to Pm_Open() fails a nonzero error code is returned (see | |
| 347 | PMError above) and the value of port is invalid. | |
| 348 | ||
| 349 | Any stream that is successfully opened should eventually be closed | |
| 350 | by calling Pm_Close(). | |
| 351 | ||
| 352 | */ | |
| 353 | PMEXPORT PmError Pm_OpenInput( PortMidiStream** stream, | |
| 354 | PmDeviceID inputDevice, | |
| 355 | void *inputDriverInfo, | |
| 356 | int32_t bufferSize, | |
| 357 | PmTimeProcPtr time_proc, | |
| 358 | void *time_info ); | |
| 359 | ||
| 360 | PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream, | |
| 361 | PmDeviceID outputDevice, | |
| 362 | void *outputDriverInfo, | |
| 363 | int32_t bufferSize, | |
| 364 | PmTimeProcPtr time_proc, | |
| 365 | void *time_info, | |
| 366 | int32_t latency ); | |
| 367 | /** @} */ | |
| 368 | ||
| 369 | /** | |
| 370 | \defgroup grp_events_filters Events and Filters Handling | |
| 371 | @{ | |
| 372 | */ | |
| 373 | ||
| 374 | /* \function PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ) | |
| 375 | Pm_SetFilter() sets filters on an open input stream to drop selected | |
| 376 | input types. By default, only active sensing messages are filtered. | |
| 377 | To prohibit, say, active sensing and sysex messages, call | |
| 378 | Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX); | |
| 379 | ||
| 380 | Filtering is useful when midi routing or midi thru functionality is being | |
| 381 | provided by the user application. | |
| 382 | For example, you may want to exclude timing messages (clock, MTC, start/stop/continue), | |
| 383 | while allowing note-related messages to pass. | |
| 384 | Or you may be using a sequencer or drum-machine for MIDI clock information but want to | |
| 385 | exclude any notes it may play. | |
| 386 | */ | |
| 387 | ||
| 388 | /* Filter bit-mask definitions */ | |
| 389 | /** filter active sensing messages (0xFE): */ | |
| 390 | #define PM_FILT_ACTIVE (1 << 0x0E) | |
| 391 | /** filter system exclusive messages (0xF0): */ | |
| 392 | #define PM_FILT_SYSEX (1 << 0x00) | |
| 393 | /** filter MIDI clock message (0xF8) */ | |
| 394 | #define PM_FILT_CLOCK (1 << 0x08) | |
| 395 | /** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ | |
| 396 | #define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)) | |
| 397 | /** filter tick messages (0xF9) */ | |
| 398 | #define PM_FILT_TICK (1 << 0x09) | |
| 399 | /** filter undefined FD messages */ | |
| 400 | #define PM_FILT_FD (1 << 0x0D) | |
| 401 | /** filter undefined real-time messages */ | |
| 402 | #define PM_FILT_UNDEFINED PM_FILT_FD | |
| 403 | /** filter reset messages (0xFF) */ | |
| 404 | #define PM_FILT_RESET (1 << 0x0F) | |
| 405 | /** filter all real-time messages */ | |
| 406 | #define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \ | |
| 407 | PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK) | |
| 408 | /** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ | |
| 409 | #define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18)) | |
| 410 | /** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/ | |
| 411 | #define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D) | |
| 412 | /** per-note aftertouch (0xA0-0xAF) */ | |
| 413 | #define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A) | |
| 414 | /** filter both channel and poly aftertouch */ | |
| 415 | #define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH) | |
| 416 | /** Program changes (0xC0-0xCF) */ | |
| 417 | #define PM_FILT_PROGRAM (1 << 0x1C) | |
| 418 | /** Control Changes (CC's) (0xB0-0xBF)*/ | |
| 419 | #define PM_FILT_CONTROL (1 << 0x1B) | |
| 420 | /** Pitch Bender (0xE0-0xEF*/ | |
| 421 | #define PM_FILT_PITCHBEND (1 << 0x1E) | |
| 422 | /** MIDI Time Code (0xF1)*/ | |
| 423 | #define PM_FILT_MTC (1 << 0x01) | |
| 424 | /** Song Position (0xF2) */ | |
| 425 | #define PM_FILT_SONG_POSITION (1 << 0x02) | |
| 426 | /** Song Select (0xF3)*/ | |
| 427 | #define PM_FILT_SONG_SELECT (1 << 0x03) | |
| 428 | /** Tuning request (0xF6)*/ | |
| 429 | #define PM_FILT_TUNE (1 << 0x06) | |
| 430 | /** All System Common messages (mtc, song position, song select, tune request) */ | |
| 431 | #define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE) | |
| 432 | ||
| 433 | ||
| 434 | PMEXPORT PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ); | |
| 435 | ||
| 436 | #define Pm_Channel(channel) (1<<(channel)) | |
| 437 | /** | |
| 438 | Pm_SetChannelMask() filters incoming messages based on channel. | |
| 439 | The mask is a 16-bit bitfield corresponding to appropriate channels. | |
| 440 | The Pm_Channel macro can assist in calling this function. | |
| 441 | i.e. to set receive only input on channel 1, call with | |
| 442 | Pm_SetChannelMask(Pm_Channel(1)); | |
| 443 | Multiple channels should be OR'd together, like | |
| 444 | Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11)) | |
| 445 | ||
| 446 | Note that channels are numbered 0 to 15 (not 1 to 16). Most | |
| 447 | synthesizer and interfaces number channels starting at 1, but | |
| 448 | PortMidi numbers channels starting at 0. | |
| 449 | ||
| 450 | All channels are allowed by default | |
| 451 | */ | |
| 452 | PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); | |
| 453 | ||
| 454 | /** | |
| 455 | Pm_Abort() terminates outgoing messages immediately | |
| 456 | The caller should immediately close the output port; | |
| 457 | this call may result in transmission of a partial midi message. | |
| 458 | There is no abort for Midi input because the user can simply | |
| 459 | ignore messages in the buffer and close an input device at | |
| 460 | any time. | |
| 461 | */ | |
| 462 | PMEXPORT PmError Pm_Abort( PortMidiStream* stream ); | |
| 463 | ||
| 464 | /** | |
| 465 | Pm_Close() closes a midi stream, flushing any pending buffers. | |
| 466 | (PortMidi attempts to close open streams when the application | |
| 467 | exits -- this is particularly difficult under Windows.) | |
| 468 | */ | |
| 469 | PMEXPORT PmError Pm_Close( PortMidiStream* stream ); | |
| 470 | ||
| 471 | /** | |
| 472 | Pm_Synchronize() instructs PortMidi to (re)synchronize to the | |
| 473 | time_proc passed when the stream was opened. Typically, this | |
| 474 | is used when the stream must be opened before the time_proc | |
| 475 | reference is actually advancing. In this case, message timing | |
| 476 | may be erratic, but since timestamps of zero mean | |
| 477 | "send immediately," initialization messages with zero timestamps | |
| 478 | can be written without a functioning time reference and without | |
| 479 | problems. Before the first MIDI message with a non-zero | |
| 480 | timestamp is written to the stream, the time reference must | |
| 481 | begin to advance (for example, if the time_proc computes time | |
| 482 | based on audio samples, time might begin to advance when an | |
| 483 | audio stream becomes active). After time_proc return values | |
| 484 | become valid, and BEFORE writing the first non-zero timestamped | |
| 485 | MIDI message, call Pm_Synchronize() so that PortMidi can observe | |
| 486 | the difference between the current time_proc value and its | |
| 487 | MIDI stream time. | |
| 488 | ||
| 489 | In the more normal case where time_proc | |
| 490 | values advance continuously, there is no need to call | |
| 491 | Pm_Synchronize. PortMidi will always synchronize at the | |
| 492 | first output message and periodically thereafter. | |
| 493 | */ | |
| 494 | PmError Pm_Synchronize( PortMidiStream* stream ); | |
| 495 | ||
| 496 | ||
| 497 | /** | |
| 498 | Pm_Message() encodes a short Midi message into a 32-bit word. If data1 | |
| 499 | and/or data2 are not present, use zero. | |
| 500 | ||
| 501 | Pm_MessageStatus(), Pm_MessageData1(), and | |
| 502 | Pm_MessageData2() extract fields from a 32-bit midi message. | |
| 503 | */ | |
| 504 | #define Pm_Message(status, data1, data2) \ | |
| 505 | ((((data2) << 16) & 0xFF0000) | \ | |
| 506 | (((data1) << 8) & 0xFF00) | \ | |
| 507 | ((status) & 0xFF)) | |
| 508 | #define Pm_MessageStatus(msg) ((msg) & 0xFF) | |
| 509 | #define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF) | |
| 510 | #define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF) | |
| 511 | ||
| 512 | typedef int32_t PmMessage; /**< see PmEvent */ | |
| 513 | /** | |
| 514 | All midi data comes in the form of PmEvent structures. A sysex | |
| 515 | message is encoded as a sequence of PmEvent structures, with each | |
| 516 | structure carrying 4 bytes of the message, i.e. only the first | |
| 517 | PmEvent carries the status byte. | |
| 518 | ||
| 519 | Note that MIDI allows nested messages: the so-called "real-time" MIDI | |
| 520 | messages can be inserted into the MIDI byte stream at any location, | |
| 521 | including within a sysex message. MIDI real-time messages are one-byte | |
| 522 | messages used mainly for timing (see the MIDI spec). PortMidi retains | |
| 523 | the order of non-real-time MIDI messages on both input and output, but | |
| 524 | it does not specify exactly how real-time messages are processed. This | |
| 525 | is particulary problematic for MIDI input, because the input parser | |
| 526 | must either prepare to buffer an unlimited number of sysex message | |
| 527 | bytes or to buffer an unlimited number of real-time messages that | |
| 528 | arrive embedded in a long sysex message. To simplify things, the input | |
| 529 | parser is allowed to pass real-time MIDI messages embedded within a | |
| 530 | sysex message, and it is up to the client to detect, process, and | |
| 531 | remove these messages as they arrive. | |
| 532 | ||
| 533 | When receiving sysex messages, the sysex message is terminated | |
| 534 | by either an EOX status byte (anywhere in the 4 byte messages) or | |
| 535 | by a non-real-time status byte in the low order byte of the message. | |
| 536 | If you get a non-real-time status byte but there was no EOX byte, it | |
| 537 | means the sysex message was somehow truncated. This is not | |
| 538 | considered an error; e.g., a missing EOX can result from the user | |
| 539 | disconnecting a MIDI cable during sysex transmission. | |
| 540 | ||
| 541 | A real-time message can occur within a sysex message. A real-time | |
| 542 | message will always occupy a full PmEvent with the status byte in | |
| 543 | the low-order byte of the PmEvent message field. (This implies that | |
| 544 | the byte-order of sysex bytes and real-time message bytes may not | |
| 545 | be preserved -- for example, if a real-time message arrives after | |
| 546 | 3 bytes of a sysex message, the real-time message will be delivered | |
| 547 | first. The first word of the sysex message will be delivered only | |
| 548 | after the 4th byte arrives, filling the 4-byte PmEvent message field. | |
| 549 | ||
| 550 | The timestamp field is observed when the output port is opened with | |
| 551 | a non-zero latency. A timestamp of zero means "use the current time", | |
| 552 | which in turn means to deliver the message with a delay of | |
| 553 | latency (the latency parameter used when opening the output port.) | |
| 554 | Do not expect PortMidi to sort data according to timestamps -- | |
| 555 | messages should be sent in the correct order, and timestamps MUST | |
| 556 | be non-decreasing. See also "Example" for Pm_OpenOutput() above. | |
| 557 | ||
| 558 | A sysex message will generally fill many PmEvent structures. On | |
| 559 | output to a PortMidiStream with non-zero latency, the first timestamp | |
| 560 | on sysex message data will determine the time to begin sending the | |
| 561 | message. PortMidi implementations may ignore timestamps for the | |
| 562 | remainder of the sysex message. | |
| 563 | ||
| 564 | On input, the timestamp ideally denotes the arrival time of the | |
| 565 | status byte of the message. The first timestamp on sysex message | |
| 566 | data will be valid. Subsequent timestamps may denote | |
| 567 | when message bytes were actually received, or they may be simply | |
| 568 | copies of the first timestamp. | |
| 569 | ||
| 570 | Timestamps for nested messages: If a real-time message arrives in | |
| 571 | the middle of some other message, it is enqueued immediately with | |
| 572 | the timestamp corresponding to its arrival time. The interrupted | |
| 573 | non-real-time message or 4-byte packet of sysex data will be enqueued | |
| 574 | later. The timestamp of interrupted data will be equal to that of | |
| 575 | the interrupting real-time message to insure that timestamps are | |
| 576 | non-decreasing. | |
| 577 | */ | |
| 578 | typedef struct { | |
| 579 | PmMessage message; | |
| 580 | PmTimestamp timestamp; | |
| 581 | } PmEvent; | |
| 582 | ||
| 583 | /** | |
| 584 | @} | |
| 585 | */ | |
| 586 | /** \defgroup grp_io Reading and Writing Midi Messages | |
| 587 | @{ | |
| 588 | */ | |
| 589 | /** | |
| 590 | Pm_Read() retrieves midi data into a buffer, and returns the number | |
| 591 | of events read. Result is a non-negative number unless an error occurs, | |
| 592 | in which case a PmError value will be returned. | |
| 593 | ||
| 594 | Buffer Overflow | |
| 595 | ||
| 596 | The problem: if an input overflow occurs, data will be lost, ultimately | |
| 597 | because there is no flow control all the way back to the data source. | |
| 598 | When data is lost, the receiver should be notified and some sort of | |
| 599 | graceful recovery should take place, e.g. you shouldn't resume receiving | |
| 600 | in the middle of a long sysex message. | |
| 601 | ||
| 602 | With a lock-free fifo, which is pretty much what we're stuck with to | |
| 603 | enable portability to the Mac, it's tricky for the producer and consumer | |
| 604 | to synchronously reset the buffer and resume normal operation. | |
| 605 | ||
| 606 | Solution: the buffer managed by PortMidi will be flushed when an overflow | |
| 607 | occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow) | |
| 608 | and ordinary processing resumes as soon as a new message arrives. The | |
| 609 | remainder of a partial sysex message is not considered to be a "new | |
| 610 | message" and will be flushed as well. | |
| 611 | ||
| 612 | */ | |
| 613 | PMEXPORT int Pm_Read( PortMidiStream *stream, PmEvent *buffer, int32_t length ); | |
| 614 | ||
| 615 | /** | |
| 616 | Pm_Poll() tests whether input is available, | |
| 617 | returning TRUE, FALSE, or an error value. | |
| 618 | */ | |
| 619 | PMEXPORT PmError Pm_Poll( PortMidiStream *stream); | |
| 620 | ||
| 621 | /** | |
| 622 | Pm_Write() writes midi data from a buffer. This may contain: | |
| 623 | - short messages | |
| 624 | or | |
| 625 | - sysex messages that are converted into a sequence of PmEvent | |
| 626 | structures, e.g. sending data from a file or forwarding them | |
| 627 | from midi input. | |
| 628 | ||
| 629 | Use Pm_WriteSysEx() to write a sysex message stored as a contiguous | |
| 630 | array of bytes. | |
| 631 | ||
| 632 | Sysex data may contain embedded real-time messages. | |
| 633 | */ | |
| 634 | PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length ); | |
| 635 | ||
| 636 | /** | |
| 637 | Pm_WriteShort() writes a timestamped non-system-exclusive midi message. | |
| 638 | Messages are delivered in order as received, and timestamps must be | |
| 639 | non-decreasing. (But timestamps are ignored if the stream was opened | |
| 640 | with latency = 0.) | |
| 641 | */ | |
| 642 | PMEXPORT PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, int32_t msg); | |
| 643 | ||
| 644 | /** | |
| 645 | Pm_WriteSysEx() writes a timestamped system-exclusive midi message. | |
| 646 | */ | |
| 647 | PMEXPORT PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg); | |
| 648 | ||
| 649 | /** @} */ | |
| 650 | ||
| 651 | #ifdef __cplusplus | |
| 652 | } | |
| 653 | #endif /* __cplusplus */ | |
| 654 | #endif /* PORT_MIDI_H */ |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmlinux.c -- PortMidi os-dependent code */ | |
| 2 | ||
| 3 | /* This file only needs to implement pm_init(), which calls various | |
| 4 | routines to register the available midi devices. This file must | |
| 5 | be separate from the main portmidi.c file because it is system | |
| 6 | dependent, and it is separate from, pmlinuxalsa.c, because it | |
| 7 | might need to register non-alsa devices as well. | |
| 8 | ||
| 9 | NOTE: if you add non-ALSA support, you need to fix :alsa_poll() | |
| 10 | in pmlinuxalsa.c, which assumes all input devices are ALSA. | |
| 11 | */ | |
| 12 | ||
| 13 | #include "stdlib.h" | |
| 14 | #include "portmidi.h" | |
| 15 | #include "pmutil.h" | |
| 16 | #include "pminternal.h" | |
| 17 | ||
| 18 | #ifdef PMALSA | |
| 19 | #include "pmlinuxalsa.h" | |
| 20 | #endif | |
| 21 | ||
| 22 | #ifdef PMNULL | |
| 23 | #include "pmlinuxnull.h" | |
| 24 | #endif | |
| 25 | ||
| 26 | PmDeviceID pm_default_input_device_id = -1; | |
| 27 | PmDeviceID pm_default_output_device_id = -1; | |
| 28 | ||
| 29 | extern PmDeviceID find_default_device(char *path, int input, PmDeviceID id); | |
| 30 | ||
| 31 | void pm_init() | |
| 32 | { | |
| 33 | /* Note: it is not an error for PMALSA to fail to initialize. | |
| 34 | * It may be a design error that the client cannot query what subsystems | |
| 35 | * are working properly other than by looking at the list of available | |
| 36 | * devices. | |
| 37 | */ | |
| 38 | #ifdef PMALSA | |
| 39 | pm_linuxalsa_init(); | |
| 40 | #endif | |
| 41 | #ifdef PMNULL | |
| 42 | pm_linuxnull_init(); | |
| 43 | #endif | |
| 44 | // this is set when we return to Pm_Initialize, but we need it | |
| 45 | // now in order to (successfully) call Pm_CountDevices() | |
| 46 | pm_initialized = TRUE; | |
| 47 | pm_default_input_device_id = find_default_device( | |
| 48 | (char *)"/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE, | |
| 49 | pm_default_input_device_id); | |
| 50 | pm_default_output_device_id = find_default_device( | |
| 51 | (char *)"/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE, | |
| 52 | pm_default_output_device_id); | |
| 53 | } | |
| 54 | ||
| 55 | void pm_term(void) | |
| 56 | { | |
| 57 | #ifdef PMALSA | |
| 58 | pm_linuxalsa_term(); | |
| 59 | #endif | |
| 60 | } | |
| 61 | ||
| 62 | PmDeviceID Pm_GetDefaultInputDeviceID() { | |
| 63 | Pm_Initialize(); | |
| 64 | return pm_default_input_device_id; | |
| 65 | } | |
| 66 | ||
| 67 | PmDeviceID Pm_GetDefaultOutputDeviceID() { | |
| 68 | Pm_Initialize(); | |
| 69 | return pm_default_output_device_id; | |
| 70 | } | |
| 71 | ||
| 72 | void *pm_alloc(size_t s) { return malloc(s); } | |
| 73 | ||
| 74 | void pm_free(void *ptr) { free(ptr); } | |
| 75 |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* | |
| 2 | * pmlinuxalsa.c -- system specific definitions | |
| 3 | * | |
| 4 | * written by: | |
| 5 | * Roger Dannenberg (port to Alsa 0.9.x) | |
| 6 | * Clemens Ladisch (provided code examples and invaluable consulting) | |
| 7 | * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation) | |
| 8 | */ | |
| 9 | ||
| 10 | #include "stdlib.h" | |
| 11 | #include "portmidi.h" | |
| 12 | #include "pmutil.h" | |
| 13 | #include "pminternal.h" | |
| 14 | #include "pmlinuxalsa.h" | |
| 15 | #include "string.h" | |
| 16 | #include "porttime.h" | |
| 17 | #include "pmlinux.h" | |
| 18 | #include "osdcomm.h" | |
| 19 | ||
| 20 | #ifdef PTR64 | |
| 21 | typedef UINT64 FPTR; | |
| 22 | #else | |
| 23 | typedef UINT32 FPTR; | |
| 24 | #endif | |
| 25 | ||
| 26 | #include <alsa/asoundlib.h> | |
| 27 | ||
| 28 | /* I used many print statements to debug this code. I left them in the | |
| 29 | * source, and you can turn them on by changing false to true below: | |
| 30 | */ | |
| 31 | #define VERBOSE_ON 0 | |
| 32 | #define VERBOSE if (VERBOSE_ON) | |
| 33 | ||
| 34 | #define MIDI_SYSEX 0xf0 | |
| 35 | #define MIDI_EOX 0xf7 | |
| 36 | ||
| 37 | #if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9 | |
| 38 | #error needs ALSA 0.9.0 or later | |
| 39 | #endif | |
| 40 | ||
| 41 | /* to store client/port in the device descriptor */ | |
| 42 | ||
| 43 | #define MAKE_DESCRIPTOR(client, port) ((void*)(FPTR)(((client) << 8) | (port))) | |
| 44 | #define GET_DESCRIPTOR_CLIENT(info) ((((int)(FPTR)(info)) >> 8) & 0xff) | |
| 45 | #define GET_DESCRIPTOR_PORT(info) (((int)(FPTR)(info)) & 0xff) | |
| 46 | ||
| 47 | #define BYTE unsigned char | |
| 48 | ||
| 49 | extern pm_fns_node pm_linuxalsa_in_dictionary; | |
| 50 | extern pm_fns_node pm_linuxalsa_out_dictionary; | |
| 51 | ||
| 52 | static snd_seq_t *seq = NULL; // all input comes here, | |
| 53 | // output queue allocated on seq | |
| 54 | static int queue, queue_used; /* one for all ports, reference counted */ | |
| 55 | ||
| 56 | typedef struct alsa_descriptor_struct { | |
| 57 | int client; | |
| 58 | int port; | |
| 59 | int this_port; | |
| 60 | int in_sysex; | |
| 61 | snd_midi_event_t *parser; | |
| 62 | int error; /* host error code */ | |
| 63 | } alsa_descriptor_node, *alsa_descriptor_type; | |
| 64 | ||
| 65 | ||
| 66 | /* get_alsa_error_text -- copy error text to potentially short string */ | |
| 67 | /**/ | |
| 68 | static void get_alsa_error_text(char *msg, int len, int err) | |
| 69 | { | |
| 70 | int errlen = strlen(snd_strerror(err)); | |
| 71 | if (errlen < len) { | |
| 72 | strcpy(msg, snd_strerror(err)); | |
| 73 | } else if (len > 20) { | |
| 74 | sprintf(msg, "Alsa error %d", err); | |
| 75 | } else if (len > 4) { | |
| 76 | strcpy(msg, "Alsa"); | |
| 77 | } else { | |
| 78 | msg[0] = 0; | |
| 79 | } | |
| 80 | } | |
| 81 | ||
| 82 | ||
| 83 | /* queue is shared by both input and output, reference counted */ | |
| 84 | static PmError alsa_use_queue(void) | |
| 85 | { | |
| 86 | if (queue_used == 0) { | |
| 87 | snd_seq_queue_tempo_t *tempo; | |
| 88 | ||
| 89 | queue = snd_seq_alloc_queue(seq); | |
| 90 | if (queue < 0) { | |
| 91 | pm_hosterror = queue; | |
| 92 | return pmHostError; | |
| 93 | } | |
| 94 | snd_seq_queue_tempo_alloca(&tempo); | |
| 95 | snd_seq_queue_tempo_set_tempo(tempo, 480000); | |
| 96 | snd_seq_queue_tempo_set_ppq(tempo, 480); | |
| 97 | pm_hosterror = snd_seq_set_queue_tempo(seq, queue, tempo); | |
| 98 | if (pm_hosterror < 0) | |
| 99 | return pmHostError; | |
| 100 | ||
| 101 | snd_seq_start_queue(seq, queue, NULL); | |
| 102 | snd_seq_drain_output(seq); | |
| 103 | } | |
| 104 | ++queue_used; | |
| 105 | return pmNoError; | |
| 106 | } | |
| 107 | ||
| 108 | ||
| 109 | static void alsa_unuse_queue(void) | |
| 110 | { | |
| 111 | if (--queue_used == 0) { | |
| 112 | snd_seq_stop_queue(seq, queue, NULL); | |
| 113 | snd_seq_drain_output(seq); | |
| 114 | snd_seq_free_queue(seq, queue); | |
| 115 | VERBOSE printf("queue freed\n"); | |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | ||
| 120 | /* midi_message_length -- how many bytes in a message? */ | |
| 121 | static int midi_message_length(PmMessage message) | |
| 122 | { | |
| 123 | message &= 0xff; | |
| 124 | if (message < 0x80) { | |
| 125 | return 0; | |
| 126 | } else if (message < 0xf0) { | |
| 127 | static const int length[] = {3, 3, 3, 3, 2, 2, 3}; | |
| 128 | return length[(message - 0x80) >> 4]; | |
| 129 | } else { | |
| 130 | static const int length[] = { | |
| 131 | -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1}; | |
| 132 | return length[message - 0xf0]; | |
| 133 | } | |
| 134 | } | |
| 135 | ||
| 136 | ||
| 137 | static PmError alsa_out_open(PmInternal *midi, void *driverInfo) | |
| 138 | { | |
| 139 | void *client_port = descriptors[midi->device_id].descriptor; | |
| 140 | alsa_descriptor_type desc = (alsa_descriptor_type) | |
| 141 | pm_alloc(sizeof(alsa_descriptor_node)); | |
| 142 | snd_seq_port_info_t *info; | |
| 143 | int err; | |
| 144 | ||
| 145 | if (!desc) return pmInsufficientMemory; | |
| 146 | ||
| 147 | snd_seq_port_info_alloca(&info); | |
| 148 | snd_seq_port_info_set_port(info, midi->device_id); | |
| 149 | snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | | |
| 150 | SND_SEQ_PORT_CAP_READ); | |
| 151 | snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | | |
| 152 | SND_SEQ_PORT_TYPE_APPLICATION); | |
| 153 | snd_seq_port_info_set_port_specified(info, 1); | |
| 154 | err = snd_seq_create_port(seq, info); | |
| 155 | if (err < 0) goto free_desc; | |
| 156 | ||
| 157 | /* fill in fields of desc, which is passed to pm_write routines */ | |
| 158 | midi->descriptor = desc; | |
| 159 | desc->client = GET_DESCRIPTOR_CLIENT(client_port); | |
| 160 | desc->port = GET_DESCRIPTOR_PORT(client_port); | |
| 161 | desc->this_port = midi->device_id; | |
| 162 | desc->in_sysex = 0; | |
| 163 | ||
| 164 | desc->error = 0; | |
| 165 | ||
| 166 | err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &desc->parser); | |
| 167 | if (err < 0) goto free_this_port; | |
| 168 | ||
| 169 | if (midi->latency > 0) { /* must delay output using a queue */ | |
| 170 | err = alsa_use_queue(); | |
| 171 | if (err < 0) goto free_parser; | |
| 172 | ||
| 173 | err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port); | |
| 174 | if (err < 0) goto unuse_queue; /* clean up and return on error */ | |
| 175 | } else { | |
| 176 | err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port); | |
| 177 | if (err < 0) goto free_parser; /* clean up and return on error */ | |
| 178 | } | |
| 179 | return pmNoError; | |
| 180 | ||
| 181 | unuse_queue: | |
| 182 | alsa_unuse_queue(); | |
| 183 | free_parser: | |
| 184 | snd_midi_event_free(desc->parser); | |
| 185 | free_this_port: | |
| 186 | snd_seq_delete_port(seq, desc->this_port); | |
| 187 | free_desc: | |
| 188 | pm_free(desc); | |
| 189 | pm_hosterror = err; | |
| 190 | if (err < 0) { | |
| 191 | get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); | |
| 192 | } | |
| 193 | return pmHostError; | |
| 194 | } | |
| 195 | ||
| 196 | ||
| 197 | static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, | |
| 198 | PmTimestamp timestamp) | |
| 199 | { | |
| 200 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 201 | snd_seq_event_t ev; | |
| 202 | int err; | |
| 203 | ||
| 204 | snd_seq_ev_clear(&ev); | |
| 205 | if (snd_midi_event_encode_byte(desc->parser, byte, &ev) == 1) { | |
| 206 | snd_seq_ev_set_dest(&ev, desc->client, desc->port); | |
| 207 | snd_seq_ev_set_source(&ev, desc->this_port); | |
| 208 | if (midi->latency > 0) { | |
| 209 | /* compute relative time of event = timestamp - now + latency */ | |
| 210 | PmTimestamp now = (midi->time_proc ? | |
| 211 | midi->time_proc(midi->time_info) : | |
| 212 | Pt_Time()); | |
| 213 | int when = timestamp; | |
| 214 | /* if timestamp is zero, send immediately */ | |
| 215 | /* otherwise compute time delay and use delay if positive */ | |
| 216 | if (when == 0) when = now; | |
| 217 | when = (when - now) + midi->latency; | |
| 218 | if (when < 0) when = 0; | |
| 219 | VERBOSE printf("timestamp %d now %d latency %d, ", | |
| 220 | (int) timestamp, (int) now, midi->latency); | |
| 221 | VERBOSE printf("scheduling event after %d\n", when); | |
| 222 | /* message is sent in relative ticks, where 1 tick = 1 ms */ | |
| 223 | snd_seq_ev_schedule_tick(&ev, queue, 1, when); | |
| 224 | /* NOTE: for cases where the user does not supply a time function, | |
| 225 | we could optimize the code by not starting Pt_Time and using | |
| 226 | the alsa tick time instead. I didn't do this because it would | |
| 227 | entail changing the queue management to start the queue tick | |
| 228 | count when PortMidi is initialized and keep it running until | |
| 229 | PortMidi is terminated. (This should be simple, but it's not | |
| 230 | how the code works now.) -RBD */ | |
| 231 | } else { /* send event out without queueing */ | |
| 232 | VERBOSE printf("direct\n"); | |
| 233 | /* ev.queue = SND_SEQ_QUEUE_DIRECT; | |
| 234 | ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */ | |
| 235 | snd_seq_ev_set_direct(&ev); | |
| 236 | } | |
| 237 | VERBOSE printf("sending event\n"); | |
| 238 | err = snd_seq_event_output(seq, &ev); | |
| 239 | if (err < 0) { | |
| 240 | desc->error = err; | |
| 241 | return pmHostError; | |
| 242 | } | |
| 243 | } | |
| 244 | return pmNoError; | |
| 245 | } | |
| 246 | ||
| 247 | ||
| 248 | static PmError alsa_out_close(PmInternal *midi) | |
| 249 | { | |
| 250 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 251 | if (!desc) return pmBadPtr; | |
| 252 | ||
| 253 | if ((pm_hosterror = snd_seq_disconnect_to(seq, desc->this_port, | |
| 254 | desc->client, desc->port))) { | |
| 255 | // if there's an error, try to delete the port anyway, but don't | |
| 256 | // change the pm_hosterror value so we retain the first error | |
| 257 | snd_seq_delete_port(seq, desc->this_port); | |
| 258 | } else { // if there's no error, delete the port and retain any error | |
| 259 | pm_hosterror = snd_seq_delete_port(seq, desc->this_port); | |
| 260 | } | |
| 261 | if (midi->latency > 0) alsa_unuse_queue(); | |
| 262 | snd_midi_event_free(desc->parser); | |
| 263 | midi->descriptor = NULL; /* destroy the pointer to signify "closed" */ | |
| 264 | pm_free(desc); | |
| 265 | if (pm_hosterror) { | |
| 266 | get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, | |
| 267 | pm_hosterror); | |
| 268 | return pmHostError; | |
| 269 | } | |
| 270 | return pmNoError; | |
| 271 | } | |
| 272 | ||
| 273 | ||
| 274 | static PmError alsa_in_open(PmInternal *midi, void *driverInfo) | |
| 275 | { | |
| 276 | void *client_port = descriptors[midi->device_id].descriptor; | |
| 277 | alsa_descriptor_type desc = (alsa_descriptor_type) | |
| 278 | pm_alloc(sizeof(alsa_descriptor_node)); | |
| 279 | snd_seq_port_info_t *info; | |
| 280 | snd_seq_port_subscribe_t *sub; | |
| 281 | snd_seq_addr_t addr; | |
| 282 | int err; | |
| 283 | ||
| 284 | if (!desc) return pmInsufficientMemory; | |
| 285 | ||
| 286 | err = alsa_use_queue(); | |
| 287 | if (err < 0) goto free_desc; | |
| 288 | ||
| 289 | snd_seq_port_info_alloca(&info); | |
| 290 | snd_seq_port_info_set_port(info, midi->device_id); | |
| 291 | snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | | |
| 292 | SND_SEQ_PORT_CAP_READ); | |
| 293 | snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | | |
| 294 | SND_SEQ_PORT_TYPE_APPLICATION); | |
| 295 | snd_seq_port_info_set_port_specified(info, 1); | |
| 296 | err = snd_seq_create_port(seq, info); | |
| 297 | if (err < 0) goto free_queue; | |
| 298 | ||
| 299 | /* fill in fields of desc, which is passed to pm_write routines */ | |
| 300 | midi->descriptor = desc; | |
| 301 | desc->client = GET_DESCRIPTOR_CLIENT(client_port); | |
| 302 | desc->port = GET_DESCRIPTOR_PORT(client_port); | |
| 303 | desc->this_port = midi->device_id; | |
| 304 | desc->in_sysex = 0; | |
| 305 | ||
| 306 | desc->error = 0; | |
| 307 | ||
| 308 | VERBOSE printf("snd_seq_connect_from: %d %d %d\n", | |
| 309 | desc->this_port, desc->client, desc->port); | |
| 310 | snd_seq_port_subscribe_alloca(&sub); | |
| 311 | addr.client = snd_seq_client_id(seq); | |
| 312 | addr.port = desc->this_port; | |
| 313 | snd_seq_port_subscribe_set_dest(sub, &addr); | |
| 314 | addr.client = desc->client; | |
| 315 | addr.port = desc->port; | |
| 316 | snd_seq_port_subscribe_set_sender(sub, &addr); | |
| 317 | snd_seq_port_subscribe_set_time_update(sub, 1); | |
| 318 | /* this doesn't seem to work: messages come in with real timestamps */ | |
| 319 | snd_seq_port_subscribe_set_time_real(sub, 0); | |
| 320 | err = snd_seq_subscribe_port(seq, sub); | |
| 321 | /* err = | |
| 322 | snd_seq_connect_from(seq, desc->this_port, desc->client, desc->port); */ | |
| 323 | if (err < 0) goto free_this_port; /* clean up and return on error */ | |
| 324 | return pmNoError; | |
| 325 | ||
| 326 | free_this_port: | |
| 327 | snd_seq_delete_port(seq, desc->this_port); | |
| 328 | free_queue: | |
| 329 | alsa_unuse_queue(); | |
| 330 | free_desc: | |
| 331 | pm_free(desc); | |
| 332 | pm_hosterror = err; | |
| 333 | if (err < 0) { | |
| 334 | get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); | |
| 335 | } | |
| 336 | return pmHostError; | |
| 337 | } | |
| 338 | ||
| 339 | static PmError alsa_in_close(PmInternal *midi) | |
| 340 | { | |
| 341 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 342 | if (!desc) return pmBadPtr; | |
| 343 | if ((pm_hosterror = snd_seq_disconnect_from(seq, desc->this_port, | |
| 344 | desc->client, desc->port))) { | |
| 345 | snd_seq_delete_port(seq, desc->this_port); /* try to close port */ | |
| 346 | } else { | |
| 347 | pm_hosterror = snd_seq_delete_port(seq, desc->this_port); | |
| 348 | } | |
| 349 | alsa_unuse_queue(); | |
| 350 | pm_free(desc); | |
| 351 | if (pm_hosterror) { | |
| 352 | get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, | |
| 353 | pm_hosterror); | |
| 354 | return pmHostError; | |
| 355 | } | |
| 356 | return pmNoError; | |
| 357 | } | |
| 358 | ||
| 359 | ||
| 360 | static PmError alsa_abort(PmInternal *midi) | |
| 361 | { | |
| 362 | /* NOTE: ALSA documentation is vague. This is supposed to | |
| 363 | * remove any pending output messages. If you can test and | |
| 364 | * confirm this code is correct, please update this comment. -RBD | |
| 365 | */ | |
| 366 | /* Unfortunately, I can't even compile it -- my ALSA version | |
| 367 | * does not implement snd_seq_remove_events_t, so this does | |
| 368 | * not compile. I'll try again, but it looks like I'll need to | |
| 369 | * upgrade my entire Linux OS -RBD | |
| 370 | */ | |
| 371 | /* | |
| 372 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 373 | snd_seq_remove_events_t info; | |
| 374 | snd_seq_addr_t addr; | |
| 375 | addr.client = desc->client; | |
| 376 | addr.port = desc->port; | |
| 377 | snd_seq_remove_events_set_dest(&info, &addr); | |
| 378 | snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST); | |
| 379 | pm_hosterror = snd_seq_remove_events(seq, &info); | |
| 380 | if (pm_hosterror) { | |
| 381 | get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, | |
| 382 | pm_hosterror); | |
| 383 | return pmHostError; | |
| 384 | } | |
| 385 | */ | |
| 386 | printf("WARNING: alsa_abort not implemented\n"); | |
| 387 | return pmNoError; | |
| 388 | } | |
| 389 | ||
| 390 | ||
| 391 | #ifdef GARBAGE | |
| 392 | This is old code here temporarily for reference | |
| 393 | static PmError alsa_write(PmInternal *midi, PmEvent *buffer, int32_t length) | |
| 394 | { | |
| 395 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 396 | int i, bytes; | |
| 397 | unsigned char byte; | |
| 398 | PmMessage msg; | |
| 399 | ||
| 400 | desc->error = 0; | |
| 401 | for (; length > 0; length--, buffer++) { | |
| 402 | VERBOSE printf("message 0x%x\n", buffer->message); | |
| 403 | if (Pm_MessageStatus(buffer->message) == MIDI_SYSEX) | |
| 404 | desc->in_sysex = TRUE; | |
| 405 | if (desc->in_sysex) { | |
| 406 | msg = buffer->message; | |
| 407 | for (i = 0; i < 4; i++) { | |
| 408 | byte = msg; /* extract next byte to send */ | |
| 409 | alsa_write_byte(midi, byte, buffer->timestamp); | |
| 410 | if (byte == MIDI_EOX) { | |
| 411 | desc->in_sysex = FALSE; | |
| 412 | break; | |
| 413 | } | |
| 414 | if (desc->error < 0) break; | |
| 415 | msg >>= 8; /* shift next byte into position */ | |
| 416 | } | |
| 417 | } else { | |
| 418 | bytes = midi_message_length(buffer->message); | |
| 419 | msg = buffer->message; | |
| 420 | for (i = 0; i < bytes; i++) { | |
| 421 | byte = msg; /* extract next byte to send */ | |
| 422 | VERBOSE printf("sending 0x%x\n", byte); | |
| 423 | alsa_write_byte(midi, byte, buffer->timestamp); | |
| 424 | if (desc->error < 0) break; | |
| 425 | msg >>= 8; /* shift next byte into position */ | |
| 426 | } | |
| 427 | } | |
| 428 | } | |
| 429 | if (desc->error < 0) return pmHostError; | |
| 430 | ||
| 431 | VERBOSE printf("snd_seq_drain_output: 0x%x\n", (unsigned int) seq); | |
| 432 | desc->error = snd_seq_drain_output(seq); | |
| 433 | if (desc->error < 0) return pmHostError; | |
| 434 | ||
| 435 | desc->error = pmNoError; | |
| 436 | return pmNoError; | |
| 437 | } | |
| 438 | #endif | |
| 439 | ||
| 440 | ||
| 441 | static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp) | |
| 442 | { | |
| 443 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 444 | VERBOSE printf("snd_seq_drain_output: 0x%x\n", (unsigned int)(FPTR) seq); | |
| 445 | desc->error = snd_seq_drain_output(seq); | |
| 446 | if (desc->error < 0) return pmHostError; | |
| 447 | ||
| 448 | desc->error = pmNoError; | |
| 449 | return pmNoError; | |
| 450 | } | |
| 451 | ||
| 452 | ||
| 453 | static PmError alsa_write_short(PmInternal *midi, PmEvent *event) | |
| 454 | { | |
| 455 | int bytes = midi_message_length(event->message); | |
| 456 | PmMessage msg = event->message; | |
| 457 | int i; | |
| 458 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 459 | for (i = 0; i < bytes; i++) { | |
| 460 | unsigned char byte = msg; | |
| 461 | VERBOSE printf("sending 0x%x\n", byte); | |
| 462 | alsa_write_byte(midi, byte, event->timestamp); | |
| 463 | if (desc->error < 0) break; | |
| 464 | msg >>= 8; /* shift next byte into position */ | |
| 465 | } | |
| 466 | if (desc->error < 0) return pmHostError; | |
| 467 | desc->error = pmNoError; | |
| 468 | return pmNoError; | |
| 469 | } | |
| 470 | ||
| 471 | ||
| 472 | /* alsa_sysex -- implements begin_sysex and end_sysex */ | |
| 473 | PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) { | |
| 474 | return pmNoError; | |
| 475 | } | |
| 476 | ||
| 477 | ||
| 478 | static PmTimestamp alsa_synchronize(PmInternal *midi) | |
| 479 | { | |
| 480 | return 0; /* linux implementation does not use this synchronize function */ | |
| 481 | /* Apparently, Alsa data is relative to the time you send it, and there | |
| 482 | is no reference. If this is true, this is a serious shortcoming of | |
| 483 | Alsa. If not true, then PortMidi has a serious shortcoming -- it | |
| 484 | should be scheduling relative to Alsa's time reference. */ | |
| 485 | } | |
| 486 | ||
| 487 | ||
| 488 | static void handle_event(snd_seq_event_t *ev) | |
| 489 | { | |
| 490 | int device_id = ev->dest.port; | |
| 491 | PmInternal *midi = descriptors[device_id].internalDescriptor; | |
| 492 | PmEvent pm_ev; | |
| 493 | PmTimeProcPtr time_proc = midi->time_proc; | |
| 494 | PmTimestamp timestamp; | |
| 495 | ||
| 496 | /* time stamp should be in ticks, using our queue where 1 tick = 1ms */ | |
| 497 | assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK); | |
| 498 | ||
| 499 | /* if no time_proc, just return "native" ticks (ms) */ | |
| 500 | if (time_proc == NULL) { | |
| 501 | timestamp = ev->time.tick; | |
| 502 | } else { /* translate time to time_proc basis */ | |
| 503 | snd_seq_queue_status_t *queue_status; | |
| 504 | snd_seq_queue_status_alloca(&queue_status); | |
| 505 | snd_seq_get_queue_status(seq, queue, queue_status); | |
| 506 | /* return (now - alsa_now) + alsa_timestamp */ | |
| 507 | timestamp = (*time_proc)(midi->time_info) + ev->time.tick - | |
| 508 | snd_seq_queue_status_get_tick_time(queue_status); | |
| 509 | } | |
| 510 | pm_ev.timestamp = timestamp; | |
| 511 | switch (ev->type) { | |
| 512 | case SND_SEQ_EVENT_NOTEON: | |
| 513 | pm_ev.message = Pm_Message(0x90 | ev->data.note.channel, | |
| 514 | ev->data.note.note & 0x7f, | |
| 515 | ev->data.note.velocity & 0x7f); | |
| 516 | pm_read_short(midi, &pm_ev); | |
| 517 | break; | |
| 518 | case SND_SEQ_EVENT_NOTEOFF: | |
| 519 | pm_ev.message = Pm_Message(0x80 | ev->data.note.channel, | |
| 520 | ev->data.note.note & 0x7f, | |
| 521 | ev->data.note.velocity & 0x7f); | |
| 522 | pm_read_short(midi, &pm_ev); | |
| 523 | break; | |
| 524 | case SND_SEQ_EVENT_KEYPRESS: | |
| 525 | pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel, | |
| 526 | ev->data.note.note & 0x7f, | |
| 527 | ev->data.note.velocity & 0x7f); | |
| 528 | pm_read_short(midi, &pm_ev); | |
| 529 | break; | |
| 530 | case SND_SEQ_EVENT_CONTROLLER: | |
| 531 | pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, | |
| 532 | ev->data.control.param & 0x7f, | |
| 533 | ev->data.control.value & 0x7f); | |
| 534 | pm_read_short(midi, &pm_ev); | |
| 535 | break; | |
| 536 | case SND_SEQ_EVENT_PGMCHANGE: | |
| 537 | pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel, | |
| 538 | ev->data.control.value & 0x7f, 0); | |
| 539 | pm_read_short(midi, &pm_ev); | |
| 540 | break; | |
| 541 | case SND_SEQ_EVENT_CHANPRESS: | |
| 542 | pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel, | |
| 543 | ev->data.control.value & 0x7f, 0); | |
| 544 | pm_read_short(midi, &pm_ev); | |
| 545 | break; | |
| 546 | case SND_SEQ_EVENT_PITCHBEND: | |
| 547 | pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel, | |
| 548 | (ev->data.control.value + 0x2000) & 0x7f, | |
| 549 | ((ev->data.control.value + 0x2000) >> 7) & 0x7f); | |
| 550 | pm_read_short(midi, &pm_ev); | |
| 551 | break; | |
| 552 | case SND_SEQ_EVENT_CONTROL14: | |
| 553 | if (ev->data.control.param < 0x20) { | |
| 554 | pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, | |
| 555 | ev->data.control.param, | |
| 556 | (ev->data.control.value >> 7) & 0x7f); | |
| 557 | pm_read_short(midi, &pm_ev); | |
| 558 | pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, | |
| 559 | ev->data.control.param + 0x20, | |
| 560 | ev->data.control.value & 0x7f); | |
| 561 | pm_read_short(midi, &pm_ev); | |
| 562 | } else { | |
| 563 | pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, | |
| 564 | ev->data.control.param & 0x7f, | |
| 565 | ev->data.control.value & 0x7f); | |
| 566 | ||
| 567 | pm_read_short(midi, &pm_ev); | |
| 568 | } | |
| 569 | break; | |
| 570 | case SND_SEQ_EVENT_SONGPOS: | |
| 571 | pm_ev.message = Pm_Message(0xf2, | |
| 572 | ev->data.control.value & 0x7f, | |
| 573 | (ev->data.control.value >> 7) & 0x7f); | |
| 574 | pm_read_short(midi, &pm_ev); | |
| 575 | break; | |
| 576 | case SND_SEQ_EVENT_SONGSEL: | |
| 577 | pm_ev.message = Pm_Message(0xf3, | |
| 578 | ev->data.control.value & 0x7f, 0); | |
| 579 | pm_read_short(midi, &pm_ev); | |
| 580 | break; | |
| 581 | case SND_SEQ_EVENT_QFRAME: | |
| 582 | pm_ev.message = Pm_Message(0xf1, | |
| 583 | ev->data.control.value & 0x7f, 0); | |
| 584 | pm_read_short(midi, &pm_ev); | |
| 585 | break; | |
| 586 | case SND_SEQ_EVENT_START: | |
| 587 | pm_ev.message = Pm_Message(0xfa, 0, 0); | |
| 588 | pm_read_short(midi, &pm_ev); | |
| 589 | break; | |
| 590 | case SND_SEQ_EVENT_CONTINUE: | |
| 591 | pm_ev.message = Pm_Message(0xfb, 0, 0); | |
| 592 | pm_read_short(midi, &pm_ev); | |
| 593 | break; | |
| 594 | case SND_SEQ_EVENT_STOP: | |
| 595 | pm_ev.message = Pm_Message(0xfc, 0, 0); | |
| 596 | pm_read_short(midi, &pm_ev); | |
| 597 | break; | |
| 598 | case SND_SEQ_EVENT_CLOCK: | |
| 599 | pm_ev.message = Pm_Message(0xf8, 0, 0); | |
| 600 | pm_read_short(midi, &pm_ev); | |
| 601 | break; | |
| 602 | case SND_SEQ_EVENT_TUNE_REQUEST: | |
| 603 | pm_ev.message = Pm_Message(0xf6, 0, 0); | |
| 604 | pm_read_short(midi, &pm_ev); | |
| 605 | break; | |
| 606 | case SND_SEQ_EVENT_RESET: | |
| 607 | pm_ev.message = Pm_Message(0xff, 0, 0); | |
| 608 | pm_read_short(midi, &pm_ev); | |
| 609 | break; | |
| 610 | case SND_SEQ_EVENT_SENSING: | |
| 611 | pm_ev.message = Pm_Message(0xfe, 0, 0); | |
| 612 | pm_read_short(midi, &pm_ev); | |
| 613 | break; | |
| 614 | case SND_SEQ_EVENT_SYSEX: { | |
| 615 | const BYTE *ptr = (const BYTE *) ev->data.ext.ptr; | |
| 616 | /* assume there is one sysex byte to process */ | |
| 617 | pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp); | |
| 618 | break; | |
| 619 | } | |
| 620 | } | |
| 621 | } | |
| 622 | ||
| 623 | ||
| 624 | static PmError alsa_poll(PmInternal *midi) | |
| 625 | { | |
| 626 | snd_seq_event_t *ev; | |
| 627 | /* expensive check for input data, gets data from device: */ | |
| 628 | while (snd_seq_event_input_pending(seq, TRUE) > 0) { | |
| 629 | /* cheap check on local input buffer */ | |
| 630 | while (snd_seq_event_input_pending(seq, FALSE) > 0) { | |
| 631 | /* check for and ignore errors, e.g. input overflow */ | |
| 632 | /* note: if there's overflow, this should be reported | |
| 633 | * all the way through to client. Since input from all | |
| 634 | * devices is merged, we need to find all input devices | |
| 635 | * and set all to the overflow state. | |
| 636 | * NOTE: this assumes every input is ALSA based. | |
| 637 | */ | |
| 638 | int rslt = snd_seq_event_input(seq, &ev); | |
| 639 | if (rslt >= 0) { | |
| 640 | handle_event(ev); | |
| 641 | } else if (rslt == -ENOSPC) { | |
| 642 | int i; | |
| 643 | for (i = 0; i < pm_descriptor_index; i++) { | |
| 644 | if (descriptors[i].pub.input) { | |
| 645 | PmInternal *midi = (PmInternal *) | |
| 646 | descriptors[i].internalDescriptor; | |
| 647 | /* careful, device may not be open! */ | |
| 648 | if (midi) Pm_SetOverflow(midi->queue); | |
| 649 | } | |
| 650 | } | |
| 651 | } | |
| 652 | } | |
| 653 | } | |
| 654 | return pmNoError; | |
| 655 | } | |
| 656 | ||
| 657 | ||
| 658 | static unsigned int alsa_has_host_error(PmInternal *midi) | |
| 659 | { | |
| 660 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 661 | return desc->error; | |
| 662 | } | |
| 663 | ||
| 664 | ||
| 665 | static void alsa_get_host_error(PmInternal *midi, char *msg, unsigned int len) | |
| 666 | { | |
| 667 | alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; | |
| 668 | int err = (pm_hosterror || desc->error); | |
| 669 | get_alsa_error_text(msg, len, err); | |
| 670 | } | |
| 671 | ||
| 672 | ||
| 673 | pm_fns_node pm_linuxalsa_in_dictionary = { | |
| 674 | none_write_short, | |
| 675 | none_sysex, | |
| 676 | none_sysex, | |
| 677 | none_write_byte, | |
| 678 | none_write_short, | |
| 679 | none_write_flush, | |
| 680 | alsa_synchronize, | |
| 681 | alsa_in_open, | |
| 682 | alsa_abort, | |
| 683 | alsa_in_close, | |
| 684 | alsa_poll, | |
| 685 | alsa_has_host_error, | |
| 686 | alsa_get_host_error | |
| 687 | }; | |
| 688 | ||
| 689 | pm_fns_node pm_linuxalsa_out_dictionary = { | |
| 690 | alsa_write_short, | |
| 691 | alsa_sysex, | |
| 692 | alsa_sysex, | |
| 693 | alsa_write_byte, | |
| 694 | alsa_write_short, /* short realtime message */ | |
| 695 | alsa_write_flush, | |
| 696 | alsa_synchronize, | |
| 697 | alsa_out_open, | |
| 698 | alsa_abort, | |
| 699 | alsa_out_close, | |
| 700 | none_poll, | |
| 701 | alsa_has_host_error, | |
| 702 | alsa_get_host_error | |
| 703 | }; | |
| 704 | ||
| 705 | ||
| 706 | /* pm_strdup -- copy a string to the heap. Use this rather than strdup so | |
| 707 | * that we call pm_alloc, not malloc. This allows portmidi to avoid | |
| 708 | * malloc which might cause priority inversion. Probably ALSA is going | |
| 709 | * to call malloc anyway, so this extra work here may be pointless. | |
| 710 | */ | |
| 711 | char *pm_strdup(const char *s) | |
| 712 | { | |
| 713 | int len = strlen(s); | |
| 714 | char *dup = (char *) pm_alloc(len + 1); | |
| 715 | strcpy(dup, s); | |
| 716 | return dup; | |
| 717 | } | |
| 718 | ||
| 719 | ||
| 720 | PmError pm_linuxalsa_init( void ) | |
| 721 | { | |
| 722 | int err; | |
| 723 | snd_seq_client_info_t *cinfo; | |
| 724 | snd_seq_port_info_t *pinfo; | |
| 725 | unsigned int caps; | |
| 726 | ||
| 727 | /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this | |
| 728 | * would cause messages to be dropped if the ALSA buffer fills up. | |
| 729 | * The correct behavior is for writes to block until there is | |
| 730 | * room to send all the data. The client should normally allocate | |
| 731 | * a large enough buffer to avoid blocking on output. | |
| 732 | * Now that blocking is enabled, the seq_event_input() will block | |
| 733 | * if there is no input data. This is not what we want, so must | |
| 734 | * call seq_event_input_pending() to avoid blocking. | |
| 735 | */ | |
| 736 | err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); | |
| 737 | if (err < 0) return err; | |
| 738 | ||
| 739 | snd_seq_client_info_alloca(&cinfo); | |
| 740 | snd_seq_port_info_alloca(&pinfo); | |
| 741 | ||
| 742 | snd_seq_client_info_set_client(cinfo, -1); | |
| 743 | while (snd_seq_query_next_client(seq, cinfo) == 0) { | |
| 744 | snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); | |
| 745 | snd_seq_port_info_set_port(pinfo, -1); | |
| 746 | while (snd_seq_query_next_port(seq, pinfo) == 0) { | |
| 747 | if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM) | |
| 748 | continue; /* ignore Timer and Announce ports on client 0 */ | |
| 749 | caps = snd_seq_port_info_get_capability(pinfo); | |
| 750 | if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE))) | |
| 751 | continue; /* ignore if you cannot read or write port */ | |
| 752 | if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) { | |
| 753 | if (pm_default_output_device_id == -1) | |
| 754 | pm_default_output_device_id = pm_descriptor_index; | |
| 755 | pm_add_device((char *)"ALSA", | |
| 756 | pm_strdup(snd_seq_port_info_get_name(pinfo)), | |
| 757 | FALSE, | |
| 758 | MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), | |
| 759 | snd_seq_port_info_get_port(pinfo)), | |
| 760 | &pm_linuxalsa_out_dictionary); | |
| 761 | } | |
| 762 | if (caps & SND_SEQ_PORT_CAP_SUBS_READ) { | |
| 763 | if (pm_default_input_device_id == -1) | |
| 764 | pm_default_input_device_id = pm_descriptor_index; | |
| 765 | pm_add_device((char *)"ALSA", | |
| 766 | pm_strdup(snd_seq_port_info_get_name(pinfo)), | |
| 767 | TRUE, | |
| 768 | MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), | |
| 769 | snd_seq_port_info_get_port(pinfo)), | |
| 770 | &pm_linuxalsa_in_dictionary); | |
| 771 | } | |
| 772 | } | |
| 773 | } | |
| 774 | return pmNoError; | |
| 775 | } | |
| 776 | ||
| 777 | ||
| 778 | void pm_linuxalsa_term(void) | |
| 779 | { | |
| 780 | if (seq) { | |
| 781 | snd_seq_close(seq); | |
| 782 | pm_free(descriptors); | |
| 783 | descriptors = NULL; | |
| 784 | pm_descriptor_index = 0; | |
| 785 | pm_descriptor_max = 0; | |
| 786 | } | |
| 787 | } |
| Added: svn:eol-style + native Added: svn:mime-type + text/plain |
| r0 | r19990 | |
|---|---|---|
| 1 | /* pmmac.c -- PortMidi os-dependent code */ | |
| 2 | ||
| 3 | /* This file only needs to implement: | |
| 4 | pm_init(), which calls various routines to register the | |
| 5 | available midi devices, | |
| 6 | Pm_GetDefaultInputDeviceID(), and | |
| 7 | Pm_GetDefaultOutputDeviceID(). | |
| 8 | It is seperate from pmmacosxcm because we might want to register | |
| 9 | non-CoreMIDI devices. | |
| 10 | */ | |
| 11 | ||
| 12 | #include "stdlib.h" | |
| 13 | #include "portmidi.h" | |
| 14 | #include "pmutil.h" | |
| 15 | #include "pminternal.h" | |
| 16 | #include "pmmacosxcm.h" | |
| 17 | ||
| 18 | PmDeviceID pm_default_input_device_id = -1; | |
| 19 | PmDeviceID pm_default_output_device_id = -1; | |
| 20 | ||
| 21 | void pm_init() | |
| 22 | { | |
| 23 | PmError err = pm_macosxcm_init(); | |
| 24 | // this is set when we return to Pm_Initialize, but we need it | |
| 25 | // now in order to (successfully) call Pm_CountDevices() | |
| 26 | pm_initialized = TRUE; | |
| 27 | if (!err) { | |
| 28 | pm_default_input_device_id = find_default_device( | |
| 29 | (char *)"/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE, | |
| 30 | pm_default_input_device_id); | |
| 31 | pm_default_output_device_id = find_default_device( | |
| 32 | (char *)"/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE, | |
| 33 | pm_default_output_device_id); | |
| 34 | } | |
| 35 | } | |
| 36 | ||
| 37 | ||
| 38 | void pm_term(void) | |
| 39 | { | |
| 40 | pm_macosxcm_term(); | |
| 41 | } | |
| 42 | ||
| 43 | ||
| 44 | PmDeviceID Pm_GetDefaultInputDeviceID() | |
| 45 | { | |
| 46 | Pm_Initialize(); | |
| 47 | return pm_default_input_device_id; | |
| 48 | } | |
| 49 | ||
| 50 | PmDeviceID Pm_GetDefaultOutputDeviceID() { | |
| 51 | Pm_Initialize(); | |
| 52 | return pm_default_output_device_id; | |
| 53 | } | |
| 54 | ||
| 55 | void *pm_alloc(size_t s) { return malloc(s); } | |
| 56 | ||
| 57 | void pm_free(void *ptr) { free(ptr); } | |
| 58 | ||
| 59 |
| Added: svn:mime-type + text/plain Added: svn:eol-style + native |
| r19989 | r19990 | |
|---|---|---|
| 22 | 22 | $(LIBOBJ)/libjpeg \ |
| 23 | 23 | $(LIBOBJ)/libflac \ |
| 24 | 24 | $(LIBOBJ)/lib7z \ |
| 25 | $(LIBOBJ)/portmidi \ | |
| 25 | 26 | |
| 26 | 27 | |
| 27 | ||
| 28 | 28 | #------------------------------------------------- |
| 29 | 29 | # utility library objects |
| 30 | 30 | #------------------------------------------------- |
| r19989 | r19990 | |
| 382 | 382 | $(LIBOBJ)/lib7z/%.o: $(LIBSRC)/lib7z/%.c | $(OSPREBUILD) |
| 383 | 383 | @echo Compiling $<... |
| 384 | 384 | $(CC) $(CDEFS) $(7ZOPTS) $(CCOMFLAGS) $(CONLYFLAGS) -I$(LIBSRC)/lib7z/ -c $< -o $@ |
| 385 | ||
| 386 | #------------------------------------------------- | |
| 387 | # portmidi library objects | |
| 388 | #------------------------------------------------- | |
| 389 | ||
| 390 | PMOPTS = | |
| 391 | ||
| 392 | # common objects | |
| 393 | LIBPMOBJS = \ | |
| 394 | $(LIBOBJ)/portmidi/portmidi.o \ | |
| 395 | $(LIBOBJ)/portmidi/porttime.o \ | |
| 396 | $(LIBOBJ)/portmidi/pmutil.o | |
| 397 | ||
| 398 | ifeq ($(TARGETOS),linux) | |
| 399 | PMOPTS = -DPMALSA=1 | |
| 400 | ||
| 401 | LIBPMOBJS += \ | |
| 402 | $(LIBOBJ)/portmidi/pmlinux.o \ | |
| 403 | $(LIBOBJ)/portmidi/pmlinuxalsa.o \ | |
| 404 | $(LIBOBJ)/portmidi/finddefaultlinux.o \ | |
| 405 | $(LIBOBJ)/portmidi/ptlinux.o | |
| 406 | ||
| 407 | endif | |
| 408 | ||
| 409 | ifeq ($(TARGETOS),macosx) | |
| 410 | LIBPMOBJS += \ | |
| 411 | $(LIBOBJ)/portmidi/pmmac.o \ | |
| 412 | $(LIBOBJ)/portmidi/pmmacosxcm.o \ | |
| 413 | $(LIBOBJ)/portmidi/finddefault.o \ | |
| 414 | $(LIBOBJ)/portmidi/readbinaryplist.o \ | |
| 415 | $(LIBOBJ)/portmidi/ptmacosx_mach.o \ | |
| 416 | $(LIBOBJ)/portmidi/osxsupport.o | |
| 417 | endif | |
| 418 | ||
| 419 | ifeq ($(TARGETOS),win32) | |
| 420 | LIBPMOBJS += \ | |
| 421 | $(LIBOBJ)/portmidi/pmwin.o \ | |
| 422 | $(LIBOBJ)/portmidi/pmwinmm.o \ | |
| 423 | $(LIBOBJ)/portmidi/ptwinmm.o | |
| 424 | endif | |
| 425 | ||
| 426 | $(OBJ)/portmidi.a: $(LIBPMOBJS) | |
| 427 | ||
| 428 | $(LIBOBJ)/portmidi/%.o: $(LIBSRC)/portmidi/%.c | $(OSPREBUILD) | |
| 429 | @echo Compiling $<... | |
| 430 | $(CC) $(CDEFS) $(PMOPTS) $(CCOMFLAGS) $(CONLYFLAGS) -I$(LIBSRC)/portmidi/ -c $< -o $@ | |
| 431 |
| Previous | 199869 Revisions | Next |