summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/portmidi/pm_linux
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/portmidi/pm_linux')
-rw-r--r--3rdparty/portmidi/pm_linux/README_LINUX.txt56
-rw-r--r--3rdparty/portmidi/pm_linux/finddefault.c30
-rw-r--r--3rdparty/portmidi/pm_linux/finddefault.h5
-rw-r--r--3rdparty/portmidi/pm_linux/pmlinux.c30
-rw-r--r--3rdparty/portmidi/pm_linux/pmlinuxalsa.c605
-rw-r--r--3rdparty/portmidi/pm_linux/pmlinuxnull.c31
-rw-r--r--3rdparty/portmidi/pm_linux/pmlinuxnull.h6
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);
+
+