summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2014-06-02 16:52:07 +0200
committerRobin Gareus <robin@gareus.org>2014-06-02 19:23:07 +0200
commit5cd2010c790875fc10eb7728f6f73462bf0a2198 (patch)
tree10c7deb13a53e3327d0ff422494c22f5844db783
parent8e9b02cfa21fc75f1b6f35c0b80295f421f3cd9e (diff)
ALSA backend: raw midi prototype
-rw-r--r--libs/backends/alsa/alsa_audiobackend.cc159
-rw-r--r--libs/backends/alsa/alsa_audiobackend.h17
-rw-r--r--libs/backends/alsa/alsa_rawmidi.cc472
-rw-r--r--libs/backends/alsa/alsa_rawmidi.h100
-rw-r--r--libs/backends/alsa/wscript1
5 files changed, 733 insertions, 16 deletions
diff --git a/libs/backends/alsa/alsa_audiobackend.cc b/libs/backends/alsa/alsa_audiobackend.cc
index 34f28b24aa..ca4fd25c28 100644
--- a/libs/backends/alsa/alsa_audiobackend.cc
+++ b/libs/backends/alsa/alsa_audiobackend.cc
@@ -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;
diff --git a/libs/backends/alsa/alsa_audiobackend.h b/libs/backends/alsa/alsa_audiobackend.h
index 4a5633ef23..7ff172efbb 100644
--- a/libs/backends/alsa/alsa_audiobackend.h
+++ b/libs/backends/alsa/alsa_audiobackend.h
@@ -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
index 0000000000..f6664797b4
--- /dev/null
+++ b/libs/backends/alsa/alsa_rawmidi.cc
@@ -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
index 0000000000..554012e66a
--- /dev/null
+++ b/libs/backends/alsa/alsa_rawmidi.h
@@ -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
diff --git a/libs/backends/alsa/wscript b/libs/backends/alsa/wscript
index 7e739405da..ff35abd30c 100644
--- a/libs/backends/alsa/wscript
+++ b/libs/backends/alsa/wscript
@@ -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 = ['.']