diff options
Diffstat (limited to '3rdparty/portmidi/pm_linux')
-rw-r--r-- | 3rdparty/portmidi/pm_linux/README_LINUX.txt | 56 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_linux/finddefault.c | 30 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_linux/finddefault.h | 5 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_linux/pmlinux.c | 30 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_linux/pmlinuxalsa.c | 605 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_linux/pmlinuxnull.c | 31 | ||||
-rw-r--r-- | 3rdparty/portmidi/pm_linux/pmlinuxnull.h | 6 |
7 files changed, 422 insertions, 341 deletions
diff --git a/3rdparty/portmidi/pm_linux/README_LINUX.txt b/3rdparty/portmidi/pm_linux/README_LINUX.txt index 818793e570f..cfbc43f571a 100644 --- a/3rdparty/portmidi/pm_linux/README_LINUX.txt +++ b/3rdparty/portmidi/pm_linux/README_LINUX.txt @@ -1,45 +1,25 @@ README_LINUX.txt for PortMidi Roger Dannenberg -14 Oct 2009 +6 Dec 2012, revised May 2022 -To make PortMidi, you need cmake and the Java SDK. -Go back up to the portmidi directory and type: +Contents: + To make PortMidi + The pmdefaults program + Setting LD_LIBRARY_PATH + A note about amd64 + Using autoconf + Using configure + Changelog -ccmake . -Type 'c' (configure) and then 'g' (generate). You may have -to manually set JAVA_INCLUDE_PATH and JAVA_JVM_LIBRARY -by typing 't' (toggle to advanced mode) and using the -editor to change the fields. You can find possible values -for JAVA_INCLUDE_PATH by typing "locate jni.h", and for -JAVA_JVM_LIBRARY by typing locate libjvm". - -You also need JAVA_INCLUDE_PATH2, but this will normally -be set automatically after you set JAVA_INCLUDE_PATH and -run "configure" (type "c" to ccmake). Normally, -JAVA_INCLUDE_PATH2 is the linux subdirectory within -JAVA_INCLUDE_PATH. - -Notice that the CMAKE_BUILD_TYPE can be Debug or Release. -Stick with Release if you are not debugging. - -After successfully generating make files with ccmake, you -can run make: - -make - -The Makefile will build all test programs and the portmidi -library. For experimental software, -especially programs running from the command line, we -recommend using the Debug version -- it will terminate your -program and print a helpful message if any PortMidi -function returns an error code. (Released software should -check for error codes and handle them, but for quick, -non-critical projects, the automatic "print and die" -handling can save some work.) +See ../README.md for general instructions. THE pmdefaults PROGRAM +(This may be obsolete. It is older than `../README.md` which +also discusses pmdefaults, and Java support may be removed +unless someone claims they use it... -RBD) + You should install pmdefaults. It provides a graphical interface for selecting default MIDI IN and OUT devices so that you don't have to build device selection interfaces into all your programs @@ -72,7 +52,7 @@ export LD_LIBRARY_PATH A NOTE ABOUT AMD64: -When compiling portmidi under linux on an AMD64, I had to add the -fPIC +When compiling portmidi under linux on an AMD64, I had to add the -fPIC flag to the gcc flags. Reason: when trying to build John Harrison's pyPortMidi gcc bailed out @@ -90,6 +70,12 @@ be "PIC-enabled". CHANGELOG +27-may-2022 Roger B. Dannenberg + Some updates to this file. + +6-dec-2012 Roger B. Dannenberg + Copied notes on Autoconf from Audacity sources + 22-jan-2010 Roger B. Dannenberg Updated instructions about Java paths diff --git a/3rdparty/portmidi/pm_linux/finddefault.c b/3rdparty/portmidi/pm_linux/finddefault.c index c3077701a60..6189a8b7148 100644 --- a/3rdparty/portmidi/pm_linux/finddefault.c +++ b/3rdparty/portmidi/pm_linux/finddefault.c @@ -7,8 +7,9 @@ #include <string.h> #include <ctype.h> #include "portmidi.h" - -extern int pm_find_default_device(char *pattern, int is_input); +#include "pmutil.h" +#include "pminternal.h" +#include "finddefault.h" #define STRING_MAX 256 @@ -21,7 +22,7 @@ void skip_spaces(FILE *inf) } /* trim leading spaces and match a string */ -int match_string(FILE *inf, char *s) +int match_string(FILE *inf, const char *s) { skip_spaces(inf); while (*s && *s == getc(inf)) s++; @@ -29,19 +30,21 @@ int match_string(FILE *inf, char *s) } -/* Parse preference files, find default device, search devices -- +/* + Parse preference files, find default device, search devices -- */ -PmDeviceID find_default_device(char *path, int input, PmDeviceID id) +PmDeviceID find_default_device(const 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; + static char const *const pref_2 = "/.java/.userPrefs/"; + static char const *const pref_3 = "prefs.xml"; + const char *pref_1 = getenv("HOME"); + const char *path_ptr; + char *full_name; FILE *inf; int c, i; if (!pref_1) goto nopref; // cannot find preference file @@ -54,8 +57,8 @@ PmDeviceID find_default_device(char *path, int input, PmDeviceID id) 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); + path_ptr++; memcpy(full_name + offset, path, path_ptr - path); full_name[offset + path_ptr - path] = 0; // end of string } else { @@ -63,7 +66,6 @@ PmDeviceID find_default_device(char *path, int input, PmDeviceID id) } strcat(full_name, pref_3); inf = fopen(full_name, "r"); - free(full_name); 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 @@ -74,9 +76,9 @@ PmDeviceID find_default_device(char *path, int input, PmDeviceID id) // 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 + if (!match_string(inf, "value")) goto nopref; // value not found + if (!match_string(inf, "=")) goto nopref; // = not found + if (!match_string(inf, "\"")) 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; diff --git a/3rdparty/portmidi/pm_linux/finddefault.h b/3rdparty/portmidi/pm_linux/finddefault.h new file mode 100644 index 00000000000..4599fff8697 --- /dev/null +++ b/3rdparty/portmidi/pm_linux/finddefault.h @@ -0,0 +1,5 @@ +/* finddefault.h -- find_default_device() declaration + Roger Dannenberg, Jan 2021 +*/ + +PmDeviceID find_default_device(const char *path, int input, PmDeviceID id); diff --git a/3rdparty/portmidi/pm_linux/pmlinux.c b/3rdparty/portmidi/pm_linux/pmlinux.c index b05c54fccb9..05ac332c368 100644 --- a/3rdparty/portmidi/pm_linux/pmlinux.c +++ b/3rdparty/portmidi/pm_linux/pmlinux.c @@ -14,6 +14,7 @@ #include "portmidi.h" #include "pmutil.h" #include "pminternal.h" +#include "finddefault.h" #ifdef PMALSA #include "pmlinuxalsa.h" @@ -23,32 +24,34 @@ #include "pmlinuxnull.h" #endif +#if !(defined(PMALSA) || defined(PMNULL)) +#error One of PMALSA or PMNULL must be defined +#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() +void pm_init(void) { /* Note: it is not an error for PMALSA to fail to initialize. * It may be a design error that the client cannot query what subsystems * are working properly other than by looking at the list of available * devices. */ - #ifdef PMALSA - pm_linuxalsa_init(); - #endif - #ifdef PMNULL +#ifdef PMALSA + pm_linuxalsa_init(); +#endif +#ifdef PMNULL pm_linuxnull_init(); - #endif +#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, + "/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, + "/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE, pm_default_output_device_id); } @@ -57,14 +60,17 @@ void pm_term(void) #ifdef PMALSA pm_linuxalsa_term(); #endif + #ifdef PMNULL + pm_linuxnull_term(); + #endif } -PmDeviceID Pm_GetDefaultInputDeviceID() { +PmDeviceID Pm_GetDefaultInputDeviceID(void) { Pm_Initialize(); return pm_default_input_device_id; } -PmDeviceID Pm_GetDefaultOutputDeviceID() { +PmDeviceID Pm_GetDefaultOutputDeviceID(void) { Pm_Initialize(); return pm_default_output_device_id; } diff --git a/3rdparty/portmidi/pm_linux/pmlinuxalsa.c b/3rdparty/portmidi/pm_linux/pmlinuxalsa.c index 9ca2cf72adf..62b82d73b25 100644 --- a/3rdparty/portmidi/pm_linux/pmlinuxalsa.c +++ b/3rdparty/portmidi/pm_linux/pmlinuxalsa.c @@ -7,6 +7,11 @@ * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation) */ +/* omit this code if PMALSA is not defined -- this mechanism allows + * selection of different MIDI interfaces (at compile time). + */ +#ifdef PMALSA + #include "stdlib.h" #include "portmidi.h" #include "pmutil.h" @@ -15,16 +20,8 @@ #include "string.h" #include "porttime.h" #include "pmlinux.h" -#include <alsa/asoundlib.h> - -typedef unsigned int UINT32; -__extension__ typedef unsigned long long UINT64; -#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: @@ -40,10 +37,9 @@ typedef UINT32 FPTR; #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 MAKE_DESCRIPTOR(client, port) ((void*)(long)(((client) << 8) | (port))) +#define GET_DESCRIPTOR_CLIENT(info) ((((long)(info)) >> 8) & 0xff) +#define GET_DESCRIPTOR_PORT(info) (((long)(info)) & 0xff) #define BYTE unsigned char @@ -54,14 +50,16 @@ 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 { +#define PORT_IS_CLOSED -999999 + +typedef struct alsa_info_struct { + int is_virtual; int client; int port; int this_port; int in_sysex; snd_midi_event_t *parser; - int error; /* host error code */ -} alsa_descriptor_node, *alsa_descriptor_type; +} alsa_info_node, *alsa_info_type; /* get_alsa_error_text -- copy error text to potentially short string */ @@ -69,36 +67,45 @@ typedef struct alsa_descriptor_struct { static void get_alsa_error_text(char *msg, int len, int err) { int errlen = strlen(snd_strerror(err)); - if (errlen < len) { + if (errlen > 0 && 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; } } +static PmError check_hosterror(int err) +{ + if (err < 0) { + get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); + pm_hosterror = TRUE; + return pmHostError; + } + return pmNoError; +} + + /* queue is shared by both input and output, reference counted */ static PmError alsa_use_queue(void) { + int err = 0; if (queue_used == 0) { snd_seq_queue_tempo_t *tempo; queue = snd_seq_alloc_queue(seq); if (queue < 0) { - pm_hosterror = queue; - return pmHostError; + return check_hosterror(queue); } snd_seq_queue_tempo_alloca(&tempo); snd_seq_queue_tempo_set_tempo(tempo, 480000); snd_seq_queue_tempo_set_ppq(tempo, 480); - pm_hosterror = snd_seq_set_queue_tempo(seq, queue, tempo); - if (pm_hosterror < 0) - return pmHostError; - + err = snd_seq_set_queue_tempo(seq, queue, tempo); + if (err < 0) { + return check_hosterror(err); + } snd_seq_start_queue(seq, queue, NULL); snd_seq_drain_output(seq); } @@ -135,77 +142,87 @@ static int midi_message_length(PmMessage message) } -static PmError alsa_out_open(PmInternal *midi, void *driverInfo) +static alsa_info_type alsa_info_create(int client_port, long id, int is_virtual) { - 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; + alsa_info_type info = (alsa_info_type) pm_alloc(sizeof(alsa_info_node)); + info->is_virtual = is_virtual; + info->this_port = id; + info->client = GET_DESCRIPTOR_CLIENT(client_port); + info->port = GET_DESCRIPTOR_PORT(client_port); + info->in_sysex = 0; + return info; +} - 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; +static PmError alsa_out_open(PmInternal *midi, void *driverInfo) +{ + int id = midi->device_id; + void *client_port = pm_descriptors[id].descriptor; + alsa_info_type ainfo = alsa_info_create((long) client_port, id, + pm_descriptors[id].pub.is_virtual); + snd_seq_port_info_t *pinfo; + int err = 0; + int queue_used = 0; - err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &desc->parser); + if (!ainfo) return pmInsufficientMemory; + midi->api_info = ainfo; + + if (!ainfo->is_virtual) { + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_port(pinfo, id); + snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_READ); + snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_port_specified(pinfo, 1); + err = snd_seq_create_port(seq, pinfo); + if (err < 0) goto free_ainfo; + } + + err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &ainfo->parser); if (err < 0) goto free_this_port; if (midi->latency > 0) { /* must delay output using a queue */ err = alsa_use_queue(); if (err < 0) goto free_parser; + queue_used++; + } - err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port); + if (!ainfo->is_virtual) { + err = snd_seq_connect_to(seq, ainfo->this_port, ainfo->client, + ainfo->port); if (err < 0) goto unuse_queue; /* clean up and return on error */ - } 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(); + if (queue_used > 0) /* only for latency>0 case */ + alsa_unuse_queue(); free_parser: - snd_midi_event_free(desc->parser); + snd_midi_event_free(ainfo->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; + snd_seq_delete_port(seq, ainfo->this_port); + free_ainfo: + pm_free(ainfo); + return check_hosterror(err); } static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, - PmTimestamp timestamp) + PmTimestamp timestamp) { - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + alsa_info_type info = (alsa_info_type) midi->api_info; snd_seq_event_t ev; - int err; + int err = 0; 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 (snd_midi_event_encode_byte(info->parser, byte, &ev) == 1) { + if (info->is_virtual) { + snd_seq_ev_set_subs(&ev); + } else { + snd_seq_ev_set_dest(&ev, info->client, info->port); + } + snd_seq_ev_set_source(&ev, info->this_port); if (midi->latency > 0) { /* compute relative time of event = timestamp - now + latency */ PmTimestamp now = (midi->time_proc ? @@ -235,126 +252,165 @@ static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */ snd_seq_ev_set_direct(&ev); } - VERBOSE printf("sending event\n"); + VERBOSE printf("sending event, timestamp %d (%d+%dns) (%s, %s)\n", + ev.time.tick, ev.time.time.tv_sec, ev.time.time.tv_nsec, + (ev.flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"), + (ev.flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs")); err = snd_seq_event_output(seq, &ev); - if (err < 0) { - desc->error = err; - return pmHostError; - } } - return pmNoError; + return check_hosterror(err); } 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); + alsa_info_type info = (alsa_info_type) midi->api_info; + int err = 0; + int error2 = 0; + if (!info) return pmBadPtr; + + if (info->this_port != PORT_IS_CLOSED) { + if (!info->is_virtual) { + err = snd_seq_disconnect_to(seq, info->this_port, + info->client, info->port); + /* even if there was an error, we still try to delete the port */ + error2 = snd_seq_delete_port(seq, info->this_port); + + if (!err) { /* retain original error if there was one */ + err = error2; /* otherwise, use port delete status */ + } + } } if (midi->latency > 0) alsa_unuse_queue(); - snd_midi_event_free(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; + snd_midi_event_free(info->parser); + midi->api_info = NULL; /* destroy the pointer to signify "closed" */ + pm_free(info); + return check_hosterror(err); +} + + +static PmError alsa_create_virtual(int is_input, const char *name, + void *device_info) +{ + snd_seq_port_info_t *pinfo; + int err; + int client, port; + + /* we need the id to set the port. */ + PmDeviceID id = pm_add_device("ALSA", name, is_input, TRUE, NULL, + (is_input ? &pm_linuxalsa_in_dictionary : + &pm_linuxalsa_out_dictionary)); + if (id < 0) { /* error -- out of memory? */ + return id; } - return pmNoError; + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_capability(pinfo, + (is_input ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE : + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ)); + snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_name(pinfo, name); + snd_seq_port_info_set_port(pinfo, id); + snd_seq_port_info_set_port_specified(pinfo, 1); + /* next 3 lines needed to generate timestamp - PaulLiu */ + snd_seq_port_info_set_timestamping(pinfo, 1); + snd_seq_port_info_set_timestamp_real(pinfo, 0); + snd_seq_port_info_set_timestamp_queue(pinfo, queue); + err = snd_seq_create_port(seq, pinfo); + if (err < 0) { + pm_undo_add_device(id); + return check_hosterror(err); + } + client = snd_seq_port_info_get_client(pinfo); + port = snd_seq_port_info_get_port(pinfo); + pm_descriptors[id].descriptor = MAKE_DESCRIPTOR(client, port); + return id; } + static PmError alsa_delete_virtual(PmDeviceID id) + { + int err = snd_seq_delete_port(seq, id); + return check_hosterror(err); + } + + static PmError alsa_in_open(PmInternal *midi, void *driverInfo) { - 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 id = midi->device_id; + void *client_port = pm_descriptors[id].descriptor; + alsa_info_type ainfo = alsa_info_create((long) client_port, id, + pm_descriptors[id].pub.is_virtual); + snd_seq_port_info_t *pinfo; snd_seq_port_subscribe_t *sub; snd_seq_addr_t addr; - int err; - - if (!desc) return pmInsufficientMemory; + int err = 0; + int is_virtual = pm_descriptors[id].pub.is_virtual; + if (!ainfo) return pmInsufficientMemory; + midi->api_info = ainfo; + err = alsa_use_queue(); - if (err < 0) goto free_desc; + if (err < 0) goto free_ainfo; - 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 */ + if (is_virtual) { + ainfo->is_virtual = TRUE; + } else { + /* create a port for this alsa client (seq) where the port + number matches the portmidi device ID of the input device */ + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_port(pinfo, id); + snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_READ); + snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_port_specified(pinfo, 1); + err = snd_seq_create_port(seq, pinfo); + if (err < 0) goto free_queue; + + /* forward messages from input to this alsa client, so this + * alsa client is the destination, and the destination port is the + * port we just created using the device ID as port number + */ + snd_seq_port_subscribe_alloca(&sub); + addr.client = snd_seq_client_id(seq); + addr.port = ainfo->this_port; + snd_seq_port_subscribe_set_dest(sub, &addr); + + /* forward from the sender which is the device named by + client and port */ + addr.client = ainfo->client; + addr.port = ainfo->port; + snd_seq_port_subscribe_set_sender(sub, &addr); + snd_seq_port_subscribe_set_time_update(sub, 1); + /* this doesn't seem to work: messages come in with real timestamps */ + snd_seq_port_subscribe_set_time_real(sub, 0); + err = snd_seq_subscribe_port(seq, sub); + if (err < 0) goto free_this_port; /* clean up and return on error */ + } return pmNoError; - free_this_port: - snd_seq_delete_port(seq, desc->this_port); + snd_seq_delete_port(seq, ainfo->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; + free_ainfo: + pm_free(ainfo); + return check_hosterror(err); } 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); + int err = 0; + alsa_info_type info = (alsa_info_type) midi->api_info; + if (!info) return pmBadPtr; + /* virtual ports stay open because the represent devices */ + if (!info->is_virtual && info->this_port != PORT_IS_CLOSED) { + err = snd_seq_delete_port(seq, info->this_port); } alsa_unuse_queue(); - pm_free(desc); - if (pm_hosterror) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, - pm_hosterror); - return pmHostError; - } - return pmNoError; + midi->api_info = NULL; + pm_free(info); + return check_hosterror(err); } @@ -370,11 +426,11 @@ static PmError alsa_abort(PmInternal *midi) * upgrade my entire Linux OS -RBD */ /* - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + info_type info = (info_type) midi->api_info; snd_seq_remove_events_t info; snd_seq_addr_t addr; - addr.client = desc->client; - addr.port = desc->port; + addr.client = info->client; + addr.port = info->port; snd_seq_remove_events_set_dest(&info, &addr); snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST); pm_hosterror = snd_seq_remove_events(seq, &info); @@ -389,65 +445,14 @@ static PmError alsa_abort(PmInternal *midi) } -#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; + int err; + alsa_info_type info = (alsa_info_type) midi->api_info; + if (!info) return pmBadPtr; + VERBOSE printf("snd_seq_drain_output: %p\n", seq); + err = snd_seq_drain_output(seq); + return check_hosterror(err); } @@ -456,16 +461,16 @@ 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; + alsa_info_type info = (alsa_info_type) midi->api_info; + if (!info) return pmBadPtr; for (i = 0; i < bytes; i++) { unsigned char byte = msg; VERBOSE printf("sending 0x%x\n", byte); alsa_write_byte(midi, byte, event->timestamp); - if (desc->error < 0) break; + if (pm_hosterror) break; msg >>= 8; /* shift next byte into position */ } - if (desc->error < 0) return pmHostError; - desc->error = pmNoError; + if (pm_hosterror) return pmHostError; return pmNoError; } @@ -489,25 +494,50 @@ static PmTimestamp alsa_synchronize(PmInternal *midi) static void handle_event(snd_seq_event_t *ev) { int device_id = ev->dest.port; - PmInternal *midi = descriptors[device_id].internalDescriptor; + PmInternal *midi = pm_descriptors[device_id].pm_internal; + // There is a race condition when closing a device and + // continuing to poll other open devices. The closed device may + // have outstanding events from before the close operation. + if (!midi) { + return; + } PmEvent pm_ev; - PmTimeProcPtr time_proc = midi->time_proc; - PmTimestamp timestamp; + PmTimestamp timestamp = midi->time_proc(midi->time_info); /* time stamp should be in ticks, using our queue where 1 tick = 1ms */ - assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK); + /* assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK); + * Currently, event timestamp is ignored. See long note below. */ - /* if no time_proc, just return "native" ticks (ms) */ - if (time_proc == NULL) { - timestamp = ev->time.tick; - } else { /* translate time to time_proc basis */ + VERBOSE { + /* translate time to time_proc basis */ snd_seq_queue_status_t *queue_status; snd_seq_queue_status_alloca(&queue_status); snd_seq_get_queue_status(seq, queue, queue_status); - /* return (now - alsa_now) + alsa_timestamp */ - timestamp = (*time_proc)(midi->time_info) + ev->time.tick - - snd_seq_queue_status_get_tick_time(queue_status); + printf("handle_event: alsa_now %d, " + "event timestamp %d (%d+%dns) (%s, %s)\n", + snd_seq_queue_status_get_tick_time(queue_status), + ev->time.tick, ev->time.time.tv_sec, ev->time.time.tv_nsec, + (ev->flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"), + (ev->flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs")); + /* OLD: portmidi timestamp is (now - alsa_now) + alsa_timestamp */ + /* timestamp = (*time_proc)(midi->time_info) + ev->time.tick - + snd_seq_queue_status_get_tick_time(queue_status); */ } + /* CURRENT: portmidi timestamp is "now". In a test, timestamps from + * hardware (MIDI over USB) were timestamped with the current ALSA + * time (snd_seq_queue_status_get_tick_time) and flags indicating + * absolute ticks, but timestamps from another application's virtual + * port, sent direct with 0 absolute ticks, were received with a + * large value that is apparently the time since the start time of + * the other application. Without any reference to our local time, + * this seems useless. PortMidi is supposed to return the local + * PortMidi time of the arrival of the message, so the best we can + * do is set the timestamp to our local clock. This seems to be a + * design flaw in ALSA -- I pointed this out a decade ago, but if + * there is a workaround, I'd still like to know. Maybe there is a + * way to use absolute real time and maybe that's sharable across + * applications by referencing the system time? + */ pm_ev.timestamp = timestamp; switch (ev->type) { case SND_SEQ_EVENT_NOTEON: @@ -618,12 +648,31 @@ static void handle_event(snd_seq_event_t *ev) pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp); break; } + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: { + /* this happens if you have an input port open and the + * device or application with virtual ports closes. We + * mark the port as closed to avoid closing a 2nd time + * when Pm_Close() is called. + */ + alsa_info_type info = (alsa_info_type) midi->api_info; + /* printf("SND_SEQ_EVENT_UNSUBSCRIBE message\n"); */ + info->this_port = PORT_IS_CLOSED; + break; + } + case SND_SEQ_EVENT_PORT_SUBSCRIBED: + break; /* someone connected to a virtual output port, not reported */ + default: + printf("portmidi handle_event: not handled type %x\n", ev->type); + break; } } static PmError alsa_poll(PmInternal *midi) { + if (!midi) { + return pmBadPtr; + } snd_seq_event_t *ev; /* expensive check for input data, gets data from device: */ while (snd_seq_event_input_pending(seq, TRUE) > 0) { @@ -641,10 +690,9 @@ static PmError alsa_poll(PmInternal *midi) 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; + for (i = 0; i < pm_descriptor_len; i++) { + if (pm_descriptors[i].pub.input) { + PmInternal *midi = pm_descriptors[i].pm_internal; /* careful, device may not be open! */ if (midi) Pm_SetOverflow(midi->queue); } @@ -656,18 +704,9 @@ static PmError alsa_poll(PmInternal *midi) } -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) +static unsigned int alsa_check_host_error(PmInternal *midi) { - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - int err = (pm_hosterror || desc->error); - get_alsa_error_text(msg, len, err); + return FALSE; } @@ -683,8 +722,7 @@ pm_fns_node pm_linuxalsa_in_dictionary = { alsa_abort, alsa_in_close, alsa_poll, - alsa_has_host_error, - alsa_get_host_error + alsa_check_host_error }; pm_fns_node pm_linuxalsa_out_dictionary = { @@ -699,8 +737,7 @@ pm_fns_node pm_linuxalsa_out_dictionary = { alsa_abort, alsa_out_close, none_poll, - alsa_has_host_error, - alsa_get_host_error + alsa_check_host_error }; @@ -718,13 +755,16 @@ char *pm_strdup(const char *s) } -PmError pm_linuxalsa_init( void ) +PmError pm_linuxalsa_init(void) { int err; snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; unsigned int caps; + /* Register interface ALSA with create_virtual fn */ + pm_add_interf("ALSA", &alsa_create_virtual, &alsa_delete_virtual); + /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this * would cause messages to be dropped if the ALSA buffer fills up. * The correct behavior is for writes to block until there is @@ -735,56 +775,61 @@ PmError pm_linuxalsa_init( void ) * call seq_event_input_pending() to avoid blocking. */ err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); - if (err < 0) return err; + if (err < 0) goto error_return; snd_seq_client_info_alloca(&cinfo); snd_seq_port_info_alloca(&pinfo); snd_seq_client_info_set_client(cinfo, -1); while (snd_seq_query_next_client(seq, cinfo) == 0) { - snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); + snd_seq_port_info_set_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))) + 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; - // FIXME: pm_strdup() result is leaked - 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); + pm_default_output_device_id = pm_descriptor_len; + pm_add_device("ALSA", + pm_strdup(snd_seq_port_info_get_name(pinfo)), + FALSE, FALSE, + MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), + snd_seq_port_info_get_port(pinfo)), + &pm_linuxalsa_out_dictionary); } if (caps & SND_SEQ_PORT_CAP_SUBS_READ) { if (pm_default_input_device_id == -1) - pm_default_input_device_id = pm_descriptor_index; - // FIXME: pm_strdup() result is leaked - 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); + pm_default_input_device_id = pm_descriptor_len; + pm_add_device("ALSA", + pm_strdup(snd_seq_port_info_get_name(pinfo)), + TRUE, FALSE, + MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), + snd_seq_port_info_get_port(pinfo)), + &pm_linuxalsa_in_dictionary); } } } return pmNoError; + error_return: + pm_linuxalsa_term(); /* clean up */ + return check_hosterror(err); } - + void pm_linuxalsa_term(void) { if (seq) { snd_seq_close(seq); - pm_free(descriptors); - descriptors = NULL; - pm_descriptor_index = 0; + pm_free(pm_descriptors); + pm_descriptors = NULL; + pm_descriptor_len = 0; pm_descriptor_max = 0; } } + +#endif diff --git a/3rdparty/portmidi/pm_linux/pmlinuxnull.c b/3rdparty/portmidi/pm_linux/pmlinuxnull.c new file mode 100644 index 00000000000..841440917ad --- /dev/null +++ b/3rdparty/portmidi/pm_linux/pmlinuxnull.c @@ -0,0 +1,31 @@ +/* + * pmlinuxnull.c -- system specific definitions + * + * written by: + * Roger Dannenberg + * + * If there is no ALSA, you can define PMNULL and build PortMidi. It will + * not report any devices, so you will not be able to open any, but if + * you wanted to disable MIDI from some application, this could be used. + * Mainly, this code shows the possibility of supporting multiple + * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux. + * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA. + */ + +#ifdef PMNULL + +#include "portmidi.h" +#include "pmlinuxnull.h" + + +PmError pm_linuxnull_init(void) +{ + return pmNoError; +} + + +void pm_linuxnull_term(void) +{ +} + +#endif diff --git a/3rdparty/portmidi/pm_linux/pmlinuxnull.h b/3rdparty/portmidi/pm_linux/pmlinuxnull.h new file mode 100644 index 00000000000..52607eb01bf --- /dev/null +++ b/3rdparty/portmidi/pm_linux/pmlinuxnull.h @@ -0,0 +1,6 @@ +/* pmlinuxnull.h -- system-specific definitions */ + +PmError pm_linuxnull_init(void); +void pm_linuxnull_term(void); + + |