From 6648074a130fed9b3cc2de0dbbfa900f5cfba0f7 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Sat, 21 Jun 2014 13:51:46 +0200 Subject: [PATCH] virtual abstraction of Alsa Raw+Seq --- libs/backends/alsa/alsa_audiobackend.cc | 63 ++++-- libs/backends/alsa/alsa_audiobackend.h | 5 +- libs/backends/alsa/alsa_midi.cc | 243 ++++++++++++++++++++++++ libs/backends/alsa/alsa_midi.h | 97 ++++++++++ libs/backends/alsa/alsa_rawmidi.cc | 211 +------------------- libs/backends/alsa/alsa_rawmidi.h | 48 +---- libs/backends/alsa/alsa_sequencer.cc | 224 +--------------------- libs/backends/alsa/alsa_sequencer.h | 48 +---- libs/backends/alsa/select_sleep.h | 35 ++++ libs/backends/alsa/wscript | 1 + 10 files changed, 449 insertions(+), 526 deletions(-) create mode 100644 libs/backends/alsa/alsa_midi.cc create mode 100644 libs/backends/alsa/alsa_midi.h create mode 100644 libs/backends/alsa/select_sleep.h diff --git a/libs/backends/alsa/alsa_audiobackend.cc b/libs/backends/alsa/alsa_audiobackend.cc index 58780acf4e..4b8fb82a7a 100644 --- a/libs/backends/alsa/alsa_audiobackend.cc +++ b/libs/backends/alsa/alsa_audiobackend.cc @@ -382,8 +382,15 @@ AlsaAudioBackend::midi_device_info(std::string const name) const { } } + assert(_midi_driver_option != _("None")); + std::map 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::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 m; m.push_back (_("None")); m.push_back (_("ALSA raw devices")); + m.push_back (_("ALSA sequencer")); return m; } @@ -406,13 +414,17 @@ std::vector AlsaAudioBackend::enumerate_midi_devices () const { std::vector s; - if (_midi_driver_option == _("None")) { + std::map 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 devices; - get_alsa_rawmidi_device_names(devices); - for (std::map::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::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::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) { assert (_rmidi_in.size() > i); - AlsaRawMidiIn *rm = static_cast(_rmidi_in.at(i)); + AlsaMidiIn *rm = static_cast(_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::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) { assert (_rmidi_out.size() > i); const AlsaMidiBuffer src = static_cast(*it)->const_buffer(); - AlsaRawMidiOut *rm = static_cast(_rmidi_out.at(i)); + AlsaMidiOut *rm = static_cast(_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()); diff --git a/libs/backends/alsa/alsa_audiobackend.h b/libs/backends/alsa/alsa_audiobackend.h index e0d1f114c3..91d133f303 100644 --- a/libs/backends/alsa/alsa_audiobackend.h +++ b/libs/backends/alsa/alsa_audiobackend.h @@ -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 _system_midi_in; std::vector _system_midi_out; - std::vector _rmidi_out; - std::vector _rmidi_in; + std::vector _rmidi_out; + std::vector _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 index 0000000000..dce84785b3 --- /dev/null +++ b/libs/backends/alsa/alsa_midi.cc @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2014 Robin Gareus + * + * 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 + +#include + +#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(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(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::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 index 0000000000..7da991d66e --- /dev/null +++ b/libs/backends/alsa/alsa_midi.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 Robin Gareus + * + * 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 +#include +#include + +#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* _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 diff --git a/libs/backends/alsa/alsa_rawmidi.cc b/libs/backends/alsa/alsa_rawmidi.cc index b3a5720aa2..e4678ba269 100644 --- a/libs/backends/alsa/alsa_rawmidi.cc +++ b/libs/backends/alsa/alsa_rawmidi.cc @@ -18,11 +18,10 @@ */ #include - #include +#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(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(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::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 diff --git a/libs/backends/alsa/alsa_rawmidi.h b/libs/backends/alsa/alsa_rawmidi.h index 896ae07246..5d9a86c8db 100644 --- a/libs/backends/alsa/alsa_rawmidi.h +++ b/libs/backends/alsa/alsa_rawmidi.h @@ -27,75 +27,39 @@ #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* _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); diff --git a/libs/backends/alsa/alsa_sequencer.cc b/libs/backends/alsa/alsa_sequencer.cc index 46e21b394a..aa0aac09fa 100644 --- a/libs/backends/alsa/alsa_sequencer.cc +++ b/libs/backends/alsa/alsa_sequencer.cc @@ -17,17 +17,20 @@ */ #include - #include +#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(4096 + 4096 * sizeof(MidiEventHeader)); - _state = 0; return; @@ -130,123 +115,14 @@ initerr: return; } -static void * pthread_process (void *arg) -{ - AlsaSeqMidiIO *d = static_cast(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::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 () { diff --git a/libs/backends/alsa/alsa_sequencer.h b/libs/backends/alsa/alsa_sequencer.h index c70870fd8a..bc00751acf 100644 --- a/libs/backends/alsa/alsa_sequencer.h +++ b/libs/backends/alsa/alsa_sequencer.h @@ -27,76 +27,36 @@ #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* _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 index 0000000000..ec6a93d4bc --- /dev/null +++ b/libs/backends/alsa/select_sleep.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2004,2014 Robin Gareus + * + * 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 +#include + +/* 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. +} diff --git a/libs/backends/alsa/wscript b/libs/backends/alsa/wscript index 3cfb49699f..173b6e0890 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_midi.cc', 'alsa_rawmidi.cc', 'alsa_sequencer.cc', 'zita-alsa-pcmi.cc',