mirror of
https://github.com/holub/mame
synced 2025-04-20 23:42:22 +03:00
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)
This commit is contained in:
parent
e000eedfbb
commit
ad80ff6c3f
31
.gitattributes
vendored
31
.gitattributes
vendored
@ -2159,6 +2159,34 @@ src/lib/libjpeg/jquant2.c svneol=native#text/plain
|
||||
src/lib/libjpeg/jutils.c svneol=native#text/plain
|
||||
src/lib/libjpeg/jversion.h svneol=native#text/plain
|
||||
src/lib/libjpeg/libjpeg.txt svneol=native#text/plain
|
||||
src/lib/portmidi/finddefault.c svneol=native#text/plain
|
||||
src/lib/portmidi/finddefaultlinux.c svneol=native#text/plain
|
||||
src/lib/portmidi/osxsupport.h svneol=native#text/plain
|
||||
src/lib/portmidi/osxsupport.m svneol=native#text/plain
|
||||
src/lib/portmidi/pminternal.h svneol=native#text/plain
|
||||
src/lib/portmidi/pmlinux.c svneol=native#text/plain
|
||||
src/lib/portmidi/pmlinux.h svneol=native#text/plain
|
||||
src/lib/portmidi/pmlinuxalsa.c svneol=native#text/plain
|
||||
src/lib/portmidi/pmlinuxalsa.h svneol=native#text/plain
|
||||
src/lib/portmidi/pmmac.c svneol=native#text/plain
|
||||
src/lib/portmidi/pmmac.h svneol=native#text/plain
|
||||
src/lib/portmidi/pmmacosxcm.c svneol=native#text/plain
|
||||
src/lib/portmidi/pmmacosxcm.h svneol=native#text/plain
|
||||
src/lib/portmidi/pmutil.c svneol=native#text/plain
|
||||
src/lib/portmidi/pmutil.h svneol=native#text/plain
|
||||
src/lib/portmidi/pmwin.c svneol=native#text/plain
|
||||
src/lib/portmidi/pmwinmm.c svneol=native#text/plain
|
||||
src/lib/portmidi/pmwinmm.h svneol=native#text/plain
|
||||
src/lib/portmidi/portmidi.c svneol=native#text/plain
|
||||
src/lib/portmidi/portmidi.h svneol=native#text/plain
|
||||
src/lib/portmidi/porttime.c svneol=native#text/plain
|
||||
src/lib/portmidi/porttime.h svneol=native#text/plain
|
||||
src/lib/portmidi/ptlinux.c svneol=native#text/plain
|
||||
src/lib/portmidi/ptmacosx_cf.c svneol=native#text/plain
|
||||
src/lib/portmidi/ptmacosx_mach.c svneol=native#text/plain
|
||||
src/lib/portmidi/ptwinmm.c svneol=native#text/plain
|
||||
src/lib/portmidi/readbinaryplist.c svneol=native#text/plain
|
||||
src/lib/portmidi/readbinaryplist.h svneol=native#text/plain
|
||||
src/lib/softfloat/README.txt svneol=native#text/plain
|
||||
src/lib/softfloat/fpu_constant.h svneol=native#text/plain
|
||||
src/lib/softfloat/fsincos.c svneol=native#text/plain
|
||||
@ -7814,6 +7842,7 @@ src/osd/osdmini/osdmini.h svneol=native#text/plain
|
||||
src/osd/osdmini/osdmini.mak svneol=native#text/plain
|
||||
src/osd/osdnet.c svneol=native#text/plain
|
||||
src/osd/osdnet.h svneol=native#text/plain
|
||||
src/osd/portmedia/pmmidi.c svneol=native#text/plain
|
||||
src/osd/sdl/README_SDL20.txt svneol=native#text/plain
|
||||
src/osd/sdl/SDL1211_opengl.h svneol=native#text/plain
|
||||
src/osd/sdl/SDLMain_tmpl.h svneol=native#text/plain
|
||||
@ -7894,6 +7923,7 @@ src/osd/sdl/sdlfile.c svneol=native#text/plain
|
||||
src/osd/sdl/sdlfile.h svneol=native#text/plain
|
||||
src/osd/sdl/sdlinc.h svneol=native#text/plain
|
||||
src/osd/sdl/sdlmain.c svneol=native#text/plain
|
||||
src/osd/sdl/sdlmidi.c svneol=native#text/plain
|
||||
src/osd/sdl/sdlmisc_os2.c svneol=native#text/plain
|
||||
src/osd/sdl/sdlmisc_unix.c svneol=native#text/plain
|
||||
src/osd/sdl/sdlmisc_win32.c svneol=native#text/plain
|
||||
@ -7991,6 +8021,7 @@ src/osd/windows/winfile.h svneol=native#text/plain
|
||||
src/osd/windows/winmain.c svneol=native#text/plain
|
||||
src/osd/windows/winmain.h svneol=native#text/plain
|
||||
src/osd/windows/winmenu.c svneol=native#text/plain
|
||||
src/osd/windows/winmidi.c svneol=native#text/plain
|
||||
src/osd/windows/winmisc.c svneol=native#text/plain
|
||||
src/osd/windows/winprefix.h svneol=native#text/plain
|
||||
src/osd/windows/winptty.c svneol=native#text/plain
|
||||
|
@ -22,7 +22,7 @@ OBJDIRS += \
|
||||
$(LIBOBJ)/libjpeg \
|
||||
$(LIBOBJ)/libflac \
|
||||
$(LIBOBJ)/lib7z \
|
||||
|
||||
$(LIBOBJ)/portmidi \
|
||||
|
||||
|
||||
#-------------------------------------------------
|
||||
@ -382,3 +382,50 @@ $(OBJ)/lib7z.a: $(LIB7ZOBJS)
|
||||
$(LIBOBJ)/lib7z/%.o: $(LIBSRC)/lib7z/%.c | $(OSPREBUILD)
|
||||
@echo Compiling $<...
|
||||
$(CC) $(CDEFS) $(7ZOPTS) $(CCOMFLAGS) $(CONLYFLAGS) -I$(LIBSRC)/lib7z/ -c $< -o $@
|
||||
|
||||
#-------------------------------------------------
|
||||
# portmidi library objects
|
||||
#-------------------------------------------------
|
||||
|
||||
PMOPTS =
|
||||
|
||||
# common objects
|
||||
LIBPMOBJS = \
|
||||
$(LIBOBJ)/portmidi/portmidi.o \
|
||||
$(LIBOBJ)/portmidi/porttime.o \
|
||||
$(LIBOBJ)/portmidi/pmutil.o
|
||||
|
||||
ifeq ($(TARGETOS),linux)
|
||||
PMOPTS = -DPMALSA=1
|
||||
|
||||
LIBPMOBJS += \
|
||||
$(LIBOBJ)/portmidi/pmlinux.o \
|
||||
$(LIBOBJ)/portmidi/pmlinuxalsa.o \
|
||||
$(LIBOBJ)/portmidi/finddefaultlinux.o \
|
||||
$(LIBOBJ)/portmidi/ptlinux.o
|
||||
|
||||
endif
|
||||
|
||||
ifeq ($(TARGETOS),macosx)
|
||||
LIBPMOBJS += \
|
||||
$(LIBOBJ)/portmidi/pmmac.o \
|
||||
$(LIBOBJ)/portmidi/pmmacosxcm.o \
|
||||
$(LIBOBJ)/portmidi/finddefault.o \
|
||||
$(LIBOBJ)/portmidi/readbinaryplist.o \
|
||||
$(LIBOBJ)/portmidi/ptmacosx_mach.o \
|
||||
$(LIBOBJ)/portmidi/osxsupport.o
|
||||
endif
|
||||
|
||||
ifeq ($(TARGETOS),win32)
|
||||
LIBPMOBJS += \
|
||||
$(LIBOBJ)/portmidi/pmwin.o \
|
||||
$(LIBOBJ)/portmidi/pmwinmm.o \
|
||||
$(LIBOBJ)/portmidi/ptwinmm.o
|
||||
endif
|
||||
|
||||
$(OBJ)/portmidi.a: $(LIBPMOBJS)
|
||||
|
||||
$(LIBOBJ)/portmidi/%.o: $(LIBSRC)/portmidi/%.c | $(OSPREBUILD)
|
||||
@echo Compiling $<...
|
||||
$(CC) $(CDEFS) $(PMOPTS) $(CCOMFLAGS) $(CONLYFLAGS) -I$(LIBSRC)/portmidi/ -c $< -o $@
|
||||
|
||||
|
57
src/lib/portmidi/finddefault.c
Normal file
57
src/lib/portmidi/finddefault.c
Normal file
@ -0,0 +1,57 @@
|
||||
/* finddefault.c -- find_default_device() implementation
|
||||
Roger Dannenberg, June 2008
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "portmidi.h"
|
||||
#include "pmutil.h"
|
||||
#include "pminternal.h"
|
||||
#include "pmmacosxcm.h"
|
||||
#include "readbinaryplist.h"
|
||||
|
||||
/* Parse preference files, find default device, search devices --
|
||||
This parses the preference file(s) once for input and once for
|
||||
output, which is inefficient but much simpler to manage. Note
|
||||
that using the readbinaryplist.c module, you cannot keep two
|
||||
plist files (user and system) open at once (due to a simple
|
||||
memory management scheme).
|
||||
*/
|
||||
PmDeviceID find_default_device(char *path, int input, PmDeviceID id)
|
||||
/* path -- the name of the preference we are searching for
|
||||
input -- true iff this is an input device
|
||||
id -- current default device id
|
||||
returns matching device id if found, otherwise id
|
||||
*/
|
||||
{
|
||||
static char *pref_file = (char *)"com.apple.java.util.prefs.plist";
|
||||
char *pref_str = NULL;
|
||||
// read device preferences
|
||||
value_ptr prefs = bplist_read_user_pref(pref_file);
|
||||
if (prefs) {
|
||||
value_ptr pref_val = value_dict_lookup_using_path(prefs, path);
|
||||
if (pref_val) {
|
||||
pref_str = value_get_asciistring(pref_val);
|
||||
}
|
||||
}
|
||||
if (!pref_str) {
|
||||
bplist_free_data(); /* look elsewhere */
|
||||
prefs = bplist_read_system_pref(pref_file);
|
||||
if (prefs) {
|
||||
value_ptr pref_val = value_dict_lookup_using_path(prefs, path);
|
||||
if (pref_val) {
|
||||
pref_str = value_get_asciistring(pref_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pref_str) { /* search devices for match */
|
||||
int i = pm_find_default_device(pref_str, input);
|
||||
if (i != pmNoDevice) {
|
||||
id = i;
|
||||
}
|
||||
}
|
||||
if (prefs) {
|
||||
bplist_free_data();
|
||||
}
|
||||
return id;
|
||||
}
|
94
src/lib/portmidi/finddefaultlinux.c
Normal file
94
src/lib/portmidi/finddefaultlinux.c
Normal file
@ -0,0 +1,94 @@
|
||||
/* finddefault.c -- find_default_device() implementation
|
||||
Roger Dannenberg, Jan 2009
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "portmidi.h"
|
||||
|
||||
extern int pm_find_default_device(char *pattern, int is_input);
|
||||
|
||||
#define STRING_MAX 256
|
||||
|
||||
/* skip over spaces, return first non-space */
|
||||
void skip_spaces(FILE *inf)
|
||||
{
|
||||
char c;
|
||||
while (isspace(c = getc(inf))) ;
|
||||
ungetc(c, inf);
|
||||
}
|
||||
|
||||
/* trim leading spaces and match a string */
|
||||
int match_string(FILE *inf, char *s)
|
||||
{
|
||||
skip_spaces(inf);
|
||||
while (*s && *s == getc(inf)) s++;
|
||||
return (*s == 0);
|
||||
}
|
||||
|
||||
|
||||
/* Parse preference files, find default device, search devices --
|
||||
*/
|
||||
PmDeviceID find_default_device(char *path, int input, PmDeviceID id)
|
||||
/* path -- the name of the preference we are searching for
|
||||
input -- true iff this is an input device
|
||||
id -- current default device id
|
||||
returns matching device id if found, otherwise id
|
||||
*/
|
||||
{
|
||||
static char *pref_2 = (char *)"/.java/.userPrefs/";
|
||||
static char *pref_3 = (char *)"prefs.xml";
|
||||
char *pref_1 = getenv("HOME");
|
||||
char *full_name, *path_ptr;
|
||||
FILE *inf;
|
||||
int c, i;
|
||||
if (!pref_1) goto nopref; // cannot find preference file
|
||||
// full_name will be larger than necessary
|
||||
full_name = malloc(strlen(pref_1) + strlen(pref_2) + strlen(pref_3) +
|
||||
strlen(path) + 2);
|
||||
strcpy(full_name, pref_1);
|
||||
strcat(full_name, pref_2);
|
||||
// copy all but last path segment to full_name
|
||||
if (*path == '/') path++; // skip initial slash in path
|
||||
path_ptr = strrchr(path, '/');
|
||||
if (path_ptr) { // copy up to slash after full_name
|
||||
path_ptr++;
|
||||
int offset = strlen(full_name);
|
||||
memcpy(full_name + offset, path, path_ptr - path);
|
||||
full_name[offset + path_ptr - path] = 0; // end of string
|
||||
} else {
|
||||
path_ptr = path;
|
||||
}
|
||||
strcat(full_name, pref_3);
|
||||
inf = fopen(full_name, "r");
|
||||
if (!inf) goto nopref; // cannot open preference file
|
||||
// We're not going to build or link in a full XML parser.
|
||||
// Instead, find the path string and quoute. Then, look for
|
||||
// "value", "=", quote. Then get string up to quote.
|
||||
while ((c = getc(inf)) != EOF) {
|
||||
char pref_str[STRING_MAX];
|
||||
if (c != '"') continue; // scan up to quote
|
||||
// look for quote string quote
|
||||
if (!match_string(inf, path_ptr)) continue; // path not found
|
||||
if (getc(inf) != '"') continue; // path not found, keep scanning
|
||||
if (!match_string(inf, (char *)"value")) goto nopref; // value not found
|
||||
if (!match_string(inf, (char *)"=")) goto nopref; // = not found
|
||||
if (!match_string(inf, (char *)"\"")) goto nopref; // quote not found
|
||||
// now read the value up to the close quote
|
||||
for (i = 0; i < STRING_MAX; i++) {
|
||||
if ((c = getc(inf)) == '"') break;
|
||||
pref_str[i] = c;
|
||||
}
|
||||
if (i == STRING_MAX) continue; // value too long, ignore
|
||||
pref_str[i] = 0;
|
||||
i = pm_find_default_device(pref_str, input);
|
||||
if (i != pmNoDevice) {
|
||||
id = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
nopref:
|
||||
return id;
|
||||
}
|
19
src/lib/portmidi/osxsupport.h
Normal file
19
src/lib/portmidi/osxsupport.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
osxsupport.h - Cocoa glue to emulated deprecated old Carbon path finder functions
|
||||
*/
|
||||
|
||||
#ifndef _OSXSUPPORT_H_
|
||||
#define _OSXSUPPORT_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
char *FindPrefsDir(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
30
src/lib/portmidi/osxsupport.m
Normal file
30
src/lib/portmidi/osxsupport.m
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
osxsupport.m - Cocoa glue to emulated deprecated old Carbon path finder functions
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <AvailabilityMacros.h>
|
||||
#include "osxsupport.h"
|
||||
|
||||
// convert an NSString to a C string
|
||||
static char *StringToChar(NSString *str)
|
||||
{
|
||||
const char *charstr = [str UTF8String];
|
||||
char *resstr = (char *)malloc(strlen(charstr)+1);
|
||||
|
||||
strcpy(resstr, charstr);
|
||||
return resstr;
|
||||
}
|
||||
|
||||
char *FindPrefsDir(void)
|
||||
{
|
||||
char *resstr = NULL;
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSPreferencePanesDirectory, NSUserDomainMask, YES);
|
||||
|
||||
if ([paths count] > 0)
|
||||
{
|
||||
resstr = StringToChar([paths objectAtIndex:0]) ;
|
||||
}
|
||||
|
||||
return resstr;
|
||||
}
|
178
src/lib/portmidi/pminternal.h
Normal file
178
src/lib/portmidi/pminternal.h
Normal file
@ -0,0 +1,178 @@
|
||||
/* pminternal.h -- header for interface implementations */
|
||||
|
||||
/* this file is included by files that implement library internals */
|
||||
/* Here is a guide to implementers:
|
||||
provide an initialization function similar to pm_winmm_init()
|
||||
add your initialization function to pm_init()
|
||||
Note that your init function should never require not-standard
|
||||
libraries or fail in any way. If the interface is not available,
|
||||
simply do not call pm_add_device. This means that non-standard
|
||||
libraries should try to do dynamic linking at runtime using a DLL
|
||||
and return without error if the DLL cannot be found or if there
|
||||
is any other failure.
|
||||
implement functions as indicated in pm_fns_type to open, read, write,
|
||||
close, etc.
|
||||
call pm_add_device() for each input and output device, passing it a
|
||||
pm_fns_type structure.
|
||||
assumptions about pm_fns_type functions are given below.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern int pm_initialized; /* see note in portmidi.c */
|
||||
|
||||
/* these are defined in system-specific file */
|
||||
void *pm_alloc(size_t s);
|
||||
void pm_free(void *ptr);
|
||||
|
||||
/* if an error occurs while opening or closing a midi stream, set these: */
|
||||
extern int pm_hosterror;
|
||||
extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
|
||||
|
||||
struct pm_internal_struct;
|
||||
|
||||
/* these do not use PmInternal because it is not defined yet... */
|
||||
typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi,
|
||||
PmEvent *buffer);
|
||||
typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,
|
||||
PmTimestamp timestamp);
|
||||
typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,
|
||||
PmTimestamp timestamp);
|
||||
typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,
|
||||
unsigned char byte, PmTimestamp timestamp);
|
||||
typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
|
||||
PmEvent *buffer);
|
||||
typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi,
|
||||
PmTimestamp timestamp);
|
||||
typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);
|
||||
/* pm_open_fn should clean up all memory and close the device if any part
|
||||
of the open fails */
|
||||
typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi,
|
||||
void *driverInfo);
|
||||
typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi);
|
||||
/* pm_close_fn should clean up all memory and close the device if any
|
||||
part of the close fails. */
|
||||
typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi);
|
||||
typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi);
|
||||
typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg,
|
||||
unsigned int len);
|
||||
typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi);
|
||||
|
||||
typedef struct {
|
||||
pm_write_short_fn write_short; /* output short MIDI msg */
|
||||
pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */
|
||||
pm_end_sysex_fn end_sysex; /* marks end of sysex message */
|
||||
pm_write_byte_fn write_byte; /* accumulate one more sysex byte */
|
||||
pm_write_realtime_fn write_realtime; /* send real-time message within sysex */
|
||||
pm_write_flush_fn write_flush; /* send any accumulated but unsent data */
|
||||
pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */
|
||||
pm_open_fn open; /* open MIDI device */
|
||||
pm_abort_fn abort; /* abort */
|
||||
pm_close_fn close; /* close device */
|
||||
pm_poll_fn poll; /* read pending midi events into portmidi buffer */
|
||||
pm_has_host_error_fn has_host_error; /* true when device has had host
|
||||
error message */
|
||||
pm_host_error_fn host_error; /* provide text readable host error message
|
||||
for device (clears and resets) */
|
||||
} pm_fns_node, *pm_fns_type;
|
||||
|
||||
|
||||
/* when open fails, the dictionary gets this set of functions: */
|
||||
extern pm_fns_node pm_none_dictionary;
|
||||
|
||||
typedef struct {
|
||||
PmDeviceInfo pub; /* some portmidi state also saved in here (for autmatic
|
||||
device closing (see PmDeviceInfo struct) */
|
||||
void *descriptor; /* ID number passed to win32 multimedia API open */
|
||||
void *internalDescriptor; /* points to PmInternal device, allows automatic
|
||||
device closing */
|
||||
pm_fns_type dictionary;
|
||||
} descriptor_node, *descriptor_type;
|
||||
|
||||
extern int pm_descriptor_max;
|
||||
extern descriptor_type descriptors;
|
||||
extern int pm_descriptor_index;
|
||||
|
||||
typedef uint32_t (*time_get_proc_type)(void *time_info);
|
||||
|
||||
typedef struct pm_internal_struct {
|
||||
int device_id; /* which device is open (index to descriptors) */
|
||||
short write_flag; /* MIDI_IN, or MIDI_OUT */
|
||||
|
||||
PmTimeProcPtr time_proc; /* where to get the time */
|
||||
void *time_info; /* pass this to get_time() */
|
||||
int32_t buffer_len; /* how big is the buffer or queue? */
|
||||
PmQueue *queue;
|
||||
|
||||
int32_t latency; /* time delay in ms between timestamps and actual output */
|
||||
/* set to zero to get immediate, simple blocking output */
|
||||
/* if latency is zero, timestamps will be ignored; */
|
||||
/* if midi input device, this field ignored */
|
||||
|
||||
int sysex_in_progress; /* when sysex status is seen, this flag becomes
|
||||
* true until EOX is seen. When true, new data is appended to the
|
||||
* stream of outgoing bytes. When overflow occurs, sysex data is
|
||||
* dropped (until an EOX or non-real-timei status byte is seen) so
|
||||
* that, if the overflow condition is cleared, we don't start
|
||||
* sending data from the middle of a sysex message. If a sysex
|
||||
* message is filtered, sysex_in_progress is false, causing the
|
||||
* message to be dropped. */
|
||||
PmMessage sysex_message; /* buffer for 4 bytes of sysex data */
|
||||
int sysex_message_count; /* how many bytes in sysex_message so far */
|
||||
|
||||
int32_t filters; /* flags that filter incoming message classes */
|
||||
int32_t channel_mask; /* filter incoming messages based on channel */
|
||||
PmTimestamp last_msg_time; /* timestamp of last message */
|
||||
PmTimestamp sync_time; /* time of last synchronization */
|
||||
PmTimestamp now; /* set by PmWrite to current time */
|
||||
int first_message; /* initially true, used to run first synchronization */
|
||||
pm_fns_type dictionary; /* implementation functions */
|
||||
void *descriptor; /* system-dependent state */
|
||||
/* the following are used to expedite sysex data */
|
||||
/* on windows, in debug mode, based on some profiling, these optimizations
|
||||
* cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte,
|
||||
* but this does not count time in the driver, so I don't know if it is
|
||||
* important
|
||||
*/
|
||||
unsigned char *fill_base; /* addr of ptr to sysex data */
|
||||
uint32_t *fill_offset_ptr; /* offset of next sysex byte */
|
||||
int32_t fill_length; /* how many sysex bytes to write */
|
||||
} PmInternal;
|
||||
|
||||
|
||||
/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */
|
||||
void pm_init(void);
|
||||
void pm_term(void);
|
||||
|
||||
/* defined by portMidi, used by pmwinmm */
|
||||
PmError none_write_short(PmInternal *midi, PmEvent *buffer);
|
||||
PmError none_write_byte(PmInternal *midi, unsigned char byte,
|
||||
PmTimestamp timestamp);
|
||||
PmTimestamp none_synchronize(PmInternal *midi);
|
||||
|
||||
PmError pm_fail_fn(PmInternal *midi);
|
||||
PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp);
|
||||
PmError pm_success_fn(PmInternal *midi);
|
||||
PmError pm_add_device(char *interf, char *name, int input, void *descriptor,
|
||||
pm_fns_type dictionary);
|
||||
uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len,
|
||||
PmTimestamp timestamp);
|
||||
void pm_read_short(PmInternal *midi, PmEvent *event);
|
||||
|
||||
#define none_write_flush pm_fail_timestamp_fn
|
||||
#define none_sysex pm_fail_timestamp_fn
|
||||
#define none_poll pm_fail_fn
|
||||
#define success_poll pm_success_fn
|
||||
|
||||
#define MIDI_REALTIME_MASK 0xf8
|
||||
#define is_real_time(msg) \
|
||||
((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)
|
||||
|
||||
int pm_find_default_device(char *pattern, int is_input);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
75
src/lib/portmidi/pmlinux.c
Normal file
75
src/lib/portmidi/pmlinux.c
Normal file
@ -0,0 +1,75 @@
|
||||
/* pmlinux.c -- PortMidi os-dependent code */
|
||||
|
||||
/* This file only needs to implement pm_init(), which calls various
|
||||
routines to register the available midi devices. This file must
|
||||
be separate from the main portmidi.c file because it is system
|
||||
dependent, and it is separate from, pmlinuxalsa.c, because it
|
||||
might need to register non-alsa devices as well.
|
||||
|
||||
NOTE: if you add non-ALSA support, you need to fix :alsa_poll()
|
||||
in pmlinuxalsa.c, which assumes all input devices are ALSA.
|
||||
*/
|
||||
|
||||
#include "stdlib.h"
|
||||
#include "portmidi.h"
|
||||
#include "pmutil.h"
|
||||
#include "pminternal.h"
|
||||
|
||||
#ifdef PMALSA
|
||||
#include "pmlinuxalsa.h"
|
||||
#endif
|
||||
|
||||
#ifdef PMNULL
|
||||
#include "pmlinuxnull.h"
|
||||
#endif
|
||||
|
||||
PmDeviceID pm_default_input_device_id = -1;
|
||||
PmDeviceID pm_default_output_device_id = -1;
|
||||
|
||||
extern PmDeviceID find_default_device(char *path, int input, PmDeviceID id);
|
||||
|
||||
void pm_init()
|
||||
{
|
||||
/* Note: it is not an error for PMALSA to fail to initialize.
|
||||
* It may be a design error that the client cannot query what subsystems
|
||||
* are working properly other than by looking at the list of available
|
||||
* devices.
|
||||
*/
|
||||
#ifdef PMALSA
|
||||
pm_linuxalsa_init();
|
||||
#endif
|
||||
#ifdef PMNULL
|
||||
pm_linuxnull_init();
|
||||
#endif
|
||||
// this is set when we return to Pm_Initialize, but we need it
|
||||
// now in order to (successfully) call Pm_CountDevices()
|
||||
pm_initialized = TRUE;
|
||||
pm_default_input_device_id = find_default_device(
|
||||
(char *)"/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE,
|
||||
pm_default_input_device_id);
|
||||
pm_default_output_device_id = find_default_device(
|
||||
(char *)"/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE,
|
||||
pm_default_output_device_id);
|
||||
}
|
||||
|
||||
void pm_term(void)
|
||||
{
|
||||
#ifdef PMALSA
|
||||
pm_linuxalsa_term();
|
||||
#endif
|
||||
}
|
||||
|
||||
PmDeviceID Pm_GetDefaultInputDeviceID() {
|
||||
Pm_Initialize();
|
||||
return pm_default_input_device_id;
|
||||
}
|
||||
|
||||
PmDeviceID Pm_GetDefaultOutputDeviceID() {
|
||||
Pm_Initialize();
|
||||
return pm_default_output_device_id;
|
||||
}
|
||||
|
||||
void *pm_alloc(size_t s) { return malloc(s); }
|
||||
|
||||
void pm_free(void *ptr) { free(ptr); }
|
||||
|
5
src/lib/portmidi/pmlinux.h
Normal file
5
src/lib/portmidi/pmlinux.h
Normal file
@ -0,0 +1,5 @@
|
||||
/* pmlinux.h */
|
||||
|
||||
extern PmDeviceID pm_default_input_device_id;
|
||||
extern PmDeviceID pm_default_output_device_id;
|
||||
|
787
src/lib/portmidi/pmlinuxalsa.c
Normal file
787
src/lib/portmidi/pmlinuxalsa.c
Normal file
@ -0,0 +1,787 @@
|
||||
/*
|
||||
* pmlinuxalsa.c -- system specific definitions
|
||||
*
|
||||
* written by:
|
||||
* Roger Dannenberg (port to Alsa 0.9.x)
|
||||
* Clemens Ladisch (provided code examples and invaluable consulting)
|
||||
* Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation)
|
||||
*/
|
||||
|
||||
#include "stdlib.h"
|
||||
#include "portmidi.h"
|
||||
#include "pmutil.h"
|
||||
#include "pminternal.h"
|
||||
#include "pmlinuxalsa.h"
|
||||
#include "string.h"
|
||||
#include "porttime.h"
|
||||
#include "pmlinux.h"
|
||||
#include "osdcomm.h"
|
||||
|
||||
#ifdef PTR64
|
||||
typedef UINT64 FPTR;
|
||||
#else
|
||||
typedef UINT32 FPTR;
|
||||
#endif
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
/* I used many print statements to debug this code. I left them in the
|
||||
* source, and you can turn them on by changing false to true below:
|
||||
*/
|
||||
#define VERBOSE_ON 0
|
||||
#define VERBOSE if (VERBOSE_ON)
|
||||
|
||||
#define MIDI_SYSEX 0xf0
|
||||
#define MIDI_EOX 0xf7
|
||||
|
||||
#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9
|
||||
#error needs ALSA 0.9.0 or later
|
||||
#endif
|
||||
|
||||
/* to store client/port in the device descriptor */
|
||||
|
||||
#define MAKE_DESCRIPTOR(client, port) ((void*)(FPTR)(((client) << 8) | (port)))
|
||||
#define GET_DESCRIPTOR_CLIENT(info) ((((int)(FPTR)(info)) >> 8) & 0xff)
|
||||
#define GET_DESCRIPTOR_PORT(info) (((int)(FPTR)(info)) & 0xff)
|
||||
|
||||
#define BYTE unsigned char
|
||||
|
||||
extern pm_fns_node pm_linuxalsa_in_dictionary;
|
||||
extern pm_fns_node pm_linuxalsa_out_dictionary;
|
||||
|
||||
static snd_seq_t *seq = NULL; // all input comes here,
|
||||
// output queue allocated on seq
|
||||
static int queue, queue_used; /* one for all ports, reference counted */
|
||||
|
||||
typedef struct alsa_descriptor_struct {
|
||||
int client;
|
||||
int port;
|
||||
int this_port;
|
||||
int in_sysex;
|
||||
snd_midi_event_t *parser;
|
||||
int error; /* host error code */
|
||||
} alsa_descriptor_node, *alsa_descriptor_type;
|
||||
|
||||
|
||||
/* get_alsa_error_text -- copy error text to potentially short string */
|
||||
/**/
|
||||
static void get_alsa_error_text(char *msg, int len, int err)
|
||||
{
|
||||
int errlen = strlen(snd_strerror(err));
|
||||
if (errlen < len) {
|
||||
strcpy(msg, snd_strerror(err));
|
||||
} else if (len > 20) {
|
||||
sprintf(msg, "Alsa error %d", err);
|
||||
} else if (len > 4) {
|
||||
strcpy(msg, "Alsa");
|
||||
} else {
|
||||
msg[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* queue is shared by both input and output, reference counted */
|
||||
static PmError alsa_use_queue(void)
|
||||
{
|
||||
if (queue_used == 0) {
|
||||
snd_seq_queue_tempo_t *tempo;
|
||||
|
||||
queue = snd_seq_alloc_queue(seq);
|
||||
if (queue < 0) {
|
||||
pm_hosterror = queue;
|
||||
return pmHostError;
|
||||
}
|
||||
snd_seq_queue_tempo_alloca(&tempo);
|
||||
snd_seq_queue_tempo_set_tempo(tempo, 480000);
|
||||
snd_seq_queue_tempo_set_ppq(tempo, 480);
|
||||
pm_hosterror = snd_seq_set_queue_tempo(seq, queue, tempo);
|
||||
if (pm_hosterror < 0)
|
||||
return pmHostError;
|
||||
|
||||
snd_seq_start_queue(seq, queue, NULL);
|
||||
snd_seq_drain_output(seq);
|
||||
}
|
||||
++queue_used;
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
static void alsa_unuse_queue(void)
|
||||
{
|
||||
if (--queue_used == 0) {
|
||||
snd_seq_stop_queue(seq, queue, NULL);
|
||||
snd_seq_drain_output(seq);
|
||||
snd_seq_free_queue(seq, queue);
|
||||
VERBOSE printf("queue freed\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* midi_message_length -- how many bytes in a message? */
|
||||
static int midi_message_length(PmMessage message)
|
||||
{
|
||||
message &= 0xff;
|
||||
if (message < 0x80) {
|
||||
return 0;
|
||||
} else if (message < 0xf0) {
|
||||
static const int length[] = {3, 3, 3, 3, 2, 2, 3};
|
||||
return length[(message - 0x80) >> 4];
|
||||
} else {
|
||||
static const int length[] = {
|
||||
-1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1};
|
||||
return length[message - 0xf0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static PmError alsa_out_open(PmInternal *midi, void *driverInfo)
|
||||
{
|
||||
void *client_port = descriptors[midi->device_id].descriptor;
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type)
|
||||
pm_alloc(sizeof(alsa_descriptor_node));
|
||||
snd_seq_port_info_t *info;
|
||||
int err;
|
||||
|
||||
if (!desc) return pmInsufficientMemory;
|
||||
|
||||
snd_seq_port_info_alloca(&info);
|
||||
snd_seq_port_info_set_port(info, midi->device_id);
|
||||
snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE |
|
||||
SND_SEQ_PORT_CAP_READ);
|
||||
snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
snd_seq_port_info_set_port_specified(info, 1);
|
||||
err = snd_seq_create_port(seq, info);
|
||||
if (err < 0) goto free_desc;
|
||||
|
||||
/* fill in fields of desc, which is passed to pm_write routines */
|
||||
midi->descriptor = desc;
|
||||
desc->client = GET_DESCRIPTOR_CLIENT(client_port);
|
||||
desc->port = GET_DESCRIPTOR_PORT(client_port);
|
||||
desc->this_port = midi->device_id;
|
||||
desc->in_sysex = 0;
|
||||
|
||||
desc->error = 0;
|
||||
|
||||
err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &desc->parser);
|
||||
if (err < 0) goto free_this_port;
|
||||
|
||||
if (midi->latency > 0) { /* must delay output using a queue */
|
||||
err = alsa_use_queue();
|
||||
if (err < 0) goto free_parser;
|
||||
|
||||
err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port);
|
||||
if (err < 0) goto unuse_queue; /* clean up and return on error */
|
||||
} else {
|
||||
err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port);
|
||||
if (err < 0) goto free_parser; /* clean up and return on error */
|
||||
}
|
||||
return pmNoError;
|
||||
|
||||
unuse_queue:
|
||||
alsa_unuse_queue();
|
||||
free_parser:
|
||||
snd_midi_event_free(desc->parser);
|
||||
free_this_port:
|
||||
snd_seq_delete_port(seq, desc->this_port);
|
||||
free_desc:
|
||||
pm_free(desc);
|
||||
pm_hosterror = err;
|
||||
if (err < 0) {
|
||||
get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
|
||||
}
|
||||
return pmHostError;
|
||||
}
|
||||
|
||||
|
||||
static PmError alsa_write_byte(PmInternal *midi, unsigned char byte,
|
||||
PmTimestamp timestamp)
|
||||
{
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
snd_seq_event_t ev;
|
||||
int err;
|
||||
|
||||
snd_seq_ev_clear(&ev);
|
||||
if (snd_midi_event_encode_byte(desc->parser, byte, &ev) == 1) {
|
||||
snd_seq_ev_set_dest(&ev, desc->client, desc->port);
|
||||
snd_seq_ev_set_source(&ev, desc->this_port);
|
||||
if (midi->latency > 0) {
|
||||
/* compute relative time of event = timestamp - now + latency */
|
||||
PmTimestamp now = (midi->time_proc ?
|
||||
midi->time_proc(midi->time_info) :
|
||||
Pt_Time());
|
||||
int when = timestamp;
|
||||
/* if timestamp is zero, send immediately */
|
||||
/* otherwise compute time delay and use delay if positive */
|
||||
if (when == 0) when = now;
|
||||
when = (when - now) + midi->latency;
|
||||
if (when < 0) when = 0;
|
||||
VERBOSE printf("timestamp %d now %d latency %d, ",
|
||||
(int) timestamp, (int) now, midi->latency);
|
||||
VERBOSE printf("scheduling event after %d\n", when);
|
||||
/* message is sent in relative ticks, where 1 tick = 1 ms */
|
||||
snd_seq_ev_schedule_tick(&ev, queue, 1, when);
|
||||
/* NOTE: for cases where the user does not supply a time function,
|
||||
we could optimize the code by not starting Pt_Time and using
|
||||
the alsa tick time instead. I didn't do this because it would
|
||||
entail changing the queue management to start the queue tick
|
||||
count when PortMidi is initialized and keep it running until
|
||||
PortMidi is terminated. (This should be simple, but it's not
|
||||
how the code works now.) -RBD */
|
||||
} else { /* send event out without queueing */
|
||||
VERBOSE printf("direct\n");
|
||||
/* ev.queue = SND_SEQ_QUEUE_DIRECT;
|
||||
ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */
|
||||
snd_seq_ev_set_direct(&ev);
|
||||
}
|
||||
VERBOSE printf("sending event\n");
|
||||
err = snd_seq_event_output(seq, &ev);
|
||||
if (err < 0) {
|
||||
desc->error = err;
|
||||
return pmHostError;
|
||||
}
|
||||
}
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
static PmError alsa_out_close(PmInternal *midi)
|
||||
{
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
if (!desc) return pmBadPtr;
|
||||
|
||||
if ((pm_hosterror = snd_seq_disconnect_to(seq, desc->this_port,
|
||||
desc->client, desc->port))) {
|
||||
// if there's an error, try to delete the port anyway, but don't
|
||||
// change the pm_hosterror value so we retain the first error
|
||||
snd_seq_delete_port(seq, desc->this_port);
|
||||
} else { // if there's no error, delete the port and retain any error
|
||||
pm_hosterror = snd_seq_delete_port(seq, desc->this_port);
|
||||
}
|
||||
if (midi->latency > 0) alsa_unuse_queue();
|
||||
snd_midi_event_free(desc->parser);
|
||||
midi->descriptor = NULL; /* destroy the pointer to signify "closed" */
|
||||
pm_free(desc);
|
||||
if (pm_hosterror) {
|
||||
get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
|
||||
pm_hosterror);
|
||||
return pmHostError;
|
||||
}
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
static PmError alsa_in_open(PmInternal *midi, void *driverInfo)
|
||||
{
|
||||
void *client_port = descriptors[midi->device_id].descriptor;
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type)
|
||||
pm_alloc(sizeof(alsa_descriptor_node));
|
||||
snd_seq_port_info_t *info;
|
||||
snd_seq_port_subscribe_t *sub;
|
||||
snd_seq_addr_t addr;
|
||||
int err;
|
||||
|
||||
if (!desc) return pmInsufficientMemory;
|
||||
|
||||
err = alsa_use_queue();
|
||||
if (err < 0) goto free_desc;
|
||||
|
||||
snd_seq_port_info_alloca(&info);
|
||||
snd_seq_port_info_set_port(info, midi->device_id);
|
||||
snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE |
|
||||
SND_SEQ_PORT_CAP_READ);
|
||||
snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
snd_seq_port_info_set_port_specified(info, 1);
|
||||
err = snd_seq_create_port(seq, info);
|
||||
if (err < 0) goto free_queue;
|
||||
|
||||
/* fill in fields of desc, which is passed to pm_write routines */
|
||||
midi->descriptor = desc;
|
||||
desc->client = GET_DESCRIPTOR_CLIENT(client_port);
|
||||
desc->port = GET_DESCRIPTOR_PORT(client_port);
|
||||
desc->this_port = midi->device_id;
|
||||
desc->in_sysex = 0;
|
||||
|
||||
desc->error = 0;
|
||||
|
||||
VERBOSE printf("snd_seq_connect_from: %d %d %d\n",
|
||||
desc->this_port, desc->client, desc->port);
|
||||
snd_seq_port_subscribe_alloca(&sub);
|
||||
addr.client = snd_seq_client_id(seq);
|
||||
addr.port = desc->this_port;
|
||||
snd_seq_port_subscribe_set_dest(sub, &addr);
|
||||
addr.client = desc->client;
|
||||
addr.port = desc->port;
|
||||
snd_seq_port_subscribe_set_sender(sub, &addr);
|
||||
snd_seq_port_subscribe_set_time_update(sub, 1);
|
||||
/* this doesn't seem to work: messages come in with real timestamps */
|
||||
snd_seq_port_subscribe_set_time_real(sub, 0);
|
||||
err = snd_seq_subscribe_port(seq, sub);
|
||||
/* err =
|
||||
snd_seq_connect_from(seq, desc->this_port, desc->client, desc->port); */
|
||||
if (err < 0) goto free_this_port; /* clean up and return on error */
|
||||
return pmNoError;
|
||||
|
||||
free_this_port:
|
||||
snd_seq_delete_port(seq, desc->this_port);
|
||||
free_queue:
|
||||
alsa_unuse_queue();
|
||||
free_desc:
|
||||
pm_free(desc);
|
||||
pm_hosterror = err;
|
||||
if (err < 0) {
|
||||
get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
|
||||
}
|
||||
return pmHostError;
|
||||
}
|
||||
|
||||
static PmError alsa_in_close(PmInternal *midi)
|
||||
{
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
if (!desc) return pmBadPtr;
|
||||
if ((pm_hosterror = snd_seq_disconnect_from(seq, desc->this_port,
|
||||
desc->client, desc->port))) {
|
||||
snd_seq_delete_port(seq, desc->this_port); /* try to close port */
|
||||
} else {
|
||||
pm_hosterror = snd_seq_delete_port(seq, desc->this_port);
|
||||
}
|
||||
alsa_unuse_queue();
|
||||
pm_free(desc);
|
||||
if (pm_hosterror) {
|
||||
get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
|
||||
pm_hosterror);
|
||||
return pmHostError;
|
||||
}
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
static PmError alsa_abort(PmInternal *midi)
|
||||
{
|
||||
/* NOTE: ALSA documentation is vague. This is supposed to
|
||||
* remove any pending output messages. If you can test and
|
||||
* confirm this code is correct, please update this comment. -RBD
|
||||
*/
|
||||
/* Unfortunately, I can't even compile it -- my ALSA version
|
||||
* does not implement snd_seq_remove_events_t, so this does
|
||||
* not compile. I'll try again, but it looks like I'll need to
|
||||
* upgrade my entire Linux OS -RBD
|
||||
*/
|
||||
/*
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
snd_seq_remove_events_t info;
|
||||
snd_seq_addr_t addr;
|
||||
addr.client = desc->client;
|
||||
addr.port = desc->port;
|
||||
snd_seq_remove_events_set_dest(&info, &addr);
|
||||
snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);
|
||||
pm_hosterror = snd_seq_remove_events(seq, &info);
|
||||
if (pm_hosterror) {
|
||||
get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
|
||||
pm_hosterror);
|
||||
return pmHostError;
|
||||
}
|
||||
*/
|
||||
printf("WARNING: alsa_abort not implemented\n");
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
#ifdef GARBAGE
|
||||
This is old code here temporarily for reference
|
||||
static PmError alsa_write(PmInternal *midi, PmEvent *buffer, int32_t length)
|
||||
{
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
int i, bytes;
|
||||
unsigned char byte;
|
||||
PmMessage msg;
|
||||
|
||||
desc->error = 0;
|
||||
for (; length > 0; length--, buffer++) {
|
||||
VERBOSE printf("message 0x%x\n", buffer->message);
|
||||
if (Pm_MessageStatus(buffer->message) == MIDI_SYSEX)
|
||||
desc->in_sysex = TRUE;
|
||||
if (desc->in_sysex) {
|
||||
msg = buffer->message;
|
||||
for (i = 0; i < 4; i++) {
|
||||
byte = msg; /* extract next byte to send */
|
||||
alsa_write_byte(midi, byte, buffer->timestamp);
|
||||
if (byte == MIDI_EOX) {
|
||||
desc->in_sysex = FALSE;
|
||||
break;
|
||||
}
|
||||
if (desc->error < 0) break;
|
||||
msg >>= 8; /* shift next byte into position */
|
||||
}
|
||||
} else {
|
||||
bytes = midi_message_length(buffer->message);
|
||||
msg = buffer->message;
|
||||
for (i = 0; i < bytes; i++) {
|
||||
byte = msg; /* extract next byte to send */
|
||||
VERBOSE printf("sending 0x%x\n", byte);
|
||||
alsa_write_byte(midi, byte, buffer->timestamp);
|
||||
if (desc->error < 0) break;
|
||||
msg >>= 8; /* shift next byte into position */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (desc->error < 0) return pmHostError;
|
||||
|
||||
VERBOSE printf("snd_seq_drain_output: 0x%x\n", (unsigned int) seq);
|
||||
desc->error = snd_seq_drain_output(seq);
|
||||
if (desc->error < 0) return pmHostError;
|
||||
|
||||
desc->error = pmNoError;
|
||||
return pmNoError;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)
|
||||
{
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
VERBOSE printf("snd_seq_drain_output: 0x%x\n", (unsigned int)(FPTR) seq);
|
||||
desc->error = snd_seq_drain_output(seq);
|
||||
if (desc->error < 0) return pmHostError;
|
||||
|
||||
desc->error = pmNoError;
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
static PmError alsa_write_short(PmInternal *midi, PmEvent *event)
|
||||
{
|
||||
int bytes = midi_message_length(event->message);
|
||||
PmMessage msg = event->message;
|
||||
int i;
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
for (i = 0; i < bytes; i++) {
|
||||
unsigned char byte = msg;
|
||||
VERBOSE printf("sending 0x%x\n", byte);
|
||||
alsa_write_byte(midi, byte, event->timestamp);
|
||||
if (desc->error < 0) break;
|
||||
msg >>= 8; /* shift next byte into position */
|
||||
}
|
||||
if (desc->error < 0) return pmHostError;
|
||||
desc->error = pmNoError;
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
/* alsa_sysex -- implements begin_sysex and end_sysex */
|
||||
PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) {
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
static PmTimestamp alsa_synchronize(PmInternal *midi)
|
||||
{
|
||||
return 0; /* linux implementation does not use this synchronize function */
|
||||
/* Apparently, Alsa data is relative to the time you send it, and there
|
||||
is no reference. If this is true, this is a serious shortcoming of
|
||||
Alsa. If not true, then PortMidi has a serious shortcoming -- it
|
||||
should be scheduling relative to Alsa's time reference. */
|
||||
}
|
||||
|
||||
|
||||
static void handle_event(snd_seq_event_t *ev)
|
||||
{
|
||||
int device_id = ev->dest.port;
|
||||
PmInternal *midi = descriptors[device_id].internalDescriptor;
|
||||
PmEvent pm_ev;
|
||||
PmTimeProcPtr time_proc = midi->time_proc;
|
||||
PmTimestamp timestamp;
|
||||
|
||||
/* time stamp should be in ticks, using our queue where 1 tick = 1ms */
|
||||
assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);
|
||||
|
||||
/* if no time_proc, just return "native" ticks (ms) */
|
||||
if (time_proc == NULL) {
|
||||
timestamp = ev->time.tick;
|
||||
} else { /* translate time to time_proc basis */
|
||||
snd_seq_queue_status_t *queue_status;
|
||||
snd_seq_queue_status_alloca(&queue_status);
|
||||
snd_seq_get_queue_status(seq, queue, queue_status);
|
||||
/* return (now - alsa_now) + alsa_timestamp */
|
||||
timestamp = (*time_proc)(midi->time_info) + ev->time.tick -
|
||||
snd_seq_queue_status_get_tick_time(queue_status);
|
||||
}
|
||||
pm_ev.timestamp = timestamp;
|
||||
switch (ev->type) {
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
pm_ev.message = Pm_Message(0x90 | ev->data.note.channel,
|
||||
ev->data.note.note & 0x7f,
|
||||
ev->data.note.velocity & 0x7f);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
pm_ev.message = Pm_Message(0x80 | ev->data.note.channel,
|
||||
ev->data.note.note & 0x7f,
|
||||
ev->data.note.velocity & 0x7f);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_KEYPRESS:
|
||||
pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel,
|
||||
ev->data.note.note & 0x7f,
|
||||
ev->data.note.velocity & 0x7f);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_CONTROLLER:
|
||||
pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
|
||||
ev->data.control.param & 0x7f,
|
||||
ev->data.control.value & 0x7f);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_PGMCHANGE:
|
||||
pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel,
|
||||
ev->data.control.value & 0x7f, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_CHANPRESS:
|
||||
pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel,
|
||||
ev->data.control.value & 0x7f, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_PITCHBEND:
|
||||
pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel,
|
||||
(ev->data.control.value + 0x2000) & 0x7f,
|
||||
((ev->data.control.value + 0x2000) >> 7) & 0x7f);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_CONTROL14:
|
||||
if (ev->data.control.param < 0x20) {
|
||||
pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
|
||||
ev->data.control.param,
|
||||
(ev->data.control.value >> 7) & 0x7f);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
|
||||
ev->data.control.param + 0x20,
|
||||
ev->data.control.value & 0x7f);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
} else {
|
||||
pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
|
||||
ev->data.control.param & 0x7f,
|
||||
ev->data.control.value & 0x7f);
|
||||
|
||||
pm_read_short(midi, &pm_ev);
|
||||
}
|
||||
break;
|
||||
case SND_SEQ_EVENT_SONGPOS:
|
||||
pm_ev.message = Pm_Message(0xf2,
|
||||
ev->data.control.value & 0x7f,
|
||||
(ev->data.control.value >> 7) & 0x7f);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_SONGSEL:
|
||||
pm_ev.message = Pm_Message(0xf3,
|
||||
ev->data.control.value & 0x7f, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_QFRAME:
|
||||
pm_ev.message = Pm_Message(0xf1,
|
||||
ev->data.control.value & 0x7f, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_START:
|
||||
pm_ev.message = Pm_Message(0xfa, 0, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_CONTINUE:
|
||||
pm_ev.message = Pm_Message(0xfb, 0, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_STOP:
|
||||
pm_ev.message = Pm_Message(0xfc, 0, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_CLOCK:
|
||||
pm_ev.message = Pm_Message(0xf8, 0, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_TUNE_REQUEST:
|
||||
pm_ev.message = Pm_Message(0xf6, 0, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_RESET:
|
||||
pm_ev.message = Pm_Message(0xff, 0, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_SENSING:
|
||||
pm_ev.message = Pm_Message(0xfe, 0, 0);
|
||||
pm_read_short(midi, &pm_ev);
|
||||
break;
|
||||
case SND_SEQ_EVENT_SYSEX: {
|
||||
const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;
|
||||
/* assume there is one sysex byte to process */
|
||||
pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static PmError alsa_poll(PmInternal *midi)
|
||||
{
|
||||
snd_seq_event_t *ev;
|
||||
/* expensive check for input data, gets data from device: */
|
||||
while (snd_seq_event_input_pending(seq, TRUE) > 0) {
|
||||
/* cheap check on local input buffer */
|
||||
while (snd_seq_event_input_pending(seq, FALSE) > 0) {
|
||||
/* check for and ignore errors, e.g. input overflow */
|
||||
/* note: if there's overflow, this should be reported
|
||||
* all the way through to client. Since input from all
|
||||
* devices is merged, we need to find all input devices
|
||||
* and set all to the overflow state.
|
||||
* NOTE: this assumes every input is ALSA based.
|
||||
*/
|
||||
int rslt = snd_seq_event_input(seq, &ev);
|
||||
if (rslt >= 0) {
|
||||
handle_event(ev);
|
||||
} else if (rslt == -ENOSPC) {
|
||||
int i;
|
||||
for (i = 0; i < pm_descriptor_index; i++) {
|
||||
if (descriptors[i].pub.input) {
|
||||
PmInternal *midi = (PmInternal *)
|
||||
descriptors[i].internalDescriptor;
|
||||
/* careful, device may not be open! */
|
||||
if (midi) Pm_SetOverflow(midi->queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
static unsigned int alsa_has_host_error(PmInternal *midi)
|
||||
{
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
return desc->error;
|
||||
}
|
||||
|
||||
|
||||
static void alsa_get_host_error(PmInternal *midi, char *msg, unsigned int len)
|
||||
{
|
||||
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
|
||||
int err = (pm_hosterror || desc->error);
|
||||
get_alsa_error_text(msg, len, err);
|
||||
}
|
||||
|
||||
|
||||
pm_fns_node pm_linuxalsa_in_dictionary = {
|
||||
none_write_short,
|
||||
none_sysex,
|
||||
none_sysex,
|
||||
none_write_byte,
|
||||
none_write_short,
|
||||
none_write_flush,
|
||||
alsa_synchronize,
|
||||
alsa_in_open,
|
||||
alsa_abort,
|
||||
alsa_in_close,
|
||||
alsa_poll,
|
||||
alsa_has_host_error,
|
||||
alsa_get_host_error
|
||||
};
|
||||
|
||||
pm_fns_node pm_linuxalsa_out_dictionary = {
|
||||
alsa_write_short,
|
||||
alsa_sysex,
|
||||
alsa_sysex,
|
||||
alsa_write_byte,
|
||||
alsa_write_short, /* short realtime message */
|
||||
alsa_write_flush,
|
||||
alsa_synchronize,
|
||||
alsa_out_open,
|
||||
alsa_abort,
|
||||
alsa_out_close,
|
||||
none_poll,
|
||||
alsa_has_host_error,
|
||||
alsa_get_host_error
|
||||
};
|
||||
|
||||
|
||||
/* pm_strdup -- copy a string to the heap. Use this rather than strdup so
|
||||
* that we call pm_alloc, not malloc. This allows portmidi to avoid
|
||||
* malloc which might cause priority inversion. Probably ALSA is going
|
||||
* to call malloc anyway, so this extra work here may be pointless.
|
||||
*/
|
||||
char *pm_strdup(const char *s)
|
||||
{
|
||||
int len = strlen(s);
|
||||
char *dup = (char *) pm_alloc(len + 1);
|
||||
strcpy(dup, s);
|
||||
return dup;
|
||||
}
|
||||
|
||||
|
||||
PmError pm_linuxalsa_init( void )
|
||||
{
|
||||
int err;
|
||||
snd_seq_client_info_t *cinfo;
|
||||
snd_seq_port_info_t *pinfo;
|
||||
unsigned int caps;
|
||||
|
||||
/* Previously, the last parameter was SND_SEQ_NONBLOCK, but this
|
||||
* would cause messages to be dropped if the ALSA buffer fills up.
|
||||
* The correct behavior is for writes to block until there is
|
||||
* room to send all the data. The client should normally allocate
|
||||
* a large enough buffer to avoid blocking on output.
|
||||
* Now that blocking is enabled, the seq_event_input() will block
|
||||
* if there is no input data. This is not what we want, so must
|
||||
* call seq_event_input_pending() to avoid blocking.
|
||||
*/
|
||||
err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
||||
if (err < 0) return err;
|
||||
|
||||
snd_seq_client_info_alloca(&cinfo);
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
|
||||
snd_seq_client_info_set_client(cinfo, -1);
|
||||
while (snd_seq_query_next_client(seq, cinfo) == 0) {
|
||||
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
|
||||
snd_seq_port_info_set_port(pinfo, -1);
|
||||
while (snd_seq_query_next_port(seq, pinfo) == 0) {
|
||||
if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)
|
||||
continue; /* ignore Timer and Announce ports on client 0 */
|
||||
caps = snd_seq_port_info_get_capability(pinfo);
|
||||
if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE)))
|
||||
continue; /* ignore if you cannot read or write port */
|
||||
if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) {
|
||||
if (pm_default_output_device_id == -1)
|
||||
pm_default_output_device_id = pm_descriptor_index;
|
||||
pm_add_device((char *)"ALSA",
|
||||
pm_strdup(snd_seq_port_info_get_name(pinfo)),
|
||||
FALSE,
|
||||
MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
|
||||
snd_seq_port_info_get_port(pinfo)),
|
||||
&pm_linuxalsa_out_dictionary);
|
||||
}
|
||||
if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {
|
||||
if (pm_default_input_device_id == -1)
|
||||
pm_default_input_device_id = pm_descriptor_index;
|
||||
pm_add_device((char *)"ALSA",
|
||||
pm_strdup(snd_seq_port_info_get_name(pinfo)),
|
||||
TRUE,
|
||||
MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
|
||||
snd_seq_port_info_get_port(pinfo)),
|
||||
&pm_linuxalsa_in_dictionary);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
void pm_linuxalsa_term(void)
|
||||
{
|
||||
if (seq) {
|
||||
snd_seq_close(seq);
|
||||
pm_free(descriptors);
|
||||
descriptors = NULL;
|
||||
pm_descriptor_index = 0;
|
||||
pm_descriptor_max = 0;
|
||||
}
|
||||
}
|
6
src/lib/portmidi/pmlinuxalsa.h
Normal file
6
src/lib/portmidi/pmlinuxalsa.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* pmlinuxalsa.h -- system-specific definitions */
|
||||
|
||||
PmError pm_linuxalsa_init(void);
|
||||
void pm_linuxalsa_term(void);
|
||||
|
||||
|
59
src/lib/portmidi/pmmac.c
Normal file
59
src/lib/portmidi/pmmac.c
Normal file
@ -0,0 +1,59 @@
|
||||
/* pmmac.c -- PortMidi os-dependent code */
|
||||
|
||||
/* This file only needs to implement:
|
||||
pm_init(), which calls various routines to register the
|
||||
available midi devices,
|
||||
Pm_GetDefaultInputDeviceID(), and
|
||||
Pm_GetDefaultOutputDeviceID().
|
||||
It is seperate from pmmacosxcm because we might want to register
|
||||
non-CoreMIDI devices.
|
||||
*/
|
||||
|
||||
#include "stdlib.h"
|
||||
#include "portmidi.h"
|
||||
#include "pmutil.h"
|
||||
#include "pminternal.h"
|
||||
#include "pmmacosxcm.h"
|
||||
|
||||
PmDeviceID pm_default_input_device_id = -1;
|
||||
PmDeviceID pm_default_output_device_id = -1;
|
||||
|
||||
void pm_init()
|
||||
{
|
||||
PmError err = pm_macosxcm_init();
|
||||
// this is set when we return to Pm_Initialize, but we need it
|
||||
// now in order to (successfully) call Pm_CountDevices()
|
||||
pm_initialized = TRUE;
|
||||
if (!err) {
|
||||
pm_default_input_device_id = find_default_device(
|
||||
(char *)"/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE,
|
||||
pm_default_input_device_id);
|
||||
pm_default_output_device_id = find_default_device(
|
||||
(char *)"/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE,
|
||||
pm_default_output_device_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void pm_term(void)
|
||||
{
|
||||
pm_macosxcm_term();
|
||||
}
|
||||
|
||||
|
||||
PmDeviceID Pm_GetDefaultInputDeviceID()
|
||||
{
|
||||
Pm_Initialize();
|
||||
return pm_default_input_device_id;
|
||||
}
|
||||
|
||||
PmDeviceID Pm_GetDefaultOutputDeviceID() {
|
||||
Pm_Initialize();
|
||||
return pm_default_output_device_id;
|
||||
}
|
||||
|
||||
void *pm_alloc(size_t s) { return malloc(s); }
|
||||
|
||||
void pm_free(void *ptr) { free(ptr); }
|
||||
|
||||
|
4
src/lib/portmidi/pmmac.h
Normal file
4
src/lib/portmidi/pmmac.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* pmmac.h */
|
||||
|
||||
extern PmDeviceID pm_default_input_device_id;
|
||||
extern PmDeviceID pm_default_output_device_id;
|
1010
src/lib/portmidi/pmmacosxcm.c
Normal file
1010
src/lib/portmidi/pmmacosxcm.c
Normal file
File diff suppressed because it is too large
Load Diff
6
src/lib/portmidi/pmmacosxcm.h
Normal file
6
src/lib/portmidi/pmmacosxcm.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* system-specific definitions */
|
||||
|
||||
PmError pm_macosxcm_init(void);
|
||||
void pm_macosxcm_term(void);
|
||||
|
||||
PmDeviceID find_default_device(char *path, int input, PmDeviceID id);
|
284
src/lib/portmidi/pmutil.c
Normal file
284
src/lib/portmidi/pmutil.c
Normal file
@ -0,0 +1,284 @@
|
||||
/* pmutil.c -- some helpful utilities for building midi
|
||||
applications that use PortMidi
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "portmidi.h"
|
||||
#include "pmutil.h"
|
||||
#include "pminternal.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#define bzero(addr, siz) memset(addr, 0, siz)
|
||||
#endif
|
||||
|
||||
// #define QUEUE_DEBUG 1
|
||||
#ifdef QUEUE_DEBUG
|
||||
#include "stdio.h"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
long head;
|
||||
long tail;
|
||||
long len;
|
||||
long overflow;
|
||||
int32_t msg_size; /* number of int32_t in a message including extra word */
|
||||
int32_t peek_overflow;
|
||||
int32_t *buffer;
|
||||
int32_t *peek;
|
||||
int32_t peek_flag;
|
||||
} PmQueueRep;
|
||||
|
||||
|
||||
PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg)
|
||||
{
|
||||
int32_t int32s_per_msg =
|
||||
(int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) &
|
||||
~(sizeof(int32_t) - 1)) / sizeof(int32_t));
|
||||
PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));
|
||||
if (!queue) /* memory allocation failed */
|
||||
return NULL;
|
||||
|
||||
/* need extra word per message for non-zero encoding */
|
||||
queue->len = num_msgs * (int32s_per_msg + 1);
|
||||
queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t));
|
||||
bzero(queue->buffer, queue->len * sizeof(int32_t));
|
||||
if (!queue->buffer) {
|
||||
pm_free(queue);
|
||||
return NULL;
|
||||
} else { /* allocate the "peek" buffer */
|
||||
queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t));
|
||||
if (!queue->peek) {
|
||||
/* free everything allocated so far and return */
|
||||
pm_free(queue->buffer);
|
||||
pm_free(queue);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
bzero(queue->buffer, queue->len * sizeof(int32_t));
|
||||
queue->head = 0;
|
||||
queue->tail = 0;
|
||||
/* msg_size is in words */
|
||||
queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */
|
||||
queue->overflow = FALSE;
|
||||
queue->peek_overflow = FALSE;
|
||||
queue->peek_flag = FALSE;
|
||||
return queue;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT PmError Pm_QueueDestroy(PmQueue *q)
|
||||
{
|
||||
PmQueueRep *queue = (PmQueueRep *) q;
|
||||
|
||||
/* arg checking */
|
||||
if (!queue || !queue->buffer || !queue->peek)
|
||||
return pmBadPtr;
|
||||
|
||||
pm_free(queue->peek);
|
||||
pm_free(queue->buffer);
|
||||
pm_free(queue);
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg)
|
||||
{
|
||||
long head;
|
||||
PmQueueRep *queue = (PmQueueRep *) q;
|
||||
int i;
|
||||
int32_t *msg_as_int32 = (int32_t *) msg;
|
||||
|
||||
/* arg checking */
|
||||
if (!queue)
|
||||
return pmBadPtr;
|
||||
/* a previous peek operation encountered an overflow, but the overflow
|
||||
* has not yet been reported to client, so do it now. No message is
|
||||
* returned, but on the next call, we will return the peek buffer.
|
||||
*/
|
||||
if (queue->peek_overflow) {
|
||||
queue->peek_overflow = FALSE;
|
||||
return pmBufferOverflow;
|
||||
}
|
||||
if (queue->peek_flag) {
|
||||
memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t));
|
||||
queue->peek_flag = FALSE;
|
||||
return pmGotData;
|
||||
}
|
||||
|
||||
head = queue->head;
|
||||
/* if writer overflows, it writes queue->overflow = tail+1 so that
|
||||
* when the reader gets to that position in the buffer, it can
|
||||
* return the overflow condition to the reader. The problem is that
|
||||
* at overflow, things have wrapped around, so tail == head, and the
|
||||
* reader will detect overflow immediately instead of waiting until
|
||||
* it reads everything in the buffer, wrapping around again to the
|
||||
* point where tail == head. So the condition also checks that
|
||||
* queue->buffer[head] is zero -- if so, then the buffer is now
|
||||
* empty, and we're at the point in the msg stream where overflow
|
||||
* occurred. It's time to signal overflow to the reader. If
|
||||
* queue->buffer[head] is non-zero, there's a message there and we
|
||||
* should read all the way around the buffer before signalling overflow.
|
||||
* There is a write-order dependency here, but to fail, the overflow
|
||||
* field would have to be written while an entire buffer full of
|
||||
* writes are still pending. I'm assuming out-of-order writes are
|
||||
* possible, but not that many.
|
||||
*/
|
||||
if (queue->overflow == head + 1 && !queue->buffer[head]) {
|
||||
queue->overflow = 0; /* non-overflow condition */
|
||||
return pmBufferOverflow;
|
||||
}
|
||||
|
||||
/* test to see if there is data in the queue -- test from back
|
||||
* to front so if writer is simultaneously writing, we don't
|
||||
* waste time discovering the write is not finished
|
||||
*/
|
||||
for (i = queue->msg_size - 1; i >= 0; i--) {
|
||||
if (!queue->buffer[head + i]) {
|
||||
return pmNoData;
|
||||
}
|
||||
}
|
||||
memcpy(msg, (char *) &queue->buffer[head + 1],
|
||||
sizeof(int32_t) * (queue->msg_size - 1));
|
||||
/* fix up zeros */
|
||||
i = queue->buffer[head];
|
||||
while (i < queue->msg_size) {
|
||||
int32_t j;
|
||||
i--; /* msg does not have extra word so shift down */
|
||||
j = msg_as_int32[i];
|
||||
msg_as_int32[i] = 0;
|
||||
i = j;
|
||||
}
|
||||
/* signal that data has been removed by zeroing: */
|
||||
bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size);
|
||||
|
||||
/* update head */
|
||||
head += queue->msg_size;
|
||||
if (head == queue->len) head = 0;
|
||||
queue->head = head;
|
||||
return pmGotData; /* success */
|
||||
}
|
||||
|
||||
|
||||
|
||||
PMEXPORT PmError Pm_SetOverflow(PmQueue *q)
|
||||
{
|
||||
PmQueueRep *queue = (PmQueueRep *) q;
|
||||
long tail;
|
||||
/* arg checking */
|
||||
if (!queue)
|
||||
return pmBadPtr;
|
||||
/* no more enqueue until receiver acknowledges overflow */
|
||||
if (queue->overflow) return pmBufferOverflow;
|
||||
tail = queue->tail;
|
||||
queue->overflow = tail + 1;
|
||||
return pmBufferOverflow;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg)
|
||||
{
|
||||
PmQueueRep *queue = (PmQueueRep *) q;
|
||||
long tail;
|
||||
int i;
|
||||
int32_t *src = (int32_t *) msg;
|
||||
int32_t *ptr;
|
||||
int32_t *dest;
|
||||
int rslt;
|
||||
if (!queue)
|
||||
return pmBadPtr;
|
||||
/* no more enqueue until receiver acknowledges overflow */
|
||||
if (queue->overflow) return pmBufferOverflow;
|
||||
rslt = Pm_QueueFull(q);
|
||||
/* already checked above: if (rslt == pmBadPtr) return rslt; */
|
||||
tail = queue->tail;
|
||||
if (rslt) {
|
||||
queue->overflow = tail + 1;
|
||||
return pmBufferOverflow;
|
||||
}
|
||||
|
||||
/* queue is has room for message, and overflow flag is cleared */
|
||||
ptr = &queue->buffer[tail];
|
||||
dest = ptr + 1;
|
||||
for (i = 1; i < queue->msg_size; i++) {
|
||||
int32_t j = src[i - 1];
|
||||
if (!j) {
|
||||
*ptr = i;
|
||||
ptr = dest;
|
||||
} else {
|
||||
*dest = j;
|
||||
}
|
||||
dest++;
|
||||
}
|
||||
*ptr = i;
|
||||
tail += queue->msg_size;
|
||||
if (tail == queue->len) tail = 0;
|
||||
queue->tail = tail;
|
||||
return pmNoError;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT int Pm_QueueEmpty(PmQueue *q)
|
||||
{
|
||||
PmQueueRep *queue = (PmQueueRep *) q;
|
||||
return (!queue) || /* null pointer -> return "empty" */
|
||||
(queue->buffer[queue->head] == 0 && !queue->peek_flag);
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT int Pm_QueueFull(PmQueue *q)
|
||||
{
|
||||
long tail;
|
||||
int i;
|
||||
PmQueueRep *queue = (PmQueueRep *) q;
|
||||
/* arg checking */
|
||||
if (!queue)
|
||||
return pmBadPtr;
|
||||
tail = queue->tail;
|
||||
/* test to see if there is space in the queue */
|
||||
for (i = 0; i < queue->msg_size; i++) {
|
||||
if (queue->buffer[tail + i]) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT void *Pm_QueuePeek(PmQueue *q)
|
||||
{
|
||||
PmError rslt;
|
||||
int32_t temp;
|
||||
PmQueueRep *queue = (PmQueueRep *) q;
|
||||
/* arg checking */
|
||||
if (!queue)
|
||||
return NULL;
|
||||
|
||||
if (queue->peek_flag) {
|
||||
return queue->peek;
|
||||
}
|
||||
/* this is ugly: if peek_overflow is set, then Pm_Dequeue()
|
||||
* returns immediately with pmBufferOverflow, but here, we
|
||||
* want Pm_Dequeue() to really check for data. If data is
|
||||
* there, we can return it
|
||||
*/
|
||||
temp = queue->peek_overflow;
|
||||
queue->peek_overflow = FALSE;
|
||||
rslt = Pm_Dequeue(q, queue->peek);
|
||||
queue->peek_overflow = temp;
|
||||
|
||||
if (rslt == 1) {
|
||||
queue->peek_flag = TRUE;
|
||||
return queue->peek;
|
||||
} else if (rslt == pmBufferOverflow) {
|
||||
/* when overflow is indicated, the queue is empty and the
|
||||
* first message that was dropped by Enqueue (signalling
|
||||
* pmBufferOverflow to its caller) would have been the next
|
||||
* message in the queue. Pm_QueuePeek will return NULL, but
|
||||
* remember that an overflow occurred. (see Pm_Dequeue)
|
||||
*/
|
||||
queue->peek_overflow = TRUE;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
127
src/lib/portmidi/pmutil.h
Normal file
127
src/lib/portmidi/pmutil.h
Normal file
@ -0,0 +1,127 @@
|
||||
/* pmutil.h -- some helpful utilities for building midi
|
||||
applications that use PortMidi
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
typedef void PmQueue;
|
||||
|
||||
/*
|
||||
A single-reader, single-writer queue is created by
|
||||
Pm_QueueCreate(), which takes the number of messages and
|
||||
the message size as parameters. The queue only accepts
|
||||
fixed sized messages. Returns NULL if memory cannot be allocated.
|
||||
|
||||
This queue implementation uses the "light pipe" algorithm which
|
||||
operates correctly even with multi-processors and out-of-order
|
||||
memory writes. (see Alexander Dokumentov, "Lock-free Interprocess
|
||||
Communication," Dr. Dobbs Portal, http://www.ddj.com/,
|
||||
articleID=189401457, June 15, 2006. This algorithm requires
|
||||
that messages be translated to a form where no words contain
|
||||
zeros. Each word becomes its own "data valid" tag. Because of
|
||||
this translation, we cannot return a pointer to data still in
|
||||
the queue when the "peek" method is called. Instead, a buffer
|
||||
is preallocated so that data can be copied there. Pm_QueuePeek()
|
||||
dequeues a message into this buffer and returns a pointer to
|
||||
it. A subsequent Pm_Dequeue() will copy from this buffer.
|
||||
|
||||
This implementation does not try to keep reader/writer data in
|
||||
separate cache lines or prevent thrashing on cache lines.
|
||||
However, this algorithm differs by doing inserts/removals in
|
||||
units of messages rather than units of machine words. Some
|
||||
performance improvement might be obtained by not clearing data
|
||||
immediately after a read, but instead by waiting for the end
|
||||
of the cache line, especially if messages are smaller than
|
||||
cache lines. See the Dokumentov article for explanation.
|
||||
|
||||
The algorithm is extended to handle "overflow" reporting. To report
|
||||
an overflow, the sender writes the current tail position to a field.
|
||||
The receiver must acknowlege receipt by zeroing the field. The sender
|
||||
will not send more until the field is zeroed.
|
||||
|
||||
Pm_QueueDestroy() destroys the queue and frees its storage.
|
||||
*/
|
||||
|
||||
PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg);
|
||||
PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue);
|
||||
|
||||
/*
|
||||
Pm_Dequeue() removes one item from the queue, copying it into msg.
|
||||
Returns 1 if successful, and 0 if the queue is empty.
|
||||
Returns pmBufferOverflow if what would have been the next thing
|
||||
in the queue was dropped due to overflow. (So when overflow occurs,
|
||||
the receiver can receive a queue full of messages before getting the
|
||||
overflow report. This protocol ensures that the reader will be
|
||||
notified when data is lost due to overflow.
|
||||
*/
|
||||
PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg);
|
||||
|
||||
|
||||
/*
|
||||
Pm_Enqueue() inserts one item into the queue, copying it from msg.
|
||||
Returns pmNoError if successful and pmBufferOverflow if the queue was
|
||||
already full. If pmBufferOverflow is returned, the overflow flag is set.
|
||||
*/
|
||||
PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg);
|
||||
|
||||
|
||||
/*
|
||||
Pm_QueueFull() returns non-zero if the queue is full
|
||||
Pm_QueueEmpty() returns non-zero if the queue is empty
|
||||
|
||||
Either condition may change immediately because a parallel
|
||||
enqueue or dequeue operation could be in progress. Furthermore,
|
||||
Pm_QueueEmpty() is optimistic: it may say false, when due to
|
||||
out-of-order writes, the full message has not arrived. Therefore,
|
||||
Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns
|
||||
false. On the other hand, Pm_QueueFull() is pessimistic: if it
|
||||
returns false, then Pm_Enqueue() is guaranteed to succeed.
|
||||
|
||||
Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL.
|
||||
Pm_QueueEmpty() returns FALSE if queue is NULL.
|
||||
*/
|
||||
PMEXPORT int Pm_QueueFull(PmQueue *queue);
|
||||
PMEXPORT int Pm_QueueEmpty(PmQueue *queue);
|
||||
|
||||
|
||||
/*
|
||||
Pm_QueuePeek() returns a pointer to the item at the head of the queue,
|
||||
or NULL if the queue is empty. The item is not removed from the queue.
|
||||
Pm_QueuePeek() will not indicate when an overflow occurs. If you want
|
||||
to get and check pmBufferOverflow messages, use the return value of
|
||||
Pm_QueuePeek() *only* as an indication that you should call
|
||||
Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would
|
||||
return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally
|
||||
clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume
|
||||
enqueuing messages. A subsequent call to Pm_QueuePeek()
|
||||
will return a pointer to the first message *after* the overflow.
|
||||
Using this as an indication to call Pm_Dequeue(), the first call
|
||||
to Pm_Dequeue() will return pmBufferOverflow. The second call will
|
||||
return success, copying the same message pointed to by the previous
|
||||
Pm_QueuePeek().
|
||||
|
||||
When to use Pm_QueuePeek(): (1) when you need to look at the message
|
||||
data to decide who should be called to receive it. (2) when you need
|
||||
to know a message is ready but cannot accept the message.
|
||||
|
||||
Note that Pm_QueuePeek() is not a fast check, so if possible, you
|
||||
might as well just call Pm_Dequeue() and accept the data if it is there.
|
||||
*/
|
||||
PMEXPORT void *Pm_QueuePeek(PmQueue *queue);
|
||||
|
||||
/*
|
||||
Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow
|
||||
condition to the reader (dequeuer). E.g. when transfering data from
|
||||
the OS to an application, if the OS indicates a buffer overrun,
|
||||
Pm_SetOverflow() can be used to insure that the reader receives a
|
||||
pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue
|
||||
is NULL, returns pmBufferOverflow if buffer is already in an overflow
|
||||
state, returns pmNoError if successfully set overflow state.
|
||||
*/
|
||||
PMEXPORT PmError Pm_SetOverflow(PmQueue *queue);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
144
src/lib/portmidi/pmwin.c
Normal file
144
src/lib/portmidi/pmwin.c
Normal file
@ -0,0 +1,144 @@
|
||||
/* pmwin.c -- PortMidi os-dependent code */
|
||||
|
||||
/* This file only needs to implement:
|
||||
pm_init(), which calls various routines to register the
|
||||
available midi devices,
|
||||
Pm_GetDefaultInputDeviceID(), and
|
||||
Pm_GetDefaultOutputDeviceID().
|
||||
This file must
|
||||
be separate from the main portmidi.c file because it is system
|
||||
dependent, and it is separate from, say, pmwinmm.c, because it
|
||||
might need to register devices for winmm, directx, and others.
|
||||
|
||||
*/
|
||||
|
||||
#include "stdlib.h"
|
||||
#include "portmidi.h"
|
||||
#include "pmutil.h"
|
||||
#include "pminternal.h"
|
||||
#include "pmwinmm.h"
|
||||
#ifdef DEBUG
|
||||
#include "stdio.h"
|
||||
#endif
|
||||
#undef UNICODE
|
||||
#include <windows.h>
|
||||
|
||||
/* pm_exit is called when the program exits.
|
||||
It calls pm_term to make sure PortMidi is properly closed.
|
||||
If DEBUG is on, we prompt for input to avoid losing error messages.
|
||||
*/
|
||||
static void pm_exit(void) {
|
||||
pm_term();
|
||||
#ifdef DEBUG
|
||||
#define STRING_MAX 80
|
||||
{
|
||||
char line[STRING_MAX];
|
||||
printf("Type ENTER...\n");
|
||||
/* note, w/o this prompting, client console application can not see one
|
||||
of its errors before closing. */
|
||||
fgets(line, STRING_MAX, stdin);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* pm_init is the windows-dependent initialization.*/
|
||||
void pm_init(void)
|
||||
{
|
||||
atexit(pm_exit);
|
||||
#ifdef DEBUG
|
||||
printf("registered pm_exit with atexit()\n");
|
||||
#endif
|
||||
pm_winmm_init();
|
||||
/* initialize other APIs (DirectX?) here */
|
||||
}
|
||||
|
||||
|
||||
void pm_term(void) {
|
||||
pm_winmm_term();
|
||||
}
|
||||
|
||||
|
||||
static PmDeviceID pm_get_default_device_id(int is_input, char *key) {
|
||||
HKEY hkey;
|
||||
#define PATTERN_MAX 256
|
||||
char pattern[PATTERN_MAX];
|
||||
DWORD pattern_max = PATTERN_MAX;
|
||||
DWORD dwType;
|
||||
/* Find first input or device -- this is the default. */
|
||||
PmDeviceID id = pmNoDevice;
|
||||
int i, j;
|
||||
Pm_Initialize(); /* make sure descriptors exist! */
|
||||
for (i = 0; i < pm_descriptor_index; i++) {
|
||||
if (descriptors[i].pub.input == is_input) {
|
||||
id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Look in registry for a default device name pattern. */
|
||||
if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software", 0, KEY_READ, &hkey) !=
|
||||
ERROR_SUCCESS) {
|
||||
return id;
|
||||
}
|
||||
if (RegOpenKeyExA(hkey, "JavaSoft", 0, KEY_READ, &hkey) !=
|
||||
ERROR_SUCCESS) {
|
||||
return id;
|
||||
}
|
||||
if (RegOpenKeyExA(hkey, "Prefs", 0, KEY_READ, &hkey) !=
|
||||
ERROR_SUCCESS) {
|
||||
return id;
|
||||
}
|
||||
if (RegOpenKeyExA(hkey, "/Port/Midi", 0, KEY_READ, &hkey) !=
|
||||
ERROR_SUCCESS) {
|
||||
return id;
|
||||
}
|
||||
if (RegQueryValueExA(hkey, key, NULL, &dwType, (BYTE *)pattern, &pattern_max) !=
|
||||
ERROR_SUCCESS) {
|
||||
return id;
|
||||
}
|
||||
|
||||
/* decode pattern: upper case encoded with "/" prefix */
|
||||
i = j = 0;
|
||||
while (pattern[i]) {
|
||||
if (pattern[i] == '/' && pattern[i + 1]) {
|
||||
pattern[j++] = toupper(pattern[++i]);
|
||||
} else {
|
||||
pattern[j++] = tolower(pattern[i]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
pattern[j] = 0; /* end of string */
|
||||
|
||||
/* now pattern is the string from the registry; search for match */
|
||||
i = pm_find_default_device(pattern, is_input);
|
||||
if (i != pmNoDevice) {
|
||||
id = i;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
PmDeviceID Pm_GetDefaultInputDeviceID() {
|
||||
return pm_get_default_device_id(TRUE,
|
||||
(char *)"/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E");
|
||||
}
|
||||
|
||||
|
||||
PmDeviceID Pm_GetDefaultOutputDeviceID() {
|
||||
return pm_get_default_device_id(FALSE,
|
||||
(char *)"/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E");
|
||||
}
|
||||
|
||||
|
||||
#include "stdio.h"
|
||||
|
||||
void *pm_alloc(size_t s) {
|
||||
return malloc(s);
|
||||
}
|
||||
|
||||
|
||||
void pm_free(void *ptr) {
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
|
1482
src/lib/portmidi/pmwinmm.c
Normal file
1482
src/lib/portmidi/pmwinmm.c
Normal file
File diff suppressed because it is too large
Load Diff
5
src/lib/portmidi/pmwinmm.h
Normal file
5
src/lib/portmidi/pmwinmm.h
Normal file
@ -0,0 +1,5 @@
|
||||
/* midiwin32.h -- system-specific definitions */
|
||||
|
||||
void pm_winmm_init( void );
|
||||
void pm_winmm_term( void );
|
||||
|
1137
src/lib/portmidi/portmidi.c
Normal file
1137
src/lib/portmidi/portmidi.c
Normal file
File diff suppressed because it is too large
Load Diff
654
src/lib/portmidi/portmidi.h
Normal file
654
src/lib/portmidi/portmidi.h
Normal file
@ -0,0 +1,654 @@
|
||||
#ifndef PORT_MIDI_H
|
||||
#define PORT_MIDI_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* PortMidi Portable Real-Time MIDI Library
|
||||
* PortMidi API Header File
|
||||
* Latest version available at: http://sourceforge.net/projects/portmedia
|
||||
*
|
||||
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||
* Copyright (c) 2001-2006 Roger B. Dannenberg
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The text above constitutes the entire PortMidi license; however,
|
||||
* the PortMusic community also makes the following non-binding requests:
|
||||
*
|
||||
* Any person wishing to distribute modifications to the Software is
|
||||
* requested to send the modifications to the original developer so that
|
||||
* they can be incorporated into the canonical version. It is also
|
||||
* requested that these non-binding requests be included along with the
|
||||
* license above.
|
||||
*/
|
||||
|
||||
/* CHANGELOG FOR PORTMIDI
|
||||
* (see ../CHANGELOG.txt)
|
||||
*
|
||||
* NOTES ON HOST ERROR REPORTING:
|
||||
*
|
||||
* PortMidi errors (of type PmError) are generic, system-independent errors.
|
||||
* When an error does not map to one of the more specific PmErrors, the
|
||||
* catch-all code pmHostError is returned. This means that PortMidi has
|
||||
* retained a more specific system-dependent error code. The caller can
|
||||
* get more information by calling Pm_HasHostError() to test if there is
|
||||
* a pending host error, and Pm_GetHostErrorText() to get a text string
|
||||
* describing the error. Host errors are reported on a per-device basis
|
||||
* because only after you open a device does PortMidi have a place to
|
||||
* record the host error code. I.e. only
|
||||
* those routines that receive a (PortMidiStream *) argument check and
|
||||
* report errors. One exception to this is that Pm_OpenInput() and
|
||||
* Pm_OpenOutput() can report errors even though when an error occurs,
|
||||
* there is no PortMidiStream* to hold the error. Fortunately, both
|
||||
* of these functions return any error immediately, so we do not really
|
||||
* need per-device error memory. Instead, any host error code is stored
|
||||
* in a global, pmHostError is returned, and the user can call
|
||||
* Pm_GetHostErrorText() to get the error message (and the invalid stream
|
||||
* parameter will be ignored.) The functions
|
||||
* pm_init and pm_term do not fail or raise
|
||||
* errors. The job of pm_init is to locate all available devices so that
|
||||
* the caller can get information via PmDeviceInfo(). If an error occurs,
|
||||
* the device is simply not listed as available.
|
||||
*
|
||||
* Host errors come in two flavors:
|
||||
* a) host error
|
||||
* b) host error during callback
|
||||
* These can occur w/midi input or output devices. (b) can only happen
|
||||
* asynchronously (during callback routines), whereas (a) only occurs while
|
||||
* synchronously running PortMidi and any resulting system dependent calls.
|
||||
* Both (a) and (b) are reported by the next read or write call. You can
|
||||
* also query for asynchronous errors (b) at any time by calling
|
||||
* Pm_HasHostError().
|
||||
*
|
||||
* NOTES ON COMPILE-TIME SWITCHES
|
||||
*
|
||||
* DEBUG assumes stdio and a console. Use this if you want automatic, simple
|
||||
* error reporting, e.g. for prototyping. If you are using MFC or some
|
||||
* other graphical interface with no console, DEBUG probably should be
|
||||
* undefined.
|
||||
* PM_CHECK_ERRORS more-or-less takes over error checking for return values,
|
||||
* stopping your program and printing error messages when an error
|
||||
* occurs. This also uses stdio for console text I/O.
|
||||
*/
|
||||
|
||||
#ifndef WIN32
|
||||
// Linux and OS X have stdint.h
|
||||
#include <stdint.h>
|
||||
#else
|
||||
#ifndef INT32_DEFINED
|
||||
// rather than having users install a special .h file for windows,
|
||||
// just put the required definitions inline here. porttime.h uses
|
||||
// these too, so the definitions are (unfortunately) duplicated there
|
||||
typedef int int32_t;
|
||||
typedef unsigned int uint32_t;
|
||||
#define INT32_DEFINED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//#ifdef _WINDLL
|
||||
//#define PMEXPORT __declspec(dllexport)
|
||||
//#else
|
||||
#define PMEXPORT
|
||||
//#endif
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
|
||||
/* default size of buffers for sysex transmission: */
|
||||
#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024
|
||||
|
||||
/** List of portmidi errors.*/
|
||||
typedef enum {
|
||||
pmNoError = 0,
|
||||
pmNoData = 0, /**< A "no error" return that also indicates no data avail. */
|
||||
pmGotData = 1, /**< A "no error" return that also indicates data available */
|
||||
pmHostError = -10000,
|
||||
pmInvalidDeviceId, /** out of range or
|
||||
* output device when input is requested or
|
||||
* input device when output is requested or
|
||||
* device is already opened
|
||||
*/
|
||||
pmInsufficientMemory,
|
||||
pmBufferTooSmall,
|
||||
pmBufferOverflow,
|
||||
pmBadPtr, /* PortMidiStream parameter is NULL or
|
||||
* stream is not opened or
|
||||
* stream is output when input is required or
|
||||
* stream is input when output is required */
|
||||
pmBadData, /** illegal midi data, e.g. missing EOX */
|
||||
pmInternalError,
|
||||
pmBufferMaxSize /** buffer is already as large as it can be */
|
||||
/* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */
|
||||
} PmError;
|
||||
|
||||
/**
|
||||
Pm_Initialize() is the library initialisation function - call this before
|
||||
using the library.
|
||||
*/
|
||||
PMEXPORT PmError Pm_Initialize( void );
|
||||
|
||||
/**
|
||||
Pm_Terminate() is the library termination function - call this after
|
||||
using the library.
|
||||
*/
|
||||
PMEXPORT PmError Pm_Terminate( void );
|
||||
|
||||
/** A single PortMidiStream is a descriptor for an open MIDI device.
|
||||
*/
|
||||
typedef void PortMidiStream;
|
||||
#define PmStream PortMidiStream
|
||||
|
||||
/**
|
||||
Test whether stream has a pending host error. Normally, the client finds
|
||||
out about errors through returned error codes, but some errors can occur
|
||||
asynchronously where the client does not
|
||||
explicitly call a function, and therefore cannot receive an error code.
|
||||
The client can test for a pending error using Pm_HasHostError(). If true,
|
||||
the error can be accessed and cleared by calling Pm_GetErrorText().
|
||||
Errors are also cleared by calling other functions that can return
|
||||
errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The
|
||||
client does not need to call Pm_HasHostError(). Any pending error will be
|
||||
reported the next time the client performs an explicit function call on
|
||||
the stream, e.g. an input or output operation. Until the error is cleared,
|
||||
no new error codes will be obtained, even for a different stream.
|
||||
*/
|
||||
PMEXPORT int Pm_HasHostError( PortMidiStream * stream );
|
||||
|
||||
|
||||
/** Translate portmidi error number into human readable message.
|
||||
These strings are constants (set at compile time) so client has
|
||||
no need to allocate storage
|
||||
*/
|
||||
PMEXPORT const char *Pm_GetErrorText( PmError errnum );
|
||||
|
||||
/** Translate portmidi host error into human readable message.
|
||||
These strings are computed at run time, so client has to allocate storage.
|
||||
After this routine executes, the host error is cleared.
|
||||
*/
|
||||
PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len);
|
||||
|
||||
#define HDRLENGTH 50
|
||||
#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less
|
||||
than this number of characters */
|
||||
|
||||
/**
|
||||
Device enumeration mechanism.
|
||||
|
||||
Device ids range from 0 to Pm_CountDevices()-1.
|
||||
|
||||
*/
|
||||
typedef int PmDeviceID;
|
||||
#define pmNoDevice -1
|
||||
typedef struct {
|
||||
int structVersion; /**< this internal structure version */
|
||||
const char *interf; /**< underlying MIDI API, e.g. MMSystem or DirectX */
|
||||
const char *name; /**< device name, e.g. USB MidiSport 1x1 */
|
||||
int input; /**< true iff input is available */
|
||||
int output; /**< true iff output is available */
|
||||
int opened; /**< used by generic PortMidi code to do error checking on arguments */
|
||||
|
||||
} PmDeviceInfo;
|
||||
|
||||
/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */
|
||||
PMEXPORT int Pm_CountDevices( void );
|
||||
/**
|
||||
Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID()
|
||||
|
||||
Return the default device ID or pmNoDevice if there are no devices.
|
||||
The result (but not pmNoDevice) can be passed to Pm_OpenMidi().
|
||||
|
||||
The default device can be specified using a small application
|
||||
named pmdefaults that is part of the PortMidi distribution. This
|
||||
program in turn uses the Java Preferences object created by
|
||||
java.util.prefs.Preferences.userRoot().node("/PortMidi"); the
|
||||
preference is set by calling
|
||||
prefs.put("PM_RECOMMENDED_OUTPUT_DEVICE", prefName);
|
||||
or prefs.put("PM_RECOMMENDED_INPUT_DEVICE", prefName);
|
||||
|
||||
In the statements above, prefName is a string describing the
|
||||
MIDI device in the form "interf, name" where interf identifies
|
||||
the underlying software system or API used by PortMdi to access
|
||||
devices and name is the name of the device. These correspond to
|
||||
the interf and name fields of a PmDeviceInfo. (Currently supported
|
||||
interfaces are "MMSystem" for Win32, "ALSA" for Linux, and
|
||||
"CoreMIDI" for OS X, so in fact, there is no choice of interface.)
|
||||
In "interf, name", the strings are actually substrings of
|
||||
the full interface and name strings. For example, the preference
|
||||
"Core, Sport" will match a device with interface "CoreMIDI"
|
||||
and name "In USB MidiSport 1x1". It will also match "CoreMIDI"
|
||||
and "In USB MidiSport 2x2". The devices are enumerated in device
|
||||
ID order, so the lowest device ID that matches the pattern becomes
|
||||
the default device. Finally, if the comma-space (", ") separator
|
||||
between interface and name parts of the preference is not found,
|
||||
the entire preference string is interpreted as a name, and the
|
||||
interface part is the empty string, which matches anything.
|
||||
|
||||
On the MAC, preferences are stored in
|
||||
/Users/$NAME/Library/Preferences/com.apple.java.util.prefs.plist
|
||||
which is a binary file. In addition to the pmdefaults program,
|
||||
there are utilities that can read and edit this preference file.
|
||||
|
||||
On the PC,
|
||||
|
||||
On Linux,
|
||||
|
||||
*/
|
||||
PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID( void );
|
||||
/** see PmDeviceID Pm_GetDefaultInputDeviceID() */
|
||||
PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID( void );
|
||||
|
||||
/**
|
||||
PmTimestamp is used to represent a millisecond clock with arbitrary
|
||||
start time. The type is used for all MIDI timestampes and clocks.
|
||||
*/
|
||||
typedef int32_t PmTimestamp;
|
||||
typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);
|
||||
|
||||
/** TRUE if t1 before t2 */
|
||||
#define PmBefore(t1,t2) ((t1-t2) < 0)
|
||||
/**
|
||||
\defgroup grp_device Input/Output Devices Handling
|
||||
@{
|
||||
*/
|
||||
/**
|
||||
Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure
|
||||
referring to the device specified by id.
|
||||
If id is out of range the function returns NULL.
|
||||
|
||||
The returned structure is owned by the PortMidi implementation and must
|
||||
not be manipulated or freed. The pointer is guaranteed to be valid
|
||||
between calls to Pm_Initialize() and Pm_Terminate().
|
||||
*/
|
||||
PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id );
|
||||
|
||||
/**
|
||||
Pm_OpenInput() and Pm_OpenOutput() open devices.
|
||||
|
||||
stream is the address of a PortMidiStream pointer which will receive
|
||||
a pointer to the newly opened stream.
|
||||
|
||||
inputDevice is the id of the device used for input (see PmDeviceID above).
|
||||
|
||||
inputDriverInfo is a pointer to an optional driver specific data structure
|
||||
containing additional information for device setup or handle processing.
|
||||
inputDriverInfo is never required for correct operation. If not used
|
||||
inputDriverInfo should be NULL.
|
||||
|
||||
outputDevice is the id of the device used for output (see PmDeviceID above.)
|
||||
|
||||
outputDriverInfo is a pointer to an optional driver specific data structure
|
||||
containing additional information for device setup or handle processing.
|
||||
outputDriverInfo is never required for correct operation. If not used
|
||||
outputDriverInfo should be NULL.
|
||||
|
||||
For input, the buffersize specifies the number of input events to be
|
||||
buffered waiting to be read using Pm_Read(). For output, buffersize
|
||||
specifies the number of output events to be buffered waiting for output.
|
||||
(In some cases -- see below -- PortMidi does not buffer output at all
|
||||
and merely passes data to a lower-level API, in which case buffersize
|
||||
is ignored.)
|
||||
|
||||
latency is the delay in milliseconds applied to timestamps to determine
|
||||
when the output should actually occur. (If latency is < 0, 0 is assumed.)
|
||||
If latency is zero, timestamps are ignored and all output is delivered
|
||||
immediately. If latency is greater than zero, output is delayed until the
|
||||
message timestamp plus the latency. (NOTE: the time is measured relative
|
||||
to the time source indicated by time_proc. Timestamps are absolute,
|
||||
not relative delays or offsets.) In some cases, PortMidi can obtain
|
||||
better timing than your application by passing timestamps along to the
|
||||
device driver or hardware. Latency may also help you to synchronize midi
|
||||
data to audio data by matching midi latency to the audio buffer latency.
|
||||
|
||||
time_proc is a pointer to a procedure that returns time in milliseconds. It
|
||||
may be NULL, in which case a default millisecond timebase (PortTime) is
|
||||
used. If the application wants to use PortTime, it should start the timer
|
||||
(call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the
|
||||
application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput,
|
||||
it may get a ptAlreadyStarted error from Pt_Start, and the application's
|
||||
preferred time resolution and callback function will be ignored.
|
||||
time_proc result values are appended to incoming MIDI data, and time_proc
|
||||
times are used to schedule outgoing MIDI data (when latency is non-zero).
|
||||
|
||||
time_info is a pointer passed to time_proc.
|
||||
|
||||
Example: If I provide a timestamp of 5000, latency is 1, and time_proc
|
||||
returns 4990, then the desired output time will be when time_proc returns
|
||||
timestamp+latency = 5001. This will be 5001-4990 = 11ms from now.
|
||||
|
||||
return value:
|
||||
Upon success Pm_Open() returns PmNoError and places a pointer to a
|
||||
valid PortMidiStream in the stream argument.
|
||||
If a call to Pm_Open() fails a nonzero error code is returned (see
|
||||
PMError above) and the value of port is invalid.
|
||||
|
||||
Any stream that is successfully opened should eventually be closed
|
||||
by calling Pm_Close().
|
||||
|
||||
*/
|
||||
PMEXPORT PmError Pm_OpenInput( PortMidiStream** stream,
|
||||
PmDeviceID inputDevice,
|
||||
void *inputDriverInfo,
|
||||
int32_t bufferSize,
|
||||
PmTimeProcPtr time_proc,
|
||||
void *time_info );
|
||||
|
||||
PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream,
|
||||
PmDeviceID outputDevice,
|
||||
void *outputDriverInfo,
|
||||
int32_t bufferSize,
|
||||
PmTimeProcPtr time_proc,
|
||||
void *time_info,
|
||||
int32_t latency );
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
\defgroup grp_events_filters Events and Filters Handling
|
||||
@{
|
||||
*/
|
||||
|
||||
/* \function PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters )
|
||||
Pm_SetFilter() sets filters on an open input stream to drop selected
|
||||
input types. By default, only active sensing messages are filtered.
|
||||
To prohibit, say, active sensing and sysex messages, call
|
||||
Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX);
|
||||
|
||||
Filtering is useful when midi routing or midi thru functionality is being
|
||||
provided by the user application.
|
||||
For example, you may want to exclude timing messages (clock, MTC, start/stop/continue),
|
||||
while allowing note-related messages to pass.
|
||||
Or you may be using a sequencer or drum-machine for MIDI clock information but want to
|
||||
exclude any notes it may play.
|
||||
*/
|
||||
|
||||
/* Filter bit-mask definitions */
|
||||
/** filter active sensing messages (0xFE): */
|
||||
#define PM_FILT_ACTIVE (1 << 0x0E)
|
||||
/** filter system exclusive messages (0xF0): */
|
||||
#define PM_FILT_SYSEX (1 << 0x00)
|
||||
/** filter MIDI clock message (0xF8) */
|
||||
#define PM_FILT_CLOCK (1 << 0x08)
|
||||
/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
|
||||
#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
|
||||
/** filter tick messages (0xF9) */
|
||||
#define PM_FILT_TICK (1 << 0x09)
|
||||
/** filter undefined FD messages */
|
||||
#define PM_FILT_FD (1 << 0x0D)
|
||||
/** filter undefined real-time messages */
|
||||
#define PM_FILT_UNDEFINED PM_FILT_FD
|
||||
/** filter reset messages (0xFF) */
|
||||
#define PM_FILT_RESET (1 << 0x0F)
|
||||
/** filter all real-time messages */
|
||||
#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \
|
||||
PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)
|
||||
/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
|
||||
#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
|
||||
/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
|
||||
#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)
|
||||
/** per-note aftertouch (0xA0-0xAF) */
|
||||
#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A)
|
||||
/** filter both channel and poly aftertouch */
|
||||
#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH)
|
||||
/** Program changes (0xC0-0xCF) */
|
||||
#define PM_FILT_PROGRAM (1 << 0x1C)
|
||||
/** Control Changes (CC's) (0xB0-0xBF)*/
|
||||
#define PM_FILT_CONTROL (1 << 0x1B)
|
||||
/** Pitch Bender (0xE0-0xEF*/
|
||||
#define PM_FILT_PITCHBEND (1 << 0x1E)
|
||||
/** MIDI Time Code (0xF1)*/
|
||||
#define PM_FILT_MTC (1 << 0x01)
|
||||
/** Song Position (0xF2) */
|
||||
#define PM_FILT_SONG_POSITION (1 << 0x02)
|
||||
/** Song Select (0xF3)*/
|
||||
#define PM_FILT_SONG_SELECT (1 << 0x03)
|
||||
/** Tuning request (0xF6)*/
|
||||
#define PM_FILT_TUNE (1 << 0x06)
|
||||
/** All System Common messages (mtc, song position, song select, tune request) */
|
||||
#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE)
|
||||
|
||||
|
||||
PMEXPORT PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters );
|
||||
|
||||
#define Pm_Channel(channel) (1<<(channel))
|
||||
/**
|
||||
Pm_SetChannelMask() filters incoming messages based on channel.
|
||||
The mask is a 16-bit bitfield corresponding to appropriate channels.
|
||||
The Pm_Channel macro can assist in calling this function.
|
||||
i.e. to set receive only input on channel 1, call with
|
||||
Pm_SetChannelMask(Pm_Channel(1));
|
||||
Multiple channels should be OR'd together, like
|
||||
Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11))
|
||||
|
||||
Note that channels are numbered 0 to 15 (not 1 to 16). Most
|
||||
synthesizer and interfaces number channels starting at 1, but
|
||||
PortMidi numbers channels starting at 0.
|
||||
|
||||
All channels are allowed by default
|
||||
*/
|
||||
PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);
|
||||
|
||||
/**
|
||||
Pm_Abort() terminates outgoing messages immediately
|
||||
The caller should immediately close the output port;
|
||||
this call may result in transmission of a partial midi message.
|
||||
There is no abort for Midi input because the user can simply
|
||||
ignore messages in the buffer and close an input device at
|
||||
any time.
|
||||
*/
|
||||
PMEXPORT PmError Pm_Abort( PortMidiStream* stream );
|
||||
|
||||
/**
|
||||
Pm_Close() closes a midi stream, flushing any pending buffers.
|
||||
(PortMidi attempts to close open streams when the application
|
||||
exits -- this is particularly difficult under Windows.)
|
||||
*/
|
||||
PMEXPORT PmError Pm_Close( PortMidiStream* stream );
|
||||
|
||||
/**
|
||||
Pm_Synchronize() instructs PortMidi to (re)synchronize to the
|
||||
time_proc passed when the stream was opened. Typically, this
|
||||
is used when the stream must be opened before the time_proc
|
||||
reference is actually advancing. In this case, message timing
|
||||
may be erratic, but since timestamps of zero mean
|
||||
"send immediately," initialization messages with zero timestamps
|
||||
can be written without a functioning time reference and without
|
||||
problems. Before the first MIDI message with a non-zero
|
||||
timestamp is written to the stream, the time reference must
|
||||
begin to advance (for example, if the time_proc computes time
|
||||
based on audio samples, time might begin to advance when an
|
||||
audio stream becomes active). After time_proc return values
|
||||
become valid, and BEFORE writing the first non-zero timestamped
|
||||
MIDI message, call Pm_Synchronize() so that PortMidi can observe
|
||||
the difference between the current time_proc value and its
|
||||
MIDI stream time.
|
||||
|
||||
In the more normal case where time_proc
|
||||
values advance continuously, there is no need to call
|
||||
Pm_Synchronize. PortMidi will always synchronize at the
|
||||
first output message and periodically thereafter.
|
||||
*/
|
||||
PmError Pm_Synchronize( PortMidiStream* stream );
|
||||
|
||||
|
||||
/**
|
||||
Pm_Message() encodes a short Midi message into a 32-bit word. If data1
|
||||
and/or data2 are not present, use zero.
|
||||
|
||||
Pm_MessageStatus(), Pm_MessageData1(), and
|
||||
Pm_MessageData2() extract fields from a 32-bit midi message.
|
||||
*/
|
||||
#define Pm_Message(status, data1, data2) \
|
||||
((((data2) << 16) & 0xFF0000) | \
|
||||
(((data1) << 8) & 0xFF00) | \
|
||||
((status) & 0xFF))
|
||||
#define Pm_MessageStatus(msg) ((msg) & 0xFF)
|
||||
#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)
|
||||
#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)
|
||||
|
||||
typedef int32_t PmMessage; /**< see PmEvent */
|
||||
/**
|
||||
All midi data comes in the form of PmEvent structures. A sysex
|
||||
message is encoded as a sequence of PmEvent structures, with each
|
||||
structure carrying 4 bytes of the message, i.e. only the first
|
||||
PmEvent carries the status byte.
|
||||
|
||||
Note that MIDI allows nested messages: the so-called "real-time" MIDI
|
||||
messages can be inserted into the MIDI byte stream at any location,
|
||||
including within a sysex message. MIDI real-time messages are one-byte
|
||||
messages used mainly for timing (see the MIDI spec). PortMidi retains
|
||||
the order of non-real-time MIDI messages on both input and output, but
|
||||
it does not specify exactly how real-time messages are processed. This
|
||||
is particulary problematic for MIDI input, because the input parser
|
||||
must either prepare to buffer an unlimited number of sysex message
|
||||
bytes or to buffer an unlimited number of real-time messages that
|
||||
arrive embedded in a long sysex message. To simplify things, the input
|
||||
parser is allowed to pass real-time MIDI messages embedded within a
|
||||
sysex message, and it is up to the client to detect, process, and
|
||||
remove these messages as they arrive.
|
||||
|
||||
When receiving sysex messages, the sysex message is terminated
|
||||
by either an EOX status byte (anywhere in the 4 byte messages) or
|
||||
by a non-real-time status byte in the low order byte of the message.
|
||||
If you get a non-real-time status byte but there was no EOX byte, it
|
||||
means the sysex message was somehow truncated. This is not
|
||||
considered an error; e.g., a missing EOX can result from the user
|
||||
disconnecting a MIDI cable during sysex transmission.
|
||||
|
||||
A real-time message can occur within a sysex message. A real-time
|
||||
message will always occupy a full PmEvent with the status byte in
|
||||
the low-order byte of the PmEvent message field. (This implies that
|
||||
the byte-order of sysex bytes and real-time message bytes may not
|
||||
be preserved -- for example, if a real-time message arrives after
|
||||
3 bytes of a sysex message, the real-time message will be delivered
|
||||
first. The first word of the sysex message will be delivered only
|
||||
after the 4th byte arrives, filling the 4-byte PmEvent message field.
|
||||
|
||||
The timestamp field is observed when the output port is opened with
|
||||
a non-zero latency. A timestamp of zero means "use the current time",
|
||||
which in turn means to deliver the message with a delay of
|
||||
latency (the latency parameter used when opening the output port.)
|
||||
Do not expect PortMidi to sort data according to timestamps --
|
||||
messages should be sent in the correct order, and timestamps MUST
|
||||
be non-decreasing. See also "Example" for Pm_OpenOutput() above.
|
||||
|
||||
A sysex message will generally fill many PmEvent structures. On
|
||||
output to a PortMidiStream with non-zero latency, the first timestamp
|
||||
on sysex message data will determine the time to begin sending the
|
||||
message. PortMidi implementations may ignore timestamps for the
|
||||
remainder of the sysex message.
|
||||
|
||||
On input, the timestamp ideally denotes the arrival time of the
|
||||
status byte of the message. The first timestamp on sysex message
|
||||
data will be valid. Subsequent timestamps may denote
|
||||
when message bytes were actually received, or they may be simply
|
||||
copies of the first timestamp.
|
||||
|
||||
Timestamps for nested messages: If a real-time message arrives in
|
||||
the middle of some other message, it is enqueued immediately with
|
||||
the timestamp corresponding to its arrival time. The interrupted
|
||||
non-real-time message or 4-byte packet of sysex data will be enqueued
|
||||
later. The timestamp of interrupted data will be equal to that of
|
||||
the interrupting real-time message to insure that timestamps are
|
||||
non-decreasing.
|
||||
*/
|
||||
typedef struct {
|
||||
PmMessage message;
|
||||
PmTimestamp timestamp;
|
||||
} PmEvent;
|
||||
|
||||
/**
|
||||
@}
|
||||
*/
|
||||
/** \defgroup grp_io Reading and Writing Midi Messages
|
||||
@{
|
||||
*/
|
||||
/**
|
||||
Pm_Read() retrieves midi data into a buffer, and returns the number
|
||||
of events read. Result is a non-negative number unless an error occurs,
|
||||
in which case a PmError value will be returned.
|
||||
|
||||
Buffer Overflow
|
||||
|
||||
The problem: if an input overflow occurs, data will be lost, ultimately
|
||||
because there is no flow control all the way back to the data source.
|
||||
When data is lost, the receiver should be notified and some sort of
|
||||
graceful recovery should take place, e.g. you shouldn't resume receiving
|
||||
in the middle of a long sysex message.
|
||||
|
||||
With a lock-free fifo, which is pretty much what we're stuck with to
|
||||
enable portability to the Mac, it's tricky for the producer and consumer
|
||||
to synchronously reset the buffer and resume normal operation.
|
||||
|
||||
Solution: the buffer managed by PortMidi will be flushed when an overflow
|
||||
occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow)
|
||||
and ordinary processing resumes as soon as a new message arrives. The
|
||||
remainder of a partial sysex message is not considered to be a "new
|
||||
message" and will be flushed as well.
|
||||
|
||||
*/
|
||||
PMEXPORT int Pm_Read( PortMidiStream *stream, PmEvent *buffer, int32_t length );
|
||||
|
||||
/**
|
||||
Pm_Poll() tests whether input is available,
|
||||
returning TRUE, FALSE, or an error value.
|
||||
*/
|
||||
PMEXPORT PmError Pm_Poll( PortMidiStream *stream);
|
||||
|
||||
/**
|
||||
Pm_Write() writes midi data from a buffer. This may contain:
|
||||
- short messages
|
||||
or
|
||||
- sysex messages that are converted into a sequence of PmEvent
|
||||
structures, e.g. sending data from a file or forwarding them
|
||||
from midi input.
|
||||
|
||||
Use Pm_WriteSysEx() to write a sysex message stored as a contiguous
|
||||
array of bytes.
|
||||
|
||||
Sysex data may contain embedded real-time messages.
|
||||
*/
|
||||
PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length );
|
||||
|
||||
/**
|
||||
Pm_WriteShort() writes a timestamped non-system-exclusive midi message.
|
||||
Messages are delivered in order as received, and timestamps must be
|
||||
non-decreasing. (But timestamps are ignored if the stream was opened
|
||||
with latency = 0.)
|
||||
*/
|
||||
PMEXPORT PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, int32_t msg);
|
||||
|
||||
/**
|
||||
Pm_WriteSysEx() writes a timestamped system-exclusive midi message.
|
||||
*/
|
||||
PMEXPORT PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif /* PORT_MIDI_H */
|
3
src/lib/portmidi/porttime.c
Normal file
3
src/lib/portmidi/porttime.c
Normal file
@ -0,0 +1,3 @@
|
||||
/* porttime.c -- portable API for millisecond timer */
|
||||
|
||||
/* There is no machine-independent implementation code to put here */
|
88
src/lib/portmidi/porttime.h
Normal file
88
src/lib/portmidi/porttime.h
Normal file
@ -0,0 +1,88 @@
|
||||
/* porttime.h -- portable interface to millisecond timer */
|
||||
|
||||
/* CHANGE LOG FOR PORTTIME
|
||||
10-Jun-03 Mark Nelson & RBD
|
||||
boost priority of timer thread in ptlinux.c implementation
|
||||
*/
|
||||
|
||||
/* Should there be a way to choose the source of time here? */
|
||||
|
||||
#ifdef WIN32
|
||||
#ifndef INT32_DEFINED
|
||||
// rather than having users install a special .h file for windows,
|
||||
// just put the required definitions inline here. portmidi.h uses
|
||||
// these too, so the definitions are (unfortunately) duplicated there
|
||||
typedef int int32_t;
|
||||
typedef unsigned int uint32_t;
|
||||
#define INT32_DEFINED
|
||||
#endif
|
||||
#else
|
||||
#include <stdint.h> // needed for int32_t
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef PMEXPORT
|
||||
#define PMEXPORT
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
ptNoError = 0, /* success */
|
||||
ptHostError = -10000, /* a system-specific error occurred */
|
||||
ptAlreadyStarted, /* cannot start timer because it is already started */
|
||||
ptAlreadyStopped, /* cannot stop timer because it is already stopped */
|
||||
ptInsufficientMemory /* memory could not be allocated */
|
||||
} PtError;
|
||||
|
||||
|
||||
typedef int32_t PtTimestamp;
|
||||
|
||||
typedef void (PtCallback)( PtTimestamp timestamp, void *userData );
|
||||
|
||||
/*
|
||||
Pt_Start() starts a real-time service.
|
||||
|
||||
resolution is the timer resolution in ms. The time will advance every
|
||||
resolution ms.
|
||||
|
||||
callback is a function pointer to be called every resolution ms.
|
||||
|
||||
userData is passed to callback as a parameter.
|
||||
|
||||
return value:
|
||||
Upon success, returns ptNoError. See PtError for other values.
|
||||
*/
|
||||
PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
|
||||
|
||||
/*
|
||||
Pt_Stop() stops the timer.
|
||||
|
||||
return value:
|
||||
Upon success, returns ptNoError. See PtError for other values.
|
||||
*/
|
||||
PMEXPORT PtError Pt_Stop(void);
|
||||
|
||||
/*
|
||||
Pt_Started() returns true iff the timer is running.
|
||||
*/
|
||||
PMEXPORT int Pt_Started(void);
|
||||
|
||||
/*
|
||||
Pt_Time() returns the current time in ms.
|
||||
*/
|
||||
PMEXPORT PtTimestamp Pt_Time(void);
|
||||
|
||||
/*
|
||||
Pt_Sleep() pauses, allowing other threads to run.
|
||||
|
||||
duration is the length of the pause in ms. The true duration
|
||||
of the pause may be rounded to the nearest or next clock tick
|
||||
as determined by resolution in Pt_Start().
|
||||
*/
|
||||
PMEXPORT void Pt_Sleep(int32_t duration);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
138
src/lib/portmidi/ptlinux.c
Normal file
138
src/lib/portmidi/ptlinux.c
Normal file
@ -0,0 +1,138 @@
|
||||
/* ptlinux.c -- portable timer implementation for linux */
|
||||
|
||||
|
||||
/* IMPLEMENTATION NOTES (by Mark Nelson):
|
||||
|
||||
Unlike Windows, Linux has no system call to request a periodic callback,
|
||||
so if Pt_Start() receives a callback parameter, it must create a thread
|
||||
that wakes up periodically and calls the provided callback function.
|
||||
If running as superuser, use setpriority() to renice thread to -20.
|
||||
One could also set the timer thread to a real-time priority (SCHED_FIFO
|
||||
and SCHED_RR), but this is dangerous for This is necessary because
|
||||
if the callback hangs it'll never return. A more serious reason
|
||||
is that the current scheduler implementation busy-waits instead
|
||||
of sleeping when realtime threads request a sleep of <=2ms (as a way
|
||||
to get around the 10ms granularity), which means the thread would never
|
||||
let anyone else on the CPU.
|
||||
|
||||
CHANGE LOG
|
||||
|
||||
18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer
|
||||
thread. Simplified implementation notes.
|
||||
|
||||
*/
|
||||
/* stdlib, stdio, unistd, and sys/types were added because they appeared
|
||||
* in a Gentoo patch, but I'm not sure why they are needed. -RBD
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include "porttime.h"
|
||||
#include "sys/time.h"
|
||||
#include "sys/resource.h"
|
||||
#include "sys/timeb.h"
|
||||
#include "pthread.h"
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
static int time_started_flag = FALSE;
|
||||
static struct timeb time_offset = {0, 0, 0, 0};
|
||||
static pthread_t pt_thread_pid;
|
||||
static int pt_thread_created = FALSE;
|
||||
|
||||
/* note that this is static data -- we only need one copy */
|
||||
typedef struct {
|
||||
int id;
|
||||
int resolution;
|
||||
PtCallback *callback;
|
||||
void *userData;
|
||||
} pt_callback_parameters;
|
||||
|
||||
static int pt_callback_proc_id = 0;
|
||||
|
||||
static void *Pt_CallbackProc(void *p)
|
||||
{
|
||||
pt_callback_parameters *parameters = (pt_callback_parameters *) p;
|
||||
int mytime = 1;
|
||||
/* to kill a process, just increment the pt_callback_proc_id */
|
||||
/* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
|
||||
parameters->id); */
|
||||
if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);
|
||||
while (pt_callback_proc_id == parameters->id) {
|
||||
/* wait for a multiple of resolution ms */
|
||||
struct timeval timeout;
|
||||
int delay = mytime++ * parameters->resolution - Pt_Time();
|
||||
if (delay < 0) delay = 0;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = delay * 1000;
|
||||
select(0, NULL, NULL, NULL, &timeout);
|
||||
(*(parameters->callback))(Pt_Time(), parameters->userData);
|
||||
}
|
||||
/* printf("Pt_CallbackProc exiting\n"); */
|
||||
// free(parameters);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
|
||||
{
|
||||
if (time_started_flag) return ptNoError;
|
||||
ftime(&time_offset); /* need this set before process runs */
|
||||
if (callback) {
|
||||
int res;
|
||||
pt_callback_parameters *parms = (pt_callback_parameters *)
|
||||
malloc(sizeof(pt_callback_parameters));
|
||||
if (!parms) return ptInsufficientMemory;
|
||||
parms->id = pt_callback_proc_id;
|
||||
parms->resolution = resolution;
|
||||
parms->callback = callback;
|
||||
parms->userData = userData;
|
||||
res = pthread_create(&pt_thread_pid, NULL,
|
||||
Pt_CallbackProc, parms);
|
||||
if (res != 0) return ptHostError;
|
||||
pt_thread_created = TRUE;
|
||||
}
|
||||
time_started_flag = TRUE;
|
||||
return ptNoError;
|
||||
}
|
||||
|
||||
|
||||
PtError Pt_Stop()
|
||||
{
|
||||
/* printf("Pt_Stop called\n"); */
|
||||
pt_callback_proc_id++;
|
||||
if (pt_thread_created) {
|
||||
pthread_join(pt_thread_pid, NULL);
|
||||
pt_thread_created = FALSE;
|
||||
}
|
||||
time_started_flag = FALSE;
|
||||
return ptNoError;
|
||||
}
|
||||
|
||||
|
||||
int Pt_Started()
|
||||
{
|
||||
return time_started_flag;
|
||||
}
|
||||
|
||||
|
||||
PtTimestamp Pt_Time()
|
||||
{
|
||||
long seconds, milliseconds;
|
||||
struct timeb now;
|
||||
ftime(&now);
|
||||
seconds = now.time - time_offset.time;
|
||||
milliseconds = now.millitm - time_offset.millitm;
|
||||
return seconds * 1000 + milliseconds;
|
||||
}
|
||||
|
||||
|
||||
void Pt_Sleep(int32_t duration)
|
||||
{
|
||||
usleep(duration * 1000);
|
||||
}
|
||||
|
||||
|
||||
|
140
src/lib/portmidi/ptmacosx_cf.c
Normal file
140
src/lib/portmidi/ptmacosx_cf.c
Normal file
@ -0,0 +1,140 @@
|
||||
/* ptmacosx.c -- portable timer implementation for mac os x */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#import <mach/mach.h>
|
||||
#import <mach/mach_error.h>
|
||||
#import <mach/mach_time.h>
|
||||
#import <mach/clock.h>
|
||||
|
||||
#include "porttime.h"
|
||||
|
||||
#define THREAD_IMPORTANCE 30
|
||||
#define LONG_TIME 1000000000.0
|
||||
|
||||
static int time_started_flag = FALSE;
|
||||
static CFAbsoluteTime startTime = 0.0;
|
||||
static CFRunLoopRef timerRunLoop;
|
||||
|
||||
typedef struct {
|
||||
int resolution;
|
||||
PtCallback *callback;
|
||||
void *userData;
|
||||
} PtThreadParams;
|
||||
|
||||
|
||||
void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info)
|
||||
{
|
||||
PtThreadParams *params = (PtThreadParams*)info;
|
||||
(*params->callback)(Pt_Time(), params->userData);
|
||||
}
|
||||
|
||||
static void* Pt_Thread(void *p)
|
||||
{
|
||||
CFTimeInterval timerInterval;
|
||||
CFRunLoopTimerContext timerContext;
|
||||
CFRunLoopTimerRef timer;
|
||||
PtThreadParams *params = (PtThreadParams*)p;
|
||||
//CFTimeInterval timeout;
|
||||
|
||||
/* raise the thread's priority */
|
||||
kern_return_t error;
|
||||
thread_extended_policy_data_t extendedPolicy;
|
||||
thread_precedence_policy_data_t precedencePolicy;
|
||||
|
||||
extendedPolicy.timeshare = 0;
|
||||
error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
|
||||
(thread_policy_t)&extendedPolicy,
|
||||
THREAD_EXTENDED_POLICY_COUNT);
|
||||
if (error != KERN_SUCCESS) {
|
||||
mach_error("Couldn't set thread timeshare policy", error);
|
||||
}
|
||||
|
||||
precedencePolicy.importance = THREAD_IMPORTANCE;
|
||||
error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,
|
||||
(thread_policy_t)&precedencePolicy,
|
||||
THREAD_PRECEDENCE_POLICY_COUNT);
|
||||
if (error != KERN_SUCCESS) {
|
||||
mach_error("Couldn't set thread precedence policy", error);
|
||||
}
|
||||
|
||||
/* set up the timer context */
|
||||
timerContext.version = 0;
|
||||
timerContext.info = params;
|
||||
timerContext.retain = NULL;
|
||||
timerContext.release = NULL;
|
||||
timerContext.copyDescription = NULL;
|
||||
|
||||
/* create a new timer */
|
||||
timerInterval = (double)params->resolution / 1000.0;
|
||||
timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval,
|
||||
0, 0, Pt_CFTimerCallback, &timerContext);
|
||||
|
||||
timerRunLoop = CFRunLoopGetCurrent();
|
||||
CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode"));
|
||||
|
||||
/* run until we're told to stop by Pt_Stop() */
|
||||
CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false);
|
||||
|
||||
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode"));
|
||||
CFRelease(timer);
|
||||
free(params);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
|
||||
{
|
||||
PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams));
|
||||
pthread_t pthread_id;
|
||||
|
||||
printf("Pt_Start() called\n");
|
||||
|
||||
// /* make sure we're not already playing */
|
||||
if (time_started_flag) return ptAlreadyStarted;
|
||||
startTime = CFAbsoluteTimeGetCurrent();
|
||||
|
||||
if (callback) {
|
||||
|
||||
params->resolution = resolution;
|
||||
params->callback = callback;
|
||||
params->userData = userData;
|
||||
|
||||
pthread_create(&pthread_id, NULL, Pt_Thread, params);
|
||||
}
|
||||
|
||||
time_started_flag = TRUE;
|
||||
return ptNoError;
|
||||
}
|
||||
|
||||
|
||||
PtError Pt_Stop()
|
||||
{
|
||||
printf("Pt_Stop called\n");
|
||||
|
||||
CFRunLoopStop(timerRunLoop);
|
||||
time_started_flag = FALSE;
|
||||
return ptNoError;
|
||||
}
|
||||
|
||||
|
||||
int Pt_Started()
|
||||
{
|
||||
return time_started_flag;
|
||||
}
|
||||
|
||||
|
||||
PtTimestamp Pt_Time()
|
||||
{
|
||||
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
|
||||
return (PtTimestamp) ((now - startTime) * 1000.0);
|
||||
}
|
||||
|
||||
|
||||
void Pt_Sleep(int32_t duration)
|
||||
{
|
||||
usleep(duration * 1000);
|
||||
}
|
131
src/lib/portmidi/ptmacosx_mach.c
Normal file
131
src/lib/portmidi/ptmacosx_mach.c
Normal file
@ -0,0 +1,131 @@
|
||||
/* ptmacosx.c -- portable timer implementation for mac os x */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <CoreAudio/HostTime.h>
|
||||
|
||||
#import <mach/mach.h>
|
||||
#import <mach/mach_error.h>
|
||||
#import <mach/mach_time.h>
|
||||
#import <mach/clock.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "porttime.h"
|
||||
#include "sys/time.h"
|
||||
#include "pthread.h"
|
||||
|
||||
//#define NSEC_PER_MSEC 1000000
|
||||
#define THREAD_IMPORTANCE 30
|
||||
|
||||
static int time_started_flag = FALSE;
|
||||
static UInt64 start_time;
|
||||
static pthread_t pt_thread_pid;
|
||||
|
||||
/* note that this is static data -- we only need one copy */
|
||||
typedef struct {
|
||||
int id;
|
||||
int resolution;
|
||||
PtCallback *callback;
|
||||
void *userData;
|
||||
} pt_callback_parameters;
|
||||
|
||||
static int pt_callback_proc_id = 0;
|
||||
|
||||
static void *Pt_CallbackProc(void *p)
|
||||
{
|
||||
pt_callback_parameters *parameters = (pt_callback_parameters *) p;
|
||||
int mytime = 1;
|
||||
|
||||
kern_return_t error;
|
||||
thread_extended_policy_data_t extendedPolicy;
|
||||
thread_precedence_policy_data_t precedencePolicy;
|
||||
|
||||
extendedPolicy.timeshare = 0;
|
||||
error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
|
||||
(thread_policy_t)&extendedPolicy,
|
||||
THREAD_EXTENDED_POLICY_COUNT);
|
||||
if (error != KERN_SUCCESS) {
|
||||
mach_error("Couldn't set thread timeshare policy", error);
|
||||
}
|
||||
|
||||
precedencePolicy.importance = THREAD_IMPORTANCE;
|
||||
error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,
|
||||
(thread_policy_t)&precedencePolicy,
|
||||
THREAD_PRECEDENCE_POLICY_COUNT);
|
||||
if (error != KERN_SUCCESS) {
|
||||
mach_error("Couldn't set thread precedence policy", error);
|
||||
}
|
||||
|
||||
|
||||
/* to kill a process, just increment the pt_callback_proc_id */
|
||||
/* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, parameters->id); */
|
||||
while (pt_callback_proc_id == parameters->id) {
|
||||
/* wait for a multiple of resolution ms */
|
||||
UInt64 wait_time;
|
||||
int delay = mytime++ * parameters->resolution - Pt_Time();
|
||||
PtTimestamp timestamp;
|
||||
if (delay < 0) delay = 0;
|
||||
wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);
|
||||
wait_time += AudioGetCurrentHostTime();
|
||||
error = mach_wait_until(wait_time);
|
||||
timestamp = Pt_Time();
|
||||
(*(parameters->callback))(timestamp, parameters->userData);
|
||||
}
|
||||
free(parameters);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
|
||||
{
|
||||
if (time_started_flag) return ptAlreadyStarted;
|
||||
start_time = AudioGetCurrentHostTime();
|
||||
|
||||
if (callback) {
|
||||
int res;
|
||||
pt_callback_parameters *parms;
|
||||
|
||||
parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));
|
||||
if (!parms) return ptInsufficientMemory;
|
||||
parms->id = pt_callback_proc_id;
|
||||
parms->resolution = resolution;
|
||||
parms->callback = callback;
|
||||
parms->userData = userData;
|
||||
res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);
|
||||
if (res != 0) return ptHostError;
|
||||
}
|
||||
|
||||
time_started_flag = TRUE;
|
||||
return ptNoError;
|
||||
}
|
||||
|
||||
|
||||
PtError Pt_Stop()
|
||||
{
|
||||
/* printf("Pt_Stop called\n"); */
|
||||
pt_callback_proc_id++;
|
||||
pthread_join(pt_thread_pid, NULL);
|
||||
time_started_flag = FALSE;
|
||||
return ptNoError;
|
||||
}
|
||||
|
||||
|
||||
int Pt_Started()
|
||||
{
|
||||
return time_started_flag;
|
||||
}
|
||||
|
||||
|
||||
PtTimestamp Pt_Time()
|
||||
{
|
||||
UInt64 clock_time, nsec_time;
|
||||
clock_time = AudioGetCurrentHostTime() - start_time;
|
||||
nsec_time = AudioConvertHostTimeToNanos(clock_time);
|
||||
return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);
|
||||
}
|
||||
|
||||
|
||||
void Pt_Sleep(int32_t duration)
|
||||
{
|
||||
usleep(duration * 1000);
|
||||
}
|
70
src/lib/portmidi/ptwinmm.c
Normal file
70
src/lib/portmidi/ptwinmm.c
Normal file
@ -0,0 +1,70 @@
|
||||
/* ptwinmm.c -- portable timer implementation for win32 */
|
||||
|
||||
|
||||
#include "porttime.h"
|
||||
#include "windows.h"
|
||||
#include "time.h"
|
||||
|
||||
|
||||
TIMECAPS caps;
|
||||
|
||||
static long time_offset = 0;
|
||||
static int time_started_flag = FALSE;
|
||||
static long time_resolution;
|
||||
static MMRESULT timer_id;
|
||||
static PtCallback *time_callback;
|
||||
|
||||
void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser,
|
||||
DWORD_PTR dw1, DWORD_PTR dw2)
|
||||
{
|
||||
(*time_callback)(Pt_Time(), (void *) dwUser);
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
|
||||
{
|
||||
if (time_started_flag) return ptAlreadyStarted;
|
||||
timeBeginPeriod(resolution);
|
||||
time_resolution = resolution;
|
||||
time_offset = timeGetTime();
|
||||
time_started_flag = TRUE;
|
||||
time_callback = callback;
|
||||
if (callback) {
|
||||
timer_id = timeSetEvent(resolution, 1, winmm_time_callback,
|
||||
(DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
|
||||
if (!timer_id) return ptHostError;
|
||||
}
|
||||
return ptNoError;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT PtError Pt_Stop()
|
||||
{
|
||||
if (!time_started_flag) return ptAlreadyStopped;
|
||||
if (time_callback && timer_id) {
|
||||
timeKillEvent(timer_id);
|
||||
time_callback = NULL;
|
||||
timer_id = 0;
|
||||
}
|
||||
time_started_flag = FALSE;
|
||||
timeEndPeriod(time_resolution);
|
||||
return ptNoError;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT int Pt_Started()
|
||||
{
|
||||
return time_started_flag;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT PtTimestamp Pt_Time()
|
||||
{
|
||||
return timeGetTime() - time_offset;
|
||||
}
|
||||
|
||||
|
||||
PMEXPORT void Pt_Sleep(int32_t duration)
|
||||
{
|
||||
Sleep(duration);
|
||||
}
|
1130
src/lib/portmidi/readbinaryplist.c
Normal file
1130
src/lib/portmidi/readbinaryplist.c
Normal file
File diff suppressed because it is too large
Load Diff
88
src/lib/portmidi/readbinaryplist.h
Normal file
88
src/lib/portmidi/readbinaryplist.h
Normal file
@ -0,0 +1,88 @@
|
||||
/* readbinaryplist.h -- header to read preference files
|
||||
|
||||
Roger B. Dannenberg, Jun 2008
|
||||
*/
|
||||
|
||||
#include <stdint.h> /* for uint8_t ... */
|
||||
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
#define MAX_KEY_SIZE 256
|
||||
|
||||
enum
|
||||
{
|
||||
// Object tags (high nybble)
|
||||
kTAG_SIMPLE = 0x00, // Null, true, false, filler, or invalid
|
||||
kTAG_INT = 0x10,
|
||||
kTAG_REAL = 0x20,
|
||||
kTAG_DATE = 0x30,
|
||||
kTAG_DATA = 0x40,
|
||||
kTAG_ASCIISTRING = 0x50,
|
||||
kTAG_UNICODESTRING = 0x60,
|
||||
kTAG_UID = 0x80,
|
||||
kTAG_ARRAY = 0xA0,
|
||||
kTAG_DICTIONARY = 0xD0,
|
||||
|
||||
// "simple" object values
|
||||
kVALUE_NULL = 0x00,
|
||||
kVALUE_FALSE = 0x08,
|
||||
kVALUE_TRUE = 0x09,
|
||||
kVALUE_FILLER = 0x0F,
|
||||
|
||||
kVALUE_FULLDATETAG = 0x33 // Dates are tagged with a whole byte.
|
||||
};
|
||||
|
||||
|
||||
typedef struct pldata_struct {
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
} pldata_node, *pldata_ptr;
|
||||
|
||||
|
||||
typedef struct array_struct {
|
||||
struct value_struct **array;
|
||||
uint64_t length;
|
||||
} array_node, *array_ptr;
|
||||
|
||||
|
||||
// a dict_node is a list of <key, value> pairs
|
||||
typedef struct dict_struct {
|
||||
struct value_struct *key;
|
||||
struct value_struct *value;
|
||||
struct dict_struct *next;
|
||||
} dict_node, *dict_ptr;
|
||||
|
||||
|
||||
// an value_node is a value with a tag telling the type
|
||||
typedef struct value_struct {
|
||||
int tag;
|
||||
union {
|
||||
int64_t integer;
|
||||
uint64_t uinteger;
|
||||
double real;
|
||||
char *string;
|
||||
pldata_ptr data;
|
||||
array_ptr array;
|
||||
struct dict_struct *dict;
|
||||
};
|
||||
} value_node, *value_ptr;
|
||||
|
||||
|
||||
value_ptr bplist_read_file(char *filename);
|
||||
value_ptr bplist_read_user_pref(char *filename);
|
||||
value_ptr bplist_read_system_pref(char *filename);
|
||||
void bplist_free_data(void);
|
||||
|
||||
/*************** functions for accessing values ****************/
|
||||
|
||||
char *value_get_asciistring(value_ptr v);
|
||||
value_ptr value_dict_lookup_using_string(value_ptr v, char *key);
|
||||
value_ptr value_dict_lookup_using_path(value_ptr v, char *path);
|
||||
|
||||
/*************** functions for debugging ***************/
|
||||
|
||||
void plist_print(value_ptr v);
|
||||
|
@ -884,6 +884,18 @@ osd_directory_entry *osd_stat(const char *path);
|
||||
file_error osd_get_full_path(char **dst, const char *path);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
MIDI I/O INTERFACES
|
||||
***************************************************************************/
|
||||
struct osd_midi_device;
|
||||
|
||||
void osd_init_midi(void);
|
||||
void osd_shutdown_midi(void);
|
||||
void osd_list_midi_devices(void);
|
||||
osd_midi_device *osd_open_midi_input(const char *devname);
|
||||
osd_midi_device *osd_open_midi_output(const char *devname);
|
||||
void osd_close_midi_channel(osd_midi_device *dev);
|
||||
|
||||
/***************************************************************************
|
||||
UNCATEGORIZED INTERFACES
|
||||
***************************************************************************/
|
||||
|
80
src/osd/portmedia/pmmidi.c
Normal file
80
src/osd/portmedia/pmmidi.c
Normal file
@ -0,0 +1,80 @@
|
||||
//============================================================
|
||||
//
|
||||
// pmmidi.c - OSD interface for PortMidi
|
||||
//
|
||||
// Copyright (c) 1996-2013, Nicola Salmoria and the MAME Team.
|
||||
// Visit http://mamedev.org for licensing and usage restrictions.
|
||||
//
|
||||
//============================================================
|
||||
|
||||
#include "emu.h"
|
||||
#include "osdcore.h"
|
||||
#include "portmidi/portmidi.h"
|
||||
|
||||
void osd_list_midi_devices(void)
|
||||
{
|
||||
#ifndef DISABLE_MIDI
|
||||
int num_devs = Pm_CountDevices();
|
||||
const PmDeviceInfo *pmInfo;
|
||||
|
||||
printf("\n");
|
||||
|
||||
if (num_devs == 0)
|
||||
{
|
||||
printf("No MIDI ports were found\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("MIDI input ports:\n");
|
||||
for (int i = 0; i < num_devs; i++)
|
||||
{
|
||||
pmInfo = Pm_GetDeviceInfo(i);
|
||||
|
||||
if (pmInfo->input)
|
||||
{
|
||||
printf("%s %s\n", pmInfo->name, (i == Pm_GetDefaultInputDeviceID()) ? "(default)" : "");
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nMIDI output ports:\n");
|
||||
for (int i = 0; i < num_devs; i++)
|
||||
{
|
||||
pmInfo = Pm_GetDeviceInfo(i);
|
||||
|
||||
if (pmInfo->output)
|
||||
{
|
||||
printf("%s %s\n", pmInfo->name, (i == Pm_GetDefaultOutputDeviceID()) ? "(default)" : "");
|
||||
}
|
||||
}
|
||||
#else
|
||||
printf("\nMIDI is not supported in this build\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
osd_midi_device *osd_open_midi_input(const char *devname)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
osd_midi_device *osd_open_midi_output(const char *devname)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void osd_close_midi_channel(osd_midi_device *dev)
|
||||
{
|
||||
}
|
||||
|
||||
void osd_init_midi(void)
|
||||
{
|
||||
#ifndef DISABLE_MIDI
|
||||
Pm_Initialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
void osd_shutdown_midi(void)
|
||||
{
|
||||
#ifndef DISABLE_MIDI
|
||||
Pm_Terminate();
|
||||
#endif
|
||||
}
|
1
src/osd/sdl/sdlmidi.c
Normal file
1
src/osd/sdl/sdlmidi.c
Normal file
@ -0,0 +1 @@
|
||||
#include "../portmedia/pmmidi.c"
|
1
src/osd/windows/winmidi.c
Normal file
1
src/osd/windows/winmidi.c
Normal file
@ -0,0 +1 @@
|
||||
#include "../portmedia/pmmidi.c"
|
Loading…
Reference in New Issue
Block a user