ardour/libs/ardour/async_midi_port.cc
David Robillard 1324c25282 WIP: Fix namespace issues and build with GCC8
The build was broken for me with both GCC 8 and clang 11 due to the lookup of
operator<<().  However, since the previous pattern of using a namespace then
defining things in that namespace in... the global namespace... sort of... is
very strange, and likely to cause further problems with ADL especially as we
move to newer language versions and libraries, I opted to go all-out here and
define things inside the appropriate namespace.

This will probably resolve some earlier issues with clang and MSVC as well,
since they each use different lookup rules that all have their own quirks
around this stuff.
2021-05-25 16:41:19 -04:00

365 lines
8.6 KiB
C++

/*
* Copyright (C) 1998-2017 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2013-2014 John Emmas <john@creativepost.co.uk>
* Copyright (C) 2014-2016 David Robillard <d@drobilla.net>
* Copyright (C) 2015-2016 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <iostream>
#include <vector>
#include <glibmm/timer.h>
#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 std;
using namespace PBD;
namespace ARDOUR {
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)
, _flush_at_cycle_start (false)
, have_timer (false)
, output_fifo (2048)
, input_fifo (1024)
, _xthread (true)
{
}
AsyncMIDIPort::~AsyncMIDIPort ()
{
}
void
AsyncMIDIPort::set_timer (boost::function<MIDI::samplecnt_t (void)>& f)
{
timer = f;
have_timer = true;
}
void
AsyncMIDIPort::flush_output_fifo (MIDI::pframes_t nframes)
{
RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0 } };
size_t written = 0;
output_fifo.get_read_vector (&vec);
MidiBuffer& mb (get_midi_buffer (nframes));
if (vec.len[0]) {
Evoral::Event<double>* evp = vec.buf[0];
assert (evp->size());
assert (evp->buffer());
for (size_t n = 0; n < vec.len[0]; ++n, ++evp) {
if (mb.push_back (evp->time(), evp->event_type (), evp->size(), evp->buffer())) {
written++;
}
}
}
if (vec.len[1]) {
Evoral::Event<double>* evp = vec.buf[1];
assert (evp->size());
assert (evp->buffer());
for (size_t n = 0; n < vec.len[1]; ++n, ++evp) {
if (mb.push_back (evp->time(), evp->event_type (), evp->size(), evp->buffer())) {
written++;
}
}
}
/* do this "atomically" after we're done pushing events into the
* MidiBuffer
*/
output_fifo.increment_read_idx (written);
}
void
AsyncMIDIPort::cycle_start (MIDI::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 (Port::sends_output()) {
flush_output_fifo (nframes);
if (_flush_at_cycle_start) {
flush_buffers (nframes);
}
}
/* copy incoming data from the port buffer into the input FIFO
and if necessary wakeup the reader
*/
if (Port::receives_input()) {
void* buffer = port_engine.get_buffer (_port_handle, nframes);
const pframes_t event_count = port_engine.get_midi_event_count (buffer);
for (pframes_t i = 0; i < event_count; ++i) {
pframes_t timestamp;
size_t size;
uint8_t const* buf;
port_engine.midi_event_get (timestamp, size, &buf, buffer, i);
if (buf[0] == 0xfe) {
/* throw away active sensing */
continue;
}
samplecnt_t when;
if (have_timer) {
when = timer ();
} else {
when = AudioEngine::instance()->sample_time_at_cycle_start() + timestamp;
}
input_fifo.write (when, Evoral::NO_EVENT, size, buf);
}
if (event_count) {
_xthread.wakeup ();
}
}
}
void
AsyncMIDIPort::cycle_end (MIDI::pframes_t nframes)
{
if (Port::sends_output() && !_flush_at_cycle_start) {
/* 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, int total_usecs_to_wait)
{
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;
}
microseconds_t now = get_microseconds ();
microseconds_t end = now + total_usecs_to_wait;
while (now < end) {
output_fifo.get_write_vector (&vec);
if (vec.len[0] + vec.len[1] >= output_fifo.bufsize() - 1) {
break;
}
Glib::usleep (check_interval_usecs);
now = get_microseconds();
}
}
int
AsyncMIDIPort::write (const MIDI::byte * msg, size_t msglen, MIDI::timestamp_t timestamp)
{
int ret = 0;
if (!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]) {
/* force each event inside the ringbuffer to own its
own buffer, but let that be null and of zero size
initially. When ::set() is called, the buffer will
be allocated to hold a *copy* of the data we're
storing, and then that buffer will be used over and
over, occasionally being upwardly resized as
necessary.
*/
if (!vec.buf[0]->owns_buffer()) {
vec.buf[0]->set_buffer (0, 0, true);
}
vec.buf[0]->set (msg, msglen, timestamp);
vec.buf[0]->set_event_type (Evoral::LIVE_MIDI_EVENT);
} else {
/* see comment in previous branch of if() statement */
if (!vec.buf[1]->owns_buffer()) {
vec.buf[1]->set_buffer (0, 0, true);
}
vec.buf[1]->set (msg, msglen, timestamp);
vec.buf[1]->set_event_type (Evoral::LIVE_MIDI_EVENT);
}
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, Evoral::LIVE_MIDI_EVENT, msglen, msg)) {
ret = msglen;
_last_write_timestamp = timestamp;
} else {
cerr << "AsyncMIDIPort (" << 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 (!Port::receives_input()) {
return 0;
}
timestamp_t time;
Evoral::EventType type;
uint32_t size;
vector<MIDI::byte> buffer(input_fifo.capacity());
while (input_fifo.read (&time, &type, &size, &buffer[0])) {
_parser->set_timestamp (time);
for (uint32_t i = 0; i < size; ++i) {
_parser->scanner (buffer[i]);
}
}
return 0;
}
void
AsyncMIDIPort::parse (MIDI::samplecnt_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);
}
} // namespace ARDOUR