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:
David Robillard
2007-07-06 00:09:53 +00:00
parent 40f353f151
commit 19273e824d
12 changed files with 519 additions and 231 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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 ();

View File

@@ -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;

View 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

View File

@@ -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)
{

View File

@@ -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
View 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

View File

@@ -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)
{

View File

@@ -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());

View File

@@ -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