From 19273e824d40534a4e31259fb8b83122b24aa4e9 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Fri, 6 Jul 2007 00:09:53 +0000 Subject: Midi CC automation sending (send points only, no linear interpolation yet). Split buffer.cc into buffer.cc audio_buffer.cc midi_buffer.cc. Renamed 'send_buffers' to 'mix_buffers'. This is the first revision of Ardour where clicking around and drawing things can send MIDI and thus generate wonderful world-changing music. Break out the champagne. git-svn-id: svn://localhost/ardour2/trunk@2115 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/SConscript | 2 + libs/ardour/ardour/automation_event.h | 16 ++- libs/ardour/ardour/midi_buffer.h | 5 +- libs/ardour/ardour/midi_track.h | 4 + libs/ardour/ardour/session.h | 4 +- libs/ardour/audio_buffer.cc | 56 ++++++++ libs/ardour/automation_event.cc | 117 ++++++++++++---- libs/ardour/buffer.cc | 187 ------------------------- libs/ardour/midi_buffer.cc | 255 ++++++++++++++++++++++++++++++++++ libs/ardour/midi_track.cc | 80 ++++++++++- libs/ardour/send.cc | 2 +- libs/ardour/session.cc | 22 +-- 12 files changed, 519 insertions(+), 231 deletions(-) create mode 100644 libs/ardour/audio_buffer.cc create mode 100644 libs/ardour/midi_buffer.cc (limited to 'libs') diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript index f93c19649f..5d56e198fb 100644 --- a/libs/ardour/SConscript +++ b/libs/ardour/SConscript @@ -46,6 +46,8 @@ audio_port.cc midi_port.cc port_set.cc buffer.cc +audio_buffer.cc +midi_buffer.cc buffer_set.cc meter.cc amp.cc diff --git a/libs/ardour/ardour/automation_event.h b/libs/ardour/ardour/automation_event.h index fb4aa52ec2..8a9b110714 100644 --- a/libs/ardour/ardour/automation_event.h +++ b/libs/ardour/ardour/automation_event.h @@ -191,10 +191,20 @@ class AutomationList : public PBD::StatefulDestructible } }; + /** Lookup cache for eval functions, range contains equivalent values */ struct LookupCache { + LookupCache() : left(-1) {} double left; /* leftmost x coordinate used when finding "range" */ std::pair range; }; + + /** Lookup cache for point finding, range contains points between left and right */ + struct SearchCache { + SearchCache() : left(-1), right(-1) {} + double left; /* leftmost x coordinate used when finding "range" */ + double right; /* rightmost x coordinate used when finding "range" */ + std::pair range; + }; static sigc::signal AutomationListCreated; @@ -205,6 +215,7 @@ class AutomationList : public PBD::StatefulDestructible mutable sigc::signal Dirty; Glib::Mutex& lock() const { return _lock; } LookupCache& lookup_cache() const { return _lookup_cache; } + SearchCache& search_cache() const { return _search_cache; } /** Called by locked entry point and various private * locations where we already hold the lock. @@ -212,6 +223,8 @@ class AutomationList : public PBD::StatefulDestructible * FIXME: Should this be private? Curve needs it.. */ double unlocked_eval (double x) const; + + bool rt_safe_earliest_event (double start, double end, double& x, double& y) const; Curve& curve() { return *_curve; } const Curve& curve() const { return *_curve; } @@ -220,7 +233,7 @@ class AutomationList : public PBD::StatefulDestructible /** Called by unlocked_eval() to handle cases of 3 or more control points. */ - virtual double multipoint_eval (double x) const; + double multipoint_eval (double x) const; AutomationList* cut_copy_clear (double, double, int op); @@ -231,6 +244,7 @@ class AutomationList : public PBD::StatefulDestructible void _x_scale (double factor); mutable LookupCache _lookup_cache; + mutable SearchCache _search_cache; Parameter _parameter; EventList _events; diff --git a/libs/ardour/ardour/midi_buffer.h b/libs/ardour/ardour/midi_buffer.h index f7223fcecd..cf2f92ff79 100644 --- a/libs/ardour/ardour/midi_buffer.h +++ b/libs/ardour/ardour/midi_buffer.h @@ -30,12 +30,13 @@ class MidiBuffer : public Buffer { public: MidiBuffer(size_t capacity); - ~MidiBuffer(); void silence(nframes_t dur, nframes_t offset=0); void read_from(const Buffer& src, nframes_t nframes, nframes_t offset); + + void copy(const MidiBuffer& copy); bool push_back(const ARDOUR::MidiEvent& event); bool push_back(const jack_midi_event_t& event); @@ -46,6 +47,8 @@ public: static size_t max_event_size() { return MAX_EVENT_SIZE; } + bool merge(const MidiBuffer& a, const MidiBuffer& b); + private: // FIXME: Jack needs to tell us this static const size_t MAX_EVENT_SIZE = 4; // bytes diff --git a/libs/ardour/ardour/midi_track.h b/libs/ardour/ardour/midi_track.h index 8b4706f7b3..a6e28405f8 100644 --- a/libs/ardour/ardour/midi_track.h +++ b/libs/ardour/ardour/midi_track.h @@ -91,6 +91,10 @@ protected: int _set_state (const XMLNode&, bool call_base); private: + + void write_controller_messages(MidiBuffer& buf, + nframes_t start_frame, nframes_t end_frame, nframes_t nframes, nframes_t offset); + int set_diskstream (boost::shared_ptr ds); void set_state_part_two (); void set_state_part_three (); diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 2c4c60a911..8a51653968 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -272,7 +272,7 @@ class Session : public PBD::StatefulDestructible BufferSet& get_silent_buffers (ChanCount count = ChanCount::ZERO); BufferSet& get_scratch_buffers (ChanCount count = ChanCount::ZERO); - BufferSet& get_send_buffers (ChanCount count = ChanCount::ZERO); + BufferSet& get_mix_buffers (ChanCount count = ChanCount::ZERO); void add_diskstream (boost::shared_ptr); boost::shared_ptr diskstream_by_id (const PBD::ID& id); @@ -986,7 +986,7 @@ class Session : public PBD::StatefulDestructible nframes_t last_stop_frame; BufferSet* _scratch_buffers; BufferSet* _silent_buffers; - BufferSet* _send_buffers; + BufferSet* _mix_buffers; nframes_t current_block_size; nframes_t _worst_output_latency; nframes_t _worst_input_latency; diff --git a/libs/ardour/audio_buffer.cc b/libs/ardour/audio_buffer.cc new file mode 100644 index 0000000000..059b61ed2f --- /dev/null +++ b/libs/ardour/audio_buffer.cc @@ -0,0 +1,56 @@ +/* + Copyright (C) 2006-2007 Paul Davis + + 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 + +#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 + +namespace ARDOUR { + + +AudioBuffer::AudioBuffer(size_t capacity) + : Buffer(DataType::AUDIO, capacity) + , _owns_data(false) + , _data(NULL) +{ + _size = capacity; // For audio buffers, size = capacity (always) + if (capacity > 0) { +#ifdef NO_POSIX_MEMALIGN + _data = (Sample *) malloc(sizeof(Sample) * capacity); +#else + posix_memalign((void**)&_data, CPU_CACHE_ALIGN, sizeof(Sample) * capacity); +#endif + assert(_data); + _owns_data = true; + clear(); + } +} + +AudioBuffer::~AudioBuffer() +{ + if (_owns_data) + free(_data); +} + + +} // namespace ARDOUR + diff --git a/libs/ardour/automation_event.cc b/libs/ardour/automation_event.cc index 09e4a926e8..2bb987ad24 100644 --- a/libs/ardour/automation_event.cc +++ b/libs/ardour/automation_event.cc @@ -71,6 +71,8 @@ AutomationList::AutomationList (Parameter id, double min_val, double max_val, do _rt_insertion_point = _events.end(); _lookup_cache.left = -1; _lookup_cache.range.first = _events.end(); + _search_cache.left = -1; + _search_cache.range.first = _events.end(); _sort_pending = false; assert(_parameter.type() != NullAutomation); @@ -91,8 +93,8 @@ AutomationList::AutomationList (const AutomationList& other) _state = other._state; _touching = other._touching; _rt_insertion_point = _events.end(); - _lookup_cache.left = -1; _lookup_cache.range.first = _events.end(); + _search_cache.range.first = _events.end(); _sort_pending = false; for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) { @@ -118,8 +120,8 @@ AutomationList::AutomationList (const AutomationList& other, double start, doubl _state = other._state; _touching = other._touching; _rt_insertion_point = _events.end(); - _lookup_cache.left = -1; _lookup_cache.range.first = _events.end(); + _search_cache.range.first = _events.end(); _sort_pending = false; /* now grab the relevant points, and shift them back if necessary */ @@ -127,7 +129,7 @@ AutomationList::AutomationList (const AutomationList& other, double start, doubl AutomationList* section = const_cast(&other)->copy (start, end); if (!section->empty()) { - for (AutomationList::iterator i = section->begin(); i != section->end(); ++i) { + for (iterator i = section->begin(); i != section->end(); ++i) { _events.push_back (new ControlEvent ((*i)->when, (*i)->value)); } } @@ -155,8 +157,8 @@ AutomationList::AutomationList (const XMLNode& node, Parameter id) _state = Off; _style = Absolute; _rt_insertion_point = _events.end(); - _lookup_cache.left = -1; _lookup_cache.range.first = _events.end(); + _search_cache.range.first = _events.end(); _sort_pending = false; set_state (node); @@ -283,7 +285,7 @@ AutomationList::extend_to (double when) void AutomationList::_x_scale (double factor) { - for (AutomationList::iterator i = _events.begin(); i != _events.end(); ++i) { + for (iterator i = _events.begin(); i != _events.end(); ++i) { (*i)->when = floor ((*i)->when * factor); } @@ -334,17 +336,17 @@ AutomationList::rt_add (double when, double value) ++far; } - if(_new_touch) { - where = far; - _rt_insertion_point = where; - - if((*where)->when == when) { - (*where)->value = value; - done = true; - } - } else { - where = _events.erase (after, far); - } + if (_new_touch) { + where = far; + _rt_insertion_point = where; + + if ((*where)->when == when) { + (*where)->value = value; + done = true; + } + } else { + where = _events.erase (after, far); + } } else { @@ -432,7 +434,7 @@ AutomationList::add (double when, double value) } void -AutomationList::erase (AutomationList::iterator i) +AutomationList::erase (iterator i) { { Glib::Mutex::Lock lm (_lock); @@ -444,7 +446,7 @@ AutomationList::erase (AutomationList::iterator i) } void -AutomationList::erase (AutomationList::iterator start, AutomationList::iterator end) +AutomationList::erase (iterator start, iterator end) { { Glib::Mutex::Lock lm (_lock); @@ -673,6 +675,7 @@ void AutomationList::mark_dirty () { _lookup_cache.left = -1; + _search_cache.left = -1; Dirty (); /* EMIT SIGNAL */ } @@ -782,7 +785,7 @@ AutomationList::truncate_start (double overall_length) { { Glib::Mutex::Lock lm (_lock); - AutomationList::iterator i; + iterator i; double first_legal_value; double first_legal_coordinate; @@ -947,27 +950,24 @@ AutomationList::unlocked_eval (double x) const double AutomationList::multipoint_eval (double x) const { - pair range; double upos, lpos; double uval, lval; double fraction; - /* only do the range lookup if x is in a different range than last time - this was called (or if the lookup cache has been marked "dirty" (left<0) - */ - + /* Only do the range lookup if x is in a different range than last time + * this was called (or if the lookup cache has been marked "dirty" (left<0) */ if ((_lookup_cache.left < 0) || ((_lookup_cache.left > x) || (_lookup_cache.range.first == _events.end()) || ((*_lookup_cache.range.second)->when < x))) { - ControlEvent cp (x, 0); + const ControlEvent cp (x, 0); TimeComparator cmp; _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, cmp); } - range = _lookup_cache.range; + pair range = _lookup_cache.range; if (range.first == range.second) { @@ -1007,6 +1007,71 @@ AutomationList::multipoint_eval (double x) const return (*range.first)->value; } +/** Get the earliest event between \a start and \a end. + * + * If an event is found, \a x and \a y are set to its coordinates. + * \return true if event is found (and \a x and \a y are valid). + */ +bool +AutomationList::rt_safe_earliest_event (double start, double end, double& x, double& y) const +{ + // FIXME: It would be nice if this was unnecessary.. + Glib::Mutex::Lock lm(_lock, Glib::TRY_LOCK); + if (!lm.locked()) { + return false; + } + + /* Only do the range lookup if x is in a different range than last time + * this was called (or if the search cache has been marked "dirty" (left<0) */ + if ((_search_cache.left < 0) || + ((_search_cache.left > start) || + (_search_cache.right < end))) { + + const ControlEvent start_point (start, 0); + const ControlEvent end_point (end, 0); + TimeComparator cmp; + + //cerr << "REBUILD: (" << _search_cache.left << ".." << _search_cache.right << ") -> (" + // << start << ".." << end << ")" << endl; + + _search_cache.range.first = lower_bound (_events.begin(), _events.end(), &start_point, cmp); + _search_cache.range.second = upper_bound (_events.begin(), _events.end(), &end_point, cmp); + + _search_cache.left = start; + _search_cache.right = end; + } + + pair range = _search_cache.range; + + if (range.first != _events.end()) { + const ControlEvent* const first = *range.first; + + /* Earliest points is in range, return it */ + if (first->when >= start && first->when < end) { + + x = first->when; + y = first->value; + + /* Move left of cache to this point + * (Optimize for immediate call this cycle within range) */ + _search_cache.left = x; + ++_search_cache.range.first; + + assert(x >= start); + assert(x < end); + return true; + + } else { + + return false; + } + + /* No points in range */ + } else { + return false; + } +} + AutomationList* AutomationList::cut (iterator start, iterator end) { diff --git a/libs/ardour/buffer.cc b/libs/ardour/buffer.cc index c91d09d1a8..8abe238a47 100644 --- a/libs/ardour/buffer.cc +++ b/libs/ardour/buffer.cc @@ -16,10 +16,6 @@ 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include -#include -using std::cerr; using std::endl; - #include #include #include @@ -45,188 +41,5 @@ Buffer::create(DataType type, size_t capacity) } -AudioBuffer::AudioBuffer(size_t capacity) - : Buffer(DataType::AUDIO, capacity) - , _owns_data(false) - , _data(NULL) -{ - _size = capacity; // For audio buffers, size = capacity (always) - if (capacity > 0) { -#ifdef NO_POSIX_MEMALIGN - _data = (Sample *) malloc(sizeof(Sample) * capacity); -#else - posix_memalign((void**)&_data, CPU_CACHE_ALIGN, sizeof(Sample) * capacity); -#endif - assert(_data); - _owns_data = true; - clear(); - } -} - -AudioBuffer::~AudioBuffer() -{ - if (_owns_data) - free(_data); -} - -// FIXME: mirroring for MIDI buffers? -MidiBuffer::MidiBuffer(size_t capacity) - : Buffer(DataType::MIDI, capacity) -// , _owns_data(true) - , _events(NULL) - , _data(NULL) -{ - assert(capacity > 0); - - _size = 0; - -#ifdef NO_POSIX_MEMALIGN - _events = (MidiEvent *) malloc(sizeof(MidiEvent) * capacity); - _data = (Byte *) malloc(sizeof(Byte) * capacity * MAX_EVENT_SIZE); -#else - posix_memalign((void**)&_events, CPU_CACHE_ALIGN, sizeof(MidiEvent) * capacity); - posix_memalign((void**)&_data, CPU_CACHE_ALIGN, sizeof(Byte) * capacity * MAX_EVENT_SIZE); -#endif - assert(_data); - assert(_events); - silence(_capacity); -} - -MidiBuffer::~MidiBuffer() -{ - free(_events); - free(_data); -} - - -/** Read events from @a src starting at time @a offset into the START of this buffer, for - * time direction @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, nframes_t nframes, nframes_t offset) -{ - assert(src.type() == DataType::MIDI); - const MidiBuffer& msrc = (MidiBuffer&)src; - - assert(_capacity >= src.size()); - - clear(); - assert(_size == 0); - - // FIXME: slow - for (size_t i=0; i < src.size(); ++i) { - const MidiEvent& ev = msrc[i]; - if (ev.time >= offset && ev.time < offset+nframes) { - push_back(ev); - } - } - - _silent = src.silent(); -} - - -/** 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 MidiEvent& ev) -{ - if (_size == _capacity) - return false; - - Byte* const write_loc = _data + (_size * MAX_EVENT_SIZE); - - memcpy(write_loc, ev.buffer, ev.size); - _events[_size] = ev; - _events[_size].buffer = write_loc; - ++_size; - - //cerr << "MidiBuffer: pushed, size = " << _size << endl; - - _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) -{ - if (_size == _capacity) - return false; - - Byte* const write_loc = _data + (_size * MAX_EVENT_SIZE); - - memcpy(write_loc, ev.buffer, ev.size); - _events[_size].time = (double)ev.time; - _events[_size].size = ev.size; - _events[_size].buffer = write_loc; - ++_size; - - //cerr << "MidiBuffer: pushed, size = " << _size << endl; - - _silent = false; - - return true; -} - - -/** 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 NULL 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. - */ -Byte* -MidiBuffer::reserve(double time, size_t size) -{ - assert(size < MAX_EVENT_SIZE); - - if (_size == _capacity) - return NULL; - - Byte* const write_loc = _data + (_size * MAX_EVENT_SIZE); - - _events[_size].time = time; - _events[_size].size = size; - _events[_size].buffer = write_loc; - ++_size; - - //cerr << "MidiBuffer: reserved, size = " << _size << endl; - - _silent = false; - - return write_loc; -} - - -void -MidiBuffer::silence(nframes_t dur, nframes_t offset) -{ - // FIXME use parameters - assert(offset == 0); - //assert(dur == _capacity); - - memset(_events, 0, sizeof(MidiEvent) * _capacity); - memset(_data, 0, sizeof(Byte) * _capacity * MAX_EVENT_SIZE); - _size = 0; - _silent = true; -} - - } // namespace ARDOUR diff --git a/libs/ardour/midi_buffer.cc b/libs/ardour/midi_buffer.cc new file mode 100644 index 0000000000..5376886a5d --- /dev/null +++ b/libs/ardour/midi_buffer.cc @@ -0,0 +1,255 @@ +/* + Copyright (C) 2006-2007 Paul Davis + Author: Dave 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 + +using namespace std; + +namespace ARDOUR { + + +// FIXME: mirroring for MIDI buffers? +MidiBuffer::MidiBuffer(size_t capacity) + : Buffer(DataType::MIDI, capacity) +// , _owns_data(true) + , _events(NULL) + , _data(NULL) +{ + assert(capacity > 0); + + _size = 0; + +#ifdef NO_POSIX_MEMALIGN + _events = (MidiEvent *) malloc(sizeof(MidiEvent) * capacity); + _data = (Byte *) malloc(sizeof(Byte) * capacity * MAX_EVENT_SIZE); +#else + posix_memalign((void**)&_events, CPU_CACHE_ALIGN, sizeof(MidiEvent) * capacity); + posix_memalign((void**)&_data, CPU_CACHE_ALIGN, sizeof(Byte) * capacity * MAX_EVENT_SIZE); +#endif + assert(_data); + assert(_events); + silence(_capacity); +} + +void +MidiBuffer::copy(const MidiBuffer& copy) +{ + assert(_capacity >= copy._capacity); + _size = 0; + + for (size_t i=0; i < copy.size(); ++i) + push_back(copy[i]); +} + +MidiBuffer::~MidiBuffer() +{ + free(_events); + free(_data); +} + + +/** Read events from @a src starting at time @a offset into the START of this buffer, for + * time direction @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, nframes_t nframes, nframes_t offset) +{ + assert(src.type() == DataType::MIDI); + const MidiBuffer& msrc = (MidiBuffer&)src; + + assert(_capacity >= src.size()); + + clear(); + assert(_size == 0); + + // FIXME: slow + for (size_t i=0; i < src.size(); ++i) { + const MidiEvent& ev = msrc[i]; + if (ev.time >= offset && ev.time < offset+nframes) { + push_back(ev); + } + } + + _silent = src.silent(); +} + + +/** 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 MidiEvent& ev) +{ + if (_size == _capacity) + return false; + + Byte* const write_loc = _data + (_size * MAX_EVENT_SIZE); + + memcpy(write_loc, ev.buffer, ev.size); + _events[_size] = ev; + _events[_size].buffer = write_loc; + ++_size; + + //cerr << "MidiBuffer: pushed, size = " << _size << endl; + + _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) +{ + if (_size == _capacity) + return false; + + Byte* const write_loc = _data + (_size * MAX_EVENT_SIZE); + + memcpy(write_loc, ev.buffer, ev.size); + _events[_size].time = (double)ev.time; + _events[_size].size = ev.size; + _events[_size].buffer = write_loc; + ++_size; + + //cerr << "MidiBuffer: pushed, size = " << _size << endl; + + _silent = false; + + return true; +} + + +/** 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 NULL 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. + */ +Byte* +MidiBuffer::reserve(double time, size_t size) +{ + assert(size < MAX_EVENT_SIZE); + + if (_size == _capacity) + return NULL; + + Byte* const write_loc = _data + (_size * MAX_EVENT_SIZE); + + _events[_size].time = time; + _events[_size].size = size; + _events[_size].buffer = write_loc; + ++_size; + + //cerr << "MidiBuffer: reserved, size = " << _size << endl; + + _silent = false; + + return write_loc; +} + + +void +MidiBuffer::silence(nframes_t dur, nframes_t offset) +{ + // FIXME use parameters + assert(offset == 0); + //assert(dur == _capacity); + + memset(_events, 0, sizeof(MidiEvent) * _capacity); + memset(_data, 0, sizeof(Byte) * _capacity * MAX_EVENT_SIZE); + _size = 0; + _silent = true; +} + + +/** 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; + + // Die if a merge isn't necessary as it's expensive + assert(a.size() > 0 && b.size() > 0); + + size_t a_index = 0; + size_t b_index = 0; + size_t count = a.size() + b.size(); + + while (count > 0 && a_index < a.size() && b_index < b.size()) { + + if (size() == capacity()) { + cerr << "WARNING: MIDI buffer overrun, events lost!" << endl; + return false; + } + + if (a_index == a.size()) { + push_back(a[a_index]); + ++a_index; + } else if (b_index == b.size()) { + push_back(b[b_index]); + ++b_index; + } else { + const MidiEvent& a_ev = a[a_index]; + const 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; + } + } + + --count; + } + + return true; +} + + +} // namespace ARDOUR + diff --git a/libs/ardour/midi_track.cc b/libs/ardour/midi_track.cc index 00b76eb08a..aa23e3d92b 100644 --- a/libs/ardour/midi_track.cc +++ b/libs/ardour/midi_track.cc @@ -72,6 +72,8 @@ MidiTrack::MidiTrack (Session& sess, string name, Route::Flag flag, TrackMode mo set_input_maximum(ChanCount(DataType::MIDI, 1)); set_output_minimum(ChanCount(DataType::MIDI, 1)); set_output_maximum(ChanCount(DataType::MIDI, 1)); + + MoreChannels(ChanCount(DataType::MIDI, 2)); /* EMIT SIGNAL */ } MidiTrack::MidiTrack (Session& sess, const XMLNode& node) @@ -84,6 +86,8 @@ MidiTrack::MidiTrack (Session& sess, const XMLNode& node) set_input_maximum(ChanCount(DataType::MIDI, 1)); set_output_minimum(ChanCount(DataType::MIDI, 1)); set_output_maximum(ChanCount(DataType::MIDI, 1)); + + MoreChannels(ChanCount(DataType::MIDI, 2)); /* EMIT SIGNAL */ } MidiTrack::~MidiTrack () @@ -559,12 +563,84 @@ MidiTrack::process_output_buffers (BufferSet& bufs, if (muted()) { IO::silence(nframes, offset); } else { - MidiBuffer& out_buf = bufs.get_midi(0); - _immediate_events.read(out_buf, 0, 0, offset + nframes-1); // all stamps = 0 + + MidiBuffer& output_buf = bufs.get_midi(0); + write_controller_messages(output_buf, start_frame, end_frame, nframes, offset); + deliver_output(bufs, start_frame, end_frame, nframes, offset); } } +void +MidiTrack::write_controller_messages(MidiBuffer& output_buf, nframes_t start_frame, nframes_t end_frame, + nframes_t nframes, nframes_t offset) +{ + BufferSet& mix_buffers = _session.get_mix_buffers(ChanCount(DataType::MIDI, 2)); + + /* FIXME: this could be more realtimey */ + + // Write immediate events (UI controls) + MidiBuffer& cc_buf = mix_buffers.get_midi(0); + cc_buf.silence(nframes, offset); + MidiEvent ev; + ev.size = 3; // CC = 3 bytes + Byte buf[ev.size]; + buf[0] = MIDI_CMD_CONTROL; + ev.buffer = buf; + + // Write controller automation + if (_session.transport_rolling()) { + for (Controls::const_iterator i = _controls.begin(); i != _controls.end(); ++i) { + const boost::shared_ptr list = (*i).second->list(); + + if ( (!list->automation_playback()) + || (list->parameter().type() != MidiCCAutomation)) + continue; + + double start = start_frame; + double x, y; + while ((*i).second->list()->rt_safe_earliest_event(start, end_frame, x, y)) { + assert(x >= start_frame); + assert(x <= end_frame); + + const nframes_t stamp = (nframes_t)floor(x - start_frame); + assert(stamp < nframes); + + assert(y >= 0.0); + assert(y <= 127.0); + + ev.time = stamp; + ev.buffer[1] = (Byte)list->parameter().id(); + ev.buffer[2] = (Byte)y; + + cc_buf.push_back(ev); + + start = x; + } + } + } + + /* FIXME: too much copying! */ + + // Merge cc buf into output + if (cc_buf.size() > 0) { + + // Both CC and route, must merge + if (output_buf.size() > 0) { + + MidiBuffer& mix_buf = mix_buffers.get_midi(1); + mix_buf.merge(output_buf, cc_buf); + output_buf.copy(mix_buf); + + } else { + output_buf.copy(cc_buf); + } + } + + // Append immediate events (UI controls) + _immediate_events.read(output_buf, 0, 0, offset + nframes-1); // all stamps = 0 +} + int MidiTrack::export_stuff (BufferSet& bufs, nframes_t nframes, nframes_t end_frame) { diff --git a/libs/ardour/send.cc b/libs/ardour/send.cc index 5567649c9a..929b1fc9a8 100644 --- a/libs/ardour/send.cc +++ b/libs/ardour/send.cc @@ -120,7 +120,7 @@ Send::run_in_place (BufferSet& bufs, nframes_t start_frame, nframes_t end_frame, // we have to copy the input, because IO::deliver_output may alter the buffers // in-place, which a send must never do. - BufferSet& sendbufs = _session.get_send_buffers(bufs.count()); + BufferSet& sendbufs = _session.get_mix_buffers(bufs.count()); sendbufs.read_from(bufs, nframes); assert(sendbufs.count() == bufs.count()); diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index a374e10a4f..e1997df980 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -110,7 +110,7 @@ Session::Session (AudioEngine &eng, : _engine (eng), _scratch_buffers(new BufferSet()), _silent_buffers(new BufferSet()), - _send_buffers(new BufferSet()), + _mix_buffers(new BufferSet()), _mmc_port (default_mmc_port), _mtc_port (default_mtc_port), _midi_port (default_midi_port), @@ -212,7 +212,7 @@ Session::Session (AudioEngine &eng, : _engine (eng), _scratch_buffers(new BufferSet()), _silent_buffers(new BufferSet()), - _send_buffers(new BufferSet()), + _mix_buffers(new BufferSet()), _mmc_port (default_mmc_port), _mtc_port (default_mtc_port), _midi_port (default_midi_port), @@ -350,7 +350,7 @@ Session::destroy () delete _scratch_buffers; delete _silent_buffers; - delete _send_buffers; + delete _mix_buffers; AudioDiskstream::free_working_buffers(); @@ -3681,14 +3681,14 @@ Session::ensure_buffers (ChanCount howmany) if (current_block_size == 0) return; // too early? (is this ok?) - // We need at least 1 MIDI scratch buffer to mix/merge - if (howmany.n_midi() < 1) - howmany.set_midi(1); + // We need at least 2 MIDI scratch buffers to mix/merge + if (howmany.n_midi() < 2) + howmany.set_midi(2); // FIXME: JACK needs to tell us maximum MIDI buffer size // Using nasty assumption (max # events == nframes) for now _scratch_buffers->ensure_buffers(howmany, current_block_size); - _send_buffers->ensure_buffers(howmany, current_block_size); + _mix_buffers->ensure_buffers(howmany, current_block_size); _silent_buffers->ensure_buffers(howmany, current_block_size); allocate_pan_automation_buffers (current_block_size, howmany.n_audio(), false); @@ -4064,11 +4064,11 @@ Session::get_scratch_buffers (ChanCount count) } BufferSet& -Session::get_send_buffers (ChanCount count) +Session::get_mix_buffers (ChanCount count) { - assert(_send_buffers->available() >= count); - _send_buffers->set_count(count); - return *_send_buffers; + assert(_mix_buffers->available() >= count); + _mix_buffers->set_count(count); + return *_mix_buffers; } uint32_t -- cgit v1.2.3