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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<AutomationList::const_iterator,AutomationList::const_iterator> 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<AutomationList::const_iterator,AutomationList::const_iterator> range;
|
||||
};
|
||||
|
||||
static sigc::signal<void, AutomationList*> AutomationListCreated;
|
||||
|
||||
@@ -205,6 +215,7 @@ class AutomationList : public PBD::StatefulDestructible
|
||||
mutable sigc::signal<void> 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<MidiDiskstream> ds);
|
||||
void set_state_part_two ();
|
||||
void set_state_part_three ();
|
||||
|
||||
@@ -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<Diskstream>);
|
||||
boost::shared_ptr<Diskstream> 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;
|
||||
|
||||
56
libs/ardour/audio_buffer.cc
Normal file
56
libs/ardour/audio_buffer.cc
Normal file
@@ -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 <ardour/audio_buffer.h>
|
||||
|
||||
#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
|
||||
|
||||
@@ -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<AutomationList*>(&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<AutomationList::const_iterator,AutomationList::const_iterator> 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<const_iterator,const_iterator> 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<const_iterator,const_iterator> 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)
|
||||
{
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
using std::cerr; using std::endl;
|
||||
|
||||
#include <ardour/buffer.h>
|
||||
#include <ardour/audio_buffer.h>
|
||||
#include <ardour/midi_buffer.h>
|
||||
@@ -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
|
||||
|
||||
|
||||
255
libs/ardour/midi_buffer.cc
Normal file
255
libs/ardour/midi_buffer.cc
Normal file
@@ -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 <iostream>
|
||||
#include <ardour/midi_buffer.h>
|
||||
|
||||
#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
|
||||
|
||||
@@ -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<AutomationList> 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)
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user