virtual abstraction of Alsa Raw+Seq
authorRobin Gareus <robin@gareus.org>
Sat, 21 Jun 2014 11:51:46 +0000 (13:51 +0200)
committerRobin Gareus <robin@gareus.org>
Sat, 21 Jun 2014 11:51:46 +0000 (13:51 +0200)
libs/backends/alsa/alsa_audiobackend.cc
libs/backends/alsa/alsa_audiobackend.h
libs/backends/alsa/alsa_midi.cc [new file with mode: 0644]
libs/backends/alsa/alsa_midi.h [new file with mode: 0644]
libs/backends/alsa/alsa_rawmidi.cc
libs/backends/alsa/alsa_rawmidi.h
libs/backends/alsa/alsa_sequencer.cc
libs/backends/alsa/alsa_sequencer.h
libs/backends/alsa/select_sleep.h [new file with mode: 0644]
libs/backends/alsa/wscript

index 58780acf4ee6fd0ea6aea42a2bf83aded2933942..4b8fb82a7a543e9306034286343e62123f2a6f84 100644 (file)
@@ -382,8 +382,15 @@ AlsaAudioBackend::midi_device_info(std::string const name) const {
                }
        }
 
+       assert(_midi_driver_option != _("None"));
+
        std::map<std::string, std::string> devices;
-       get_alsa_rawmidi_device_names(devices);
+       if (_midi_driver_option == _("ALSA raw devices")) {
+               get_alsa_rawmidi_device_names(devices);
+       } else {
+               get_alsa_sequencer_names (devices);
+       }
+
        for (std::map<std::string, std::string>::const_iterator i = devices.begin (); i != devices.end(); ++i) {
                if (i->first == name) {
                        _midi_devices[name] = new AlsaMidiDeviceInfo();
@@ -399,6 +406,7 @@ AlsaAudioBackend::enumerate_midi_options () const
        std::vector<std::string> m;
        m.push_back (_("None"));
        m.push_back (_("ALSA raw devices"));
+       m.push_back (_("ALSA sequencer"));
        return m;
 }
 
@@ -406,13 +414,17 @@ std::vector<AudioBackend::DeviceStatus>
 AlsaAudioBackend::enumerate_midi_devices () const
 {
        std::vector<AudioBackend::DeviceStatus> s;
-       if (_midi_driver_option == _("None")) {
+       std::map<std::string, std::string> devices;
+
+       if (_midi_driver_option == _("ALSA raw devices")) {
+               get_alsa_rawmidi_device_names (devices);
+       }
+       else if (_midi_driver_option == _("ALSA sequencer")) {
+               get_alsa_sequencer_names (devices);
+       } else {
                return s;
        }
 
-       std::map<std::string, std::string> devices;
-       get_alsa_rawmidi_device_names(devices);
-
        for (std::map<std::string, std::string>::const_iterator i = devices.begin (); i != devices.end(); ++i) {
                s.push_back (DeviceStatus (i->first, true));
        }
@@ -422,7 +434,7 @@ AlsaAudioBackend::enumerate_midi_devices () const
 int
 AlsaAudioBackend::set_midi_option (const std::string& opt)
 {
-       if (opt != _("None") && opt != _("ALSA raw devices")) {
+       if (opt != _("None") && opt != _("ALSA raw devices") && opt != _("ALSA sequencer")) {
                return -1;
        }
        _midi_driver_option = opt;
@@ -620,13 +632,13 @@ AlsaAudioBackend::stop ()
        }
 
        while (!_rmidi_out.empty ()) {
-               AlsaRawMidiIO *m = _rmidi_out.back ();
+               AlsaMidiIO *m = _rmidi_out.back ();
                m->stop();
                _rmidi_out.pop_back ();
                delete m;
        }
        while (!_rmidi_in.empty ()) {
-               AlsaRawMidiIO *m = _rmidi_in.back ();
+               AlsaMidiIO *m = _rmidi_in.back ();
                m->stop();
                _rmidi_in.pop_back ();
                delete m;
@@ -954,18 +966,27 @@ AlsaAudioBackend::register_system_midi_ports()
 
        if (_midi_driver_option == _("None")) {
                return 0;
+       } else if (_midi_driver_option == _("ALSA raw devices")) {
+               get_alsa_rawmidi_device_names(devices);
+       } else {
+               get_alsa_sequencer_names (devices);
        }
-       get_alsa_rawmidi_device_names(devices);
 
        for (std::map<std::string, std::string>::const_iterator i = devices.begin (); i != devices.end(); ++i) {
                struct AlsaMidiDeviceInfo * nfo = midi_device_info(i->first);
                if (!nfo) continue;
                if (!nfo->enabled) continue;
 
-               AlsaRawMidiOut *mout = new AlsaRawMidiOut (i->second.c_str());
+               AlsaMidiOut *mout;
+               if (_midi_driver_option == _("ALSA raw devices")) {
+                       mout = new AlsaRawMidiOut (i->second.c_str());
+               } else {
+                       mout = new AlsaSeqMidiOut (i->second.c_str());
+               }
+
                if (mout->state ()) {
                        PBD::warning << string_compose (
-                                       _("AlsaRawMidiOut: failed to open midi device '%1'."), i->second)
+                                       _("AlsaMidiOut: failed to open midi device '%1'."), i->second)
                                << endmsg;
                        delete mout;
                } else {
@@ -973,7 +994,7 @@ AlsaAudioBackend::register_system_midi_ports()
                        mout->sync_time (g_get_monotonic_time());
                        if (mout->start ()) {
                                PBD::warning << string_compose (
-                                               _("AlsaRawMidiOut: failed to start midi device '%1'."), i->second)
+                                               _("AlsaMidiOut: failed to start midi device '%1'."), i->second)
                                        << endmsg;
                                delete mout;
                        } else {
@@ -992,10 +1013,16 @@ AlsaAudioBackend::register_system_midi_ports()
                        }
                }
 
-               AlsaRawMidiIn *midin = new AlsaRawMidiIn (i->second.c_str());
+               AlsaMidiIn *midin;
+               if (_midi_driver_option == _("ALSA raw devices")) {
+                       midin = new AlsaRawMidiIn (i->second.c_str());
+               } else {
+                       midin = new AlsaSeqMidiIn (i->second.c_str());
+               }
+
                if (midin->state ()) {
                        PBD::warning << string_compose (
-                                       _("AlsaRawMidiIn: failed to open midi device '%1'."), i->second)
+                                       _("AlsaMidiIn: failed to open midi device '%1'."), i->second)
                                << endmsg;
                        delete midin;
                } else {
@@ -1003,7 +1030,7 @@ AlsaAudioBackend::register_system_midi_ports()
                        midin->sync_time (g_get_monotonic_time());
                        if (midin->start ()) {
                                PBD::warning << string_compose (
-                                               _("AlsaRawMidiIn: failed to start midi device '%1'."), i->second)
+                                               _("AlsaMidiIn: failed to start midi device '%1'."), i->second)
                                        << endmsg;
                                delete midin;
                        } else {
@@ -1401,10 +1428,10 @@ AlsaAudioBackend::main_process_thread ()
                                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));
+                                       AlsaMidiIn *rm = static_cast<AlsaMidiIn*>(_rmidi_in.at(i));
                                        void *bptr = (*it)->get_buffer(0);
                                        pframes_t time;
-                                       uint8_t data[64]; // match MaxAlsaRawEventSize in alsa_rawmidi.cc
+                                       uint8_t data[64]; // match MaxAlsaEventSize in alsa_rawmidi.cc
                                        size_t size = sizeof(data);
                                        midi_clear(bptr);
                                        while (rm->recv_event (time, data, size)) {
@@ -1434,7 +1461,7 @@ AlsaAudioBackend::main_process_thread ()
                                for (std::vector<AlsaPort*>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) {
                                        assert (_rmidi_out.size() > i);
                                        const AlsaMidiBuffer src = static_cast<const AlsaMidiPort*>(*it)->const_buffer();
-                                       AlsaRawMidiOut *rm = static_cast<AlsaRawMidiOut*>(_rmidi_out.at(i));
+                                       AlsaMidiOut *rm = static_cast<AlsaMidiOut*>(_rmidi_out.at(i));
                                        rm->sync_time (clock1);
                                        for (AlsaMidiBuffer::const_iterator mit = src.begin (); mit != src.end (); ++mit) {
                                                rm->send_event ((*mit)->timestamp(), (*mit)->data(), (*mit)->size());
index e0d1f114c314410d5aabf52788a6f2052f4873b5..91d133f303bcefb92143d16084512ac4c610257e 100644 (file)
@@ -36,6 +36,7 @@
 
 #include "zita-alsa-pcmi.h"
 #include "alsa_rawmidi.h"
+#include "alsa_sequencer.h"
 
 namespace ARDOUR {
 
@@ -367,8 +368,8 @@ class AlsaAudioBackend : public AudioBackend {
                std::vector<AlsaPort *> _system_midi_in;
                std::vector<AlsaPort *> _system_midi_out;
 
-               std::vector<AlsaRawMidiOut *> _rmidi_out;
-               std::vector<AlsaRawMidiIn  *> _rmidi_in;
+               std::vector<AlsaMidiOut *> _rmidi_out;
+               std::vector<AlsaMidiIn  *> _rmidi_in;
 
                struct PortConnectData {
                        std::string a;
diff --git a/libs/backends/alsa/alsa_midi.cc b/libs/backends/alsa/alsa_midi.cc
new file mode 100644 (file)
index 0000000..dce8478
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * 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_midi.h"
+#include "rt_thread.h"
+
+#include "pbd/error.h"
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+#ifndef NDEBUG
+#define _DEBUGPRINT(STR) fprintf(stderr, STR);
+#else
+#define _DEBUGPRINT(STR) ;
+#endif
+
+AlsaMidiIO::AlsaMidiIO ()
+       : _state (-1)
+       , _running (false)
+       , _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);
+
+       // 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));
+}
+
+AlsaMidiIO::~AlsaMidiIO ()
+{
+       delete _rb;
+       pthread_mutex_destroy (&_notify_mutex);
+       pthread_cond_destroy (&_notify_ready);
+       free (_pfds);
+}
+
+static void * pthread_process (void *arg)
+{
+       AlsaMidiIO *d = static_cast<AlsaMidiIO *>(arg);
+       d->main_process_thread ();
+       pthread_exit (0);
+       return 0;
+}
+
+int
+AlsaMidiIO::start ()
+{
+       if (_realtime_pthread_create (SCHED_FIFO, -21, 100000,
+                               &_main_thread, pthread_process, this))
+       {
+               if (pthread_create (&_main_thread, NULL, pthread_process, this)) {
+                       PBD::error << _("AlsaMidiIO: Failed to create process thread.") << endmsg;
+                       return -1;
+               } else {
+                       PBD::warning << _("AlsaMidiIO: Cannot acquire realtime permissions.") << endmsg;
+               }
+       }
+       int timeout = 5000;
+       while (!_running && --timeout > 0) { Glib::usleep (1000); }
+       if (timeout == 0 || !_running) {
+               return -1;
+       }
+       return 0;
+}
+
+int
+AlsaMidiIO::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 << _("AlsaMidiIO: Failed to terminate.") << endmsg;
+               return -1;
+       }
+       return 0;
+}
+
+void
+AlsaMidiIO::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
+AlsaMidiIO::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("AlsaMidiIO MJ: %.1f ms\n", tdiff);
+       }
+#endif
+       _clock_monotonic = tme;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AlsaMidiOut::AlsaMidiOut ()
+       : AlsaMidiIO ()
+{
+}
+
+int
+AlsaMidiOut::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("AlsaMidiOut: 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;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AlsaMidiIn::AlsaMidiIn ()
+       : AlsaMidiIO ()
+{
+}
+
+size_t
+AlsaMidiIn::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;
+       }
+
+       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("AlsaMidiIn 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));
+
+       assert (h.size > 0);
+       if (h.size > size) {
+               _DEBUGPRINT("AlsaMidiIn::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("AlsaMidiIn::recv_event Garbled MIDI EVENT DATA!!\n");
+               return 0;
+       }
+       if (h.time < _clock_monotonic) {
+#ifdef DEBUG_TIMING
+               printf("AlsaMidiIn 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("AlsaMidiIn 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;
+}
+
+int
+AlsaMidiIn::queue_event (const uint64_t time, const uint8_t *data, const size_t size) {
+       const uint32_t  buf_size = sizeof(MidiEventHeader) + size;
+
+       if (size == 0) {
+               return -1;
+       }
+       if (_rb->write_space() < buf_size) {
+               _DEBUGPRINT("AlsaMidiIn: ring buffer overflow\n");
+               return -1;
+       }
+       struct MidiEventHeader h (time, size);
+       _rb->write ((uint8_t*) &h, sizeof(MidiEventHeader));
+       _rb->write (data, size);
+       return 0;
+}
diff --git a/libs/backends/alsa/alsa_midi.h b/libs/backends/alsa/alsa_midi.h
new file mode 100644 (file)
index 0000000..7da991d
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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_midi_h__
+#define __libbackend_alsa_midi_h__
+
+#include <stdint.h>
+#include <poll.h>
+#include <pthread.h>
+
+#include "pbd/ringbuffer.h"
+#include "ardour/types.h"
+
+namespace ARDOUR {
+
+class AlsaMidiIO {
+public:
+       AlsaMidiIO ();
+       virtual ~AlsaMidiIO ();
+
+       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;
+
+       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;
+
+protected:
+       virtual void init (const char *device_name, const bool input) = 0;
+
+};
+
+class AlsaMidiOut : virtual public AlsaMidiIO
+{
+public:
+       AlsaMidiOut ();
+
+       int send_event (const pframes_t, const uint8_t *, const size_t);
+};
+
+class AlsaMidiIn : virtual public AlsaMidiIO
+{
+public:
+       AlsaMidiIn ();
+
+       size_t recv_event (pframes_t &, uint8_t *, size_t &);
+
+protected:
+       int queue_event (const uint64_t, const uint8_t *, const size_t);
+};
+
+} // namespace
+
+#endif
index b3a5720aa27632e2e510f10f188658d1ab2d325d..e4678ba26906a13eda1efc4a284fe33f41bbd667 100644 (file)
  */
 
 #include <unistd.h>
-
 #include <glibmm.h>
 
+#include "select_sleep.h"
 #include "alsa_rawmidi.h"
-#include "rt_thread.h"
 
 #include "pbd/error.h"
 #include "i18n.h"
@@ -40,17 +39,9 @@ using namespace ARDOUR;
 #endif
 
 AlsaRawMidiIO::AlsaRawMidiIO (const char *device, const bool input)
-       : _state (-1)
-       , _running (false)
+       : AlsaMidiIO()
        , _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);
 }
 
@@ -61,10 +52,6 @@ AlsaRawMidiIO::~AlsaRawMidiIO ()
                snd_rawmidi_close (_device);
                _device = 0;
        }
-       delete _rb;
-       pthread_mutex_destroy (&_notify_mutex);
-       pthread_cond_destroy (&_notify_ready);
-       free (_pfds);
 }
 
 void
@@ -87,12 +74,6 @@ AlsaRawMidiIO::init (const char *device_name, const bool input)
        _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
@@ -124,121 +105,14 @@ initerr:
        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, -21, 100000,
-                               &_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)
+               , AlsaMidiOut ()
 {
 }
 
-
-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 ()
 {
@@ -351,6 +225,7 @@ retry:
 
 AlsaRawMidiIn::AlsaRawMidiIn (const char *device)
                : AlsaRawMidiIO (device, true)
+               , AlsaMidiIn ()
                , _event(0,0)
                , _first_time(true)
                , _unbuffered_bytes(0)
@@ -360,70 +235,6 @@ AlsaRawMidiIn::AlsaRawMidiIn (const char *device)
 {
 }
 
-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 ()
 {
@@ -485,20 +296,8 @@ AlsaRawMidiIn::main_process_thread ()
 
 int
 AlsaRawMidiIn::queue_event (const uint64_t time, const uint8_t *data, const size_t size) {
-       const uint32_t  buf_size = sizeof(MidiEventHeader) + size;
        _event._pending = false;
-
-       if (size == 0) {
-               return -1;
-       }
-       if (_rb->write_space() < buf_size) {
-               _DEBUGPRINT("AlsaRawMidiIn: ring buffer overflow\n");
-               return -1;
-       }
-       struct MidiEventHeader h (time, size);
-       _rb->write ((uint8_t*) &h, sizeof(MidiEventHeader));
-       _rb->write (data, size);
-       return 0;
+       return AlsaMidiIn::queue_event(time, data, size);
 }
 
 void
index 896ae072468c311353903c31a466667780ed18fc..5d9a86c8dbd9acbd80c7c8621b1225d5113faf32 100644 (file)
 
 #include "pbd/ringbuffer.h"
 #include "ardour/types.h"
+#include "alsa_midi.h"
 
 namespace ARDOUR {
 
-class AlsaRawMidiIO {
+class AlsaRawMidiIO : virtual public AlsaMidiIO {
 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
+class AlsaRawMidiOut : public AlsaRawMidiIO, public AlsaMidiOut
 {
 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
+class AlsaRawMidiIn : public AlsaRawMidiIO, public AlsaMidiIn
 {
 public:
        AlsaRawMidiIn (const char *device);
 
        void* main_process_thread ();
 
-       size_t recv_event (pframes_t &, uint8_t *, size_t &);
-
-private:
+protected:
        int queue_event (const uint64_t, const uint8_t *, const size_t);
+private:
        void parse_events (const uint64_t, const uint8_t *, const size_t);
        bool process_byte (const uint64_t, const uint8_t);
 
index 46e21b394a42da1391e2d1ce829ebd8e3f8cd752..aa0aac09fa9f7ee7186d7e3c6ce014cf7868bd82 100644 (file)
  */
 
 #include <unistd.h>
-
 #include <glibmm.h>
 
+#include "select_sleep.h"
 #include "alsa_sequencer.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 MaxAlsaSeqEventSize (64)
+
 #ifndef NDEBUG
 #define _DEBUGPRINT(STR) fprintf(stderr, STR);
 #else
@@ -35,17 +38,9 @@ using namespace ARDOUR;
 #endif
 
 AlsaSeqMidiIO::AlsaSeqMidiIO (const char *device, const bool input)
-       : _state (-1)
-       , _running (false)
+       : AlsaMidiIO()
        , _seq (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);
 }
 
@@ -55,10 +50,6 @@ AlsaSeqMidiIO::~AlsaSeqMidiIO ()
                snd_seq_close (_seq);
                _seq = 0;
        }
-       delete _rb;
-       pthread_mutex_destroy (&_notify_mutex);
-       pthread_cond_destroy (&_notify_ready);
-       free (_pfds);
 }
 
 void
@@ -102,24 +93,18 @@ AlsaSeqMidiIO::init (const char *device_name, const bool input)
 
        if (input) {
                if (snd_seq_connect_from (_seq, _port, port.client, port.port) < 0) {
-                       _DEBUGPRINT("AlsaSeqMidiIO: cannot connect port.\n");
+                       _DEBUGPRINT("AlsaSeqMidiIO: cannot connect input port.\n");
                        goto initerr;
                }
        } else {
                if (snd_seq_connect_to (_seq, _port, port.client, port.port) < 0) {
-                       _DEBUGPRINT("AlsaSeqMidiIO: cannot connect port.\n");
+                       _DEBUGPRINT("AlsaSeqMidiIO: cannot connect output port.\n");
                        goto initerr;
                }
        }
 
        snd_seq_nonblock(_seq, 1);
 
-       // 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));
-
        _state = 0;
        return;
 
@@ -130,123 +115,14 @@ initerr:
        return;
 }
 
-static void * pthread_process (void *arg)
-{
-       AlsaSeqMidiIO *d = static_cast<AlsaSeqMidiIO *>(arg);
-       d->main_process_thread ();
-       pthread_exit (0);
-       return 0;
-}
-
-int
-AlsaSeqMidiIO::start ()
-{
-       if (_realtime_pthread_create (SCHED_FIFO, -21, 100000,
-                               &_main_thread, pthread_process, this))
-       {
-               if (pthread_create (&_main_thread, NULL, pthread_process, this)) {
-                       PBD::error << _("AlsaSeqMidiIO: Failed to create process thread.") << endmsg;
-                       return -1;
-               } else {
-                       PBD::warning << _("AlsaSeqMidiIO: Cannot acquire realtime permissions.") << endmsg;
-               }
-       }
-       int timeout = 5000;
-       while (!_running && --timeout > 0) { Glib::usleep (1000); }
-       if (timeout == 0 || !_running) {
-               return -1;
-       }
-       return 0;
-}
-
-int
-AlsaSeqMidiIO::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 << _("AlsaSeqMidiIO: Failed to terminate.") << endmsg;
-               return -1;
-       }
-       return 0;
-}
-
-void
-AlsaSeqMidiIO::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
-AlsaSeqMidiIO::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("AlsaSeqMidiIO 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);
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 AlsaSeqMidiOut::AlsaSeqMidiOut (const char *device)
                : AlsaSeqMidiIO (device, false)
+               , AlsaMidiOut ()
 {
 }
 
-
-int
-AlsaSeqMidiOut::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("AlsaSeqMidiOut: 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;
-}
-
-#define MaxAlsaSeqEventSize 64
-
 void *
 AlsaSeqMidiOut::main_process_thread ()
 {
@@ -353,90 +229,10 @@ retry:
 
 AlsaSeqMidiIn::AlsaSeqMidiIn (const char *device)
                : AlsaSeqMidiIO (device, true)
+               , AlsaMidiIn ()
 {
 }
 
-size_t
-AlsaSeqMidiIn::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("AlsaSeqMidiIn 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("AlsaSeqMidiIn::recv_event Garbled MIDI EVENT HEADER!!\n");
-               return 0;
-       }
-#endif
-       assert (h.size > 0);
-       if (h.size > size) {
-               _DEBUGPRINT("AlsaSeqMidiIn::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("AlsaSeqMidiIn::recv_event Garbled MIDI EVENT DATA!!\n");
-               return 0;
-       }
-       if (h.time < _clock_monotonic) {
-#ifdef DEBUG_TIMING
-               printf("AlsaSeqMidiIn 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("AlsaSeqMidiIn 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;
-}
-
-int
-AlsaSeqMidiIn::queue_event (const uint64_t time, const uint8_t *data, const size_t size) {
-       const uint32_t  buf_size = sizeof(MidiEventHeader) + size;
-
-       if (size == 0) {
-               return -1;
-       }
-       if (_rb->write_space() < buf_size) {
-               _DEBUGPRINT("AlsaSeqMidiIn: ring buffer overflow\n");
-               return -1;
-       }
-       struct MidiEventHeader h (time, size);
-       _rb->write ((uint8_t*) &h, sizeof(MidiEventHeader));
-       _rb->write (data, size);
-       return 0;
-}
-
 void *
 AlsaSeqMidiIn::main_process_thread ()
 {
index c70870fd8ad27d21fd681a1ca88ad9d44102b1d4..bc00751acfdcaa2f687d316b53d9b643987ef56d 100644 (file)
 
 #include "pbd/ringbuffer.h"
 #include "ardour/types.h"
+#include "alsa_midi.h"
 
 namespace ARDOUR {
 
-class AlsaSeqMidiIO {
+class AlsaSeqMidiIO : virtual public AlsaMidiIO {
 public:
        AlsaSeqMidiIO (const char *port_name, const bool input);
        virtual ~AlsaSeqMidiIO ();
 
-       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_seq_t *_seq;
-       //snd_seq_addr_t _port;
        int _port;
 
-       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 AlsaSeqMidiOut : public AlsaSeqMidiIO
+class AlsaSeqMidiOut : public AlsaSeqMidiIO, public AlsaMidiOut
 {
 public:
        AlsaSeqMidiOut (const char *port_name);
-
        void* main_process_thread ();
-       int send_event (const pframes_t, const uint8_t *, const size_t);
 };
 
-class AlsaSeqMidiIn : public AlsaSeqMidiIO
+class AlsaSeqMidiIn : public AlsaSeqMidiIO, public AlsaMidiIn
 {
 public:
        AlsaSeqMidiIn (const char *port_name);
 
        void* main_process_thread ();
-       size_t recv_event (pframes_t &, uint8_t *, size_t &);
-
-private:
-       int queue_event (const uint64_t, const uint8_t *, const size_t);
 };
 
 } // namespace
diff --git a/libs/backends/alsa/select_sleep.h b/libs/backends/alsa/select_sleep.h
new file mode 100644 (file)
index 0000000..ec6a93d
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2004,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 <stdint.h>
+#include <sys/select.h>
+
+/* select() sleeps _at most_ a given time.
+ * (compared to usleep() or nanosleep() which sleep at least a given time)
+ */
+static void select_sleep (uint64_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);
+       // on Linux, tv reflects the actual time slept.
+}
index 3cfb49699f399b520a37ad9609115229f0b057b3..173b6e08909a98c9e2a2066177bd4d04cd4c9e35 100644 (file)
@@ -25,6 +25,7 @@ def build(bld):
     obj = bld(features = 'cxx cxxshlib')
     obj.source = [
             'alsa_audiobackend.cc',
+            'alsa_midi.cc',
             'alsa_rawmidi.cc',
             'alsa_sequencer.cc',
             'zita-alsa-pcmi.cc',