ALSA backend: raw midi prototype
authorRobin Gareus <robin@gareus.org>
Mon, 2 Jun 2014 14:52:07 +0000 (16:52 +0200)
committerRobin Gareus <robin@gareus.org>
Mon, 2 Jun 2014 17:23:07 +0000 (19:23 +0200)
libs/backends/alsa/alsa_audiobackend.cc
libs/backends/alsa/alsa_audiobackend.h
libs/backends/alsa/alsa_rawmidi.cc [new file with mode: 0644]
libs/backends/alsa/alsa_rawmidi.h [new file with mode: 0644]
libs/backends/alsa/wscript

index 34f28b24aaeee9b84a4ab417f45a0c80e149c5be..ca4fd25c281c868c8a9151c4faf2f7b8f25d5ee0 100644 (file)
@@ -26,6 +26,7 @@
 #include "alsa_audiobackend.h"
 #include "rt_thread.h"
 
+#include "pbd/compose.h"
 #include "pbd/error.h"
 #include "ardour/port_manager.h"
 #include "i18n.h"
@@ -48,8 +49,6 @@ AlsaAudioBackend::AlsaAudioBackend (AudioEngine& e, AudioBackendInfo& info)
        , _dsp_load (0)
        , _n_inputs (0)
        , _n_outputs (0)
-       , _n_midi_inputs (0)
-       , _n_midi_outputs (0)
        , _systemic_input_latency (0)
        , _systemic_output_latency (0)
        , _processed_samples (0)
@@ -273,24 +272,49 @@ AlsaAudioBackend::systemic_output_latency () const
 }
 
 /* MIDI */
+void
+AlsaAudioBackend::enumerate_midi_devices (std::vector<std::string> &m) const
+{
+       int cardnum = -1;
+       while (snd_card_next (&cardnum) >= 0 && cardnum >= 0) {
+               snd_ctl_t *handle;
+               std::string devname = "hw:";
+               devname += PBD::to_string (cardnum, std::dec);
+
+               if (snd_ctl_open (&handle, devname.c_str(), 0) >= 0) {
+                       int device = -1;
+                       // TODO iterate over sub-devices
+                       if (snd_ctl_rawmidi_next_device (handle, &device) >= 0 && device >= 0) {
+                               m.push_back (devname);
+                       }
+                       snd_ctl_close(handle);
+               }
+       }
+}
+
 std::vector<std::string>
 AlsaAudioBackend::enumerate_midi_options () const
 {
        std::vector<std::string> m;
        m.push_back (_("-None-"));
+       enumerate_midi_devices(m);
+       if (m.size() > 2) {
+               m.push_back (_("-All-"));
+       }
        return m;
 }
 
 int
-AlsaAudioBackend::set_midi_option (const std::string& /* opt*/)
+AlsaAudioBackend::set_midi_option (const std::string& opt)
 {
-       return -1;
+       _midi_device = opt;
+       return 0;
 }
 
 std::string
 AlsaAudioBackend::midi_option () const
 {
-       return "";
+       return _midi_device;
 }
 
 /* State Control */
@@ -315,9 +339,13 @@ AlsaAudioBackend::_start (bool for_latency_measurement)
                PBD::warning << _("AlsaAudioBackend: recovering from unclean shutdown, port registry is not empty.") << endmsg;
                _system_inputs.clear();
                _system_outputs.clear();
+               _system_midi_in.clear();
+               _system_midi_out.clear();
                _ports.clear();
        }
 
+       assert(_rmidi_in.size() == 0);
+       assert(_rmidi_out.size() == 0);
        assert(_pcmi == 0);
 
   _pcmi = new Alsa_pcmi (_capture_device.c_str(), _playback_device.c_str(), 0, _samplerate, _samples_per_period, _periods_per_cycle, 0);
@@ -365,7 +393,9 @@ AlsaAudioBackend::_start (bool for_latency_measurement)
                _systemic_output_latency = 0;
        }
 
-       if (register_system_ports()) {
+       register_system_midi_ports();
+
+       if (register_system_audio_ports()) {
                PBD::error << _("AlsaAudioBackend: failed to register system ports.") << endmsg;
                delete _pcmi; _pcmi = 0;
                return -1;
@@ -418,6 +448,20 @@ AlsaAudioBackend::stop ()
                PBD::error << _("AlsaAudioBackend: failed to terminate.") << endmsg;
                return -1;
        }
+
+       while (!_rmidi_out.empty ()) {
+               AlsaRawMidiIO *m = _rmidi_out.back ();
+               m->stop();
+               _rmidi_out.pop_back ();
+               delete m;
+       }
+       while (!_rmidi_in.empty ()) {
+               AlsaRawMidiIO *m = _rmidi_in.back ();
+               m->stop();
+               _rmidi_in.pop_back ();
+               delete m;
+       }
+
        unregister_system_ports();
        delete _pcmi; _pcmi = 0;
        return 0;
@@ -694,14 +738,12 @@ AlsaAudioBackend::unregister_port (PortEngine::PortHandle port_handle)
 }
 
 int
-AlsaAudioBackend::register_system_ports()
+AlsaAudioBackend::register_system_audio_ports()
 {
        LatencyRange lr;
 
        const int a_ins = _n_inputs > 0 ? _n_inputs : 2;
        const int a_out = _n_outputs > 0 ? _n_outputs : 2;
-       const int m_ins = _n_midi_inputs > 0 ? _n_midi_inputs : 2;
-       const int m_out = _n_midi_outputs > 0 ? _n_midi_outputs : 2;
 
        /* audio ports */
        lr.min = lr.max = _samples_per_period * _periods_per_cycle + _systemic_input_latency;
@@ -723,8 +765,68 @@ AlsaAudioBackend::register_system_ports()
                set_latency_range (p, false, lr);
                _system_outputs.push_back(static_cast<AlsaPort*>(p));
        }
+       return 0;
+}
+
+int
+AlsaAudioBackend::register_system_midi_ports()
+{
+       LatencyRange lr;
+       std::vector<std::string> devices;
+
+       if (_midi_device == _("-None-")) {
+               return 0;
+       }
+       else if (_midi_device == _("-All-")) {
+               enumerate_midi_devices(devices);
+       } else {
+               devices.push_back(_midi_device);
+       }
+
+       for (std::vector<std::string>::const_iterator i = devices.begin (); i != devices.end (); ++i) {
+
+               AlsaRawMidiOut *mout = new AlsaRawMidiOut (i->c_str());
+               if (mout->state ()) {
+                       PBD::warning << string_compose (
+                                       _("AlsaRawMidiOut: failed to open midi device '%1'."), *i)
+                               << endmsg;
+                       delete mout;
+               } else {
+                       mout->setup_timing(_samples_per_period, _samplerate);
+                       mout->sync_time (g_get_monotonic_time());
+                       if (mout->start ()) {
+                               PBD::warning << string_compose (
+                                               _("AlsaRawMidiOut: failed to start midi device '%1'."), *i)
+                                       << endmsg;
+                               delete mout;
+                       } else {
+                               _rmidi_out.push_back (mout);
+                       }
+               }
+
+               AlsaRawMidiIn *midin = new AlsaRawMidiIn (i->c_str());
+               if (midin->state ()) {
+                       PBD::warning << string_compose (
+                                       _("AlsaRawMidiIn: failed to open midi device '%1'."), *i)
+                               << endmsg;
+                       delete midin;
+               } else {
+                       midin->setup_timing(_samples_per_period, _samplerate);
+                       midin->sync_time (g_get_monotonic_time());
+                       if (midin->start ()) {
+                               PBD::warning << string_compose (
+                                               _("AlsaRawMidiIn: failed to start midi device '%1'."), *i)
+                                       << endmsg;
+                               delete midin;
+                       } else {
+                               _rmidi_in.push_back (midin);
+                       }
+               }
+       }
+
+       const int m_ins = _rmidi_in.size();
+       const int m_out = _rmidi_out.size();
 
-       /* midi ports */
        lr.min = lr.max = _samples_per_period + _systemic_input_latency;
        for (int i = 1; i <= m_ins; ++i) {
                char tmp[64];
@@ -732,6 +834,7 @@ AlsaAudioBackend::register_system_ports()
                PortHandle p = add_port(std::string(tmp), DataType::MIDI, static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
                if (!p) return -1;
                set_latency_range (p, false, lr);
+               _system_midi_in.push_back(static_cast<AlsaPort*>(p));
        }
 
        lr.min = lr.max = _samples_per_period + _systemic_output_latency;
@@ -741,6 +844,7 @@ AlsaAudioBackend::register_system_ports()
                PortHandle p = add_port(std::string(tmp), DataType::MIDI, static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
                if (!p) return -1;
                set_latency_range (p, false, lr);
+               _system_midi_out.push_back(static_cast<AlsaPort*>(p));
        }
 
        return 0;
@@ -752,6 +856,8 @@ AlsaAudioBackend::unregister_system_ports()
        size_t i = 0;
        _system_inputs.clear();
        _system_outputs.clear();
+       _system_midi_in.clear();
+       _system_midi_out.clear();
        while (i <  _ports.size ()) {
                AlsaPort* port = _ports[i];
                if (port->is_physical () && port->is_terminal ()) {
@@ -1110,6 +1216,23 @@ AlsaAudioBackend::main_process_thread ()
                                }
                                _pcmi->capt_done (_samples_per_period);
 
+                               /* de-queue midi*/
+                               i = 0;
+                               for (std::vector<AlsaPort*>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) {
+                                       assert (_rmidi_in.size() > i);
+                                       AlsaRawMidiIn *rm = static_cast<AlsaRawMidiIn*>(_rmidi_in.at(i));
+                                       void *bptr = (*it)->get_buffer(0);
+                                       pframes_t time;
+                                       uint8_t data[64]; // match MaxAlsaRawEventSize in alsa_rawmidi.cc
+                                       size_t size = sizeof(data);
+                                       midi_clear(bptr);
+                                       while (rm->recv_event (time, data, size)) {
+                                               midi_event_put(bptr, time, data, size);
+                                               size = sizeof(data);
+                                       }
+                                       rm->sync_time (clock1);
+                               }
+
                                for (std::vector<AlsaPort*>::const_iterator it = _system_outputs.begin (); it != _system_outputs.end (); ++it) {
                                        memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
                                }
@@ -1119,6 +1242,18 @@ AlsaAudioBackend::main_process_thread ()
                                        return 0;
                                }
 
+                               /* queue midi*/
+                               i = 0;
+                               for (std::vector<AlsaPort*>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) {
+                                       assert (_rmidi_out.size() > i);
+                                       AlsaRawMidiOut *rm = static_cast<AlsaRawMidiOut*>(_rmidi_out.at(i));
+                                       const AlsaMidiBuffer *src = static_cast<const AlsaMidiBuffer*>((*it)->get_buffer(0));
+                                       rm->sync_time (clock1); // ?? use clock pre DSP load?
+                                       for (AlsaMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) {
+                                               rm->send_event ((*mit)->timestamp(), (*mit)->data(), (*mit)->size());
+                                       }
+                               }
+
                                /* write back audio */
                                i = 0;
                                _pcmi->play_init (_samples_per_period);
@@ -1151,6 +1286,10 @@ AlsaAudioBackend::main_process_thread ()
                        for (std::vector<AlsaPort*>::const_iterator it = _system_inputs.begin (); it != _system_inputs.end (); ++it) {
                                memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
                        }
+                       for (std::vector<AlsaPort*>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it) {
+                               static_cast<AlsaMidiBuffer*>((*it)->get_buffer(0))->clear ();
+                       }
+
                        if (engine.process_callback (_samples_per_period)) {
                                _pcmi->pcm_stop ();
                                return 0;
index 4a5633ef232e82e690d33ca4c81417eefc413088..7ff172efbb15b9d427810184faf8065853b109ca 100644 (file)
@@ -34,6 +34,7 @@
 #include "ardour/audio_backend.h"
 
 #include "zita-alsa-pcmi.h"
+#include "alsa_rawmidi.h"
 
 namespace ARDOUR {
 
@@ -282,8 +283,10 @@ class AlsaAudioBackend : public AudioBackend {
                bool  _running;
                bool  _freewheeling;
 
+               void enumerate_midi_devices (std::vector<std::string> &) const;
                std::string _capture_device;
                std::string _playback_device;
+               std::string _midi_device;
 
                float  _samplerate;
                size_t _samples_per_period;
@@ -294,9 +297,6 @@ class AlsaAudioBackend : public AudioBackend {
                uint32_t _n_inputs;
                uint32_t _n_outputs;
 
-               uint32_t _n_midi_inputs;
-               uint32_t _n_midi_outputs;
-
                uint32_t _systemic_input_latency;
                uint32_t _systemic_output_latency;
 
@@ -319,13 +319,18 @@ class AlsaAudioBackend : public AudioBackend {
 
                /* port engine */
                PortHandle add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags);
-               int register_system_ports ();
+               int register_system_audio_ports ();
+               int register_system_midi_ports ();
                void unregister_system_ports ();
 
                std::vector<AlsaPort *> _ports;
-               std::vector<AlsaPort*> _system_inputs;
-               std::vector<AlsaPort*> _system_outputs;
+               std::vector<AlsaPort *> _system_inputs;
+               std::vector<AlsaPort *> _system_outputs;
+               std::vector<AlsaPort *> _system_midi_in;
+               std::vector<AlsaPort *> _system_midi_out;
 
+               std::vector<AlsaRawMidiOut *> _rmidi_out;
+               std::vector<AlsaRawMidiIn  *> _rmidi_in;
 
                struct PortConnectData {
                        std::string a;
diff --git a/libs/backends/alsa/alsa_rawmidi.cc b/libs/backends/alsa/alsa_rawmidi.cc
new file mode 100644 (file)
index 0000000..f666479
--- /dev/null
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2014 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <unistd.h>
+
+#include <glibmm.h>
+
+#include "alsa_rawmidi.h"
+#include "rt_thread.h"
+
+#include "pbd/error.h"
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+/* max bytes per individual midi-event
+ * events larger than this are ignored */
+#define MaxAlsaRawEventSize (64)
+
+#ifndef NDEBUG
+#define _DEBUGPRINT(STR) fprintf(stderr, STR);
+#else
+#define _DEBUGPRINT(STR) ;
+#endif
+
+AlsaRawMidiIO::AlsaRawMidiIO (const char *device, const bool input)
+       : _state (-1)
+       , _running (false)
+       , _device (0)
+       , _pfds (0)
+       , _sample_length_us (1e6 / 48000.0)
+       , _period_length_us (1.024e6 / 48000.0)
+       , _samples_per_period (1024)
+       , _rb (0)
+{
+       pthread_mutex_init (&_notify_mutex, 0);
+       pthread_cond_init (&_notify_ready, 0);
+       init (device, input);
+}
+
+AlsaRawMidiIO::~AlsaRawMidiIO ()
+{
+       if (_device) {
+               snd_rawmidi_close (_device);
+               _device = 0;
+       }
+       delete _rb;
+       pthread_mutex_destroy (&_notify_mutex);
+       pthread_cond_destroy (&_notify_ready);
+       free (_pfds);
+}
+
+void
+AlsaRawMidiIO::init (const char *device_name, const bool input)
+{
+       if (snd_rawmidi_open (
+                               input ? &_device : NULL,
+                               input ? NULL : &_device,
+                               device_name, SND_RAWMIDI_NONBLOCK) < 0) {
+               return;
+       }
+
+       _npfds = snd_rawmidi_poll_descriptors_count (_device);
+       if (_npfds < 1) {
+               _DEBUGPRINT("AlsaRawMidiIO: no poll descriptor(s).\n");
+               snd_rawmidi_close (_device);
+               _device = 0;
+               return;
+       }
+       _pfds = (struct pollfd*) malloc (_npfds * sizeof(struct pollfd));
+       snd_rawmidi_poll_descriptors (_device, _pfds, _npfds);
+
+       // MIDI (hw port) 31.25 kbaud
+       // worst case here is  8192 SPP and 8KSPS for which we'd need
+       // 4000 bytes sans MidiEventHeader.
+       // since we're not always in sync, let's use 4096.
+       _rb = new RingBuffer<uint8_t>(4096 + 4096 * sizeof(MidiEventHeader));
+
+#if 0
+       _state = 0;
+#else
+       snd_rawmidi_params_t *params;
+       if (snd_rawmidi_params_malloc (&params)) {
+               goto initerr;
+       }
+       if (snd_rawmidi_params_current (_device, params)) {
+               goto initerr;
+       }
+       if (snd_rawmidi_params_set_avail_min (_device, params, 1)) {
+               goto initerr;
+       }
+       if ( snd_rawmidi_params_set_buffer_size (_device, params, 64)) {
+               goto initerr;
+       }
+       if (snd_rawmidi_params_set_no_active_sensing (_device, params, 1)) {
+               goto initerr;
+       }
+
+       _state = 0;
+       return;
+
+initerr:
+       _DEBUGPRINT("AlsaRawMidiIO: parameter setup error\n");
+       snd_rawmidi_close (_device);
+       _device = 0;
+#endif
+       return;
+}
+
+static void * pthread_process (void *arg)
+{
+       AlsaRawMidiIO *d = static_cast<AlsaRawMidiIO *>(arg);
+       d->main_process_thread ();
+       pthread_exit (0);
+       return 0;
+}
+
+int
+AlsaRawMidiIO::start ()
+{
+       if (_realtime_pthread_create (SCHED_FIFO, -19,
+                               &_main_thread, pthread_process, this))
+       {
+               if (pthread_create (&_main_thread, NULL, pthread_process, this)) {
+                       PBD::error << _("AlsaRawMidiIO: Failed to create process thread.") << endmsg;
+                       return -1;
+               } else {
+                       PBD::warning << _("AlsaRawMidiIO: Cannot acquire realtime permissions.") << endmsg;
+               }
+       }
+       int timeout = 5000;
+       while (!_running && --timeout > 0) { Glib::usleep (1000); }
+       if (timeout == 0 || !_running) {
+               return -1;
+       }
+       return 0;
+}
+
+int
+AlsaRawMidiIO::stop ()
+{
+       void *status;
+       if (!_running) {
+               return 0;
+       }
+
+       _running = false;
+
+       pthread_mutex_lock (&_notify_mutex);
+       pthread_cond_signal (&_notify_ready);
+       pthread_mutex_unlock (&_notify_mutex);
+
+       if (pthread_join (_main_thread, &status)) {
+               PBD::error << _("AlsaRawMidiIO: Failed to terminate.") << endmsg;
+               return -1;
+       }
+       return 0;
+}
+
+void
+AlsaRawMidiIO::setup_timing (const size_t samples_per_period, const float samplerate)
+{
+       _period_length_us = (double) samples_per_period * 1e6 / samplerate;
+       _sample_length_us = 1e6 / samplerate;
+       _samples_per_period = samples_per_period;
+}
+
+void
+AlsaRawMidiIO::sync_time (const uint64_t tme)
+{
+       // TODO consider a PLL, if this turns out to be the bottleneck for jitter
+       // also think about using
+       // snd_pcm_status_get_tstamp() and snd_rawmidi_status_get_tstamp()
+       // instead of monotonic clock.
+#ifdef DEBUG_TIMING
+       double tdiff = (_clock_monotonic + _period_length_us - tme) / 1000.0;
+       if (abs(tdiff) >= .05) {
+               printf("AlsaRawMidiIO MJ: %.1f ms\n", tdiff);
+       }
+#endif
+       _clock_monotonic = tme;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// select sleeps _at most_ (compared to usleep() which sleeps at least)
+static void select_sleep (uint32_t usec) {
+       if (usec <= 10) return;
+       fd_set fd;
+       int max_fd=0;
+       struct timeval tv;
+       tv.tv_sec = usec / 1000000;
+       tv.tv_usec = usec % 1000000;
+       FD_ZERO (&fd);
+       select (max_fd, &fd, NULL, NULL, &tv);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AlsaRawMidiOut::AlsaRawMidiOut (const char *device)
+               : AlsaRawMidiIO (device, false)
+{
+}
+
+
+int
+AlsaRawMidiOut::send_event (const pframes_t time, const uint8_t *data, const size_t size)
+{
+       const uint32_t  buf_size = sizeof (MidiEventHeader) + size;
+       if (_rb->write_space() < buf_size) {
+               _DEBUGPRINT("AlsaRawMidiOut: ring buffer overflow\n");
+               return -1;
+       }
+       struct MidiEventHeader h (_clock_monotonic + time * _sample_length_us, size);
+       _rb->write ((uint8_t*) &h, sizeof(MidiEventHeader));
+       _rb->write (data, size);
+
+       if (pthread_mutex_trylock (&_notify_mutex) == 0) {
+               pthread_cond_signal (&_notify_ready);
+               pthread_mutex_unlock (&_notify_mutex);
+       }
+       return 0;
+}
+
+void *
+AlsaRawMidiOut::main_process_thread ()
+{
+       _running = true;
+       pthread_mutex_lock (&_notify_mutex);
+       while (_running) {
+               bool have_data = false;
+               struct MidiEventHeader h(0,0);
+               uint8_t data[MaxAlsaRawEventSize];
+
+               const uint32_t read_space = _rb->read_space();
+
+               if (read_space > sizeof(MidiEventHeader)) {
+                       if (_rb->read ((uint8_t*)&h, sizeof(MidiEventHeader)) != sizeof(MidiEventHeader)) {
+                               _DEBUGPRINT("AlsaRawMidiOut: Garbled MIDI EVENT HEADER!!\n");
+                               break;
+                       }
+                       assert (read_space >= h.size);
+                       if (h.size > MaxAlsaRawEventSize) {
+                               _rb->increment_read_idx (h.size);
+                               _DEBUGPRINT("AlsaRawMidiOut: MIDI event too large!\n");
+                               continue;
+                       }
+                       if (_rb->read (&data[0], h.size) != h.size) {
+                               _DEBUGPRINT("AlsaRawMidiOut: Garbled MIDI EVENT DATA!!\n");
+                               break;
+                       }
+                       have_data = true;
+               }
+
+               if (!have_data) {
+                       pthread_cond_wait (&_notify_ready, &_notify_mutex);
+                       continue;
+               }
+
+               uint64_t now = g_get_monotonic_time();
+               while (h.time > now + 500) {
+                       select_sleep(h.time - now);
+                       now = g_get_monotonic_time();
+               }
+
+retry:
+               int perr = poll (_pfds, _npfds, 10 /* ms */);
+               if (perr < 0) {
+                       PBD::error << _("AlsaRawMidiOut: Error polling device. Terminating Midi Thread.") << endmsg;
+                       break;
+               }
+               if (perr == 0) {
+                       _DEBUGPRINT("AlsaRawMidiOut: poll() timed out.\n");
+                       goto retry;
+               }
+
+               unsigned short revents = 0;
+               if (snd_rawmidi_poll_descriptors_revents (_device, _pfds, _npfds, &revents)) {
+                       PBD::error << _("AlsaRawMidiOut: Failed to poll device. Terminating Midi Thread.") << endmsg;
+                       break;
+               }
+
+               if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
+                       PBD::error << _("AlsaRawMidiOut: poll error. Terminating Midi Thread.") << endmsg;
+                       break;
+               }
+
+               if (!(revents & POLLOUT)) {
+                       _DEBUGPRINT("AlsaRawMidiOut: POLLOUT not ready.\n");
+                       select_sleep (1000);
+                       goto retry;
+               }
+
+               ssize_t err = snd_rawmidi_write (_device, data, h.size);
+
+               if ((err == -EAGAIN) || (err == -EWOULDBLOCK)) {
+                       select_sleep (1000);
+                       goto retry;
+               }
+               if (err < 0) {
+                       PBD::error << _("AlsaRawMidiOut: write failed. Terminating Midi Thread.") << endmsg;
+                       break;
+               }
+               if ((size_t) err < h.size) {
+                       _DEBUGPRINT("AlsaRawMidiOut: short write\n");
+                       memmove(&data[0], &data[err], err);
+                       h.size -= err;
+                       goto retry;
+               }
+               snd_rawmidi_drain (_device);
+       }
+
+       pthread_mutex_unlock (&_notify_mutex);
+       _DEBUGPRINT("AlsaRawMidiOut: MIDI OUT THREAD STOPPED\n");
+       return 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+AlsaRawMidiIn::AlsaRawMidiIn (const char *device)
+               : AlsaRawMidiIO (device, true)
+{
+}
+
+size_t
+AlsaRawMidiIn::recv_event (pframes_t &time, uint8_t *data, size_t &size)
+{
+       const uint32_t read_space = _rb->read_space();
+       struct MidiEventHeader h(0,0);
+
+       if (read_space <= sizeof(MidiEventHeader)) {
+               return 0;
+       }
+
+#if 1
+       // check if event is in current cycle
+       RingBuffer<uint8_t>::rw_vector vector;
+       _rb->get_read_vector(&vector);
+       if (vector.len[0] >= sizeof(MidiEventHeader)) {
+               memcpy((uint8_t*)&h, vector.buf[0], sizeof(MidiEventHeader));
+       } else {
+               if (vector.len[0] > 0) {
+                       memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
+               }
+               memcpy (((uint8_t*)&h) + vector.len[0], vector.buf[1], sizeof(MidiEventHeader) - vector.len[0]);
+       }
+
+       if (h.time >= _clock_monotonic + _period_length_us ) {
+#ifdef DEBUG_TIMING
+               printf("AlsaRawMidiIn DEBUG: POSTPONE EVENT TO NEXT CYCLE: %.1f spl\n", ((h.time - _clock_monotonic) / _sample_length_us));
+#endif
+               return 0;
+       }
+       _rb->increment_read_idx (sizeof(MidiEventHeader));
+#else
+       if (_rb->read ((uint8_t*)&h, sizeof(MidiEventHeader)) != sizeof(MidiEventHeader)) {
+               _DEBUGPRINT("AlsaRawMidiIn::recv_event Garbled MIDI EVENT HEADER!!\n");
+               return 0;
+       }
+#endif
+       assert (h.size > 0);
+       if (h.size > size) {
+               _DEBUGPRINT("AlsaRawMidiIn::recv_event MIDI event too large!\n");
+               _rb->increment_read_idx (h.size);
+               return 0;
+       }
+       if (_rb->read (&data[0], h.size) != h.size) {
+               _DEBUGPRINT("AlsaRawMidiIn::recv_event Garbled MIDI EVENT DATA!!\n");
+               return 0;
+       }
+       if (h.time < _clock_monotonic) {
+#ifdef DEBUG_TIMING
+               printf("AlsaRawMidiIn DEBUG: MIDI TIME < 0 %.1f spl\n", ((_clock_monotonic - h.time) / -_sample_length_us));
+#endif
+               time = 0;
+       } else if (h.time >= _clock_monotonic + _period_length_us ) {
+#ifdef DEBUG_TIMING
+               printf("AlsaRawMidiIn DEBUG: MIDI TIME > PERIOD %.1f spl\n", ((h.time - _clock_monotonic) / _sample_length_us));
+#endif
+               time = _samples_per_period - 1;
+       } else {
+               time = floor ((h.time - _clock_monotonic) / _sample_length_us);
+       }
+       assert(time < _samples_per_period);
+       size = h.size;
+       return h.size;
+}
+
+void *
+AlsaRawMidiIn::main_process_thread ()
+{
+       _running = true;
+       while (_running) {
+               unsigned short revents = 0;
+
+               int perr = poll (_pfds, _npfds, 100 /* ms */);
+               if (perr < 0) {
+                       PBD::error << _("AlsaRawMidiIn: Error polling device. Terminating Midi Thread.") << endmsg;
+                       break;
+               }
+               if (perr == 0) {
+                       continue;
+               }
+
+               if (snd_rawmidi_poll_descriptors_revents (_device, _pfds, _npfds, &revents)) {
+                       PBD::error << _("AlsaRawMidiIn: Failed to poll device. Terminating Midi Thread.") << endmsg;
+                       break;
+               }
+
+               if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
+                       PBD::error << _("AlsaRawMidiIn: poll error. Terminating Midi Thread.") << endmsg;
+                       break;
+               }
+
+               if (!(revents & POLLIN)) {
+                       _DEBUGPRINT("AlsaRawMidiOut: POLLIN not ready.\n");
+                       select_sleep (1000);
+                       continue;
+               }
+
+               uint8_t data[MaxAlsaRawEventSize];
+               uint64_t time = g_get_monotonic_time();
+               ssize_t err = snd_rawmidi_read (_device, data, sizeof(data));
+
+               if ((err == -EAGAIN) || (err == -EWOULDBLOCK)) {
+                       continue;
+               }
+               if (err < 0) {
+                       PBD::error << _("AlsaRawMidiIn: read error. Terminating Midi") << endmsg;
+                       break;
+               }
+               if (err == 0) {
+                       _DEBUGPRINT("AlsaRawMidiIn: zero read\n");
+                       continue;
+               }
+
+               if (!(data[0] & 0x80)) {
+                       _DEBUGPRINT("AlsaRawMidiIn: invalid midi message.\n");
+               }
+               // TODO parse MIDI-events? break on status-bytes
+               {
+                       ssize_t size = err;
+                       const uint32_t  buf_size = sizeof(MidiEventHeader) + size;
+                       if (_rb->write_space() < buf_size) {
+                               _DEBUGPRINT("AlsaRawMidiIn: ring buffer overflow\n");
+                               continue;
+                       }
+                       struct MidiEventHeader h (time, size);
+                       _rb->write ((uint8_t*) &h, sizeof(MidiEventHeader));
+                       _rb->write (data, size);
+               }
+       }
+
+       _DEBUGPRINT("AlsaRawMidiIn: MIDI IN THREAD STOPPED\n");
+       return 0;
+}
diff --git a/libs/backends/alsa/alsa_rawmidi.h b/libs/backends/alsa/alsa_rawmidi.h
new file mode 100644 (file)
index 0000000..554012e
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 Robin Gareus <robin@gareus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __libbackend_alsa_rawmidi_h__
+#define __libbackend_alsa_rawmidi_h__
+
+#include <stdint.h>
+#include <poll.h>
+#include <pthread.h>
+
+#include <alsa/asoundlib.h>
+
+#include "pbd/ringbuffer.h"
+#include "ardour/types.h"
+
+namespace ARDOUR {
+
+class AlsaRawMidiIO {
+public:
+       AlsaRawMidiIO (const char *device, const bool input);
+       virtual ~AlsaRawMidiIO ();
+
+       int state (void) const { return _state; }
+       int start ();
+       int stop ();
+
+       void setup_timing (const size_t samples_per_period, const float samplerate);
+       void sync_time(uint64_t);
+
+       virtual void* main_process_thread () = 0;
+
+protected:
+       pthread_t _main_thread;
+       pthread_mutex_t _notify_mutex;
+       pthread_cond_t _notify_ready;
+
+       int  _state;
+       bool  _running;
+
+       snd_rawmidi_t *_device;
+       int _npfds;
+       struct pollfd *_pfds;
+
+       double _sample_length_us;
+       double _period_length_us;
+       size_t _samples_per_period;
+       uint64_t _clock_monotonic;
+
+       struct MidiEventHeader {
+               uint64_t time;
+               size_t size;
+               MidiEventHeader(const uint64_t t, const size_t s)
+                       : time(t)
+                       , size(s) {}
+       };
+
+       RingBuffer<uint8_t>* _rb;
+
+private:
+       void init (const char *device_name, const bool input);
+
+};
+
+class AlsaRawMidiOut : public AlsaRawMidiIO
+{
+public:
+       AlsaRawMidiOut (const char *device);
+
+       void* main_process_thread ();
+       int send_event (const pframes_t, const uint8_t *, const size_t);
+};
+
+class AlsaRawMidiIn : public AlsaRawMidiIO
+{
+public:
+       AlsaRawMidiIn (const char *device);
+
+       void* main_process_thread ();
+
+       size_t recv_event (pframes_t &, uint8_t *, size_t &);
+};
+
+} // namespace
+
+#endif
index 7e739405da0e3d29f5a873418573601e6d884535..ff35abd30ca625654449bc6933efb851e662d7cb 100644 (file)
@@ -25,6 +25,7 @@ def build(bld):
     obj = bld(features = 'cxx cxxshlib')
     obj.source = [
             'alsa_audiobackend.cc',
+            'alsa_rawmidi.cc',
             'zita-alsa-pcmi.cc',
             ]
     obj.includes = ['.']