The Big Change: Store time in MidiModel as tempo time, not frame time.
[ardour.git] / libs / ardour / midi_model.cc
index da0fa364b980cd7a97a14938acdb7a10b64aaa16..6573bcfd68bdbac4317a11f77d1d4034e2db7b2d 100644 (file)
@@ -24,6 +24,7 @@
 #include <algorithm>
 #include <stdexcept>
 #include <stdint.h>
+#include <pbd/error.h>
 #include <pbd/enumwriter.h>
 #include <midi++/events.h>
 
 
 using namespace std;
 using namespace ARDOUR;
-
+using namespace PBD;
 
 MidiModel::MidiModel(MidiSource *s, size_t size)
-       : ControlSet()
-       , Automatable(s->session(), "midi model")
-       , Sequence(size)
+       : AutomatableSequence<TimeType>(s->session(), size)
        , _midi_source(s)
 {
+       //cerr << "MidiModel \"" << s->name() << "\" constructed: " << this << endl;
 }
 
 /** Start a new command.
@@ -62,13 +62,12 @@ MidiModel::DeltaCommand* MidiModel::new_delta_command(const string name)
  * The command will constitute one item on the undo stack.
  */
 void
-MidiModel::apply_command(Command* cmd)
+MidiModel::apply_command(Session& session, Command* cmd)
 {
-       _session.begin_reversible_command(cmd->name());
+       session.begin_reversible_command(cmd->name());
        (*cmd)();
-       assert(is_sorted());
-       _session.commit_reversible_command(cmd);
-       _edited = true;
+       session.commit_reversible_command(cmd);
+       set_edited(true);
 }
 
 
@@ -90,7 +89,7 @@ MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m,
 }
 
 void
-MidiModel::DeltaCommand::add(const boost::shared_ptr<Evoral::Note> note)
+MidiModel::DeltaCommand::add(const boost::shared_ptr< Evoral::Note<TimeType> > note)
 {
        //cerr << "MEC: apply" << endl;
        _removed_notes.remove(note);
@@ -98,7 +97,7 @@ MidiModel::DeltaCommand::add(const boost::shared_ptr<Evoral::Note> note)
 }
 
 void
-MidiModel::DeltaCommand::remove(const boost::shared_ptr<Evoral::Note> note)
+MidiModel::DeltaCommand::remove(const boost::shared_ptr< Evoral::Note<TimeType> > note)
 {
        //cerr << "MEC: remove" << endl;
        _added_notes.remove(note);
@@ -110,35 +109,22 @@ 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());
-       double iter_time = -1.0;
-
-       if (reset_iter) {
-               if (_model->_read_iter.get_event_pointer().get()) {
-                       iter_time = _model->_read_iter->time();
-               } else {
-                       cerr << "MidiModel::DeltaCommand::operator(): WARNING: _read_iter points to no event" << endl;
-               }
-               _model->_read_iter = _model->end(); // drop read lock
-       }
-
-       assert( ! _model->_read_iter.locked());
-
+       
        _model->write_lock();
 
-       for (std::list< boost::shared_ptr<Evoral::Note> >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
+       // Store the current seek position so we can restore the read iterator
+       // after modifying the contents of the model
+       const TimeType read_time = _model->read_time();
+
+       for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
                _model->add_note_unlocked(*i);
 
-       for (std::list< boost::shared_ptr<Evoral::Note> >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
+       for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
                _model->remove_note_unlocked(*i);
 
        _model->write_unlock();
-
-       if (reset_iter && iter_time != -1.0) {
-               _model->_read_iter = const_iterator(*_model.get(), iter_time);
-       }
+       // FIXME: race?
+       _model->read_seek(read_time); // restore read position
 
        _model->ContentsChanged(); /* EMIT SIGNAL */
 }
@@ -148,43 +134,28 @@ 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());
-       double iter_time = -1.0;
-
-       if (reset_iter) {
-               if (_model->_read_iter.get_event_pointer().get()) {
-                       iter_time = _model->_read_iter->time();
-               } else {
-                       cerr << "MidiModel::DeltaCommand::undo(): WARNING: _read_iter points to no event" << endl;
-               }
-               _model->_read_iter = _model->end(); // drop read lock
-       }
-
-       assert( ! _model->_read_iter.locked());
-
+       
        _model->write_lock();
 
-       for (std::list< boost::shared_ptr<Evoral::Note> >::iterator i = _added_notes.begin(); i
-                       != _added_notes.end(); ++i)
+       // Store the current seek position so we can restore the read iterator
+       // after modifying the contents of the model
+       const TimeType read_time = _model->read_time();
+
+       for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
                _model->remove_note_unlocked(*i);
 
-       for (std::list< boost::shared_ptr<Evoral::Note> >::iterator i =
-                       _removed_notes.begin(); i != _removed_notes.end(); ++i)
+       for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
                _model->add_note_unlocked(*i);
 
        _model->write_unlock();
-
-       if (reset_iter && iter_time != -1.0) {
-               _model->_read_iter = const_iterator(*_model.get(), iter_time);
-       }
+       // FIXME: race?
+       _model->read_seek(read_time); // restore read position
 
        _model->ContentsChanged(); /* EMIT SIGNAL */
 }
 
 XMLNode&
-MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr<Evoral::Note> note)
+MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr< Evoral::Note<TimeType> > note)
 {
        XMLNode *xml_note = new XMLNode("note");
        ostringstream note_str(ios::ate);
@@ -199,9 +170,9 @@ MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr<Evoral::Note> note
        time_str << int(note->time());
        xml_note->add_property("time", time_str.str());
 
-       ostringstream duration_str(ios::ate);
-       duration_str <<(unsigned int) note->duration();
-       xml_note->add_property("duration", duration_str.str());
+       ostringstream length_str(ios::ate);
+       length_str <<(unsigned int) note->length();
+       xml_note->add_property("length", length_str.str());
 
        ostringstream velocity_str(ios::ate);
        velocity_str << (unsigned int) note->velocity();
@@ -210,29 +181,58 @@ MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr<Evoral::Note> note
        return *xml_note;
 }
 
-boost::shared_ptr<Evoral::Note> MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note)
+boost::shared_ptr< Evoral::Note<MidiModel::TimeType> >
+MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note)
 {
        unsigned int note;
-       istringstream note_str(xml_note->property("note")->value());
-       note_str >> note;
-
+       XMLProperty* prop;
        unsigned int channel;
-       istringstream channel_str(xml_note->property("channel")->value());
-       channel_str >> channel;
-
        unsigned int time;
-       istringstream time_str(xml_note->property("time")->value());
-       time_str >> time;
+       unsigned int length;
+       unsigned int velocity;
 
-       unsigned int duration;
-       istringstream duration_str(xml_note->property("duration")->value());
-       duration_str >> duration;
+       if ((prop = xml_note->property("note")) != 0) {
+               istringstream note_str(prop->value());
+               note_str >> note;
+       } else {
+               warning << "note information missing note value" << endmsg;
+               note = 127;
+       }
 
-       unsigned int velocity;
-       istringstream velocity_str(xml_note->property("velocity")->value());
-       velocity_str >> velocity;
+       if ((prop = xml_note->property("channel")) != 0) {
+               istringstream channel_str(prop->value());
+               channel_str >> channel;
+       } else {
+               warning << "note information missing channel" << endmsg;
+               channel = 0;
+       }
+
+       if ((prop = xml_note->property("time")) != 0) {
+               istringstream time_str(prop->value());
+               time_str >> time;
+       } else {
+               warning << "note information missing time" << endmsg;
+               time = 0;
+       }
 
-       boost::shared_ptr<Evoral::Note> note_ptr(new Evoral::Note(channel, time, duration, note, velocity));
+       if ((prop = xml_note->property("length")) != 0) {
+               istringstream length_str(prop->value());
+               length_str >> length;
+       } else {
+               warning << "note information missing length" << endmsg;
+               note = 1;
+       }
+
+       if ((prop = xml_note->property("velocity")) != 0) {
+               istringstream velocity_str(prop->value());
+               velocity_str >> velocity;
+       } else {
+               warning << "note information missing velocity" << endmsg;
+               velocity = 127;
+       }
+
+       boost::shared_ptr< Evoral::Note<TimeType> > note_ptr(new Evoral::Note<TimeType>(
+                       channel, time, length, note, velocity));
        return note_ptr;
 }
 
@@ -240,7 +240,8 @@ boost::shared_ptr<Evoral::Note> MidiModel::DeltaCommand::unmarshal_note(XMLNode
 #define REMOVED_NOTES_ELEMENT "removed_notes"
 #define DELTA_COMMAND_ELEMENT "DeltaCommand"
 
-int MidiModel::DeltaCommand::set_state(const XMLNode& delta_command)
+int
+MidiModel::DeltaCommand::set_state(const XMLNode& delta_command)
 {
        if (delta_command.name() != string(DELTA_COMMAND_ELEMENT)) {
                return 1;
@@ -261,10 +262,11 @@ int MidiModel::DeltaCommand::set_state(const XMLNode& delta_command)
        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());
+       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(
@@ -279,13 +281,6 @@ XMLNode& MidiModel::DeltaCommand::get_state()
        return *delta_command;
 }
 
-struct EventTimeComparator {
-       typedef const Evoral::Event* value_type;
-       inline bool operator()(const Evoral::Event& a, const Evoral::Event& b) const {
-               return a.time() >= b.time();
-       }
-};
-
 /** Write the model to a MidiSource (i.e. save the model).
  * This is different from manually using read to write to a source in that
  * note off events are written regardless of the track mode.  This is so the
@@ -293,26 +288,30 @@ struct EventTimeComparator {
  * to percussive, save, reload, then switch it back to sustained without
  * destroying the original note durations.
  */
-bool MidiModel::write_to(boost::shared_ptr<MidiSource> source)
+bool
+MidiModel::write_to(boost::shared_ptr<MidiSource> source)
 {
        read_lock();
 
        const bool old_percussive = percussive();
        set_percussive(false);
+
+       source->drop_model();
        
-       for (const_iterator i = begin(); i != end(); ++i) {
-               source->append_event_unlocked(Frames, *i);
+       for (Evoral::Sequence<TimeType>::const_iterator i = begin(); i != end(); ++i) {
+               source->append_event_unlocked_beats(*i);
        }
                
        set_percussive(old_percussive);
        
        read_unlock();
-       _edited = false;
+       set_edited(false);
 
        return true;
 }
 
-XMLNode& MidiModel::get_state()
+XMLNode&
+MidiModel::get_state()
 {
        XMLNode *node = new XMLNode("MidiModel");
        return *node;