13
0
livetrax/libs/ardour/midi_buffer.cc

604 lines
15 KiB
C++

/*
Copyright (C) 2006-2007 Paul Davis
Author: David Robillard
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 <iostream>
#include "pbd/malign.h"
#include "pbd/compose.h"
#include "pbd/debug.h"
#include "pbd/stacktrace.h"
#include "ardour/debug.h"
#include "ardour/midi_buffer.h"
#include "ardour/port.h"
using namespace std;
using namespace ARDOUR;
using namespace PBD;
// FIXME: mirroring for MIDI buffers?
MidiBuffer::MidiBuffer(size_t capacity)
: Buffer (DataType::MIDI)
, _data (0)
, _size (0)
{
if (capacity) {
resize (capacity);
silence (capacity);
}
}
MidiBuffer::~MidiBuffer()
{
cache_aligned_free(_data);
}
void
MidiBuffer::resize(size_t size)
{
if (_data && size < _capacity) {
if (_size < size) {
/* truncate */
_size = size;
}
return;
}
cache_aligned_free (_data);
cache_aligned_malloc ((void**) &_data, size);
_size = 0;
_capacity = size;
assert(_data);
}
void
MidiBuffer::copy(const MidiBuffer& copy)
{
assert(_capacity >= copy._size);
_size = copy._size;
memcpy(_data, copy._data, copy._size);
}
void
MidiBuffer::copy(MidiBuffer const * const copy)
{
assert(_capacity >= copy->size ());
_size = copy->size ();
memcpy(_data, copy->data(), _size);
}
/** Read events from @a src starting at time @a offset into the START of this buffer, for
* time duration @a nframes. Relative time, where 0 = start of buffer.
*
* Note that offset and nframes refer to sample time, NOT buffer offsets or event counts.
*/
void
MidiBuffer::read_from (const Buffer& src, framecnt_t nframes, frameoffset_t dst_offset, frameoffset_t /* src_offset*/)
{
assert (src.type() == DataType::MIDI);
assert (&src != this);
const MidiBuffer& msrc = (const MidiBuffer&) src;
assert (_capacity >= msrc.size());
if (dst_offset == 0) {
clear ();
assert (_size == 0);
}
for (MidiBuffer::const_iterator i = msrc.begin(); i != msrc.end(); ++i) {
const Evoral::MIDIEvent<TimeType> ev(*i, false);
if (dst_offset >= 0) {
/* Positive offset: shifting events from internal
buffer view of time (always relative to to start of
current possibly split cycle) to from global/port
view of time (always relative to start of process
cycle).
Check it is within range of this (split) cycle, then shift.
*/
if (ev.time() >= 0 && ev.time() < nframes) {
push_back (ev.time() + dst_offset, ev.size(), ev.buffer());
} else {
cerr << "\t!!!! MIDI event @ " << ev.time() << " skipped, not within range 0 .. " << nframes << ": ";
}
} else {
/* Negative offset: shifting events from global/port
view of time (always relative to start of process
cycle) back to internal buffer view of time (always
relative to to start of current possibly split
cycle.
Shift first, then check it is within range of this
(split) cycle.
*/
const framepos_t evtime = ev.time() + dst_offset;
if (evtime >= 0 && evtime < nframes) {
push_back (evtime, ev.size(), ev.buffer());
} else {
cerr << "\t!!!! MIDI event @ " << evtime << " (based on " << ev.time() << " + " << dst_offset << ") skipped, not within range 0 .. " << nframes << ": ";
}
}
}
_silent = src.silent();
}
void
MidiBuffer::merge_from (const Buffer& src, framecnt_t /*nframes*/, frameoffset_t /*dst_offset*/, frameoffset_t /*src_offset*/)
{
const MidiBuffer* mbuf = dynamic_cast<const MidiBuffer*>(&src);
assert (mbuf);
assert (mbuf != this);
/* XXX use nframes, and possible offsets */
merge_in_place (*mbuf);
}
/** Push an event into the buffer.
*
* Note that the raw MIDI pointed to by ev will be COPIED and unmodified.
* That is, the caller still owns it, if it needs freeing it's Not My Problem(TM).
* Realtime safe.
* @return false if operation failed (not enough room)
*/
bool
MidiBuffer::push_back(const Evoral::MIDIEvent<TimeType>& ev)
{
return push_back (ev.time(), ev.size(), ev.buffer());
}
/** Push MIDI data into the buffer.
*
* Note that the raw MIDI pointed to by @param data will be COPIED and unmodified.
* That is, the caller still owns it, if it needs freeing it's Not My Problem(TM).
* Realtime safe.
* @return false if operation failed (not enough room)
*/
bool
MidiBuffer::push_back(TimeType time, size_t size, const uint8_t* data)
{
const size_t stamp_size = sizeof(TimeType);
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::MidiIO)) {
DEBUG_STR_DECL(a);
DEBUG_STR_APPEND(a, string_compose ("midibuffer %1 push event @ %2 sz %3 ", this, time, size));
for (size_t i=0; i < size; ++i) {
DEBUG_STR_APPEND(a,hex);
DEBUG_STR_APPEND(a,"0x");
DEBUG_STR_APPEND(a,(int)data[i]);
DEBUG_STR_APPEND(a,' ');
}
DEBUG_STR_APPEND(a,'\n');
DEBUG_TRACE (DEBUG::MidiIO, DEBUG_STR(a).str());
}
#endif
if (_size + stamp_size + size >= _capacity) {
return false;
}
if (!Evoral::midi_event_is_valid(data, size)) {
return false;
}
uint8_t* const write_loc = _data + _size;
*(reinterpret_cast<TimeType*>((uintptr_t)write_loc)) = time;
memcpy(write_loc + stamp_size, data, size);
_size += stamp_size + size;
_silent = false;
return true;
}
bool
MidiBuffer::insert_event(const Evoral::MIDIEvent<TimeType>& ev)
{
if (size() == 0) {
return push_back(ev);
}
const size_t stamp_size = sizeof(TimeType);
const size_t bytes_to_merge = stamp_size + ev.size();
if (_size + bytes_to_merge >= _capacity) {
cerr << "MidiBuffer::push_back failed (buffer is full)" << endl;
PBD::stacktrace (cerr, 20);
return false;
}
TimeType t = ev.time();
ssize_t insert_offset = -1;
for (MidiBuffer::iterator m = begin(); m != end(); ++m) {
if ((*m).time() < t) {
continue;
}
if ((*m).time() == t) {
const uint8_t our_midi_status_byte = *(_data + m.offset + sizeof (TimeType));
if (second_simultaneous_midi_byte_is_first (ev.type(), our_midi_status_byte)) {
continue;
}
}
insert_offset = m.offset;
break;
}
if (insert_offset == -1) {
return push_back(ev);
}
// don't use memmove - it may use malloc(!)
// memmove (_data + insert_offset + bytes_to_merge, _data + insert_offset, _size - insert_offset);
for (ssize_t a = _size + bytes_to_merge - 1, b = _size - 1; b >= insert_offset; --b, --a) {
_data[a] = _data[b];
}
uint8_t* const write_loc = _data + insert_offset;
*(reinterpret_cast<TimeType*>((uintptr_t)write_loc)) = t;
memcpy(write_loc + stamp_size, ev.buffer(), ev.size());
_size += bytes_to_merge;
return true;
}
uint32_t
MidiBuffer::write(TimeType time, Evoral::EventType type, uint32_t size, const uint8_t* buf)
{
insert_event(Evoral::MIDIEvent<TimeType>(type, time, size, const_cast<uint8_t*>(buf)));
return size;
}
/** Reserve space for a new event in the buffer.
*
* This call is for copying MIDI directly into the buffer, the data location
* (of sufficient size to write \a size bytes) is returned, or 0 on failure.
* This call MUST be immediately followed by a write to the returned data
* location, or the buffer will be corrupted and very nasty things will happen.
*/
uint8_t*
MidiBuffer::reserve(TimeType time, size_t size)
{
const size_t stamp_size = sizeof(TimeType);
if (_size + stamp_size + size >= _capacity) {
return 0;
}
// write timestamp
uint8_t* write_loc = _data + _size;
*(reinterpret_cast<TimeType*>((uintptr_t)write_loc)) = time;
// move write_loc to begin of MIDI buffer data to write to
write_loc += stamp_size;
_size += stamp_size + size;
_silent = false;
return write_loc;
}
void
MidiBuffer::silence (framecnt_t /*nframes*/, framecnt_t /*offset*/)
{
/* XXX iterate over existing events, find all in range given by offset & nframes,
and delete them.
*/
_size = 0;
_silent = true;
}
bool
MidiBuffer::second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b)
{
bool b_first = false;
/* two events at identical times. we need to determine
the order in which they should occur.
the rule is:
Controller messages
Program Change
Note Off
Note On
Note Pressure
Channel Pressure
Pitch Bend
*/
if ((a) >= 0xf0 || (b) >= 0xf0 || ((a & 0xf) != (b & 0xf))) {
/* if either message is not a channel message, or if the channels are
* different, we don't care about the type.
*/
b_first = true;
} else {
switch (b & 0xf0) {
case MIDI_CMD_CONTROL:
b_first = true;
break;
case MIDI_CMD_PGM_CHANGE:
switch (a & 0xf0) {
case MIDI_CMD_CONTROL:
break;
case MIDI_CMD_PGM_CHANGE:
case MIDI_CMD_NOTE_OFF:
case MIDI_CMD_NOTE_ON:
case MIDI_CMD_NOTE_PRESSURE:
case MIDI_CMD_CHANNEL_PRESSURE:
case MIDI_CMD_BENDER:
b_first = true;
}
break;
case MIDI_CMD_NOTE_OFF:
switch (a & 0xf0) {
case MIDI_CMD_CONTROL:
case MIDI_CMD_PGM_CHANGE:
break;
case MIDI_CMD_NOTE_OFF:
case MIDI_CMD_NOTE_ON:
case MIDI_CMD_NOTE_PRESSURE:
case MIDI_CMD_CHANNEL_PRESSURE:
case MIDI_CMD_BENDER:
b_first = true;
}
break;
case MIDI_CMD_NOTE_ON:
switch (a & 0xf0) {
case MIDI_CMD_CONTROL:
case MIDI_CMD_PGM_CHANGE:
case MIDI_CMD_NOTE_OFF:
break;
case MIDI_CMD_NOTE_ON:
case MIDI_CMD_NOTE_PRESSURE:
case MIDI_CMD_CHANNEL_PRESSURE:
case MIDI_CMD_BENDER:
b_first = true;
}
break;
case MIDI_CMD_NOTE_PRESSURE:
switch (a & 0xf0) {
case MIDI_CMD_CONTROL:
case MIDI_CMD_PGM_CHANGE:
case MIDI_CMD_NOTE_OFF:
case MIDI_CMD_NOTE_ON:
break;
case MIDI_CMD_NOTE_PRESSURE:
case MIDI_CMD_CHANNEL_PRESSURE:
case MIDI_CMD_BENDER:
b_first = true;
}
break;
case MIDI_CMD_CHANNEL_PRESSURE:
switch (a & 0xf0) {
case MIDI_CMD_CONTROL:
case MIDI_CMD_PGM_CHANGE:
case MIDI_CMD_NOTE_OFF:
case MIDI_CMD_NOTE_ON:
case MIDI_CMD_NOTE_PRESSURE:
break;
case MIDI_CMD_CHANNEL_PRESSURE:
case MIDI_CMD_BENDER:
b_first = true;
}
break;
case MIDI_CMD_BENDER:
switch (a & 0xf0) {
case MIDI_CMD_CONTROL:
case MIDI_CMD_PGM_CHANGE:
case MIDI_CMD_NOTE_OFF:
case MIDI_CMD_NOTE_ON:
case MIDI_CMD_NOTE_PRESSURE:
case MIDI_CMD_CHANNEL_PRESSURE:
break;
case MIDI_CMD_BENDER:
b_first = true;
}
break;
}
}
return b_first;
}
/** Merge \a other into this buffer. Realtime safe. */
bool
MidiBuffer::merge_in_place (const MidiBuffer &other)
{
if (other.size() && size()) {
DEBUG_TRACE (DEBUG::MidiIO, string_compose ("merge in place, sizes %1/%2\n", size(), other.size()));
}
if (other.size() == 0) {
return true;
}
if (size() == 0) {
copy (other);
return true;
}
if (size() + other.size() > _capacity) {
return false;
}
const_iterator them = other.begin();
iterator us = begin();
while (them != other.end()) {
size_t bytes_to_merge;
ssize_t merge_offset;
/* gather up total size of events that are earlier than
the event referenced by "us"
*/
merge_offset = -1;
bytes_to_merge = 0;
while (them != other.end() && (*them).time() < (*us).time()) {
if (merge_offset == -1) {
merge_offset = them.offset;
}
bytes_to_merge += sizeof (TimeType) + (*them).size();
++them;
}
/* "them" now points to either:
*
* 1) an event that has the same or later timestamp than the
* event pointed to by "us"
*
* OR
*
* 2) the end of the "other" buffer
*
* if "sz" is non-zero, there is data to be merged from "other"
* into this buffer before we do anything else, corresponding
* to the events from "other" that we skipped while advancing
* "them".
*/
if (bytes_to_merge) {
assert(merge_offset >= 0);
/* move existing */
memmove (_data + us.offset + bytes_to_merge, _data + us.offset, _size - us.offset);
/* increase _size */
_size += bytes_to_merge;
assert (_size <= _capacity);
/* insert new stuff */
memcpy (_data + us.offset, other._data + merge_offset, bytes_to_merge);
/* update iterator to our own events. this is a miserable hack */
us.offset += bytes_to_merge;
}
/* if we're at the end of the other buffer, we're done */
if (them == other.end()) {
break;
}
/* if we have two messages messages with the same timestamp. we
* must order them correctly.
*/
if ((*us).time() == (*them).time()) {
DEBUG_TRACE (DEBUG::MidiIO,
string_compose ("simultaneous MIDI events discovered during merge, times %1/%2 status %3/%4\n",
(*us).time(), (*them).time(),
(int) *(_data + us.offset + sizeof (TimeType)),
(int) *(other._data + them.offset + sizeof (TimeType))));
uint8_t our_midi_status_byte = *(_data + us.offset + sizeof (TimeType));
uint8_t their_midi_status_byte = *(other._data + them.offset + sizeof (TimeType));
bool them_first = second_simultaneous_midi_byte_is_first (our_midi_status_byte, their_midi_status_byte);
DEBUG_TRACE (DEBUG::MidiIO, string_compose ("other message came first ? %1\n", them_first));
if (!them_first) {
/* skip past our own event */
++us;
}
bytes_to_merge = sizeof (TimeType) + (*them).size();
/* move our remaining events later in the buffer by
* enough to fit the one message we're going to merge
*/
memmove (_data + us.offset + bytes_to_merge, _data + us.offset, _size - us.offset);
/* increase _size */
_size += bytes_to_merge;
assert(_size <= _capacity);
/* insert new stuff */
memcpy (_data + us.offset, other._data + them.offset, bytes_to_merge);
/* update iterator to our own events. this is a miserable hack */
us.offset += bytes_to_merge;
/* 'us' is now an iterator to the event right after the
new ones that we merged
*/
if (them_first) {
/* need to skip the event pointed to by 'us'
since its at the same time as 'them'
(still), and we'll enter
*/
if (us != end()) {
++us;
}
}
/* we merged one event from the other buffer, so
* advance the iterator there.
*/
++them;
} else {
/* advance past our own events to get to the correct insertion
point for the next event(s) from "other"
*/
while (us != end() && (*us).time() <= (*them).time()) {
++us;
}
}
/* check to see if we reached the end of this buffer while
* looking for the insertion point.
*/
if (us == end()) {
/* just append the rest of other and we're done*/
memcpy (_data + us.offset, other._data + them.offset, other._size - them.offset);
_size += other._size - them.offset;
assert(_size <= _capacity);
break;
}
}
return true;
}