From 988f5d2b5343850e19ad1512cefe6c53953aa02e Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Mon, 7 Oct 2024 06:50:04 +0200 Subject: Added bunch of examples --- portmidi/pm_linux/README_LINUX.txt | 99 ++++ portmidi/pm_linux/pmlinux.c | 68 +++ portmidi/pm_linux/pmlinuxalsa.c | 893 +++++++++++++++++++++++++++++++++++++ portmidi/pm_linux/pmlinuxalsa.h | 6 + portmidi/pm_linux/pmlinuxnull.c | 31 ++ portmidi/pm_linux/pmlinuxnull.h | 6 + 6 files changed, 1103 insertions(+) create mode 100755 portmidi/pm_linux/README_LINUX.txt create mode 100755 portmidi/pm_linux/pmlinux.c create mode 100755 portmidi/pm_linux/pmlinuxalsa.c create mode 100755 portmidi/pm_linux/pmlinuxalsa.h create mode 100644 portmidi/pm_linux/pmlinuxnull.c create mode 100644 portmidi/pm_linux/pmlinuxnull.h (limited to 'portmidi/pm_linux') diff --git a/portmidi/pm_linux/README_LINUX.txt b/portmidi/pm_linux/README_LINUX.txt new file mode 100755 index 0000000..cfbc43f --- /dev/null +++ b/portmidi/pm_linux/README_LINUX.txt @@ -0,0 +1,99 @@ +README_LINUX.txt for PortMidi +Roger Dannenberg +6 Dec 2012, revised May 2022 + +Contents: + To make PortMidi + The pmdefaults program + Setting LD_LIBRARY_PATH + A note about amd64 + Using autoconf + Using configure + Changelog + + +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 +and so users have a single place to set a preference. + +Follow the instructions above to run ccmake, making sure that +CMAKE_BUILD_TYPE is Release. Run make as described above. Then: + +sudo make install + +This will install PortMidi libraries and the pmdefault program. +You must alos have the environment variable LD_LIBRARY_PATH set +to include /usr/local/lib (where libpmjni.so is installed). + +Now, you can run pmdefault. + + +SETTING LD_LIBRARY_PATH + +pmdefaults will not work unless LD_LIBRARY_PATH includes a +directory (normally /usr/local/lib) containing libpmjni.so, +installed as described above. + +To set LD_LIBRARY_PATH, you might want to add this to your +~/.profile (if you use the bash shell): + +LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib +export LD_LIBRARY_PATH + + +A NOTE ABOUT AMD64: + +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 +with this error: + +./linux/libportmidi.a(pmlinux.o): relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC +./linux/libportmidi.a: could not read symbols: Bad value +collect2: ld returned 1 exit status +error: command 'gcc' failed with exit status 1 + +What they said: +http://www.gentoo.org/proj/en/base/amd64/howtos/index.xml?part=1&chap=3 +On certain architectures (AMD64 amongst them), shared libraries *must* +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 + +14-oct-2009 Roger B. Dannenberg + Using CMake now for building and configuration + +29-aug-2006 Roger B. Dannenberg + Fixed PortTime to join with time thread for clean exit. + +28-aug-2006 Roger B. Dannenberg + Updated this documentation. + +08-Jun-2004 Roger B. Dannenberg + Updated code to use new system abstraction. + +12-Apr-2003 Roger B. Dannenberg + Fixed pm_test/test.c to filter clocks and active messages. + Integrated changes from Clemens Ladisch: + cleaned up pmlinuxalsa.c + record timestamp on sysex input + deallocate some resources previously left open diff --git a/portmidi/pm_linux/pmlinux.c b/portmidi/pm_linux/pmlinux.c new file mode 100755 index 0000000..3766427 --- /dev/null +++ b/portmidi/pm_linux/pmlinux.c @@ -0,0 +1,68 @@ +/* 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 + +#if !(defined(PMALSA) || defined(PMNULL)) +#error One of PMALSA or PMNULL must be defined +#endif + +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 +} + +void pm_term(void) +{ + #ifdef PMALSA + pm_linuxalsa_term(); + #endif + #ifdef PMNULL + pm_linuxnull_term(); + #endif +} + +PmDeviceID Pm_GetDefaultInputDeviceID() { + Pm_Initialize(); + return pm_default_input_device_id; +} + +PmDeviceID Pm_GetDefaultOutputDeviceID() { + Pm_Initialize(); + return pm_default_output_device_id; +} + +void *pm_alloc(size_t s) { return malloc(s); } + +void pm_free(void *ptr) { free(ptr); } + diff --git a/portmidi/pm_linux/pmlinuxalsa.c b/portmidi/pm_linux/pmlinuxalsa.c new file mode 100755 index 0000000..b2e43e1 --- /dev/null +++ b/portmidi/pm_linux/pmlinuxalsa.c @@ -0,0 +1,893 @@ +/* + * 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) + */ + +/* 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" +#include "pminternal.h" +#include "pmlinuxalsa.h" +#include "string.h" +#include "porttime.h" + +#include + +/* 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*)(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 + +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 */ + +#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; +} alsa_info_node, *alsa_info_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 > 0 && errlen < len) { + strcpy(msg, snd_strerror(err)); + } else if (len > 20) { + sprintf(msg, "Alsa error %d", err); + } 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) { + 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); + 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); + } + ++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 alsa_info_type alsa_info_create(int client_port, long id, int is_virtual) +{ + 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; +} + + +/* search system dependent extra parameters for string */ +static const char *get_sysdep_name(enum PmSysDepPropertyKey key, + PmSysDepInfo *info) +{ + /* the version where all current properties were introduced is 210 */ + if (info && info->structVersion >= 210) { + int i; + for (i = 0; i < info->length; i++) { /* search for key */ + if (info->properties[i].key == key) { + return info->properties[i].value; + } + } + } + return NULL; +} + + +static void maybe_set_client_name(PmSysDepInfo *driverInfo) +{ + if (!seq) { // make sure seq is created and we have info + return; + } + + const char *client_name = get_sysdep_name(pmKeyAlsaClientName, + (PmSysDepInfo *) driverInfo); + if (client_name) { + snd_seq_set_client_name(seq, client_name); + printf("maybe_set_client_name set client to %s\n", client_name); + } +} + + +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 using_the_queue = 0; + + if (!ainfo) return pmInsufficientMemory; + midi->api_info = ainfo; + + snd_seq_port_info_alloca(&pinfo); + if (!ainfo->is_virtual) { + 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_CAP_SUBS_READ); + snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + const char *port_name = get_sysdep_name(pmKeyAlsaPortName, + (PmSysDepInfo *) driverInfo); + if (port_name) { + snd_seq_port_info_set_name(pinfo, port_name); + } + 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; + using_the_queue++; + } + + 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 */ + } + + maybe_set_client_name(driverInfo); + + return pmNoError; + + unuse_queue: + if (using_the_queue > 0) /* only for latency>0 case */ + alsa_unuse_queue(); + free_parser: + snd_midi_event_free(ainfo->parser); + free_this_port: + 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) +{ + alsa_info_type info = (alsa_info_type) midi->api_info; + snd_seq_event_t ev; + int err = 0; + + snd_seq_ev_clear(&ev); + 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 ? + 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, 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); + } + return check_hosterror(err); +} + + +static PmError alsa_out_close(PmInternal *midi) +{ + 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(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; + } + 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) +{ + 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 = 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_ainfo; + + snd_seq_port_info_alloca(&pinfo); + if (is_virtual) { + ainfo->is_virtual = TRUE; + if (snd_seq_get_port_info(seq, ainfo->port, pinfo)) { + pinfo = NULL; + goto free_ainfo; + } + } 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_set_port(pinfo, id); + snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_WRITE); + + 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); + + const char *port_name = get_sysdep_name(pmKeyAlsaPortName, + (PmSysDepInfo *) driverInfo); + if (port_name) { + snd_seq_port_info_set_name(pinfo, port_name); + } + + 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 */ + } + + maybe_set_client_name(driverInfo); + + return pmNoError; + free_this_port: + snd_seq_delete_port(seq, ainfo->this_port); + free_queue: + alsa_unuse_queue(); + free_ainfo: + pm_free(ainfo); + return check_hosterror(err); +} + +static PmError alsa_in_close(PmInternal *midi) +{ + 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(); + midi->api_info = NULL; + pm_free(info); + return check_hosterror(err); +} + + +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 + */ + /* + info_type info = (info_type) midi->api_info; + snd_seq_remove_events_t info; + snd_seq_addr_t addr; + 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); + 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; +} + + +static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp) +{ + 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); +} + + +static PmError alsa_write_short(PmInternal *midi, PmEvent *event) +{ + int bytes = midi_message_length(event->message); + PmMessage msg = event->message; + int i; + 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 (pm_hosterror) break; + msg >>= 8; /* shift next byte into position */ + } + if (pm_hosterror) return pmHostError; + 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 = 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; + 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); + * Currently, event timestamp is ignored. See long note below. */ + + 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); + 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: + 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; + } + 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) { + /* 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_len; i++) { + if (pm_descriptors[i].pub.input) { + PmInternal *midi_i = pm_descriptors[i].pm_internal; + /* careful, device may not be open! */ + if (midi_i) Pm_SetOverflow(midi_i->queue); + } + } + } + } + } + return pmNoError; +} + + +static unsigned int alsa_check_host_error(PmInternal *midi) +{ + return FALSE; +} + + +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_check_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_check_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; + + /* 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 + * 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) 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_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_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_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(pm_descriptors); + pm_descriptors = NULL; + pm_descriptor_len = 0; + pm_descriptor_max = 0; + } +} + +#endif diff --git a/portmidi/pm_linux/pmlinuxalsa.h b/portmidi/pm_linux/pmlinuxalsa.h new file mode 100755 index 0000000..d4bff16 --- /dev/null +++ b/portmidi/pm_linux/pmlinuxalsa.h @@ -0,0 +1,6 @@ +/* pmlinuxalsa.h -- system-specific definitions */ + +PmError pm_linuxalsa_init(void); +void pm_linuxalsa_term(void); + + diff --git a/portmidi/pm_linux/pmlinuxnull.c b/portmidi/pm_linux/pmlinuxnull.c new file mode 100644 index 0000000..257f3d6 --- /dev/null +++ b/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/portmidi/pm_linux/pmlinuxnull.h b/portmidi/pm_linux/pmlinuxnull.h new file mode 100644 index 0000000..9835825 --- /dev/null +++ b/portmidi/pm_linux/pmlinuxnull.h @@ -0,0 +1,6 @@ +/* pmlinuxnull.h -- system-specific definitions */ + +PmError pm_linuxnull_init(void); +void pm_linuxnull_term(void); + + -- cgit v1.2.3