Reading of MIDI CC from MIDI regions (MidiModel). UI still needs work though..

Various fixes for linear/integer AutomationList interpolation (for CC).


git-svn-id: svn://localhost/ardour2/trunk@2359 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard
2007-08-31 05:02:45 +00:00
parent 056b2a59d5
commit 51c0f6c442
11 changed files with 312 additions and 99 deletions

View File

@@ -542,9 +542,9 @@ env = conf.Finish()
opt_flags = []
if env['GPROFILE'] == 1:
debug_flags = [ '-g', '-pg' ]
debug_flags = [ '-O0', '-g', '-pg' ]
else:
debug_flags = [ '-g' ]
debug_flags = [ '-O0', '-g' ]
# guess at the platform, used to define compiler flags

View File

@@ -52,7 +52,7 @@ public:
boost::shared_ptr<AutomationControl> control_factory(boost::shared_ptr<AutomationList> list);
typedef std::map<Parameter,boost::shared_ptr<AutomationControl> > Controls;
Controls& controls() { return _controls; }
Controls& controls() { return _controls; }
const Controls& controls() const { return _controls; }
virtual void add_control(boost::shared_ptr<AutomationControl>);

View File

@@ -234,7 +234,8 @@ class AutomationList : public PBD::StatefulDestructible
*/
double unlocked_eval (double x) const;
bool rt_safe_earliest_event (double start, double end, double& x, double& y) const;
bool rt_safe_earliest_event (double start, double end, double& x, double& y, bool start_inclusive=false) const;
bool rt_safe_earliest_event_unlocked (double start, double end, double& x, double& y, bool start_inclusive=false) const;
Curve& curve() { return *_curve; }
const Curve& curve() const { return *_curve; }
@@ -256,8 +257,8 @@ class AutomationList : public PBD::StatefulDestructible
void build_search_cache_if_necessary(double start, double end) const;
bool rt_safe_earliest_event_discrete (double start, double end, double& x, double& y) const;
bool rt_safe_earliest_event_linear (double start, double end, double& x, double& y) const;
bool rt_safe_earliest_event_discrete_unlocked (double start, double end, double& x, double& y, bool inclusive) const;
bool rt_safe_earliest_event_linear_unlocked (double start, double end, double& x, double& y, bool inclusive) const;
AutomationList* cut_copy_clear (double, double, int op);

View File

@@ -96,16 +96,35 @@ struct MidiEvent {
_size = copy._size;
return *this;
}
inline bool operator==(const MidiEvent& other) const {
if (_time != other._time)
return false;
if (_size != other._size)
return false;
if (_buffer == other._buffer)
return true;
for (size_t i=0; i < _size; ++i)
if (_buffer[i] != other._buffer[i])
return false;
return true;
}
inline bool operator!=(const MidiEvent& other) const { return ! operator==(other); }
inline bool owns_buffer() const { return _owns_buffer; }
inline void set_buffer(Byte* buf) {
inline void set_buffer(Byte* buf, bool own) {
if (_owns_buffer) {
free(_buffer);
_buffer = NULL;
}
_buffer = buf;
_owns_buffer = false;
_owns_buffer = own;
}
#else

View File

@@ -55,8 +55,8 @@ public:
// This is crap.
void write_lock() { _lock.writer_lock(); _automation_lock.lock(); }
void write_unlock() { _lock.writer_unlock(); _automation_lock.unlock(); }
void read_lock() const { _lock.reader_lock(); _automation_lock.lock(); }
void read_unlock() const { _lock.reader_unlock(); _automation_lock.unlock(); }
void read_lock() const { _lock.reader_lock(); /*_automation_lock.lock();*/ }
void read_unlock() const { _lock.reader_unlock(); /*_automation_lock.unlock();*/ }
void clear() { _notes.clear(); }
@@ -140,33 +140,41 @@ public:
/** Read iterator */
class const_iterator {
public:
const_iterator(MidiModel& model, double t);
const_iterator(const MidiModel& model, double t);
~const_iterator();
const MidiEvent& operator*() const { return _event; }
const MidiEvent& operator*() const { return _event; }
const MidiEvent* operator->() const { return &_event; }
const const_iterator& operator++(); // prefix only
bool operator==(const const_iterator& other) const;
bool operator!=(const const_iterator& other) const { return ! operator==(other); }
private:
const MidiModel& _model;
const MidiModel* _model;
MidiEvent _event;
typedef std::priority_queue<const Note*,std::vector<const Note*>, LaterNoteEndComparator>
ActiveNotes;
mutable ActiveNotes _active_notes;
Notes::iterator _note_iter;
std::vector<MidiControlIterator> _control_iters;
bool _is_end;
bool _locked;
Notes::const_iterator _note_iter;
std::vector<MidiControlIterator> _control_iters;
std::vector<MidiControlIterator>::iterator _control_iter;
};
const_iterator begin() const { return const_iterator(*this, 0); }
const const_iterator& end() const { return _end_iter; }
private:
friend class DeltaCommand;
void add_note_unlocked(const Note& note);
void remove_note_unlocked(const Note& note);
friend class const_iterator;
bool control_to_midi_event(MidiEvent& ev, const MidiControlIterator& iter);
bool control_to_midi_event(MidiEvent& ev, const MidiControlIterator& iter) const;
#ifndef NDEBUG
bool is_sorted() const;
@@ -185,14 +193,19 @@ private:
WriteNotes _write_notes;
bool _writing;
bool _edited;
const const_iterator _end_iter;
mutable nframes_t _next_read;
mutable const_iterator _read_iter;
// note state for read():
// (TODO: Remove and replace with iterator)
typedef std::priority_queue<const Note*,std::vector<const Note*>,
LaterNoteEndComparator> ActiveNotes;
mutable ActiveNotes _active_notes;
//mutable ActiveNotes _active_notes;
};
} /* namespace ARDOUR */

View File

@@ -342,7 +342,7 @@ MidiRingBuffer::read(MidiBuffer& dst, nframes_t start, nframes_t end, nframes_t
}
} else {
printf("MRB - SKIPPING EVENT (with time %f)\n", ev.time());
printf("MRB - SKIPPING EVENT AT TIME %f\n", ev.time());
}
}

View File

@@ -1036,7 +1036,7 @@ AutomationList::build_search_cache_if_necessary(double start, double end) const
const ControlEvent start_point (start, 0);
const ControlEvent end_point (end, 0);
//cerr << "REBUILD: (" << _search_cache.left << ".." << _search_cache.right << ") -> ("
//cerr << "REBUILD: (" << _search_cache.left << ".." << _search_cache.right << ") := ("
// << start << ".." << end << ")" << endl;
_search_cache.range.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
@@ -1050,31 +1050,50 @@ AutomationList::build_search_cache_if_necessary(double start, double end) const
/** Get the earliest event between \a start and \a end, using the current interpolation style.
*
* 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
{
if (_interpolation == Discrete)
return rt_safe_earliest_event_discrete(start, end, x, y);
else
return rt_safe_earliest_event_linear(start, end, x, y);
}
/** Get the earliest event between \a start and \a end (Discrete (lack of) interpolation)
*
* If an event is found, \a x and \a y are set to its coordinates.
* \param inclusive Include events with timestamp exactly equal to \a start
* \return true if event is found (and \a x and \a y are valid).
*/
bool
AutomationList::rt_safe_earliest_event_discrete (double start, double end, double& x, double& y) const
AutomationList::rt_safe_earliest_event(double start, double end, double& x, double& y, bool inclusive) const
{
// FIXME: It would be nice if this was unnecessary..
Glib::Mutex::Lock lm(_lock, Glib::TRY_LOCK);
if (!lm.locked()) {
return false;
}
return rt_safe_earliest_event_unlocked(start, end, x, y, inclusive);
}
/** Get the earliest event between \a start and \a end, using the current interpolation style.
*
* If an event is found, \a x and \a y are set to its coordinates.
*
* \param inclusive Include events with timestamp exactly equal to \a start
* \return true if event is found (and \a x and \a y are valid).
*/
bool
AutomationList::rt_safe_earliest_event_unlocked(double start, double end, double& x, double& y, bool inclusive) const
{
if (_interpolation == Discrete)
return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
else
return rt_safe_earliest_event_linear_unlocked(start, end, x, y, inclusive);
}
/** Get the earliest event between \a start and \a end (Discrete (lack of) interpolation)
*
* If an event is found, \a x and \a y are set to its coordinates.
*
* \param inclusive Include events with timestamp exactly equal to \a start
* \return true if event is found (and \a x and \a y are valid).
*/
bool
AutomationList::rt_safe_earliest_event_discrete_unlocked (double start, double end, double& x, double& y, bool inclusive) const
{
build_search_cache_if_necessary(start, end);
const pair<const_iterator,const_iterator>& range = _search_cache.range;
@@ -1082,8 +1101,10 @@ AutomationList::rt_safe_earliest_event_discrete (double start, double end, doubl
if (range.first != _events.end()) {
const ControlEvent* const first = *range.first;
const bool past_start = (inclusive ? first->when >= start : first->when > start);
/* Earliest points is in range, return it */
if (first->when >= start && first->when < end) {
if (past_start >= start && first->when < end) {
x = first->when;
y = first->value;
@@ -1098,7 +1119,6 @@ AutomationList::rt_safe_earliest_event_discrete (double start, double end, doubl
return true;
} else {
return false;
}
@@ -1111,20 +1131,19 @@ AutomationList::rt_safe_earliest_event_discrete (double start, double end, doubl
/** Get the earliest time the line crosses an integer (Linear interpolation).
*
* If an event is found, \a x and \a y are set to its coordinates.
*
* \param inclusive Include events with timestamp exactly equal to \a start
* \return true if event is found (and \a x and \a y are valid).
*/
bool
AutomationList::rt_safe_earliest_event_linear (double start, double end, double& x, double& y) const
AutomationList::rt_safe_earliest_event_linear_unlocked (double start, double end, double& x, double& y, bool inclusive) const
{
// FIXME: It would be nice if this was unnecessary..
Glib::Mutex::Lock lm(_lock, Glib::TRY_LOCK);
if (!lm.locked()) {
return false;
}
//cerr << "earliest_event(" << start << ", " << end << ", " << x << ", " << y << ", " << inclusive << endl;
if (_events.size() < 2)
return false;
return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
// Hack to avoid infinitely repeating the same event
build_search_cache_if_necessary(start, end);
pair<const_iterator,const_iterator> range = _search_cache.range;
@@ -1138,6 +1157,7 @@ AutomationList::rt_safe_earliest_event_linear (double start, double end, double&
if (range.first == _events.begin() || (*range.first)->when == start) {
first = *range.first;
next = *(++range.first);
++_search_cache.range.first;
/* Step is before first */
} else {
@@ -1147,48 +1167,70 @@ AutomationList::rt_safe_earliest_event_linear (double start, double end, double&
next = *range.first;
}
if (first->when == start) {
x = start;
if (inclusive && first->when == start) {
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;
//++_search_cache.range.first;
return true;
}
/*cerr << first->value << " @ " << first->when << " ---> "
<< next->value << " @ " << next->when << endl;*/
const double slope = (next->value - first->value) / (double)(next->when - first->when);
const double start_y = first->value + (slope * (start - first->when));
//cerr << "start y: " << start_y << endl;
if (start_y >= first->value) {
x = first->when + ((ceil(start_y) - first->value) / (double)slope);
y = ceil(start_y);
} else {
x = first->when + ((floor(start_y) - first->value) / (double)slope);
y = floor(start_y);
if (abs(first->value - next->value) <= 1) {
if (next->when <= end && (!inclusive || next->when > start)) {
x = next->when;
y = next->value;
/* Move left of cache to this point
* (Optimize for immediate call this cycle within range) */
_search_cache.left = x;
//++_search_cache.range.first;
return true;
} else {
return false;
}
}
if (x >= start && x < end) {
//cerr << y << " @ " << x << endl;
const double slope = (next->value - first->value) / (double)(next->when - first->when);
//cerr << "start y: " << start_y << endl;
x = floor(x);
//y = first->value + (slope * fabs(start - first->when));
y = first->value;
if (first->value < next->value) // ramping up
y = ceil(y);
else // ramping down
y = floor(y);
x = first->when + (y - first->value) / (double)slope;
while ((inclusive && x < start) || x <= start && y != next->value) {
if (first->value < next->value) // ramping up
y += 1.0;
else // ramping down
y -= 1.0;
x = first->when + (y - first->value) / (double)slope;
}
/*cerr << first->value << " @ " << first->when << " ... "
<< next->value << " @ " << next->when
<< " = " << y << " @ " << x << endl;*/
assert( (y >= first->value && y <= next->value)
|| (y <= first->value && y >= next->value) );
const bool past_start = (inclusive ? x >= start : x > start);
if (past_start && x < end) {
/* Move left of cache to this point
* (Optimize for immediate call this cycle within range) */
_search_cache.left = x;
if (x >= next->when)
++_search_cache.range.first;
return true;
} else {
//cerr << "\tNo: " << start_y << ", " << x << endl;
return false;
}

View File

@@ -119,7 +119,7 @@ MidiBuffer::push_back(const MidiEvent& ev)
memcpy(write_loc, ev.buffer(), ev.size());
_events[_size] = ev;
_events[_size].set_buffer(write_loc);
_events[_size].set_buffer(write_loc, false);
++_size;
//cerr << "MidiBuffer: pushed, size = " << _size << endl;
@@ -148,7 +148,7 @@ MidiBuffer::push_back(const jack_midi_event_t& ev)
memcpy(write_loc, ev.buffer, ev.size);
_events[_size].time() = (double)ev.time;
_events[_size].size() = ev.size;
_events[_size].set_buffer(write_loc);
_events[_size].set_buffer(write_loc, false);
++_size;
//cerr << "MidiBuffer: pushed, size = " << _size << endl;
@@ -178,7 +178,7 @@ MidiBuffer::reserve(double time, size_t size)
_events[_size].time() = time;
_events[_size].size() = size;
_events[_size].set_buffer(write_loc);
_events[_size].set_buffer(write_loc, false);
++_size;
//cerr << "MidiBuffer: reserved, size = " << _size << endl;

View File

@@ -22,6 +22,7 @@
#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <stdint.h>
#include <pbd/enumwriter.h>
#include <ardour/midi_model.h>
@@ -36,50 +37,165 @@ using namespace ARDOUR;
// Read iterator (const_iterator)
MidiModel::const_iterator::const_iterator(MidiModel& model, double t)
: _model(model)
MidiModel::const_iterator::const_iterator(const MidiModel& model, double t)
: _model(&model)
, _is_end( (t == DBL_MAX) || model.empty())
, _locked( ! _is_end)
{
model.read_lock();
//cerr << "Created MIDI iterator @ " << t << "(is end: " << _is_end << ")" << endl;
if (_is_end)
return;
model.read_lock();
_note_iter = model.notes().end();
for (MidiModel::Notes::iterator i = model.notes().begin(); i != model.notes().end(); ++i) {
for (MidiModel::Notes::const_iterator i = model.notes().begin(); i != model.notes().end(); ++i) {
if ((*i).time() >= t) {
_note_iter = i;
break;
}
}
MidiControlIterator earliest_control = make_pair(boost::shared_ptr<AutomationList>(), make_pair(DBL_MAX, 0.0));
MidiControlIterator earliest_control = make_pair(boost::shared_ptr<AutomationList>(),
make_pair(DBL_MAX, 0.0));
_control_iters.reserve(model.controls().size());
for (Automatable::Controls::const_iterator i = model.controls().begin();
i != model.controls().end(); ++i) {
assert(i->first.type() == MidiCCAutomation);
double x, y;
i->second->list()->rt_safe_earliest_event(t, DBL_MAX, x, y);
bool ret = i->second->list()->rt_safe_earliest_event_unlocked(t, DBL_MAX, x, y);
if (!ret) {
cerr << "MIDI Iterator: CC " << i->first.id() << " (size " << i->second->list()->size()
<< ") has no events past " << t << endl;
continue;
}
assert(x >= 0);
assert(y >= 0);
assert(y <= UINT8_MAX);
const MidiControlIterator new_iter = make_pair(i->second->list(), make_pair(x, y));
if (x < earliest_control.second.first)
earliest_control = new_iter;
//cerr << "MIDI Iterator: CC " << i->first.id() << " added (" << x << ", " << y << ")" << endl;
_control_iters.push_back(new_iter);
if (x < earliest_control.second.first) {
earliest_control = new_iter;
_control_iter = _control_iters.end();
--_control_iter;
}
}
if (_note_iter != model.notes().end())
if (_note_iter != model.notes().end()) {
_event = MidiEvent(_note_iter->on_event(), false);
++_note_iter;
}
if (earliest_control.first != 0 && earliest_control.second.first < _event.time())
if (earliest_control.first && earliest_control.second.first < _event.time())
model.control_to_midi_event(_event, earliest_control);
else
_control_iter = _control_iters.end();
if (_event.size() == 0) {
//cerr << "Created MIDI iterator @ " << t << " is at end." << endl;
_is_end = true;
_model->read_unlock();
_locked = false;
//} else {
// printf("MIDI Iterator = %X @ %lf\n", _event.type(), _event.time());
}
}
MidiModel::const_iterator::~const_iterator()
{
_model.read_unlock();
if (_locked)
_model->read_unlock();
}
const MidiModel::const_iterator&
MidiModel::const_iterator::operator++()
{
if (_is_end)
throw std::logic_error("Attempt to iterate past end of MidiModel");
assert(_event.is_note() || _event.is_cc());
// Increment past current control event
if (_control_iter->first && _event.is_cc()) {
double x, y;
const bool ret = _control_iter->first->rt_safe_earliest_event_unlocked(
_control_iter->second.first, DBL_MAX, x, y, false);
if (ret) {
//cerr << "Incremented " << _control_iter->first->parameter().id() << " to " << x << endl;
_control_iter->second.first = x;
_control_iter->second.second = y;
} else {
//cerr << "Hit end of " << _control_iter->first->parameter().id() << endl;
_control_iter->first.reset();
_control_iter->second.first = DBL_MAX;
}
}
// Now find and point at the earliest event
_control_iter = _control_iters.begin();
for (std::vector<MidiControlIterator>::iterator i = _control_iters.begin();
i != _control_iters.end(); ++i) {
if (i->second.first < _control_iter->second.first) {
_control_iter = i;
}
}
enum Type { NIL, NOTE, CC };
Type type = NIL;
if (_note_iter != _model->notes().end())
type = NOTE;
if (_control_iter != _control_iters.end() && _control_iter->second.first != DBL_MAX)
if (_note_iter == _model->notes().end() || _control_iter->second.first < _note_iter->time())
type = CC;
if (type == NOTE) {
//cerr << "MIDI Iterator = note" << endl;
_event = MidiEvent(_note_iter->on_event(), false);
++_note_iter;
} else if (type == CC) {
//cerr << "MIDI Iterator = CC" << endl;
_model->control_to_midi_event(_event, *_control_iter);
} else {
//cerr << "MIDI Iterator = NIL" << endl;
_is_end = true;
_model->read_unlock();
_locked = false;
}
return *this;
}
bool
MidiModel::const_iterator::operator==(const const_iterator& other) const
{
if (_is_end)
if (other._is_end)
return true;
else
return false;
else
return (_event == other._event);
}
// MidiModel
MidiModel::MidiModel(Session& s, size_t size)
@@ -88,7 +204,10 @@ MidiModel::MidiModel(Session& s, size_t size)
, _note_mode(Sustained)
, _writing(false)
, _edited(false)
, _active_notes(LaterNoteEndComparator())
//, _active_notes(LaterNoteEndComparator())
, _end_iter(*this, DBL_MAX)
, _next_read(UINT32_MAX)
, _read_iter(*this, DBL_MAX)
{
}
@@ -98,10 +217,26 @@ MidiModel::MidiModel(Session& s, size_t size)
* \return number of events written to \a dst
*/
size_t
MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes_t stamp_offset) const
MidiModel::read(MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes_t stamp_offset) const
{
size_t read_events = 0;
if (start != _next_read) {
_read_iter = const_iterator(*this, (double)start);
cerr << "Repositioning iterator from " << _next_read << " to " << start << endl;
//} else {
// cerr << "Using cached iterator at " << _next_read << endl;
}
_next_read = start + nframes;
while (_read_iter != end() && _read_iter->time() < start + nframes) {
dst.write(_read_iter->time() + stamp_offset, _read_iter->size(), _read_iter->buffer());
++_read_iter;
++read_events;
}
#if 0
/* FIXME: cache last lookup value to avoid O(n) search every time */
if (_note_mode == Sustained) {
@@ -161,17 +296,17 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
}
}
}
#endif
return read_events;
}
bool
MidiModel::control_to_midi_event(MidiEvent& ev, const MidiControlIterator& iter)
MidiModel::control_to_midi_event(MidiEvent& ev, const MidiControlIterator& iter) const
{
if (iter.first->parameter().type() == MidiCCAutomation) {
if (!ev.owns_buffer() || ev.size() < 3)
ev = MidiEvent(iter.second.first, 3, (Byte*)malloc(3), true);
if (ev.size() < 3)
ev.set_buffer((Byte*)malloc(3), true);
assert(iter.first);
assert(iter.first->parameter().id() <= INT8_MAX);
@@ -418,11 +553,10 @@ void
MidiModel::append_cc_unlocked(double time, uint8_t number, uint8_t value)
{
Parameter param(MidiCCAutomation, number);
//cerr << "MidiModel " << this << " add CC " << (int)number << " = " << (int)value
// << " @ " << time << endl;
boost::shared_ptr<AutomationControl> control = Automatable::control(param, true);
//cerr << "MidiModel " << this << "(" << control.get() << ") add CC " << (int)number << " = " << (int)value
// << " @ " << time << endl;
control->list()->fast_simple_add(time, (double)value);
}

View File

@@ -589,7 +589,10 @@ MidiTrack::write_controller_messages(MidiBuffer& output_buf, nframes_t start_fra
buf[0] = MIDI_CMD_CONTROL;
MidiEvent ev(0, 3, buf, false);
// Write controller automation
// Write track controller automation
#if 0
// This now lives in MidiModel. Any need for track automation like this?
// Relative Velocity?
if (_session.transport_rolling()) {
for (Controls::const_iterator i = _controls.begin(); i != _controls.end(); ++i) {
const boost::shared_ptr<AutomationList> list = (*i).second->list();
@@ -637,6 +640,7 @@ MidiTrack::write_controller_messages(MidiBuffer& output_buf, nframes_t start_fra
output_buf.copy(cc_buf);
}
}
#endif
// Append immediate events (UI controls)
_immediate_events.read(output_buf, 0, 0, offset + nframes-1); // all stamps = 0

View File

@@ -2151,7 +2151,7 @@ Session::update_route_solo_state ()
shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
if ((*i)->soloed()) {
mute = true;
if (dynamic_cast<Track*>((*i).get())) {