mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-06 06:44:57 +01:00
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.
365 lines
8.6 KiB
C++
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
|