diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2013-09-12 11:28:50 -0400 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2013-09-12 11:28:50 -0400 |
commit | bb826f4beebfcedc50570b818c305560d2233e47 (patch) | |
tree | 1484460f914e64fdbdce58230ffe7934bc0761f4 /libs/ardour/async_midi_port.cc | |
parent | 8c5cff60912c7e0a7256f635641399500d8d00d9 (diff) | |
parent | f85b362351a5f9167f93b6988f2c8a4c7e03a33c (diff) |
incomplete merge of master into windows (requires upcoming changes to master to be complete)
Diffstat (limited to 'libs/ardour/async_midi_port.cc')
-rw-r--r-- | libs/ardour/async_midi_port.cc | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/libs/ardour/async_midi_port.cc b/libs/ardour/async_midi_port.cc new file mode 100644 index 0000000000..77ca62961e --- /dev/null +++ b/libs/ardour/async_midi_port.cc @@ -0,0 +1,303 @@ +/* + Copyright (C) 1998 Paul Barton-Davis + + 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. + + $Id$ +*/ + +#include <iostream> + +#include "pbd/error.h" +#include "pbd/stacktrace.h" + +#include "midi++/types.h" + +#include "ardour/async_midi_port.h" +#include "ardour/audioengine.h" +#include "ardour/midi_buffer.h" + +using namespace MIDI; +using namespace ARDOUR; +using namespace std; +using namespace PBD; + +namespace Evoral { + template class EventRingBuffer<MIDI::timestamp_t>; +} + +pthread_t AsyncMIDIPort::_process_thread; + +#define port_engine AudioEngine::instance()->port_engine() + +AsyncMIDIPort::AsyncMIDIPort (string const & name, PortFlags flags) + : MidiPort (name, flags) + , MIDI::Port (name, MIDI::Port::Flags (0)) + , _currently_in_cycle (false) + , _last_write_timestamp (0) + , output_fifo (512) + , input_fifo (1024) + , xthread (true) +{ +} + +AsyncMIDIPort::~AsyncMIDIPort () +{ +} + +void +AsyncMIDIPort::flush_output_fifo (pframes_t nframes) +{ + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0 } }; + size_t written; + + output_fifo.get_read_vector (&vec); + + MidiBuffer& mb (get_midi_buffer (nframes)); + + if (vec.len[0]) { + Evoral::Event<double>* evp = vec.buf[0]; + + for (size_t n = 0; n < vec.len[0]; ++n, ++evp) { + mb.push_back (evp->time(), evp->size(), evp->buffer()); + } + } + + if (vec.len[1]) { + Evoral::Event<double>* evp = vec.buf[1]; + + for (size_t n = 0; n < vec.len[1]; ++n, ++evp) { + mb.push_back (evp->time(), evp->size(), evp->buffer()); + } + } + + if ((written = vec.len[0] + vec.len[1]) != 0) { + output_fifo.increment_read_idx (written); + } +} + +void +AsyncMIDIPort::cycle_start (pframes_t nframes) +{ + _currently_in_cycle = true; + MidiPort::cycle_start (nframes); + + /* dump anything waiting in the output FIFO at the start of the port + * buffer + */ + + if (ARDOUR::Port::sends_output()) { + flush_output_fifo (nframes); + } + + /* copy incoming data from the port buffer into the input FIFO + and if necessary wakeup the reader + */ + + if (ARDOUR::Port::receives_input()) { + MidiBuffer& mb (get_midi_buffer (nframes)); + pframes_t when = AudioEngine::instance()->sample_time_at_cycle_start(); + + for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) { + input_fifo.write (when, (Evoral::EventType) 0, (*b).size(), (*b).buffer()); + } + + if (!mb.empty()) { + xthread.wakeup (); + } + } + +} + +void +AsyncMIDIPort::cycle_end (pframes_t nframes) +{ + if (ARDOUR::Port::sends_output()) { + /* move any additional data from output FIFO into the port + buffer. + */ + flush_output_fifo (nframes); + } + + MidiPort::cycle_end (nframes); + + _currently_in_cycle = false; +} + +/** wait for the output FIFO to be emptied by successive process() callbacks. + * + * Cannot be called from a processing thread. + */ +void +AsyncMIDIPort::drain (int check_interval_usecs) +{ + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} }; + + if (!AudioEngine::instance()->running() || AudioEngine::instance()->session() == 0) { + /* no more process calls - it will never drain */ + return; + } + + + if (is_process_thread()) { + error << "Process thread called MIDI::AsyncMIDIPort::drain() - this cannot work" << endmsg; + return; + } + + while (1) { + output_fifo.get_write_vector (&vec); + if (vec.len[0] + vec.len[1] >= output_fifo.bufsize() - 1) { + break; + } + usleep (check_interval_usecs); + } +} + +int +AsyncMIDIPort::write (const MIDI::byte * msg, size_t msglen, MIDI::timestamp_t timestamp) +{ + int ret = 0; + + if (!ARDOUR::Port::sends_output()) { + return ret; + } + + if (!is_process_thread()) { + + /* this is the best estimate of "when" this MIDI data is being + * delivered + */ + + _parser->set_timestamp (AudioEngine::instance()->sample_time() + timestamp); + for (size_t n = 0; n < msglen; ++n) { + _parser->scanner (msg[n]); + } + + Glib::Threads::Mutex::Lock lm (output_fifo_lock); + RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} }; + + output_fifo.get_write_vector (&vec); + + if (vec.len[0] + vec.len[1] < 1) { + error << "no space in FIFO for non-process thread MIDI write" << endmsg; + return 0; + } + + if (vec.len[0]) { + if (!vec.buf[0]->owns_buffer()) { + vec.buf[0]->set_buffer (0, 0, true); + } + vec.buf[0]->set (msg, msglen, timestamp); + } else { + if (!vec.buf[1]->owns_buffer()) { + vec.buf[1]->set_buffer (0, 0, true); + } + vec.buf[1]->set (msg, msglen, timestamp); + } + + output_fifo.increment_write_idx (1); + + ret = msglen; + + } else { + + _parser->set_timestamp (AudioEngine::instance()->sample_time_at_cycle_start() + timestamp); + for (size_t n = 0; n < msglen; ++n) { + _parser->scanner (msg[n]); + } + + if (timestamp >= _cycle_nframes) { + std::cerr << "attempting to write MIDI event of " << msglen << " MIDI::bytes at time " + << timestamp << " of " << _cycle_nframes + << " (this will not work - needs a code fix)" + << std::endl; + } + + /* This is the process thread, which makes checking + * _currently_in_cycle atomic and safe, since it is only + * set from cycle_start() and cycle_end(), also called + * only from the process thread. + */ + + if (_currently_in_cycle) { + + MidiBuffer& mb (get_midi_buffer (_cycle_nframes)); + + if (timestamp == 0) { + timestamp = _last_write_timestamp; + } + + if (mb.push_back (timestamp, msglen, msg)) { + ret = msglen; + _last_write_timestamp = timestamp; + + } else { + cerr << "AsyncMIDIPort (" << ARDOUR::Port::name() << "): write of " << msglen << " @ " << timestamp << " failed\n" << endl; + PBD::stacktrace (cerr, 20); + ret = 0; + } + } else { + cerr << "write to JACK midi port failed: not currently in a process cycle." << endl; + PBD::stacktrace (cerr, 20); + } + } + + return ret; +} + + +int +AsyncMIDIPort::read (MIDI::byte *, size_t) +{ + if (!ARDOUR::Port::receives_input()) { + return 0; + } + + timestamp_t time; + Evoral::EventType type; + uint32_t size; + MIDI::byte buffer[input_fifo.capacity()]; + + while (input_fifo.read (&time, &type, &size, buffer)) { + _parser->set_timestamp (time); + for (uint32_t i = 0; i < size; ++i) { + _parser->scanner (buffer[i]); + } + } + + return 0; +} + +void +AsyncMIDIPort::parse (framecnt_t) +{ + MIDI::byte buf[1]; + + /* see ::read() to realize why buf is not used */ + read (buf, sizeof (buf)); +} + +void +AsyncMIDIPort::set_process_thread (pthread_t thr) +{ + _process_thread = thr; +} + +bool +AsyncMIDIPort::is_process_thread() +{ + return pthread_equal (pthread_self(), _process_thread); +} + |