The attached diff adds input support to portmidi.
For each open input device, a thread is started, waiting for input using
poll(2).

The following program can be used for testing input and ouput:
/usr/ports/pobj/portmidi-217/build-amd64/Release/test
(it is not installed by the port)

If I understand the API correctly, portmidi is not a good choice for realtime
midi-input. There is no callback functionality for reading, so busy-waiting is
needed, like this:

while (Pm_Poll(midi)) {
    Pm_Read(midi, buffer, 1);
}

(see http://portmedia.sourceforge.net/portmidi/doxygen/group__grp__io.html)

Currently, audacity is the only user of portmidi, only using it for output.
.. so I'm not sure how useful this diff is ;)

Any comments or ok?

Index: Makefile
===================================================================
RCS file: /cvs/ports/audio/portmidi/Makefile,v
retrieving revision 1.1.1.1
diff -u -p -u -p -r1.1.1.1 Makefile
--- Makefile    23 Mar 2019 13:30:08 -0000      1.1.1.1
+++ Makefile    4 Apr 2019 11:03:08 -0000
@@ -4,6 +4,7 @@ COMMENT =       library for real time input an
 
 DISTNAME =     portmidi-src-217
 PKGNAME =      portmidi-217
+REVISION =     0
 
 SHARED_LIBS =  portmidi        0.0
 
Index: files/pm_sndio/pmsndio.c
===================================================================
RCS file: /cvs/ports/audio/portmidi/files/pm_sndio/pmsndio.c,v
retrieving revision 1.1.1.1
diff -u -p -u -p -r1.1.1.1 pmsndio.c
--- files/pm_sndio/pmsndio.c    23 Mar 2019 13:30:08 -0000      1.1.1.1
+++ files/pm_sndio/pmsndio.c    4 Apr 2019 11:03:08 -0000
@@ -3,9 +3,13 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <sndio.h>
+#include <string.h>
+#include <poll.h>
+#include <pthread.h>
 #include "portmidi.h"
 #include "pmutil.h"
 #include "pminternal.h"
+#include "porttime.h"
 
 
 PmDeviceID pm_default_input_device_id = -1;
@@ -14,28 +18,70 @@ PmDeviceID pm_default_output_device_id =
 extern pm_fns_node pm_sndio_in_dictionary;
 extern pm_fns_node pm_sndio_out_dictionary;
 
+#define NDEVS 9
+#define SYSEX_MAXLEN 256 
+
+struct mio_dev {
+    char name[16];
+    struct mio_hdl *hdl;
+    int mode;
+    char errmsg[PM_HOST_ERROR_MSG_LEN];
+    pthread_t thread;
+} devs[NDEVS];
+
+static void set_mode(struct mio_dev *, unsigned int);
+
 void pm_init()
 {
-    // Add output devices
-    pm_add_device("SNDIO",
-      "default",
-      FALSE,
-      (void *)0,
-      &pm_sndio_out_dictionary);
-    pm_add_device("SNDIO",
-      "midi/0",
-      FALSE,
-      (void *)1,
-      &pm_sndio_out_dictionary);
+    int i, j = 0;
+
+    /* default */
+    strcpy(devs[j].name, MIO_PORTANY);
+    pm_add_device("SNDIO", devs[j].name, TRUE, (void *) &devs[j],
+        &pm_sndio_in_dictionary);
+    pm_add_device("SNDIO", devs[j].name, FALSE, (void *) &devs[j],
+        &pm_sndio_out_dictionary);
+    j++;
+
+    /* midithru */
+    for (i = 0; i < 4; i++) {
+        sprintf(devs[j].name, "midithru/%d", i);
+        pm_add_device("SNDIO", devs[j].name, TRUE, (void *) &devs[j],
+          &pm_sndio_in_dictionary);
+        pm_add_device("SNDIO", devs[j].name, FALSE, (void *) &devs[j],
+          &pm_sndio_out_dictionary);
+        j++;
+    }
+
+    /* rmidi */
+    for (i = 0; i < 4; i++) {
+        sprintf(devs[j].name, "rmidi/%d", i);
+        pm_add_device("SNDIO", devs[j].name, TRUE, (void *) &devs[j],
+          &pm_sndio_in_dictionary);
+        pm_add_device("SNDIO", devs[j].name, FALSE, (void *) &devs[j],
+          &pm_sndio_out_dictionary);
+        j++;
+    }
 
     // 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_output_device_id = 0;
+    pm_default_input_device_id = 0;
+    pm_default_output_device_id = 1;
 }
 
 void pm_term(void)
 {
+    int i;
+    for(i = 0; i < NDEVS; i++) {
+        if (devs[i].mode != 0) {
+            set_mode(&devs[i], 0);
+            if (devs[i].thread) {
+                pthread_join(devs[i].thread, NULL);
+                devs[i].thread = NULL;
+            }
+        }
+    }
 }
 
 PmDeviceID Pm_GetDefaultInputDeviceID() {
@@ -68,74 +114,226 @@ static int midi_message_length(PmMessage
     }
 }
 
+void* input_thread(void *param)
+{
+    PmInternal *midi = (PmInternal*)param;
+    struct mio_dev *dev = (struct mio_dev *) midi->descriptor;
+    struct pollfd pfd[1];
+    nfds_t nfds;
+    unsigned char status, byte = 0;
+    int count = 0, msglen;
+    PmEvent pm_ev, pm_ev_rt;
+    unsigned char sysex_data[SYSEX_MAXLEN];
+
+    while(dev->mode & MIO_IN) {
+               nfds = mio_pollfd(dev->hdl, pfd, POLLIN);
+        if (poll(pfd, nfds, 100) < 0) {
+            fprintf(stderr, "poll error; aborting midi thread\n");
+            break;
+        }
+        if (!(mio_revents(dev->hdl, pfd) & POLLIN))
+            continue;
+
+        if (!mio_read(dev->hdl, &byte, 1)) {
+            fprintf(stderr, "read error; aborting midi thread\n");
+            break;
+        }
+
+        if (byte & 0x80) {
+            if (byte >= 0xf8) {
+                /* realtime message */
+                pm_ev_rt.message = byte;
+                pm_ev_rt.timestamp = Pt_Time();
+                pm_read_short(midi, &pm_ev_rt);
+                continue;
+            }
+            status = byte;
+            switch(byte) {
+            case 0xf0: /* sysex */
+                sysex_data[0] = byte;
+                msglen = 0;
+                count = 0;
+                break;
+            case 0xf7: /* sysex end */
+                sysex_data[count] = byte;
+                break;
+            default: /*status */
+                pm_ev.message = byte;
+                pm_ev.timestamp = Pt_Time();
+                msglen = midi_message_length(pm_ev.message);
+                count = 0;
+                break;
+            }
+        } else { /* data */
+            if (status == 0xf0) /* sysex data */
+                if (count < SYSEX_MAXLEN - 1) {
+                    sysex_data[count] = byte;
+                } else {
+                    fprintf(stderr, "the message is too long\n");
+                    continue;
+                }
+            else if (count > 0) /* short messge */
+                pm_ev.message |= (byte << (8 * count));
+        }
+
+        count++;
+
+        if (status == 0xf7) /* sysex data received */
+            pm_read_bytes(midi, &sysex_data[0], count, Pt_Time());
+        else if (count == msglen) /* short message received */
+            pm_read_short(midi, &pm_ev);
+    }
+    pthread_exit(NULL);
+    return NULL;
+}
+
+static void set_mode(struct mio_dev *dev, unsigned int mode) {
+    if (dev->mode != 0)
+        mio_close(dev->hdl);
+    dev->mode = 0;
+    if (mode != 0)
+        dev->hdl = mio_open(dev->name, mode, 1);
+    if (dev->hdl)
+        dev->mode = mode;
+}
+
 static PmError sndio_out_open(PmInternal *midi, void *driverInfo)
 {
-    const char *device = descriptors[midi->device_id].pub.name;
-    struct mio_hdl *mio;
+    descriptor_type desc = &descriptors[midi->device_id];
+    struct mio_dev *dev = (struct mio_dev *) desc->descriptor;
 
-    mio = mio_open(device, MIO_OUT, 0);
-    if (!mio) {
-        fprintf(stderr, "mio_open failed\n");
+    if (dev->mode & MIO_OUT)
+        return pmNoError;
+
+    set_mode(dev, dev->mode | MIO_OUT);
+    if (!(dev->mode & MIO_OUT)) {
+        snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN,
+          "mio_open (output) failed: %s\n", dev->name);
         return pmHostError;
     }
-    midi->descriptor = mio;
 
+    midi->descriptor = (void *)dev;
     return pmNoError;
 }
+
+static PmError sndio_in_open(PmInternal *midi, void *driverInfo)
+{
+    descriptor_type desc = &descriptors[midi->device_id];
+    struct mio_dev *dev = (struct mio_dev *) desc->descriptor;
+
+    if (dev->mode & MIO_IN)
+        return pmNoError;
+
+    set_mode(dev, dev->mode | MIO_IN);
+    if (!(dev->mode & MIO_IN)) {
+        snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN,
+          "mio_open (input) failed: %s\n", dev->name);
+        return pmHostError;
+    }
+    midi->descriptor = (void *)dev;
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_create(&dev->thread, &attr, input_thread, ( void* )midi);
+    return pmNoError;
+}
+
 static PmError sndio_out_close(PmInternal *midi)
 {
-    mio_close(midi->descriptor);
+    struct mio_dev *dev = (struct mio_dev *) midi->descriptor;
+
+    if (dev->mode & MIO_OUT)
+        set_mode(dev, dev->mode & ~MIO_OUT);
+    return pmNoError;
+}
+
+static PmError sndio_in_close(PmInternal *midi)
+{
+    struct mio_dev *dev = (struct mio_dev *) midi->descriptor;
+
+    if (dev->mode & MIO_IN) {
+        set_mode(dev, dev->mode & ~MIO_IN);
+        pthread_join(dev->thread, NULL);
+        dev->thread = NULL;
+    }
     return pmNoError;
 }
+
 static PmError sndio_abort(PmInternal *midi)
 {
-    mio_close(midi->descriptor);
     return pmNoError;
 }
+
 static PmTimestamp sndio_synchronize(PmInternal *midi)
 {
     return 0;
 }
-static PmError sndio_write_byte(PmInternal *midi, unsigned char byte,
-                        PmTimestamp timestamp)
+
+static PmError do_write(struct mio_dev *dev, const void *addr, size_t nbytes)
 {
-    size_t w = mio_write(midi->descriptor, &byte, 1);
-    if (w != 1) {
-        fprintf(stderr, "mio_write failed\n");
+    int nfds, revents;
+    struct pollfd pfds[1];
+
+    do {
+        nfds = mio_pollfd(dev->hdl, pfds, POLLOUT);
+        if (poll(pfds, nfds, -1) < 0) {
+            fprintf(stderr, "poll error; write failed\n");
+            break;
+        }
+        revents = mio_revents(dev->hdl, pfds);
+    } while (!(revents & POLLOUT));
+
+    size_t w = mio_write(dev->hdl, addr, nbytes);
+
+    if (w != nbytes) {
+        snprintf(dev->errmsg, PM_HOST_ERROR_MSG_LEN, 
+          "mio_write failed, bytes written:%zu\n", w);
         return pmHostError;
     }
     return pmNoError;
 }
-static PmError sndio_write_short(PmInternal *midi, PmEvent *event)
+
+static PmError sndio_write_byte(PmInternal *midi, unsigned char byte,
+                        PmTimestamp timestamp)
 {
-    int bytes = midi_message_length(event->message);
-    PmMessage msg = event->message;
-    int i;
-    for (i = 0; i < bytes; i++) {
-        unsigned char byte = msg;
-        sndio_write_byte(midi, byte, event->timestamp);
-        msg >>= 8;
-    }
+    struct mio_dev *dev = (struct mio_dev *) midi->descriptor;
 
-    return pmNoError;
+    return do_write(dev, &byte, 1);
 }
-static PmError sndio_write_flush(PmInternal *midi, PmTimestamp timestamp)
+
+static PmError sndio_write_short(PmInternal *midi, PmEvent *event)
 {
+    struct mio_dev *dev = (struct mio_dev *) midi->descriptor;
+    int nbytes = midi_message_length(event->message);
+
+    if (midi->latency > 0) {
+        /* XXX the event should be queued for later playback */
+        return do_write(dev, &event->message, nbytes);
+    } else {
+        return do_write(dev, &event->message, nbytes);
+    }
     return pmNoError;
 }
-PmError sndio_sysex(PmInternal *midi, PmTimestamp timestamp)
+
+static PmError sndio_write_flush(PmInternal *midi, PmTimestamp timestamp)
 {
     return pmNoError;
 }
+
 static unsigned int sndio_has_host_error(PmInternal *midi)
 {
-    return 0;
+    struct mio_dev *dev = (struct mio_dev *) midi->descriptor;
+
+    return (dev->errmsg[0] != '\0');
 }
+
 static void sndio_get_host_error(PmInternal *midi, char *msg, unsigned int len)
 {
+    struct mio_dev *dev = (struct mio_dev *) midi->descriptor;
+
+    strlcpy(msg, dev->errmsg, len);
+    dev->errmsg[0] = '\0';
 }
 
-/*
 pm_fns_node pm_sndio_in_dictionary = {
     none_write_short,
     none_sysex,
@@ -147,16 +345,15 @@ pm_fns_node pm_sndio_in_dictionary = {
     sndio_in_open,
     sndio_abort,
     sndio_in_close,
-    sndio_poll,
+    success_poll,
     sndio_has_host_error,
     sndio_get_host_error
 };
-*/
 
 pm_fns_node pm_sndio_out_dictionary = {
     sndio_write_short,
-    sndio_sysex,
-    sndio_sysex,
+    none_sysex,
+    none_sysex,
     sndio_write_byte,
     sndio_write_short,
     sndio_write_flush,

Reply via email to