diff --git a/libs/ardour/ardour/automation_event.h b/libs/ardour/ardour/automation_event.h index 4f8406bd9b..18190aa9b6 100644 --- a/libs/ardour/ardour/automation_event.h +++ b/libs/ardour/ardour/automation_event.h @@ -238,7 +238,7 @@ class AutomationList : public PBD::StatefulDestructible 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. * diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h index 5f58b29ad8..c4d319f6bc 100644 --- a/libs/ardour/ardour/midi_model.h +++ b/libs/ardour/ardour/midi_model.h @@ -226,6 +226,9 @@ private: WriteNotes _write_notes[16]; bool _writing; bool _edited; + + typedef std::vector< boost::shared_ptr > AutomationLists; + AutomationLists _dirty_automations; const const_iterator _end_iter; diff --git a/libs/ardour/automatable.cc b/libs/ardour/automatable.cc index 8b8b843384..feeef25e7e 100644 --- a/libs/ardour/automatable.cc +++ b/libs/ardour/automatable.cc @@ -198,14 +198,21 @@ Automatable::describe_parameter (Parameter param) { /* derived classes like PluginInsert should override this */ - if (param == Parameter(GainAutomation)) + if (param == Parameter(GainAutomation)) { return _("Fader"); - else if (param.type() == PanAutomation) + } else if (param.type() == PanAutomation) { return (string_compose(_("Pan %1"), param.id())); - else if (param.type() == MidiCCAutomation) - return string_compose("CC %1", param.id()); - else + } else if (param.type() == MidiCCAutomation) { + return string_compose("CC %1 [%2]", param.id(), int(param.channel()) + 1); + } else if (param.type() == MidiPgmChangeAutomation) { + return string_compose("Program [%1]", int(param.channel()) + 1); + } else if (param.type() == MidiPitchBenderAutomation) { + return string_compose("Bender [%1]", int(param.channel()) + 1); + } else if (param.type() == MidiChannelAftertouchAutomation) { + return string_compose("Aftertouch [%1]", int(param.channel()) + 1); + } else { return param.to_string(); + } } void @@ -455,7 +462,11 @@ Automatable::transport_stopped (nframes_t now) boost::shared_ptr Automatable::control_factory(boost::shared_ptr list) { - if (list->parameter().type() == MidiCCAutomation) { + if ( + list->parameter().type() == MidiCCAutomation || + list->parameter().type() == MidiPgmChangeAutomation || + list->parameter().type() == MidiChannelAftertouchAutomation + ) { // FIXME: this will die horribly if this is not a MidiTrack return boost::shared_ptr(new MidiTrack::MidiControl((MidiTrack*)this, list)); } else { diff --git a/libs/ardour/automation_event.cc b/libs/ardour/automation_event.cc index af390953f4..e47de7510d 100644 --- a/libs/ardour/automation_event.cc +++ b/libs/ardour/automation_event.cc @@ -1436,6 +1436,10 @@ AutomationList::get_state () XMLNode& AutomationList::state (bool full) { + cerr << "getting "; + if(full) + cerr << "full "; + cerr << "state for AutomationList " << _parameter.to_string() << " list size: " << size() << endl; XMLNode* root = new XMLNode (X_("AutomationList")); char buf[64]; LocaleGuard lg (X_("POSIX")); diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc index cda36ad50a..b2fe90c019 100644 --- a/libs/ardour/midi_model.cc +++ b/libs/ardour/midi_model.cc @@ -1,22 +1,22 @@ /* - Copyright (C) 2007 Paul Davis - Written by Dave Robillard, 2007 + Copyright (C) 2007 Paul Davis + Written by Dave Robillard, 2007 - 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 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. + 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. + 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. -*/ + */ #define __STDC_LIMIT_MACROS 1 @@ -35,49 +35,38 @@ using namespace std; using namespace ARDOUR; - -void -MidiModel::write_lock() -{ - _lock.writer_lock(); - _automation_lock.lock(); +void MidiModel::write_lock() { + _lock.writer_lock(); + _automation_lock.lock(); } -void -MidiModel::write_unlock() -{ - _lock.writer_unlock(); - _automation_lock.unlock(); +void MidiModel::write_unlock() { + _lock.writer_unlock(); + _automation_lock.unlock(); } -void -MidiModel::read_lock() const -{ - _lock.reader_lock(); - /*_automation_lock.lock();*/ +void MidiModel::read_lock() const { + _lock.reader_lock(); + /*_automation_lock.lock();*/ } -void -MidiModel::read_unlock() const -{ - _lock.reader_unlock(); - /*_automation_lock.unlock();*/ +void MidiModel::read_unlock() const { + _lock.reader_unlock(); + /*_automation_lock.unlock();*/ } // Read iterator (const_iterator) -MidiModel::const_iterator::const_iterator(const MidiModel& model, double t) - : _model(&model) - , _is_end( (t == DBL_MAX) || model.empty()) - , _locked( ! _is_end) -{ +MidiModel::const_iterator::const_iterator(const MidiModel& model, double t) : + _model(&model), _is_end( (t == DBL_MAX) || model.empty()), + _locked( !_is_end) { //cerr << "Created MIDI iterator @ " << t << " (is end: " << _is_end << ")" << endl; - + if (_is_end) return; model.read_lock(); - + _note_iter = model.notes().end(); // find first note which begins after t for (MidiModel::Notes::const_iterator i = model.notes().begin(); i != model.notes().end(); ++i) { @@ -86,18 +75,18 @@ MidiModel::const_iterator::const_iterator(const MidiModel& model, double t) break; } } - + MidiControlIterator earliest_control(boost::shared_ptr(), DBL_MAX, 0.0); _control_iters.reserve(model.controls().size()); for (Automatable::Controls::const_iterator i = model.controls().begin(); - i != model.controls().end(); ++i) { + i != model.controls().end(); ++i) { assert( - i->first.type() == MidiCCAutomation || - i->first.type() == MidiPgmChangeAutomation || - i->first.type() == MidiPitchBenderAutomation || - i->first.type() == MidiChannelAftertouchAutomation + i->first.type() == MidiCCAutomation || + i->first.type() == MidiPgmChangeAutomation || + i->first.type() == MidiPitchBenderAutomation || + i->first.type() == MidiChannelAftertouchAutomation ); double x, y; @@ -106,14 +95,14 @@ MidiModel::const_iterator::const_iterator(const MidiModel& model, double t) //cerr << "MIDI Iterator: CC " << i->first.id() << " (size " << i->second->list()->size() // << ") has no events past " << t << endl; continue; - } + } assert(x >= 0); assert(y >= i->first.min()); assert(y <= i->first.max()); - + const MidiControlIterator new_iter(i->second->list(), x, y); - + //cerr << "MIDI Iterator: CC " << i->first.id() << " added (" << x << ", " << y << ")" << endl; _control_iters.push_back(new_iter); @@ -132,14 +121,14 @@ MidiModel::const_iterator::const_iterator(const MidiModel& model, double t) } if (earliest_control.automation_list && earliest_control.x < _event.time()) - model.control_to_midi_event(_event, earliest_control); + model.control_to_midi_event(_event, earliest_control); else - _control_iter = _control_iters.end(); - + _control_iter = _control_iters.end(); + if (_event.size() == 0) { //cerr << "Created MIDI iterator @ " << t << " is at end." << endl; _is_end = true; - + // FIXME: possible race condition here.... if(_locked) { _model->read_unlock(); @@ -150,23 +139,20 @@ MidiModel::const_iterator::const_iterator(const MidiModel& model, double t) } } - -MidiModel::const_iterator::~const_iterator() -{ +MidiModel::const_iterator::~const_iterator() { if (_locked) { _model->read_unlock(); -} + } } -const MidiModel::const_iterator& -MidiModel::const_iterator::operator++() +const MidiModel::const_iterator& MidiModel::const_iterator::operator++() { if (_is_end) - throw std::logic_error("Attempt to iterate past end of MidiModel"); + throw std::logic_error("Attempt to iterate past end of MidiModel"); /*cerr << "const_iterator::operator++: _event type:" << hex << "0x" << int(_event.type()) - << " buffer: 0x" << int(_event.buffer()[0]) << " 0x" << int(_event.buffer()[1]) - << " 0x" << int(_event.buffer()[2]) << endl;*/ + << " buffer: 0x" << int(_event.buffer()[0]) << " 0x" << int(_event.buffer()[1]) + << " 0x" << int(_event.buffer()[2]) << endl;*/ if(! (_event.is_note() || _event.is_cc() || _event.is_pgm_change() || _event.is_pitch_bender() || _event.is_channel_aftertouch()) ) { cerr << "FAILED event buffer: " << hex << int(_event.buffer()[0]) << int(_event.buffer()[1]) << int(_event.buffer()[2]) << endl; @@ -181,7 +167,7 @@ MidiModel::const_iterator::operator++() // v--- this crashes because of a null pointer in the stl containers linked list chain // the crash occurs in _control_iter->automation_list->size(); const bool ret = _control_iter->automation_list->rt_safe_earliest_event_unlocked( - _control_iter->x, DBL_MAX, x, y, false); + _control_iter->x, DBL_MAX, x, y, false); if (ret) { //cerr << "Incremented " << _control_iter->automation_list->parameter().id() << " to " << x << endl; @@ -207,17 +193,17 @@ MidiModel::const_iterator::operator++() } } - enum Type { NIL, NOTE_ON, NOTE_OFF, AUTOMATION }; - - Type type = NIL; - double t = 0; + enum Type {NIL, NOTE_ON, NOTE_OFF, AUTOMATION}; + + Type type = NIL; + double t = 0; // Next earliest note on if (_note_iter != _model->notes().end()) { type = NOTE_ON; t = (*_note_iter)->time(); } - + // Use the next earliest note off iff it's earlier than the note on if (_model->note_mode() == Sustained && (! _active_notes.empty())) { if (type == NIL || _active_notes.top()->end_time() <= (*_note_iter)->time()) { @@ -225,13 +211,13 @@ MidiModel::const_iterator::operator++() t = _active_notes.top()->end_time(); } } - + // Use the next earliest controller iff it's earlier than the note event if (_control_iter != _control_iters.end() && _control_iter->x != DBL_MAX && _control_iter != old_control_iter) - if (type == NIL || _control_iter->x < t) - type = AUTOMATION; + if (type == NIL || _control_iter->x < t) + type = AUTOMATION; if (type == NOTE_ON) { cerr << "********** MIDI Iterator = note on" << endl; @@ -250,27 +236,23 @@ MidiModel::const_iterator::operator++() _is_end = true; } - assert(_is_end || _event.size() > 0); + assert(_is_end || _event.size()> 0); return *this; } - -bool -MidiModel::const_iterator::operator==(const const_iterator& other) const +bool MidiModel::const_iterator::operator==(const const_iterator& other) const { if (_is_end || other._is_end) - return (_is_end == other._is_end); + return (_is_end == other._is_end); else - return (_event == other._event); + return (_event == other._event); } - -MidiModel::const_iterator& -MidiModel::const_iterator::operator=(const const_iterator& other) +MidiModel::const_iterator& MidiModel::const_iterator::operator=(const const_iterator& other) { if (_locked && _model != other._model) - _model->read_unlock(); + _model->read_unlock(); assert( ! other._event.owns_buffer()); @@ -282,26 +264,19 @@ MidiModel::const_iterator::operator=(const const_iterator& other) _note_iter = other._note_iter; _control_iters = other._control_iters; _control_iter = other._control_iter; - + assert( ! _event.owns_buffer()); - + return *this; } - // MidiModel -MidiModel::MidiModel(MidiSource *s, size_t size) - : Automatable(s->session(), "midi model") - , _notes(size) - , _note_mode(Sustained) - , _writing(false) - , _edited(false) - , _end_iter(*this, DBL_MAX) - , _next_read(UINT32_MAX) - , _read_iter(*this, DBL_MAX) - , _midi_source(s) -{ +MidiModel::MidiModel(MidiSource *s, size_t size) : + Automatable(s->session(), "midi model"), _notes(size), + _note_mode(Sustained), _writing(false), _edited(false), _end_iter( + *this, DBL_MAX), _next_read(UINT32_MAX), _read_iter(*this, + DBL_MAX), _midi_source(s) { assert(_end_iter._is_end); assert( ! _end_iter._locked); } @@ -310,9 +285,8 @@ MidiModel::MidiModel(MidiSource *s, size_t size) * adding \a stamp_offset to each event's timestamp. * \return number of events written to \a dst */ -size_t -MidiModel::read(MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes_t stamp_offset, nframes_t negative_stamp_offset) const -{ +size_t MidiModel::read(MidiRingBuffer& dst, nframes_t start, nframes_t nframes, + nframes_t stamp_offset, nframes_t negative_stamp_offset) const { //cerr << this << " MM::read @ " << start << " frames: " << nframes << " -> " << stamp_offset << endl; //cerr << this << " MM # notes: " << n_notes() << endl; @@ -328,29 +302,28 @@ MidiModel::read(MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes _next_read = start + nframes; while (_read_iter != end() && _read_iter->time() < start + nframes) { - assert(_read_iter->size() > 0); - dst.write(_read_iter->time() + stamp_offset - negative_stamp_offset, _read_iter->size(), _read_iter->buffer()); - + assert(_read_iter->size()> 0); + dst.write(_read_iter->time() + stamp_offset - negative_stamp_offset, + _read_iter->size(), _read_iter->buffer()); + /* - cerr << this << " MidiModel::read event @ " << _read_iter->time() - << " type: " << hex << int(_read_iter->type()) << dec - << " note: " << int(_read_iter->note()) - << " velocity: " << int(_read_iter->velocity()) - << endl; - */ - + cerr << this << " MidiModel::read event @ " << _read_iter->time() + << " type: " << hex << int(_read_iter->type()) << dec + << " note: " << int(_read_iter->note()) + << " velocity: " << int(_read_iter->velocity()) + << endl; + */ + ++_read_iter; ++read_events; } return read_events; } - -bool -MidiModel::control_to_midi_event(MIDI::Event& ev, const MidiControlIterator& iter) const -{ - switch(iter.automation_list->parameter().type()) { +bool MidiModel::control_to_midi_event(MIDI::Event& ev, + const MidiControlIterator& iter) const { + switch (iter.automation_list->parameter().type()) { case MidiCCAutomation: if (ev.size() < 3) ev.set_buffer((Byte*)malloc(3), true); @@ -365,7 +338,7 @@ MidiModel::control_to_midi_event(MIDI::Event& ev, const MidiControlIterator& ite ev.time() = iter.x; ev.size() = 3; return true; - + case MidiPgmChangeAutomation: if (ev.size() < 3) ev.set_buffer((Byte*)malloc(3), true); @@ -380,7 +353,7 @@ MidiModel::control_to_midi_event(MIDI::Event& ev, const MidiControlIterator& ite ev.time() = iter.x; ev.size() = 3; return true; - + case MidiPitchBenderAutomation: if (ev.size() < 3) ev.set_buffer((Byte*)malloc(3), true); @@ -394,7 +367,7 @@ MidiModel::control_to_midi_event(MIDI::Event& ev, const MidiControlIterator& ite ev.buffer()[2] = (((Byte)iter.y) >> 7) & 0x7F; // MSB ev.time() = iter.x; ev.size() = 3; - return true; + return true; case MidiChannelAftertouchAutomation: if (ev.size() < 3) @@ -404,19 +377,19 @@ MidiModel::control_to_midi_event(MIDI::Event& ev, const MidiControlIterator& ite assert(iter.automation_list->parameter().channel() < 16); assert(iter.automation_list->parameter().id() <= INT8_MAX); assert(iter.y <= INT8_MAX); - ev.buffer()[0] = MIDI_CMD_CHANNEL_PRESSURE + iter.automation_list->parameter().channel(); + ev.buffer()[0] + = MIDI_CMD_CHANNEL_PRESSURE + iter.automation_list->parameter().channel(); ev.buffer()[1] = (Byte)iter.y; ev.buffer()[2] = 0; ev.time() = iter.x; ev.size() = 3; return true; - + default: return false; } } - /** Begin a write of events to the model. * * If \a mode is Sustained, complete notes with duration are constructed as note @@ -424,35 +397,31 @@ MidiModel::control_to_midi_event(MIDI::Event& ev, const MidiControlIterator& ite * stored; note off events are discarded entirely and all contained notes will * have duration 0. */ -void -MidiModel::start_write() -{ +void MidiModel::start_write() { //cerr << "MM " << this << " START WRITE, MODE = " << enum_2_string(_note_mode) << endl; write_lock(); _writing = true; for (int i = 0; i < 16; ++i) _write_notes[i].clear(); + + _dirty_automations.clear(); write_unlock(); } - - /** Finish a write of events to the model. * * If \a delete_stuck is true and the current mode is Sustained, note on events * that were never resolved with a corresonding note off will be deleted. * Otherwise they will remain as notes with duration 0. */ -void -MidiModel::end_write(bool delete_stuck) -{ +void MidiModel::end_write(bool delete_stuck) { write_lock(); assert(_writing); - + //cerr << "MM " << this << " END WRITE: " << _notes.size() << " NOTES\n"; if (_note_mode == Sustained && delete_stuck) { - for (Notes::iterator n = _notes.begin(); n != _notes.end() ; ) { + for (Notes::iterator n = _notes.begin(); n != _notes.end() ;) { if ((*n)->duration() == 0) { cerr << "WARNING: Stuck note lost: " << (*n)->note() << endl; n = _notes.erase(n); @@ -471,54 +440,61 @@ MidiModel::end_write(bool delete_stuck) } _write_notes[i].clear(); } + + for(AutomationLists::const_iterator i = _dirty_automations.begin(); i != _dirty_automations.end(); ++i) { + (*i)->Dirty.emit(); + (*i)->lookup_cache().left = -1; + (*i)->search_cache().left = -1; + } _writing = false; write_unlock(); } - /** Append \a in_event to model. NOT realtime safe. * * Timestamps of events in \a buf are expected to be relative to * the start of this model (t=0) and MUST be monotonically increasing * and MUST be >= the latest event currently in the model. */ -void -MidiModel::append(const MIDI::Event& ev) -{ +void MidiModel::append(const MIDI::Event& ev) { write_lock(); _edited = true; - + cerr << "MidiModel::append event type: " << hex << "0x" << int(ev.type()) << endl; assert(_notes.empty() || ev.time() >= _notes.back()->time()); assert(_writing); if (ev.is_note_on()) { - append_note_on_unlocked(ev.channel(), ev.time(), ev.note(), ev.velocity()); + append_note_on_unlocked(ev.channel(), ev.time(), ev.note(), + ev.velocity()); } else if (ev.is_note_off()) { append_note_off_unlocked(ev.channel(), ev.time(), ev.note()); } else if (ev.is_cc()) { - append_automation_event_unlocked(MidiCCAutomation, ev.channel(), ev.time(), ev.cc_number(), ev.cc_value()); + append_automation_event_unlocked(MidiCCAutomation, ev.channel(), + ev.time(), ev.cc_number(), ev.cc_value()); } else if (ev.is_pgm_change()) { - append_automation_event_unlocked(MidiPgmChangeAutomation, ev.channel(), ev.time(), ev.pgm_number(), 0); + append_automation_event_unlocked(MidiPgmChangeAutomation, ev.channel(), + ev.time(), ev.pgm_number(), 0); } else if (ev.is_pitch_bender()) { - append_automation_event_unlocked(MidiPitchBenderAutomation, ev.channel(), ev.time(), ev.pitch_bender_lsb(), ev.pitch_bender_msb()); + append_automation_event_unlocked(MidiPitchBenderAutomation, + ev.channel(), ev.time(), ev.pitch_bender_lsb(), + ev.pitch_bender_msb()); } else if (ev.is_channel_aftertouch()) { - append_automation_event_unlocked(MidiChannelAftertouchAutomation, ev.channel(), ev.time(), ev.channel_aftertouch(), 0); - } else { + append_automation_event_unlocked(MidiChannelAftertouchAutomation, + ev.channel(), ev.time(), ev.channel_aftertouch(), 0); + } else { printf("MM Unknown event type %X\n", ev.type()); } - + write_unlock(); } - -void -MidiModel::append_note_on_unlocked(uint8_t chan, double time, uint8_t note_num, uint8_t velocity) -{ +void MidiModel::append_note_on_unlocked(uint8_t chan, double time, + uint8_t note_num, uint8_t velocity) { /*cerr << "MidiModel " << this << " chan " << (int)chan << - " note " << (int)note_num << " on @ " << time << endl;*/ + " note " << (int)note_num << " on @ " << time << endl;*/ assert(chan < 16); assert(_writing); @@ -529,16 +505,14 @@ MidiModel::append_note_on_unlocked(uint8_t chan, double time, uint8_t note_num, //cerr << "MM Sustained: Appending active note on " << (unsigned)(uint8_t)note_num << endl; _write_notes[chan].push_back(_notes.size() - 1); }/* else { - cerr << "MM Percussive: NOT appending active note on" << endl; - }*/ + cerr << "MM Percussive: NOT appending active note on" << endl; + }*/ } - -void -MidiModel::append_note_off_unlocked(uint8_t chan, double time, uint8_t note_num) -{ +void MidiModel::append_note_off_unlocked(uint8_t chan, double time, + uint8_t note_num) { /*cerr << "MidiModel " << this << " chan " << (int)chan << - " note " << (int)note_num << " off @ " << time << endl;*/ + " note " << (int)note_num << " off @ " << time << endl;*/ assert(chan < 16); assert(_writing); @@ -550,13 +524,14 @@ MidiModel::append_note_off_unlocked(uint8_t chan, double time, uint8_t note_num) } /* FIXME: make _write_notes fixed size (127 noted) for speed */ - + /* FIXME: note off velocity for that one guy out there who actually has * keys that send it */ bool resolved = false; - for (WriteNotes::iterator n = _write_notes[chan].begin(); n != _write_notes[chan].end(); ++n) { + for (WriteNotes::iterator n = _write_notes[chan].begin(); n + != _write_notes[chan].end(); ++n) { Note& note = *_notes[*n].get(); //cerr << (unsigned)(uint8_t)note.note() << " ? " << (unsigned)note_num << endl; if (note.note() == note_num) { @@ -571,24 +546,22 @@ MidiModel::append_note_off_unlocked(uint8_t chan, double time, uint8_t note_num) if (!resolved) cerr << "MidiModel " << this << " spurious note off chan " << (int)chan - << ", note " << (int)note_num << " @ " << time << endl; + << ", note " << (int)note_num << " @ " << time << endl; } - -void -MidiModel::append_automation_event_unlocked(AutomationType type, uint8_t chan, double time, uint8_t first_byte, uint8_t second_byte) -{ +void MidiModel::append_automation_event_unlocked(AutomationType type, + uint8_t chan, double time, uint8_t first_byte, uint8_t second_byte) { //cerr << "MidiModel " << this << " chan " << (int)chan << // " CC " << (int)number << " = " << (int)value << " @ " << time << endl; - + assert(chan < 16); assert(_writing); _edited = true; double value; - + uint32_t id = 0; - - switch(type) { + + switch (type) { case MidiCCAutomation: id = first_byte; value = double(second_byte); @@ -601,55 +574,50 @@ MidiModel::append_automation_event_unlocked(AutomationType type, uint8_t chan, d case MidiPitchBenderAutomation: id = 0; value = double((0x7F & second_byte) << 7 | (0x7F & first_byte)); - break; + break; default: assert(false); } - + Parameter param(type, id, chan); boost::shared_ptr control = Automatable::control(param, true); control->list()->fast_simple_add(time, value); + cerr << "control list size after fast simple add: " << control->list()->size() << endl; } -void -MidiModel::add_note_unlocked(const boost::shared_ptr note) -{ +void MidiModel::add_note_unlocked(const boost::shared_ptr note) { //cerr << "MidiModel " << this << " add note " << (int)note.note() << " @ " << note.time() << endl; _edited = true; - Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note, note_time_comparator); + Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note, + note_time_comparator); _notes.insert(i, note); } - -void -MidiModel::remove_note_unlocked(const boost::shared_ptr note) -{ +void MidiModel::remove_note_unlocked(const boost::shared_ptr note) { _edited = true; //cerr << "MidiModel " << this << " remove note " << (int)note.note() << " @ " << note.time() << endl; - for(Notes::iterator n = _notes.begin(); n != _notes.end(); ++n) { + for (Notes::iterator n = _notes.begin(); n != _notes.end(); ++n) { Note& _n = *(*n); const Note& _note = *note; // TODO: There is still the issue, that after restarting ardour // persisted undo does not work, because of rounding errors in the // event times after saving/restoring to/from MIDI files cerr << "======================================= " << endl; - cerr << int(_n.note()) << "@" << int(_n.time()) << "[" << int(_n.channel()) << "] --" << int(_n.duration()) << "-- #" << int(_n.velocity()) << endl; - cerr << int(_note.note()) << "@" << int(_note.time()) << "[" << int(_note.channel()) << "] --" << int(_note.duration()) << "-- #" << int(_note.velocity()) << endl; + cerr << int(_n.note()) << "@" << int(_n.time()) << "[" << int(_n.channel()) << "] --" << int(_n.duration()) << "-- #" << int(_n.velocity()) << endl; + cerr << int(_note.note()) << "@" << int(_note.time()) << "[" << int(_note.channel()) << "] --" << int(_note.duration()) << "-- #" << int(_note.velocity()) << endl; cerr << "Equal: " << bool(_n == _note) << endl; cerr << endl << endl; - if(_n == _note) { + if (_n == _note) { _notes.erase(n); // we have to break here, because erase invalidates all iterators, ie. n itself break; - } + } } } /** Slow! for debugging only. */ #ifndef NDEBUG -bool -MidiModel::is_sorted() const -{ +bool MidiModel::is_sorted() const { bool t = 0; for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) if ((*n)->time() < t) @@ -667,22 +635,17 @@ MidiModel::is_sorted() const * can be held on to for as long as the caller wishes, or discarded without * formality, until apply_command is called and ownership is taken. */ -MidiModel::DeltaCommand* -MidiModel::new_delta_command(const string name) -{ - DeltaCommand* cmd = new DeltaCommand(_midi_source->model(), name); +MidiModel::DeltaCommand* MidiModel::new_delta_command(const string name) { + DeltaCommand* cmd = new DeltaCommand(_midi_source->model(), name); return cmd; } - /** Apply a command. * * Ownership of cmd is taken, it must not be deleted by the caller. * The command will constitute one item on the undo stack. */ -void -MidiModel::apply_command(Command* cmd) -{ +void MidiModel::apply_command(Command* cmd) { _session.begin_reversible_command(cmd->name()); (*cmd)(); assert(is_sorted()); @@ -690,50 +653,71 @@ MidiModel::apply_command(Command* cmd) _edited = true; } - // MidiEditCommand -MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr m, const std::string& name) - : Command(name), _model(m), _name(name) -{ - +MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr m, + const std::string& name) : + Command(name), _model(m), _name(name) { + } -MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr m, const XMLNode& node) - : _model(m) -{ +MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr m, + const XMLNode& node) : + _model(m) { set_state(node); } -void -MidiModel::DeltaCommand::add(const boost::shared_ptr note) -{ +void MidiModel::DeltaCommand::add(const boost::shared_ptr note) { //cerr << "MEC: apply" << endl; _removed_notes.remove(note); _added_notes.push_back(note); } - -void -MidiModel::DeltaCommand::remove(const boost::shared_ptr note) -{ +void MidiModel::DeltaCommand::remove(const boost::shared_ptr note) { //cerr << "MEC: remove" << endl; _added_notes.remove(note); _removed_notes.push_back(note); } - -void -MidiModel::DeltaCommand::operator()() +void MidiModel::DeltaCommand::operator()() { // This could be made much faster by using a priority_queue for added and // removed notes (or sort here), and doing a single iteration over _model - + // Need to reset iterator to drop the read lock it holds, or we'll deadlock - const bool reset_iter = (_model->_read_iter.locked()); - const double iter_time = _model->_read_iter->time(); + const bool reset_iter = (_model->_read_iter.locked()); + const double iter_time = _model->_read_iter->time(); + + if (reset_iter) + _model->_read_iter = _model->end(); // drop read lock + + assert( ! _model->_read_iter.locked()); + + _model->write_lock(); + + for (std::list< boost::shared_ptr >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) + _model->add_note_unlocked(*i); + + for (std::list< boost::shared_ptr >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) + _model->remove_note_unlocked(*i); + + _model->write_unlock(); + + if (reset_iter) + _model->_read_iter = const_iterator(*_model.get(), iter_time); + + _model->ContentsChanged(); /* EMIT SIGNAL */ +} + +void MidiModel::DeltaCommand::undo() { + // This could be made much faster by using a priority_queue for added and + // removed notes (or sort here), and doing a single iteration over _model + + // Need to reset iterator to drop the read lock it holds, or we'll deadlock + const bool reset_iter = (_model->_read_iter.locked()); + const double iter_time = _model->_read_iter->time(); if (reset_iter) _model->_read_iter = _model->end(); // drop read lock @@ -741,56 +725,25 @@ MidiModel::DeltaCommand::operator()() assert( ! _model->_read_iter.locked()); _model->write_lock(); - - for (std::list< boost::shared_ptr >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) - _model->add_note_unlocked(*i); - - for (std::list< boost::shared_ptr >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) + + for (std::list< boost::shared_ptr >::iterator i = _added_notes.begin(); i + != _added_notes.end(); ++i) _model->remove_note_unlocked(*i); - + + for (std::list< boost::shared_ptr >::iterator i = + _removed_notes.begin(); i != _removed_notes.end(); ++i) + _model->add_note_unlocked(*i); + _model->write_unlock(); if (reset_iter) _model->_read_iter = const_iterator(*_model.get(), iter_time); - + _model->ContentsChanged(); /* EMIT SIGNAL */ } - -void -MidiModel::DeltaCommand::undo() -{ - // This could be made much faster by using a priority_queue for added and - // removed notes (or sort here), and doing a single iteration over _model - - // Need to reset iterator to drop the read lock it holds, or we'll deadlock - const bool reset_iter = (_model->_read_iter.locked()); - const double iter_time = _model->_read_iter->time(); - - if (reset_iter) - _model->_read_iter = _model->end(); // drop read lock - - assert( ! _model->_read_iter.locked()); - - _model->write_lock(); - - for (std::list< boost::shared_ptr >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) - _model->remove_note_unlocked(*i); - - for (std::list< boost::shared_ptr >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) - _model->add_note_unlocked(*i); - - _model->write_unlock(); - - if (reset_iter) - _model->_read_iter = const_iterator(*_model.get(), iter_time); - - _model->ContentsChanged(); /* EMIT SIGNAL */ -} - -XMLNode & -MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr note) -{ +XMLNode & MidiModel::DeltaCommand::marshal_note( + const boost::shared_ptr note) { XMLNode *xml_note = new XMLNode("note"); ostringstream note_str(ios::ate); note_str << int(note->note()); @@ -798,8 +751,8 @@ MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr note) ostringstream channel_str(ios::ate); channel_str << int(note->channel()); - xml_note->add_property("channel", channel_str.str()); - + xml_note->add_property("channel", channel_str.str()); + ostringstream time_str(ios::ate); time_str << int(note->time()); xml_note->add_property("time", time_str.str()); @@ -811,13 +764,12 @@ MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr note) ostringstream velocity_str(ios::ate); velocity_str << (unsigned int) note->velocity(); xml_note->add_property("velocity", velocity_str.str()); - + return *xml_note; } -boost::shared_ptr -MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note) -{ +boost::shared_ptr MidiModel::DeltaCommand::unmarshal_note( + XMLNode *xml_note) { unsigned int note; istringstream note_str(xml_note->property("note")->value()); note_str >> note; @@ -837,7 +789,7 @@ MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note) unsigned int velocity; istringstream velocity_str(xml_note->property("velocity")->value()); velocity_str >> velocity; - + boost::shared_ptr note_ptr(new Note(channel, time, duration, note, velocity)); return note_ptr; } @@ -846,59 +798,51 @@ MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note) #define REMOVED_NOTES_ELEMENT "removed_notes" #define DELTA_COMMAND_ELEMENT "DeltaCommand" -int -MidiModel::DeltaCommand::set_state (const XMLNode& delta_command) -{ - if(delta_command.name() != string(DELTA_COMMAND_ELEMENT)) { +int MidiModel::DeltaCommand::set_state(const XMLNode& delta_command) { + if (delta_command.name() != string(DELTA_COMMAND_ELEMENT)) { return 1; } - + _added_notes.clear(); XMLNode *added_notes = delta_command.child(ADDED_NOTES_ELEMENT); XMLNodeList notes = added_notes->children(); - transform(notes.begin(), notes.end(), back_inserter(_added_notes), - sigc::mem_fun(*this, &DeltaCommand::unmarshal_note)); - + transform(notes.begin(), notes.end(), back_inserter(_added_notes), + sigc::mem_fun(*this, &DeltaCommand::unmarshal_note)); + _removed_notes.clear(); XMLNode *removed_notes = delta_command.child(REMOVED_NOTES_ELEMENT); notes = removed_notes->children(); - transform(notes.begin(), notes.end(), back_inserter(_removed_notes), + transform(notes.begin(), notes.end(), back_inserter(_removed_notes), sigc::mem_fun(*this, &DeltaCommand::unmarshal_note)); - + return 0; } -XMLNode& -MidiModel::DeltaCommand::get_state () -{ +XMLNode& MidiModel::DeltaCommand::get_state() { XMLNode *delta_command = new XMLNode(DELTA_COMMAND_ELEMENT); delta_command->add_property("midi_source", _model->midi_source()->id().to_s()); - - XMLNode *added_notes = delta_command->add_child(ADDED_NOTES_ELEMENT); - for_each(_added_notes.begin(), _added_notes.end(), - sigc::compose(sigc::mem_fun(*added_notes, &XMLNode::add_child_nocopy), - sigc::mem_fun(*this, &DeltaCommand::marshal_note))); - - XMLNode *removed_notes = delta_command->add_child(REMOVED_NOTES_ELEMENT); - for_each(_removed_notes.begin(), _removed_notes.end(), - sigc::compose(sigc::mem_fun(*removed_notes, &XMLNode::add_child_nocopy), - sigc::mem_fun(*this, &DeltaCommand::marshal_note))); + + XMLNode *added_notes = delta_command->add_child(ADDED_NOTES_ELEMENT); + for_each(_added_notes.begin(), _added_notes.end(), sigc::compose( + sigc::mem_fun(*added_notes, &XMLNode::add_child_nocopy), + sigc::mem_fun(*this, &DeltaCommand::marshal_note))); + + XMLNode *removed_notes = delta_command->add_child(REMOVED_NOTES_ELEMENT); + for_each(_removed_notes.begin(), _removed_notes.end(), sigc::compose( + sigc::mem_fun(*removed_notes, &XMLNode::add_child_nocopy), + sigc::mem_fun(*this, &DeltaCommand::marshal_note))); return *delta_command; } - struct EventTimeComparator { typedef const MIDI::Event* value_type; - inline bool operator()(const MIDI::Event* a, - const MIDI::Event* b) const { + inline bool operator()(const MIDI::Event* a, const MIDI::Event* b) const { return a->time() >= b->time(); } }; -bool -MidiModel::write_to(boost::shared_ptr source) -{ +bool MidiModel::write_to(boost::shared_ptr source) { cerr << "Writing model to " << source->name() << endl; /* This could be done using a temporary MidiRingBuffer and using @@ -917,15 +861,15 @@ MidiModel::write_to(boost::shared_ptr source) LaterNoteEndComparator cmp; ActiveNotes active_notes(cmp); - + EventTimeComparator comp; typedef std::priority_queue< - const MIDI::Event*, - std::deque, - EventTimeComparator> MidiEvents; - + const MIDI::Event*, + std::deque, + EventTimeComparator> MidiEvents; + MidiEvents events(comp); - + /* Why sort manyally, when a priority queue does the job for us, * (I am probably wrong here, but I needed that to test program * change code quickly) ??? @@ -934,8 +878,9 @@ MidiModel::write_to(boost::shared_ptr source) for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) { // Write any pending note offs earlier than this note on - while ( ! active_notes.empty() ) { - const boost::shared_ptr earliest_off = active_notes.top(); + while ( !active_notes.empty() ) { + const boost::shared_ptr earliest_off = + active_notes.top(); const MIDI::Event& off_ev = earliest_off->off_event(); if (off_ev.time() <= (*n)->time()) { events.push(&off_ev); @@ -950,43 +895,35 @@ MidiModel::write_to(boost::shared_ptr source) if ((*n)->duration() > 0) active_notes.push(*n); } - + // Write any trailing note offs - while ( ! active_notes.empty() ) { + while ( !active_notes.empty() ) { events.push(&active_notes.top()->off_event()); active_notes.pop(); } - - while(!events.empty()) { + + while (!events.empty()) { source->append_event_unlocked(Frames, *events.top()); - cerr << "MidiModel::write_to appending event with time:" << dec << int(events.top()->time()) << hex - << " buffer: 0x" << int(events.top()->buffer()[0]) << " 0x" << int(events.top()->buffer()[1]) - << " 0x" << int(events.top()->buffer()[2]) << endl; + //cerr << "MidiModel::write_to appending event with time:" << dec << int(events.top()->time()) << hex << " buffer: 0x" << int(events.top()->buffer()[0]) << " 0x" << int(events.top()->buffer()[1]) << " 0x" << int(events.top()->buffer()[2]) << endl; events.pop(); } - + _edited = false; - + read_unlock(); return true; } -XMLNode& -MidiModel::get_state() -{ +XMLNode& MidiModel::get_state() { XMLNode *node = new XMLNode("MidiModel"); return *node; } -const MidiSource * -MidiModel::midi_source() const -{ - return _midi_source; +const MidiSource * MidiModel::midi_source() const { + return _midi_source; } -void -MidiModel::set_midi_source(MidiSource *source) -{ - _midi_source = source; -} +void MidiModel::set_midi_source(MidiSource *source) { + _midi_source = source; +} diff --git a/libs/ardour/midi_track.cc b/libs/ardour/midi_track.cc index 9cdf36144b..a4a47e8be5 100644 --- a/libs/ardour/midi_track.cc +++ b/libs/ardour/midi_track.cc @@ -724,11 +724,35 @@ MidiTrack::write_immediate_event(size_t size, const Byte* buf) void MidiTrack::MidiControl::set_value(float val) { - assert(val >= 0); - assert(val <= 127.0); + assert(val >= _list->parameter().min()); + assert(val <= _list->parameter().max()); if ( ! _list->automation_playback()) { - Byte ev[3] = { MIDI_CMD_CONTROL, _list->parameter().id(), (int)val }; + Byte ev[3] = { _list->parameter().channel(), int(val), 0.0 }; + switch(AutomationType type = _list->parameter().type()) { + case MidiCCAutomation: + ev[0] += MIDI_CMD_CONTROL; + ev[1] = _list->parameter().id(); + ev[2] = int(val); + break; + + case MidiPgmChangeAutomation: + ev[0] += MIDI_CMD_PGM_CHANGE; + break; + + case MidiChannelAftertouchAutomation: + ev[0] += MIDI_CMD_CHANNEL_PRESSURE; + break; + + case MidiPitchBenderAutomation: + ev[0] += MIDI_CMD_BENDER; + ev[1] = 0x7F & int(val); + ev[2] = 0x7F & (int(val) >> 7); + break; + + default: + assert(false); + } _route->write_immediate_event(3, ev); } diff --git a/libs/ardour/note.cc b/libs/ardour/note.cc index 27f50e4092..0ddd745242 100644 --- a/libs/ardour/note.cc +++ b/libs/ardour/note.cc @@ -40,6 +40,7 @@ Note::Note(uint8_t chan, double t, double d, uint8_t n, uint8_t v) assert(duration() == d); assert(note() == n); assert(velocity() == v); + assert(_on_event.channel() == _off_event.channel()); } @@ -64,6 +65,8 @@ Note::Note(const Note& copy) assert(note() == copy.note()); assert(velocity() == copy.velocity()); assert(duration() == copy.duration()); + assert(_on_event.channel() == _off_event.channel()); + assert(channel() == copy.channel()); } @@ -86,7 +89,9 @@ Note::operator=(const Note& copy) assert(note() == copy.note()); assert(velocity() == copy.velocity()); assert(duration() == copy.duration()); - + assert(_on_event.channel() == _off_event.channel()); + assert(channel() == copy.channel()); + return *this; } diff --git a/libs/ardour/parameter.cc b/libs/ardour/parameter.cc index 13834688a3..aa480e47a2 100644 --- a/libs/ardour/parameter.cc +++ b/libs/ardour/parameter.cc @@ -87,13 +87,13 @@ Parameter::to_string() const } else if (_type == PluginAutomation) { return string_compose("parameter-%1", _id); } else if (_type == MidiCCAutomation) { - return string_compose("midicc-%1-%2", _channel, _id); + return string_compose("midicc-%1-%2", int(_channel), _id); } else if (_type == MidiPgmChangeAutomation) { - return string_compose("midi_pgm_change-%1", _channel); + return string_compose("midi-pgm-change-%1", int(_channel)); } else if (_type == MidiPitchBenderAutomation) { - return string_compose("midi_pitch_bender-%1", _channel); + return string_compose("midi-pitch-bender-%1", int(_channel)); } else if (_type == MidiChannelAftertouchAutomation) { - return string_compose("midi_channel_aftertouch-%1", _channel); + return string_compose("midi-channel-aftertouch-%1", int(_channel)); } else { PBD::warning << "Uninitialized Parameter to_string() called." << endmsg; return ""; diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc index 132cff27d9..371824b833 100644 --- a/libs/ardour/smf_source.cc +++ b/libs/ardour/smf_source.cc @@ -451,8 +451,7 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt) void SMFSource::append_event_unlocked(EventTimeUnit unit, const MIDI::Event& ev) { - printf("%s - append chan = %u, time = %lf, size = %u, data = ", _path.c_str(), - (unsigned)ev.channel(), ev.time(), ev.size()); + //printf("%s - append chan = %u, time = %lf, size = %u, data = ", _path.c_str(), (unsigned)ev.channel(), ev.time(), ev.size()); for (size_t i=0; i < ev.size(); ++i) { printf("%X ", ev.buffer()[i]); }