575 lines
13 KiB
C++
575 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2019 Paul Davis <paul@linuxaudiosystems.com>
|
|
*
|
|
* 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 <algorithm> // std::reverse
|
|
|
|
#include "pbd/malign.h"
|
|
#include "pbd/compose.h"
|
|
#include "pbd/error.h"
|
|
#include "pbd/debug.h"
|
|
|
|
#include "temporal/beats.h"
|
|
|
|
#include "ardour/debug.h"
|
|
#include "ardour/midi_buffer.h"
|
|
#include "ardour/midi_state_tracker.h"
|
|
#include "ardour/rt_midibuffer.h"
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
|
|
template<class TimeType, class DistanceType>
|
|
RTMidiBufferBase<TimeType,DistanceType>::RTMidiBufferBase ()
|
|
: _size (0)
|
|
, _capacity (0)
|
|
, _data (0)
|
|
, _reversed (false)
|
|
, _pool_size (0)
|
|
, _pool_capacity (0)
|
|
, _pool (0)
|
|
{
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
RTMidiBufferBase<TimeType,DistanceType>::~RTMidiBufferBase()
|
|
{
|
|
cache_aligned_free (_data);
|
|
cache_aligned_free (_pool);
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
void
|
|
RTMidiBufferBase<TimeType,DistanceType>::resize (size_t size)
|
|
{
|
|
if (_data && size < _capacity) {
|
|
|
|
if (_size < size) {
|
|
/* truncate */
|
|
_size = size;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Item* old_data = _data;
|
|
|
|
cache_aligned_malloc ((void**) &_data, size * sizeof (Item));
|
|
|
|
if (_size) {
|
|
assert (old_data);
|
|
memcpy ((void*) _data, (void*) old_data, _size * sizeof (Item));
|
|
cache_aligned_free (old_data);
|
|
}
|
|
|
|
_capacity = size;
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
bool
|
|
RTMidiBufferBase<TimeType,DistanceType>::reversed () const
|
|
{
|
|
return _reversed;
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
void
|
|
RTMidiBufferBase<TimeType,DistanceType>::reverse ()
|
|
{
|
|
if (_size == 0) {
|
|
return;
|
|
}
|
|
|
|
Item* previous_note_on[16][128];
|
|
uint8_t note_num;
|
|
uint8_t channel;
|
|
int32_t i;
|
|
|
|
memset (previous_note_on, 0, sizeof (Item*) * 16 * 128);
|
|
|
|
if (_reversed) {
|
|
i = _size - 1;
|
|
} else {
|
|
i = 0;
|
|
}
|
|
|
|
/* iterate from start to end, or end-to-start, depending on current
|
|
* _reversed status. Find each note on, and swap it with the relevant
|
|
* note off.
|
|
*/
|
|
|
|
while ((_reversed && (i >= 0)) || (!_reversed && (i < (int32_t) _size))) {
|
|
|
|
Item* item = &_data[i];
|
|
|
|
if (!item->bytes[0]) {
|
|
/* event is 3 bytes or less, so regular MIDI data */
|
|
switch (item->bytes[1] & 0xf0) { /* status byte */
|
|
case MIDI_CMD_NOTE_ON:
|
|
note_num = item->bytes[2];
|
|
channel = item->bytes[1] & 0xf;
|
|
if (!previous_note_on[channel][note_num]) {
|
|
previous_note_on[channel][note_num] = item;
|
|
} else {
|
|
std::cerr << "error: note is already on! ... ignored\n";
|
|
}
|
|
break;
|
|
case MIDI_CMD_NOTE_OFF: /* note off */
|
|
note_num = item->bytes[2];
|
|
channel = item->bytes[1] & 0xf;
|
|
if (previous_note_on[channel][note_num]) {
|
|
swap (item->bytes[1], previous_note_on[channel][note_num]->bytes[1]);
|
|
previous_note_on[channel][note_num] = 0;
|
|
} else {
|
|
std::cerr << "discovered note off without preceding note on... ignored\n";
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_reversed) {
|
|
--i;
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
_reversed = !_reversed;
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
void
|
|
RTMidiBufferBase<TimeType,DistanceType>::dump (uint32_t cnt)
|
|
{
|
|
cerr << this << " total items: " << _size << " within " << _capacity << " blob pool: " << _pool_capacity << " used " << _pool_size << endl;
|
|
|
|
for (uint32_t i = 0; i < _size && i < cnt; ++i) {
|
|
|
|
Item* item = &_data[i];
|
|
uint8_t* addr;
|
|
uint32_t size;
|
|
|
|
if (item->bytes[0]) {
|
|
|
|
/* more than 3 bytes ... indirect */
|
|
|
|
uint32_t offset = item->offset & ~(1<<(CHAR_BIT-1));
|
|
Blob* blob = reinterpret_cast<Blob*> (&_pool[offset]);
|
|
|
|
size = blob->size;
|
|
addr = blob->data;
|
|
|
|
} else {
|
|
|
|
/* MIDI data is in bytes[1..3] (variable depending on message type */
|
|
size = Evoral::midi_event_size (item->bytes[1]);
|
|
addr = &item->bytes[1];
|
|
|
|
}
|
|
|
|
cerr << i << " @ " << item->timestamp << " sz=" << size << '\t';
|
|
|
|
cerr << hex;
|
|
for (size_t j =0 ; j < size; ++j) {
|
|
cerr << "0x" << hex << (int)addr[j] << dec << '/' << (int)addr[j] << ' ';
|
|
}
|
|
cerr << dec << endl;
|
|
}
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
uint32_t
|
|
RTMidiBufferBase<TimeType,DistanceType>::write (TimeType time, Evoral::EventType /*type*/, uint32_t size, const uint8_t* buf)
|
|
{
|
|
/* This buffer stores only MIDI, we don't care about the value of "type" */
|
|
|
|
if (_size + size >= _capacity) {
|
|
if (size > 1024) {
|
|
resize (_capacity + size + 1024); // XXX 1024 is completely arbitrary
|
|
} else {
|
|
resize (_capacity + 1024); // XXX 1024 is completely arbitrary
|
|
}
|
|
}
|
|
|
|
_data[_size].timestamp = time;
|
|
|
|
if (size > 3) {
|
|
|
|
uint32_t off = store_blob (size, buf);
|
|
|
|
/* non-zero MSbit indicates that the data (more than 3 bytes) is not inline */
|
|
_data[_size].offset = (off | (1<<(CHAR_BIT-1)));
|
|
|
|
} else {
|
|
|
|
assert ((int) size == Evoral::midi_event_size (buf[0]));
|
|
|
|
/* zero MSbit indicates that the data (up to 3 bytes) is inline */
|
|
_data[_size].bytes[0] = 0;
|
|
|
|
switch (size) {
|
|
case 3:
|
|
_data[_size].bytes[3] = buf[2];
|
|
/* fallthru */
|
|
case 2:
|
|
_data[_size].bytes[2] = buf[1];
|
|
/* fallthru */
|
|
case 1:
|
|
_data[_size].bytes[1] = buf[0];
|
|
break;
|
|
}
|
|
}
|
|
|
|
++_size;
|
|
|
|
return size;
|
|
}
|
|
|
|
/* requires C++20 to be usable */
|
|
/*
|
|
static
|
|
bool
|
|
item_timestamp_earlier (ARDOUR::template<class TimeType, class DistanceType>
|
|
RTMidiBufferBase<TimeType,DistanceType>::Item const & item, samplepos_t const & time)
|
|
{
|
|
return item.timestamp < time;
|
|
}
|
|
*/
|
|
|
|
template<class TimeType, class DistanceType>
|
|
void
|
|
RTMidiBufferBase<TimeType,DistanceType>::track (MidiStateTracker& mst, TimeType start, TimeType end)
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock, Glib::Threads::TRY_LOCK);
|
|
|
|
if (!lm.locked()) {
|
|
return;
|
|
}
|
|
|
|
bool reverse;
|
|
Item foo;
|
|
Item* iend;
|
|
Item* item;
|
|
foo.timestamp = start;
|
|
|
|
if (start < end) {
|
|
iend = _data+_size;
|
|
item = lower_bound (_data, iend, foo, [](Item const & a, Item const & b) { return a.timestamp < b.timestamp; });
|
|
reverse = false;
|
|
} else {
|
|
iend = _data;
|
|
--iend; /* yes, this is technically "illegal" but we will never indirect */
|
|
Item* uend = _data + _size;
|
|
item = upper_bound (_data, uend, foo, [](Item const & a, Item const & b) { return a.timestamp < b.timestamp; });
|
|
|
|
if (item == uend) {
|
|
--item;
|
|
}
|
|
|
|
reverse = true;
|
|
}
|
|
|
|
while ((item != iend) && ((reverse && (item->timestamp > end)) || (!reverse && (item->timestamp < end)))) {
|
|
|
|
TimeType evtime = item->timestamp;
|
|
|
|
/* Adjust event times to be relative to 'start', taking
|
|
* 'offset' into account.
|
|
*/
|
|
|
|
if (reverse) {
|
|
if (evtime > start) {
|
|
--item;
|
|
continue;
|
|
}
|
|
} else {
|
|
if (evtime < start) {
|
|
++item;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
uint8_t* addr;
|
|
|
|
if (item->bytes[0]) {
|
|
|
|
/* more than 3 bytes ... indirect */
|
|
|
|
uint32_t offset = item->offset & ~(1<<(CHAR_BIT-1));
|
|
Blob* blob = reinterpret_cast<Blob*> (&_pool[offset]);
|
|
|
|
addr = blob->data;
|
|
|
|
} else {
|
|
|
|
addr = &item->bytes[1];
|
|
|
|
}
|
|
|
|
mst.track (addr);
|
|
|
|
if (reverse) {
|
|
--item;
|
|
} else {
|
|
++item;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
uint32_t
|
|
RTMidiBufferBase<TimeType,DistanceType>::read (MidiBuffer& dst, TimeType start, TimeType end, MidiNoteTracker& tracker, DistanceType offset)
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock, Glib::Threads::TRY_LOCK);
|
|
|
|
if (!lm.locked()) {
|
|
return 0;
|
|
}
|
|
|
|
bool reverse;
|
|
Item foo;
|
|
Item* iend;
|
|
Item* item;
|
|
foo.timestamp = start;
|
|
uint32_t count = 0;
|
|
|
|
if (start < end) {
|
|
iend = _data+_size;
|
|
item = lower_bound (_data, iend, foo, [](Item const & a, Item const & b) { return a.timestamp < b.timestamp; });
|
|
reverse = false;
|
|
} else {
|
|
iend = _data;
|
|
--iend; /* yes, this is technically "illegal" but we will never indirect */
|
|
Item* uend = _data + _size;
|
|
item = upper_bound (_data, uend, foo, [](Item const & a, Item const & b) { return a.timestamp < b.timestamp; });
|
|
|
|
if (item == uend) {
|
|
--item;
|
|
}
|
|
|
|
reverse = true;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
TimeType unadjusted_time;
|
|
Item* last = &_data[_size-1];
|
|
#endif
|
|
|
|
DEBUG_TRACE (DEBUG::MidiRingBuffer, string_compose ("read from %1 .. %2 .. initial index = %3 (time = %4) (range in list of %7 %5..%6)\n", start, end, item - _data, item->timestamp, _data->timestamp, last->timestamp, _size));
|
|
// dump (999);
|
|
|
|
while ((item != iend) && ((reverse && (item->timestamp > end)) || (!reverse && (item->timestamp < end)))) {
|
|
|
|
TimeType evtime = item->timestamp;
|
|
|
|
#ifndef NDEBUG
|
|
unadjusted_time = evtime;
|
|
#endif
|
|
/* Adjust event times to be relative to 'start', taking
|
|
* 'offset' into account.
|
|
*/
|
|
|
|
if (reverse) {
|
|
if (evtime > start) {
|
|
--item;
|
|
continue;
|
|
}
|
|
evtime = start - evtime;
|
|
} else {
|
|
if (evtime < start) {
|
|
++item;
|
|
continue;
|
|
}
|
|
evtime -= start;
|
|
}
|
|
|
|
evtime += offset;
|
|
|
|
uint32_t size;
|
|
uint8_t* addr;
|
|
|
|
if (item->bytes[0]) {
|
|
|
|
/* more than 3 bytes ... indirect */
|
|
|
|
uint32_t offset = item->offset & ~(1<<(CHAR_BIT-1));
|
|
Blob* blob = reinterpret_cast<Blob*> (&_pool[offset]);
|
|
|
|
size = blob->size;
|
|
addr = blob->data;
|
|
|
|
} else {
|
|
|
|
size = Evoral::midi_event_size (item->bytes[1]);
|
|
addr = &item->bytes[1];
|
|
|
|
}
|
|
|
|
const samplepos_t evsamples (timepos_t (evtime).samples());
|
|
|
|
if (!dst.push_back (evsamples, Evoral::MIDI_EVENT, size, addr)) {
|
|
DEBUG_TRACE (DEBUG::MidiRingBuffer, string_compose ("MidiRingBuffer: overflow in destination MIDI buffer, stopped after %1 events, dst size = %2\n", count, dst.size()));
|
|
break;
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::MidiRingBuffer, string_compose ("read event sz %1 @ %2 (=> %3 via -%4 +%5\n", size, unadjusted_time, evtime, start, offset));
|
|
|
|
#if 0
|
|
cerr << "\tevent @ " << unadjusted_time << " evtime " << evtime << " off " << offset << " sz=" << size << '\t';
|
|
cerr << "\t0x" << hex << (int)addr[0] << dec << ' ';
|
|
for (size_t j = 1 ; j < size; ++j) {
|
|
cerr << "0x" << hex << (int)addr[j] << dec << '/' << (int)addr[j] << ' ';
|
|
}
|
|
cerr << '\n';
|
|
#endif
|
|
|
|
tracker.track (addr);
|
|
|
|
if (reverse) {
|
|
--item;
|
|
} else {
|
|
++item;
|
|
}
|
|
++count;
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::MidiRingBuffer, string_compose ("total events found for %1 .. %2 = %3\n", start, end, count));
|
|
return count;
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
uint32_t
|
|
RTMidiBufferBase<TimeType,DistanceType>::alloc_blob (uint32_t size)
|
|
{
|
|
if (_pool_size + size > _pool_capacity) {
|
|
uint8_t* old_pool = _pool;
|
|
|
|
_pool_capacity += size * 4;
|
|
|
|
cache_aligned_malloc ((void **) &_pool, (_pool_capacity * sizeof (Blob)));
|
|
memcpy (_pool, old_pool, _pool_size * sizeof (Blob));
|
|
cache_aligned_free (old_pool);
|
|
}
|
|
|
|
uint32_t offset = _pool_size;
|
|
#if defined(__arm__) || defined(__aarch64__)
|
|
_pool_size += ((size - 1) | 3) + 1;
|
|
#else
|
|
_pool_size += size;
|
|
#endif
|
|
|
|
return offset;
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
uint32_t
|
|
RTMidiBufferBase<TimeType,DistanceType>::store_blob (uint32_t size, uint8_t const * data)
|
|
{
|
|
uint32_t offset = alloc_blob (size);
|
|
uint8_t* addr = &_pool[offset];
|
|
|
|
*(reinterpret_cast<uint32_t*> (addr)) = size;
|
|
addr += sizeof (size);
|
|
memcpy (addr, data, size);
|
|
|
|
return offset;
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
void
|
|
RTMidiBufferBase<TimeType,DistanceType>::clear ()
|
|
{
|
|
/* mark main array as empty */
|
|
_size = 0;
|
|
/* free the entire current pool size, if any */
|
|
_pool_size = 0;
|
|
/* rendering new data .. it will not be reversed */
|
|
_reversed = false;
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
DistanceType
|
|
RTMidiBufferBase<TimeType,DistanceType>::span() const
|
|
{
|
|
if (_size == 0 || _size == 1) {
|
|
return DistanceType ();
|
|
}
|
|
|
|
const Item* last = &_data[_size-1];
|
|
const Item* first = &_data[0];
|
|
|
|
return last->timestamp - first->timestamp;
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
void
|
|
RTMidiBufferBase<TimeType,DistanceType>::track_state (TimeType when, MidiStateTracker& mst) const
|
|
{
|
|
uint32_t evsize;
|
|
|
|
for (uint32_t index = 0; index < _size; ++index) {
|
|
if (_data[index].timestamp >= when) {
|
|
break;
|
|
}
|
|
mst.track (bytes (_data[index], evsize));
|
|
}
|
|
}
|
|
|
|
template<class TimeType, class DistanceType>
|
|
void
|
|
RTMidiBufferBase<TimeType,DistanceType>::convert (RTMidiBufferBase<Temporal::Beats,Temporal::Beats>& beats)
|
|
{
|
|
/* Convert timestamps, taking advantage of the fact that beats and
|
|
* samples are both 64 bit integers, and thus Item::timestamp is the
|
|
* same size and type for both.
|
|
*/
|
|
|
|
for (uint32_t n = 0; n < _size; ++n) {
|
|
auto item = &_data[n];
|
|
Temporal::Beats b = timepos_t (item->timestamp).beats ();
|
|
item->timestamp = b.to_ticks ();
|
|
}
|
|
|
|
/* Hand over all the data */
|
|
|
|
beats._data = reinterpret_cast<RTMidiBufferBase<Temporal::Beats,Temporal::Beats>::Item*> (_data);
|
|
beats._size = _size;
|
|
beats._capacity = _capacity;
|
|
beats._reversed = _reversed;
|
|
beats._pool = _pool;
|
|
beats._pool_size = _pool_size;
|
|
beats._pool_size = _pool_size;
|
|
|
|
_data = nullptr;
|
|
_pool = nullptr;
|
|
_size = 0;
|
|
_pool_size = 0;
|
|
_pool_capacity = 0;
|
|
}
|
|
|
|
|
|
|
|
// Explicit instantiation
|
|
namespace ARDOUR {
|
|
template class RTMidiBufferBase<samplepos_t,samplecnt_t>;
|
|
template class RTMidiBufferBase<Temporal::Beats,Temporal::Beats>;
|
|
}
|