diff options
author | Robin Gareus <robin@gareus.org> | 2014-06-02 16:52:07 +0200 |
---|---|---|
committer | Robin Gareus <robin@gareus.org> | 2014-06-02 19:23:07 +0200 |
commit | 5cd2010c790875fc10eb7728f6f73462bf0a2198 (patch) | |
tree | 10c7deb13a53e3327d0ff422494c22f5844db783 | |
parent | 8e9b02cfa21fc75f1b6f35c0b80295f421f3cd9e (diff) |
ALSA backend: raw midi prototype
-rw-r--r-- | libs/backends/alsa/alsa_audiobackend.cc | 159 | ||||
-rw-r--r-- | libs/backends/alsa/alsa_audiobackend.h | 17 | ||||
-rw-r--r-- | libs/backends/alsa/alsa_rawmidi.cc | 472 | ||||
-rw-r--r-- | libs/backends/alsa/alsa_rawmidi.h | 100 | ||||
-rw-r--r-- | libs/backends/alsa/wscript | 1 |
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 (¶ms)) { + 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 = ['.'] |