X-Git-Url: https://main.carlh.net/gitweb/?p=ardour.git;a=blobdiff_plain;f=libs%2Fardour%2Fmidi_buffer.cc;h=41d638dc781fc84d221849bbefc5405344a3e1fc;hp=1f5b0c5fa9231663270f8941eb1ac33d12e95796;hb=c8c6bca6587450ff64303dbc994a4cd28d6ce7aa;hpb=3d239bb9d59f555d08fd9362e9ce50de1255c633 diff --git a/libs/ardour/midi_buffer.cc b/libs/ardour/midi_buffer.cc index 1f5b0c5fa9..41d638dc78 100644 --- a/libs/ardour/midi_buffer.cc +++ b/libs/ardour/midi_buffer.cc @@ -1,97 +1,91 @@ /* - Copyright (C) 2006-2007 Paul Davis - Author: Dave Robillard - + 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 -#include -#ifdef __x86_64__ -static const int CPU_CACHE_ALIGN = 64; -#else -static const int CPU_CACHE_ALIGN = 16; /* arguably 32 on most arches, but it matters less */ -#endif +#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, capacity) - , _events(0) - , _data(0) -// , _owns_data(false) + : Buffer (DataType::MIDI) + , _data (0) + , _size (0) { if (capacity) { - resize (_capacity); - silence(_capacity); + resize (capacity); + silence (capacity); } } - + MidiBuffer::~MidiBuffer() { - if (_events) { - free(_events); - } - if (_data) { - free(_data); - } + cache_aligned_free(_data); } void -MidiBuffer::resize (size_t size) +MidiBuffer::resize(size_t size) { - assert(size > 0); + if (_data && size < _capacity) { + + if (_size < size) { + /* truncate */ + _size = size; + } - if (size < _capacity) { return; } - if (_data) { - free (_data); - } + cache_aligned_free (_data); - if (_events) { - free (_events); - } + cache_aligned_malloc ((void**) &_data, size); _size = 0; _capacity = size; -#ifdef NO_POSIX_MEMALIGN - _events = (Evoral::MIDIEvent *) malloc(sizeof(Evoral::MIDIEvent) * _capacity); - _data = (uint8_t *) malloc(sizeof(uint8_t) * _capacity * MAX_EVENT_SIZE); -#else - posix_memalign((void**)&_events, CPU_CACHE_ALIGN, sizeof(Evoral::Event) * _capacity); - posix_memalign((void**)&_data, CPU_CACHE_ALIGN, sizeof(uint8_t) * _capacity * MAX_EVENT_SIZE); -#endif assert(_data); - assert(_events); } void MidiBuffer::copy(const MidiBuffer& copy) { - assert(_capacity >= copy._capacity); - _size = 0; + assert(_capacity >= copy._size); + _size = copy._size; + memcpy(_data, copy._data, copy._size); +} - for (size_t i = 0; i < copy.size(); ++i) - push_back(copy[i]); +void +MidiBuffer::copy(MidiBuffer const * const copy) +{ + assert(_capacity >= copy->size ()); + _size = copy->size (); + memcpy(_data, copy->data(), _size); } @@ -101,39 +95,49 @@ MidiBuffer::copy(const MidiBuffer& copy) * Note that offset and nframes refer to sample time, NOT buffer offsets or event counts. */ void -MidiBuffer::read_from(const Buffer& src, nframes_t nframes, nframes_t offset) +MidiBuffer::read_from (const Buffer& src, framecnt_t nframes, framecnt_t dst_offset, framecnt_t /* src_offset*/) { - assert(src.type() == DataType::MIDI); - assert(&src != this); + assert (src.type() == DataType::MIDI); + assert (&src != this); - const MidiBuffer& msrc = (MidiBuffer&)src; - - assert(_capacity >= msrc.size()); + const MidiBuffer& msrc = (const MidiBuffer&) src; - if (offset == 0) { - clear(); - assert(_size == 0); + assert (_capacity >= msrc.size()); + + if (dst_offset == 0) { + clear (); + assert (_size == 0); } - - // FIXME: slow - for (size_t i=0; i < msrc.size(); ++i) { - const Evoral::MIDIEvent& ev = msrc[i]; - //cout << "MidiBuffer::read_from event type: " << int(ev.type()) - // << " time: " << ev.time() << " buffer size: " << _size << endl; - if (ev.time() < offset) { - //cout << "MidiBuffer::read_from skipped event before " << offset << endl; - } else if (ev.time() < (nframes + offset)) { - //cout << "MidiBuffer::read_from appending event" << endl; - push_back(ev); + + framecnt_t offset = Port::port_offset(); + + for (MidiBuffer::const_iterator i = msrc.begin(); i != msrc.end(); ++i) { + const Evoral::MIDIEvent ev(*i, false); + if (ev.time() >= offset && ev.time() < (nframes + offset)) { + push_back (ev); } else { - //cerr << "MidiBuffer::read_from skipped event after " - // << nframes << " + " << offset << endl; + cerr << "MIDI event @ " << ev.time() << " skipped, not within range " + << offset << " .. " << (nframes + offset) << ":"; + for (size_t xx = 0; xx < ev.size(); ++xx) { + cerr << ' ' << hex << (int) ev.buffer()[xx]; + } + cerr << dec << endl; } } _silent = src.silent(); } +void +MidiBuffer::merge_from (const Buffer& src, framecnt_t /*nframes*/, framecnt_t /*dst_offset*/, framecnt_t /*src_offset*/) +{ + const MidiBuffer* mbuf = dynamic_cast(&src); + assert (mbuf); + assert (mbuf != this); + + /* XXX use nframes, and possible offsets */ + merge_in_place (*mbuf); +} /** Push an event into the buffer. * @@ -143,67 +147,114 @@ MidiBuffer::read_from(const Buffer& src, nframes_t nframes, nframes_t offset) * @return false if operation failed (not enough room) */ bool -MidiBuffer::push_back(const Evoral::MIDIEvent& ev) +MidiBuffer::push_back(const Evoral::MIDIEvent& ev) { - if (_size == _capacity) { - cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; - return false; - } + return push_back (ev.time(), ev.size(), ev.buffer()); +} - uint8_t* const write_loc = _data + (_size * MAX_EVENT_SIZE); - memcpy(write_loc, ev.buffer(), ev.size()); - _events[_size] = ev; - _events[_size].set_buffer(ev.size(), write_loc, false); +/** 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 - /*cerr << "MidiBuffer: pushed @ " << _events[_size].time() - << " size = " << _size << endl; - for (size_t i = 0; i < _events[_size].size(); ++i) { - printf("%X ", _events[_size].buffer()[i]); + if (_size + stamp_size + size >= _capacity) { + return false; } - printf("\n");*/ - ++_size; + if (!Evoral::midi_event_is_valid(data, size)) { + return false; + } + + uint8_t* const write_loc = _data + _size; + *(reinterpret_cast((uintptr_t)write_loc)) = time; + memcpy(write_loc + stamp_size, data, size); + + _size += stamp_size + size; _silent = false; return true; } - -/** 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 jack_midi_event_t& ev) +MidiBuffer::insert_event(const Evoral::MIDIEvent& ev) { - if (_size == _capacity) { + 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; } - uint8_t* const write_loc = _data + (_size * MAX_EVENT_SIZE); + TimeType t = ev.time(); - memcpy(write_loc, ev.buffer, ev.size); - _events[_size].time() = (double)ev.time; - _events[_size].set_buffer(ev.size, write_loc, false); + 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); + } - /*cerr << "MidiBuffer: pushed @ " << _events[_size].time() - << " size = " << _size << endl; - for (size_t i = 0; i < _events[_size].size(); ++i) { - printf("%X ", _events[_size].buffer()[i]); + // 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]; } - printf("\n");*/ - - ++_size; - _silent = false; + + uint8_t* const write_loc = _data + insert_offset; + *(reinterpret_cast((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(type, time, size, const_cast(buf))); + return size; +} /** Reserve space for a new event in the buffer. * @@ -213,24 +264,21 @@ MidiBuffer::push_back(const jack_midi_event_t& ev) * location, or the buffer will be corrupted and very nasty things will happen. */ uint8_t* -MidiBuffer::reserve(double time, size_t size) +MidiBuffer::reserve(TimeType time, size_t size) { - if (size > MAX_EVENT_SIZE) { - cerr << "WARNING: Failed to reserve " << size << " bytes for event"; + const size_t stamp_size = sizeof(TimeType); + if (_size + stamp_size + size >= _capacity) { return 0; } - if (_size == _capacity) - return 0; + // write timestamp + uint8_t* write_loc = _data + _size; + *(reinterpret_cast((uintptr_t)write_loc)) = time; - uint8_t* const write_loc = _data + (_size * MAX_EVENT_SIZE); - - _events[_size].time() = time; - _events[_size].set_buffer(size, write_loc, false); - ++_size; - - //cerr << "MidiBuffer: reserved, size = " << _size << endl; + // 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; @@ -238,100 +286,296 @@ MidiBuffer::reserve(double time, size_t size) void -MidiBuffer::silence(nframes_t dur, nframes_t offset) +MidiBuffer::silence (framecnt_t /*nframes*/, framecnt_t /*offset*/) { - // FIXME use parameters - if (offset != 0) - cerr << "WARNING: MidiBuffer::silence w/ offset != 0 (not implemented)" << endl; + /* XXX iterate over existing events, find all in range given by offset & nframes, + and delete them. + */ - memset(_events, 0, sizeof(Evoral::Event) * _capacity); - memset(_data, 0, sizeof(uint8_t) * _capacity * MAX_EVENT_SIZE); _size = 0; _silent = true; } bool -MidiBuffer::merge_in_place( const MidiBuffer &other ) +MidiBuffer::second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b) { - if( other.size() == 0 ) + 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( this->size() == 0 ) { - copy( other ); + if (size() == 0) { + copy (other); return true; } - { - MidiBuffer merge_buffer( 0 ); - Evoral::MIDIEvent onstack_events[_capacity]; - uint8_t onstack_data[_capacity * MAX_EVENT_SIZE]; - merge_buffer._events = onstack_events; - merge_buffer._data = onstack_data; - merge_buffer._size = 0; + if (size() + other.size() > _capacity) { + return false; + } - bool retval = merge_buffer.merge( *this, other ); + const_iterator them = other.begin(); + iterator us = begin(); - copy( merge_buffer ); + while (them != other.end()) { - // set pointers to zero again, so destructor - // does not end in calling free() for memory - // on the stack; - merge_buffer._events = 0; - merge_buffer._data = 0; + size_t bytes_to_merge; + ssize_t merge_offset; - return retval; - } -} + /* gather up total size of events that are earlier than + the event referenced by "us" + */ -/** Clear, and merge \a a and \a b into this buffer. - * - * FIXME: This is slow. - * - * \return true if complete merge was successful - */ -bool -MidiBuffer::merge(const MidiBuffer& a, const MidiBuffer& b) -{ - _size = 0; + merge_offset = -1; + bytes_to_merge = 0; - // This is mostly the case :( - if( this == &a ) - merge_in_place( b ); + while (them != other.end() && (*them).time() < (*us).time()) { + if (merge_offset == -1) { + merge_offset = them.offset; + } + bytes_to_merge += sizeof (TimeType) + (*them).size(); + ++them; + } - if( this == &b ) - merge_in_place( a ); + /* "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; + } - size_t a_index = 0; - size_t b_index = 0; - size_t count = a.size() + b.size(); + /* if we're at the end of the other buffer, we're done */ - while (count > 0) { - - if (size() == capacity()) { - cerr << "WARNING: MIDI buffer overrun, events lost!" << endl; - return false; + if (them == other.end()) { + break; } - - if (a_index == a.size()) { - push_back(b[b_index]); - ++b_index; - } else if (b_index == b.size()) { - push_back(a[a_index]); - ++a_index; + + /* 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 { - const Evoral::MIDIEvent& a_ev = a[a_index]; - const Evoral::MIDIEvent& b_ev = b[b_index]; - - if (a_ev.time() <= b_ev.time()) { - push_back(a_ev); - ++a_index; - } else { - push_back(b_ev); - ++b_index; + + /* 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; } } - --count; + /* 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;