X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fmidi_buffer.cc;h=516d5a98c771b86686dd6335d3f67aa1260bf6b6;hb=77bd398153240390362fea83533627e6d767ddb3;hp=403f0a86ac44b81c6ed9d05d3f5044093756a52d;hpb=e0cf3b6354e2c92a6adabe85cb6d6d3c547c287c;p=ardour.git diff --git a/libs/ardour/midi_buffer.cc b/libs/ardour/midi_buffer.cc index 403f0a86ac..516d5a98c7 100644 --- a/libs/ardour/midi_buffer.cc +++ b/libs/ardour/midi_buffer.cc @@ -22,9 +22,11 @@ #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; @@ -32,34 +34,40 @@ using namespace PBD; // FIXME: mirroring for MIDI buffers? MidiBuffer::MidiBuffer(size_t capacity) - : Buffer(DataType::MIDI, capacity) - , _data(0) + : Buffer (DataType::MIDI) + , _data (0) + , _size (0) { if (capacity) { - resize(_capacity); - silence(_capacity); + resize (capacity); + silence (capacity); } } MidiBuffer::~MidiBuffer() { - free(_data); + cache_aligned_free(_data); } void MidiBuffer::resize(size_t size) { - assert(size > 0); + if (_data && size < _capacity) { + + if (_size < size) { + /* truncate */ + _size = size; + } - if (size < _capacity) { return; } - free(_data); + cache_aligned_free (_data); + + cache_aligned_malloc ((void**) &_data, size); _size = 0; _capacity = size; - cache_aligned_malloc ((void**) &_data, _capacity); assert(_data); } @@ -72,6 +80,14 @@ MidiBuffer::copy(const MidiBuffer& copy) 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. @@ -79,12 +95,12 @@ 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, framecnt_t nframes, framecnt_t dst_offset, framecnt_t src_offset) +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 = (MidiBuffer&) src; + const MidiBuffer& msrc = (const MidiBuffer&) src; assert (_capacity >= msrc.size()); @@ -93,15 +109,40 @@ MidiBuffer::read_from (const Buffer& src, framecnt_t nframes, framecnt_t dst_off assert (_size == 0); } - /* XXX use dst_offset somehow */ - for (MidiBuffer::const_iterator i = msrc.begin(); i != msrc.end(); ++i) { const Evoral::MIDIEvent ev(*i, false); - if (ev.time() >= src_offset && ev.time() < (nframes+src_offset)) { - push_back (ev); + + 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 { - cerr << "MIDI event @ " << ev.time() << " skipped, not within range " - << src_offset << " .. " << (nframes + src_offset) << endl; + /* 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 << ": "; + } } } @@ -109,7 +150,7 @@ MidiBuffer::read_from (const Buffer& src, framecnt_t nframes, framecnt_t dst_off } void -MidiBuffer::merge_from (const Buffer& src, framecnt_t /*nframes*/, framecnt_t /*dst_offset*/, framecnt_t /*src_offset*/) +MidiBuffer::merge_from (const Buffer& src, framecnt_t /*nframes*/, frameoffset_t /*dst_offset*/, frameoffset_t /*src_offset*/) { const MidiBuffer* mbuf = dynamic_cast(&src); assert (mbuf); @@ -129,27 +170,15 @@ MidiBuffer::merge_from (const Buffer& src, framecnt_t /*nframes*/, framecnt_t /* bool MidiBuffer::push_back(const Evoral::MIDIEvent& ev) { - const size_t stamp_size = sizeof(TimeType); - /*cerr << "MidiBuffer: pushing event @ " << ev.time() - << " size = " << ev.size() << endl;*/ - - if (_size + stamp_size + ev.size() >= _capacity) { - cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; - return false; - } - - if (!Evoral::midi_event_is_valid(ev.buffer(), ev.size())) { - cerr << "WARNING: MidiBuffer ignoring illegal MIDI event" << endl; - return false; - } - - push_back(ev.time(), ev.size(), ev.buffer()); - - return true; + return push_back (ev.time(), ev.size(), ev.buffer()); } -/** Push an event into the 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 @@ -158,7 +187,7 @@ MidiBuffer::push_back(TimeType time, size_t size, const uint8_t* data) const size_t stamp_size = sizeof(TimeType); #ifndef NDEBUG - if (DEBUG::MidiIO & PBD::debug_bits) { + 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) { @@ -173,17 +202,15 @@ MidiBuffer::push_back(TimeType time, size_t size, const uint8_t* data) #endif if (_size + stamp_size + size >= _capacity) { - cerr << "MidiBuffer::push_back failed (buffer is full)" << endl; return false; } if (!Evoral::midi_event_is_valid(data, size)) { - cerr << "WARNING: MidiBuffer ignoring illegal MIDI event" << endl; return false; } uint8_t* const write_loc = _data + _size; - *((TimeType*)write_loc) = time; + *(reinterpret_cast((uintptr_t)write_loc)) = time; memcpy(write_loc + stamp_size, data, size); _size += stamp_size + size; @@ -192,38 +219,63 @@ MidiBuffer::push_back(TimeType time, size_t size, const uint8_t* data) 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() == 0) { + return push_back(ev); + } + const size_t stamp_size = sizeof(TimeType); - if (_size + stamp_size + ev.size >= _capacity) { + 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; } - if (!Evoral::midi_event_is_valid(ev.buffer, ev.size)) { - cerr << "WARNING: MidiBuffer ignoring illegal MIDI event" << endl; - 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); } - uint8_t* const write_loc = _data + _size; - *((TimeType*)write_loc) = ev.time; - memcpy(write_loc + stamp_size, ev.buffer, ev.size); + // 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]; + } - _size += stamp_size + ev.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. * @@ -242,7 +294,7 @@ MidiBuffer::reserve(TimeType time, size_t size) // write timestamp uint8_t* write_loc = _data + _size; - *((TimeType*)write_loc) = time; + *(reinterpret_cast((uintptr_t)write_loc)) = time; // move write_loc to begin of MIDI buffer data to write to write_loc += stamp_size; @@ -272,9 +324,9 @@ MidiBuffer::second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b) /* 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 @@ -283,22 +335,22 @@ MidiBuffer::second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b) 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: @@ -311,7 +363,8 @@ MidiBuffer::second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b) case MIDI_CMD_BENDER: b_first = true; } - + break; + case MIDI_CMD_NOTE_OFF: switch (a & 0xf0) { case MIDI_CMD_CONTROL: @@ -325,7 +378,7 @@ MidiBuffer::second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b) b_first = true; } break; - + case MIDI_CMD_NOTE_ON: switch (a & 0xf0) { case MIDI_CMD_CONTROL: @@ -352,7 +405,7 @@ MidiBuffer::second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b) b_first = true; } break; - + case MIDI_CMD_CHANNEL_PRESSURE: switch (a & 0xf0) { case MIDI_CMD_CONTROL: @@ -381,15 +434,15 @@ MidiBuffer::second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b) break; } } - + return b_first; } - + /** Merge \a other into this buffer. Realtime safe. */ bool -MidiBuffer::merge_in_place(const MidiBuffer &other) +MidiBuffer::merge_in_place (const MidiBuffer &other) { - if (other.size() || size()) { + if (other.size() && size()) { DEBUG_TRACE (DEBUG::MidiIO, string_compose ("merge in place, sizes %1/%2\n", size(), other.size())); } @@ -397,146 +450,116 @@ MidiBuffer::merge_in_place(const MidiBuffer &other) return true; } - if (_size == 0) { - copy(other); + if (size() == 0) { + copy (other); return true; } - if (_size + other.size() > _capacity) { - cerr << "MidiBuffer::merge failed (no space)" << endl; + if (size() + other.size() > _capacity) { return false; } -#ifndef NDEBUG -#ifdef TEST_MIDI_MERGE - size_t test_orig_us_size = _size; - size_t test_orig_them_size = other._size; - TimeType test_time = 0; - size_t test_us_count = 0; - size_t test_them_count = 0; - for (iterator i = begin(); i != end(); ++i) { - assert(Evoral::midi_event_is_valid((*i).buffer(), (*i).size())); - assert((*i).time() >= test_time); - test_time = (*i).time(); - ++test_us_count; - } - test_time = 0; - for (const_iterator i = other.begin(); i != other.end(); ++i) { - assert(Evoral::midi_event_is_valid((*i).buffer(), (*i).size())); - assert((*i).time() >= test_time); - test_time = (*i).time(); - ++test_them_count; - } -#endif -#endif - const_iterator them = other.begin(); iterator us = begin(); while (them != other.end()) { - size_t sz; - ssize_t src; + size_t bytes_to_merge; + ssize_t merge_offset; /* gather up total size of events that are earlier than the event referenced by "us" */ - src = -1; - sz = 0; + merge_offset = -1; + bytes_to_merge = 0; while (them != other.end() && (*them).time() < (*us).time()) { - if (src == -1) { - src = them.offset; + if (merge_offset == -1) { + merge_offset = them.offset; } - sz += sizeof (TimeType) + (*them).size(); + bytes_to_merge += sizeof (TimeType) + (*them).size(); ++them; } - if (sz) { - assert(src >= 0); + /* "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 + sz, _data + us.offset, _size - us.offset); + memmove (_data + us.offset + bytes_to_merge, _data + us.offset, _size - us.offset); /* increase _size */ - _size += sz; - assert(_size <= _capacity); + _size += bytes_to_merge; + assert (_size <= _capacity); /* insert new stuff */ - memcpy (_data + us.offset, other._data + src, sz); + memcpy (_data + us.offset, other._data + merge_offset, bytes_to_merge); /* update iterator to our own events. this is a miserable hack */ - us.offset += sz; - } 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; - } + us.offset += bytes_to_merge; } - if (us == end()) { + /* if we're at the end of the other buffer, we're done */ - /* just append the rest of other */ - - memcpy (_data + us.offset, other._data + them.offset, other._size - them.offset); - _size += other._size - them.offset; - assert(_size <= _capacity); + if (them == other.end()) { break; + } - } else if (them != other.end()) { + /* if we have two messages messages with the same timestamp. we + * must order them correctly. + */ - /* to get here implies that we've encountered two - * messages with the same timestamp. we must order - * them correctly. - */ + if ((*us).time() == (*them).time()) { - DEBUG_TRACE (DEBUG::MidiIO, + 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)))); - if ((*us).time() != (*them).time()) { - cerr << " in merge code for two events:\n" - << '\t' << (*us) << endl - << '\t' << (*them) << endl - << "about to crash ...\n"; - } - assert ((*us).time() == (*them).time()); - 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; } - - sz = sizeof (TimeType) + (*them).size(); - + + 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 + sz, _data + us.offset, _size - us.offset); + memmove (_data + us.offset + bytes_to_merge, _data + us.offset, _size - us.offset); /* increase _size */ - _size += sz; + _size += bytes_to_merge; assert(_size <= _capacity); /* insert new stuff */ - memcpy (_data + us.offset, other._data + them.offset, sz); + memcpy (_data + us.offset, other._data + them.offset, bytes_to_merge); /* update iterator to our own events. this is a miserable hack */ - us.offset += sz; + 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 + (still), and we'll enter */ if (us != end()) { @@ -549,71 +572,32 @@ MidiBuffer::merge_in_place(const MidiBuffer &other) */ ++them; - } - } - -#ifndef NDEBUG -#ifdef TEST_MIDI_MERGE - assert(_size == test_orig_us_size + test_orig_them_size); - size_t test_final_count = 0; - test_time = 0; - for (iterator i = begin(); i != end(); ++i) { - cerr << "CHECK " << test_final_count << " / " << test_us_count + test_them_count << endl; - assert(Evoral::midi_event_is_valid((*i).buffer(), (*i).size())); - assert((*i).time() >= test_time); - test_time = (*i).time(); - ++test_final_count; - } - assert(test_final_count = test_us_count + test_them_count); -#endif -#endif - return true; -} + } else { -/** Clear, and merge \a a and \a b into this buffer. - * - * \return true if complete merge was successful - */ -bool -MidiBuffer::merge(const MidiBuffer& a, const MidiBuffer& b) -{ - _size = 0; + /* advance past our own events to get to the correct insertion + point for the next event(s) from "other" + */ - if (this == &a) { - return merge_in_place(b); - } else if (this == &b) { - return merge_in_place(a); - } + while (us != end() && (*us).time() <= (*them).time()) { + ++us; + } + } - const_iterator ai = a.begin(); - const_iterator bi = b.begin(); + /* check to see if we reached the end of this buffer while + * looking for the insertion point. + */ - resize(a.size() + b.size()); - while (ai != a.end() && bi != b.end()) { - if ((*ai).time() < (*bi).time()) { - memcpy(_data + _size, (*ai).buffer(), (*ai).size()); - _size += (*ai).size(); - ++ai; - } else { - memcpy(_data + _size, (*bi).buffer(), (*bi).size()); - _size += (*bi).size(); - ++bi; - } - } + if (us == end()) { - while (ai != a.end()) { - memcpy(_data + _size, (*ai).buffer(), (*ai).size()); - _size += (*ai).size(); - ++ai; - } + /* just append the rest of other and we're done*/ - while (bi != b.end()) { - memcpy(_data + _size, (*bi).buffer(), (*bi).size()); - _size += (*bi).size(); - ++bi; + memcpy (_data + us.offset, other._data + them.offset, other._size - them.offset); + _size += other._size - them.offset; + assert(_size <= _capacity); + break; + } } return true; } -