the start (only the start) of MIDI diff commands
authorPaul Davis <paul@linuxaudiosystems.com>
Sun, 6 Sep 2009 18:11:55 +0000 (18:11 +0000)
committerPaul Davis <paul@linuxaudiosystems.com>
Sun, 6 Sep 2009 18:11:55 +0000 (18:11 +0000)
git-svn-id: svn://localhost/ardour2/branches/3.0@5637 d708f5d6-7413-0410-9779-e7cbd77b26cf

gtk2_ardour/ardour3_ui_dark.rc.in
gtk2_ardour/midi_region_view.cc
gtk2_ardour/midi_region_view.h
libs/ardour/ardour/midi_model.h
libs/ardour/enums.cc
libs/ardour/midi_model.cc
libs/ardour/session_state.cc
libs/evoral/evoral/Note.hpp
libs/evoral/src/Note.cpp

index 8046d4d07594807809a107fec8cc88c9d19fd7ff..860f7f4a019abf7662108e5eb8d9abb7e8e0e4e6 100644 (file)
@@ -1636,5 +1636,5 @@ widget "*RegionListWholeFile" style:highest "treeview_parent_node"
 widget "*EditorHScrollbar" style:highest "editor_hscrollbar"
 widget "*OddPortGroups" style:highest "odd_port_groups"
 widget "*EvenPortGroups" style:highest "even_port_groups"
-Widget "*MidiListView" style:highest "white_tree_view"
+widget "*MidiListView" style:highest "white_tree_view"
 Widget "*MidiListView*" style:highest "white_tree_view"
index 5e494605b86fba2e576e41bd0ce61c2f34b43710..d0ec3892628e098039fdd5f94dcb0bc6f502339f 100644 (file)
@@ -261,7 +261,6 @@ MidiRegionView::canvas_event(GdkEvent* ev)
                } else if (ev->key.keyval == GDK_Delete) {
 
                        delete_selection();
-                       apply_command();
                        return true;
 
                } else if (ev->key.keyval == GDK_Tab) {
@@ -309,8 +308,8 @@ MidiRegionView::canvas_event(GdkEvent* ev)
 
                } else if (ev->key.keyval == GDK_Control_L) {
                        return true;
-               }
 
+               }
                return false;
 
        case GDK_KEY_RELEASE:
@@ -584,7 +583,15 @@ MidiRegionView::start_delta_command(string name)
 }
 
 void
-MidiRegionView::command_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
+MidiRegionView::start_diff_command(string name)
+{
+       if (!_diff_command) {
+               _diff_command = _model->new_diff_command(name);
+       }
+}
+
+void
+MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
 {
        if (_delta_command) {
                _delta_command->add(note);
@@ -598,15 +605,25 @@ MidiRegionView::command_add_note(const boost::shared_ptr<NoteType> note, bool se
 }
 
 void
-MidiRegionView::command_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
+MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
 {
        if (_delta_command && ev->note()) {
                _delta_command->remove(ev->note());
        }
 }
+
+void
+MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev, 
+                                   MidiModel::DiffCommand::Property property,
+                                   uint8_t val)
+{
+       if (_diff_command) {
+               _diff_command->change (ev->note(), property, val);
+       }
+}
        
 void
-MidiRegionView::apply_command()
+MidiRegionView::apply_delta()
 {
        if (!_delta_command) {
                return;
@@ -626,7 +643,27 @@ MidiRegionView::apply_command()
 }
 
 void
-MidiRegionView::apply_command_as_subcommand()
+MidiRegionView::apply_diff ()
+{
+       if (!_diff_command) {
+               return;
+       }
+
+       // Mark all selected notes for selection when model reloads
+       for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+               _marked_for_selection.insert((*i)->note());
+       }
+       
+       _model->apply_command(trackview.session(), _diff_command);
+       _diff_command = NULL; 
+       midi_view()->midi_track()->diskstream()->playlist_modified();
+
+       _marked_for_selection.clear();
+       _marked_for_velocity.clear();
+}
+
+void
+MidiRegionView::apply_delta_as_subcommand()
 {
        if (!_delta_command) {
                return;
@@ -645,15 +682,36 @@ MidiRegionView::apply_command_as_subcommand()
        _marked_for_velocity.clear();
 }
 
+void
+MidiRegionView::apply_diff_as_subcommand()
+{
+       if (!_diff_command) {
+               return;
+       }
+
+       // Mark all selected notes for selection when model reloads
+       for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+               _marked_for_selection.insert((*i)->note());
+       }
+       
+       _model->apply_command_as_subcommand(trackview.session(), _diff_command);
+       _diff_command = NULL; 
+       midi_view()->midi_track()->diskstream()->playlist_modified();
+
+       _marked_for_selection.clear();
+       _marked_for_velocity.clear();
+}
+
 void
 MidiRegionView::abort_command()
 {
        delete _delta_command;
-       _delta_command = NULL;
+       _delta_command = 0;
+       delete _diff_command;
+       _diff_command = 0;
        clear_selection();
 }
 
-
 void
 MidiRegionView::redisplay_model()
 {
@@ -1277,7 +1335,7 @@ MidiRegionView::delete_selection()
 
        _selection.clear();
 
-       apply_command ();
+       apply_delta ();
 }
 
 void
@@ -1558,14 +1616,14 @@ MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
 
                copy->set_note(new_pitch);
                
-               command_remove_note(*i);
+               delta_remove_note(*i);
                cerr << "Adding note: " << *copy << endl;
-               command_add_note(copy, (*i)->selected());
+               delta_add_note(copy, (*i)->selected());
 
                i = next;
        }
 
-       apply_command();
+       apply_delta();
        
        // care about notes being moved beyond the upper/lower bounds on the canvas
        if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
@@ -1720,15 +1778,15 @@ MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bo
 
                // resize beginning of note
                if (note_end == CanvasNote::NOTE_ON && current_frame < copy->end_time()) {
-                       command_remove_note(canvas_note);
+                       delta_remove_note(canvas_note);
                        copy->on_event().time() = current_frame;
-                       command_add_note(copy, _selection.find(canvas_note) != _selection.end());
+                       delta_add_note(copy, _selection.find(canvas_note) != _selection.end());
                }
                // resize end of note
                if (note_end == CanvasNote::NOTE_OFF && current_frame > copy->time()) {
-                       command_remove_note(canvas_note);
+                       delta_remove_note(canvas_note);
                        copy->off_event().time() = current_frame;
-                       command_add_note(copy, _selection.find(canvas_note) != _selection.end());
+                       delta_add_note(copy, _selection.find(canvas_note) != _selection.end());
                }
 
                delete resize_rect;
@@ -1736,7 +1794,7 @@ MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bo
        }
 
        _resize_data.clear();
-       apply_command();
+       apply_delta();
 }
 
 void
@@ -1752,25 +1810,23 @@ MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bo
                copy->set_velocity(velocity);                   
        }
 
-       command_remove_note(event);
-       command_add_note(copy, event->selected(), true);
+       delta_remove_note(event);
+       delta_add_note(copy, event->selected(), true);
 }
 
 void
 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
 {
-       const boost::shared_ptr<NoteType> copy(new NoteType(*(event->note().get())));
+       uint8_t new_note;
 
        if (relative) {
-               uint8_t new_note = copy->note() + note;
-               clamp_to_0_127(new_note);
-               copy->set_note(new_note);
+               new_note = event->note()->note() + note;
        } else {
-               copy->set_note(note);                   
+               new_note = note;
        }
 
-       command_remove_note(event);
-       command_add_note(copy, event->selected(), false);
+       clamp_to_0_127 (new_note);
+       diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
 }
 
 void
@@ -1825,8 +1881,8 @@ MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_del
                copy->set_length (copy->length() + end_delta);
        }
 
-       command_remove_note(event);
-       command_add_note(copy, event->selected(), false);
+       delta_remove_note(event);
+       delta_add_note(copy, event->selected(), false);
 }
 
 void
@@ -1848,8 +1904,8 @@ MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime de
                copy->set_time (delta);
        }
 
-       command_remove_note(event);
-       command_add_note(copy, event->selected(), false);
+       delta_remove_note(event);
+       delta_add_note(copy, event->selected(), false);
 }
 
 void
@@ -1888,7 +1944,7 @@ MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
                i = next;
        }
        
-       apply_command();
+       apply_delta();
 }
 
 
@@ -1925,7 +1981,7 @@ MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
                }
        }
 
-       start_delta_command (_("transpose"));
+       start_diff_command (_("transpose"));
 
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
                Selection::iterator next = i;
@@ -1934,7 +1990,7 @@ MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
                i = next;
        }
 
-       apply_command ();
+       apply_diff ();
 }
 
 void
@@ -1971,7 +2027,7 @@ MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool e
                i = next;
        }
 
-       apply_command ();
+       apply_delta ();
 
 }
 
@@ -2032,7 +2088,7 @@ MidiRegionView::nudge_notes (bool forward)
                i = next;
        }
 
-       apply_command ();
+       apply_delta ();
 }
 
 
@@ -2049,13 +2105,13 @@ MidiRegionView::change_channel(uint8_t channel)
 
                copy->set_channel(channel);
                
-               command_remove_note(event);
-               command_add_note(copy, event->selected());
+               delta_remove_note(event);
+               delta_add_note(copy, event->selected());
                
                i = next;
        }
        
-       apply_command();
+       apply_delta();
 }
 
 
@@ -2152,14 +2208,14 @@ MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
                case Copy:
                        break;
                case Cut:
-                       command_remove_note (*i);
+                       delta_remove_note (*i);
                        break;
                case Clear:
                        break;
                }
        }
 
-       apply_command();
+       apply_delta();
 }
 
 MidiCutBuffer*
@@ -2212,7 +2268,7 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
 
                        /* make all newly added notes selected */
 
-                       command_add_note (copied_note, true);
+                       delta_add_note (copied_note, true);
                        end_point = copied_note->end_time();
                }
 
@@ -2233,7 +2289,7 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
                trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
        }
        
-       apply_command ();
+       apply_delta ();
 }
 
 void
@@ -2243,8 +2299,8 @@ MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
        boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
        
        start_delta_command (_("step add"));
-       command_add_note (new_note, true, false);
-       apply_command ();
+       delta_add_note (new_note, true, false);
+       apply_delta();
 
        /* potentially extend region to hold new note */
 
@@ -2333,7 +2389,7 @@ MidiRegionView::replace_selected (NoteList& replacements)
                tmp = i;
                ++tmp;
 
-               command_remove_note (*i);
+               delta_remove_note (*i);
                remove_from_selection (*i);
 
                i = tmp;
@@ -2342,9 +2398,9 @@ MidiRegionView::replace_selected (NoteList& replacements)
        _selection.clear ();
 
        for (NoteList::iterator i = replacements.begin(); i != replacements.end(); ++i) {
-               command_add_note (*i, true, false);
+               delta_add_note (*i, true, false);
        }
        
-       apply_command_as_subcommand ();
+       apply_delta_as_subcommand ();
 }
 
index c32fd2cb69a7172c35c1348affc33cdfc21a8d2a..611ba4c142a354ce4c694071b8fec6347fa8c1c6 100644 (file)
@@ -169,11 +169,16 @@ class MidiRegionView : public RegionView
        void display_model(boost::shared_ptr<ARDOUR::MidiModel> model);
 
        void start_delta_command(std::string name = "midi edit");
-       void command_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity=false);
-       void command_remove_note(ArdourCanvas::CanvasNoteEvent* ev);
+       void delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity=false);
+       void delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev);
 
-       void apply_command();
-       void apply_command_as_subcommand();
+       void start_diff_command(std::string name = "midi edit");
+       void diff_add_change(ArdourCanvas::CanvasNoteEvent* ev, ARDOUR::MidiModel::DiffCommand::Property, uint8_t val);
+
+       void apply_delta();
+       void apply_diff();
+       void apply_delta_as_subcommand();
+       void apply_diff_as_subcommand();
        void abort_command();
 
        void   note_entered(ArdourCanvas::CanvasNoteEvent* ev);
@@ -354,6 +359,7 @@ class MidiRegionView : public RegionView
        ArdourCanvas::CanvasNote**           _active_notes;
        ArdourCanvas::Group*                 _note_group;
        ARDOUR::MidiModel::DeltaCommand*     _delta_command;
+       ARDOUR::MidiModel::DiffCommand*      _diff_command;
 
        MouseState _mouse_state;
        int _pressed_button;
index 967372fa9a221932f43a4b5f8a96f115194eba6a..46d4cf794c5e9153d6beef435b56e83ec7e64128 100644 (file)
@@ -89,10 +89,62 @@ public:
                NoteList _removed_notes;
        };
 
+
+       /** Change note properties.
+        * More efficient than DeltaCommand and has the important property that
+        * it leaves the objects in the MidiModel (Notes) the same, thus
+        * enabling selection and other state to persist across command
+        * do/undo/redo.
+        */
+       class DiffCommand : public Command {
+       public:
+               enum Property {
+                       NoteNumber,
+                       Velocity,
+                       StartTime,
+                       Length,
+                       Channel
+               };
+
+               DiffCommand (boost::shared_ptr<MidiModel> m, const std::string& name);
+               DiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node);
+
+               const std::string& name() const { return _name; }
+               
+               void operator()();
+               void undo();
+               
+               int set_state (const XMLNode&);
+               XMLNode& get_state ();
+               
+               void change (const boost::shared_ptr<Evoral::Note<TimeType> > note, 
+                            Property prop, uint8_t new_value);
+               
+       private:
+               boost::shared_ptr<MidiModel> _model;
+               const std::string            _name;
+
+               struct NotePropertyChange {
+                   DiffCommand::Property property;
+                   boost::shared_ptr<Evoral::Note<TimeType> > note;
+                   uint8_t old_value;
+                   uint8_t new_value;
+               }; 
+               
+               typedef std::list<NotePropertyChange> ChangeList;
+               ChangeList _changes;
+
+               XMLNode &marshal_change(const NotePropertyChange&);
+               NotePropertyChange unmarshal_change(XMLNode *xml_note);
+       };
+
        MidiModel::DeltaCommand* new_delta_command(const std::string name="midi edit");
+       MidiModel::DiffCommand*  new_diff_command(const std::string name="midi edit");
        void                     apply_command(Session& session, Command* cmd);
        void                     apply_command_as_subcommand(Session& session, Command* cmd);
 
+
+
        bool write_to(boost::shared_ptr<MidiSource> source);
                
        // MidiModel doesn't use the normal AutomationList serialisation code
@@ -104,6 +156,8 @@ public:
        
        const MidiSource* midi_source() const { return _midi_source; }
        void set_midi_source(MidiSource* source) { _midi_source = source; } 
+
+       boost::shared_ptr<Evoral::Note<TimeType> > find_note (boost::shared_ptr<Evoral::Note<TimeType> >);
        
 private:
        friend class DeltaCommand;
index efe72ddb4bea4167c0e4340fb3e542b89bb4f078..92810534eebe31a4b75055b8024284f6ad020ee1 100644 (file)
@@ -28,6 +28,7 @@
 #include "ardour/export_profile_manager.h"
 #include "ardour/io.h"
 #include "ardour/location.h"
+#include "ardour/midi_model.h"
 #include "ardour/midi_track.h"
 #include "ardour/mute_master.h"
 #include "ardour/panner.h"
@@ -109,6 +110,7 @@ setup_enum_writer ()
        Delivery::Role _Delivery_Role;
        IO::Direction _IO_Direction;
        MuteMaster::MutePoint _MuteMaster_MutePoint;
+       MidiModel::DiffCommand::Property _MidiModel_DiffCommand_Property;
 
 #define REGISTER(e) enum_writer->register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
 #define REGISTER_BITS(e) enum_writer->register_bits (typeid(e).name(), i, s); i.clear(); s.clear()
@@ -522,4 +524,11 @@ setup_enum_writer ()
        REGISTER_CLASS_ENUM (IO, Input);
        REGISTER_CLASS_ENUM (IO, Output);
        REGISTER (_IO_Direction);
+
+       REGISTER_CLASS_ENUM (MidiModel::DiffCommand, NoteNumber);
+       REGISTER_CLASS_ENUM (MidiModel::DiffCommand, Channel);
+       REGISTER_CLASS_ENUM (MidiModel::DiffCommand, Velocity);
+       REGISTER_CLASS_ENUM (MidiModel::DiffCommand, StartTime);
+       REGISTER_CLASS_ENUM (MidiModel::DiffCommand, Length);
+       REGISTER (_MidiModel_DiffCommand_Property);
 }
index da524307f6a6ff346801f9a899e71cbf7c42997a..60c8829b613869151f28808aff15e9c108c74fc2 100644 (file)
@@ -43,7 +43,7 @@ MidiModel::MidiModel(MidiSource* s, size_t size)
 {
 }
 
-/** Start a new command.
+/** Start a new Delta command.
  *
  * This has no side-effects on the model or Session, the returned command
  * can be held on to for as long as the caller wishes, or discarded without
@@ -56,6 +56,19 @@ MidiModel::new_delta_command(const string name)
        return cmd;
 }
 
+/** Start a new Diff command.
+ *
+ * This has no side-effects on the model or Session, the returned command
+ * 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::DiffCommand*
+MidiModel::new_diff_command(const string name)
+{
+       DiffCommand* cmd = new DiffCommand(_midi_source->model(), name);
+       return cmd;
+}
+
 /** Apply a command.
  *
  * Ownership of cmd is taken, it must not be deleted by the caller.
@@ -225,7 +238,7 @@ MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note)
                length_str >> length;
        } else {
                warning << "note information missing length" << endmsg;
-               note = 1;
+               length = 1;
        }
 
        if ((prop = xml_note->property("velocity")) != 0) {
@@ -286,6 +299,320 @@ MidiModel::DeltaCommand::get_state()
        return *delta_command;
 }
 
+/************** DIFF COMMAND ********************/
+
+#define DIFF_NOTES_ELEMENT "changed_notes"
+#define DIFF_COMMAND_ELEMENT "DiffCommand"
+
+MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
+       : Command(name)
+       , _model(m)
+       , _name(name)
+{
+       assert(_model);
+}
+
+MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const XMLNode& node)
+       : _model(m)
+{
+       assert(_model);
+       set_state(node);
+}
+
+void
+MidiModel::DiffCommand::change(const boost::shared_ptr< Evoral::Note<TimeType> > note, Property prop,
+                              uint8_t new_value)
+{
+       NotePropertyChange change;
+
+       change.note = note;
+       change.property = prop;
+       change.new_value = new_value;
+
+       switch (prop) {
+       case NoteNumber:
+               change.old_value = note->note();
+               break;
+       case Velocity:
+               change.old_value = note->velocity();
+               break;
+       case StartTime:
+               change.old_value = note->time();
+               break;
+       case Length:
+               change.old_value = note->length();
+               break;
+       case Channel:
+               change.old_value = note->channel();
+               break;
+       }
+
+       _changes.push_back (change);
+}
+
+void
+MidiModel::DiffCommand::operator()()
+{
+       Glib::Mutex::Lock lm (_model->_midi_source->mutex());
+       _model->_midi_source->invalidate(); // release model read lock
+       _model->write_lock();
+
+       for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+               Property prop = i->property;
+               switch (prop) {
+               case NoteNumber:
+                       i->note->set_note (i->new_value);
+                       break;
+               case Velocity:
+                       i->note->set_velocity (i->new_value);
+                       break;
+               case StartTime:
+                       i->note->set_time (i->new_value);
+                       break;
+               case Length:
+                       i->note->set_length (i->new_value);
+                       break;
+               case Channel:
+                       i->note->set_channel (i->new_value);
+                       break;
+               }
+       }
+       
+       _model->write_unlock();
+       _model->ContentsChanged(); /* EMIT SIGNAL */
+}
+
+void
+MidiModel::DiffCommand::undo()
+{
+       Glib::Mutex::Lock lm (_model->_midi_source->mutex());
+       _model->_midi_source->invalidate(); // release model read lock
+       _model->write_lock();
+
+       for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+               Property prop = i->property;
+               switch (prop) {
+               case NoteNumber:
+                       i->note->set_note (i->old_value);
+                       break;
+               case Velocity:
+                       i->note->set_velocity (i->old_value);
+                       break;
+               case StartTime:
+                       i->note->set_time (i->old_value);
+                       break;
+               case Length:
+                       i->note->set_length (i->old_value);
+                       break;
+               case Channel:
+                       i->note->set_channel (i->old_value);
+                       break;
+               }
+       }
+
+       _model->write_unlock();
+       _model->ContentsChanged(); /* EMIT SIGNAL */
+}
+
+XMLNode&
+MidiModel::DiffCommand::marshal_change(const NotePropertyChange& change)
+{
+       XMLNode* xml_change = new XMLNode("change");
+       
+       /* first, the change itself */
+
+       xml_change->add_property ("property", enum_2_string (change.property));
+
+       {
+               ostringstream old_value_str (ios::ate);
+               old_value_str << (unsigned int) change.old_value;
+               xml_change->add_property ("old", old_value_str.str());
+       }
+
+       {
+               ostringstream new_value_str (ios::ate);
+               new_value_str << (unsigned int) change.old_value;
+               xml_change->add_property ("new", new_value_str.str());
+       }
+
+       /* now the rest of the note */
+       
+       if (change.property != NoteNumber) {
+               ostringstream note_str(ios::ate);
+               note_str << int(change.note->note());
+               xml_change->add_property("note", note_str.str());
+       }
+       
+       if (change.property != Channel) {
+               ostringstream channel_str(ios::ate);
+               channel_str << int(change.note->channel());
+               xml_change->add_property("channel", channel_str.str());
+       }
+
+       if (change.property != StartTime) {
+               ostringstream time_str(ios::ate);
+               time_str << int(change.note->time());
+               xml_change->add_property("time", time_str.str());
+       }
+
+       if (change.property != Length) {
+               ostringstream length_str(ios::ate);
+               length_str <<(unsigned int) change.note->length();
+               xml_change->add_property("length", length_str.str());
+       }
+
+       if (change.property != Velocity) {
+               ostringstream velocity_str(ios::ate);
+               velocity_str << (unsigned int) change.note->velocity();
+               xml_change->add_property("velocity", velocity_str.str());
+       }
+
+       return *xml_change;
+}
+
+MidiModel::DiffCommand::NotePropertyChange
+MidiModel::DiffCommand::unmarshal_change(XMLNode *xml_change)
+{
+       XMLProperty* prop;
+       NotePropertyChange change;
+       unsigned int note;
+       unsigned int channel;
+       unsigned int time;
+       unsigned int length;
+       unsigned int velocity;
+
+       if ((prop = xml_change->property("property")) != 0) {
+               change.property = (Property) string_2_enum (prop->value(), change.property);
+       } else {
+               fatal << "!!!" << endmsg;
+               /*NOTREACHED*/
+       }
+
+       if ((prop = xml_change->property ("old")) != 0) {
+               istringstream old_str (prop->value());
+               old_str >> change.old_value;
+       } else {
+               fatal << "!!!" << endmsg;
+               /*NOTREACHED*/
+       }
+
+       if ((prop = xml_change->property ("new")) == 0) {
+               istringstream new_str (prop->value());
+               new_str >> change.new_value;
+       } else {
+               fatal << "!!!" << endmsg;
+               /*NOTREACHED*/
+       }
+
+       if (change.property != NoteNumber) {
+               if ((prop = xml_change->property("note")) != 0) {
+                       istringstream note_str(prop->value());
+                       note_str >> note;
+               } else {
+                       warning << "note information missing note value" << endmsg;
+                       note = 127;
+               }
+       } else {
+               note = change.new_value;
+       }
+
+       if (change.property != Channel) {
+               if ((prop = xml_change->property("channel")) != 0) {
+                       istringstream channel_str(prop->value());
+                       channel_str >> channel;
+               } else {
+                       warning << "note information missing channel" << endmsg;
+                       channel = 0;
+               }
+       } else {
+               channel = change.new_value;
+       }
+
+       if (change.property != StartTime) {
+               if ((prop = xml_change->property("time")) != 0) {
+                       istringstream time_str(prop->value());
+                       time_str >> time;
+               } else {
+                       warning << "note information missing time" << endmsg;
+                       time = 0;
+               }
+       } else {
+               time = change.new_value;
+       }
+
+       if (change.property != Length) {
+               if ((prop = xml_change->property("length")) != 0) {
+                       istringstream length_str(prop->value());
+                       length_str >> length;
+               } else {
+                       warning << "note information missing length" << endmsg;
+                       length = 1;
+               }
+       } else {
+               length = change.new_value;
+       }
+
+       if (change.property != Velocity) {
+               if ((prop = xml_change->property("velocity")) != 0) {
+                       istringstream velocity_str(prop->value());
+                       velocity_str >> velocity;
+               } else {
+                       warning << "note information missing velocity" << endmsg;
+                       velocity = 127;
+               }
+       } else {
+               velocity = change.new_value;
+       }
+
+       /* we must point at the instance of the note that is actually in the model.
+          so go look for it ...
+       */
+
+       boost::shared_ptr<Evoral::Note<TimeType> > new_note (new Evoral::Note<TimeType> (channel, time, length, note, velocity));
+
+       change.note = _model->find_note (new_note);
+
+       if (!change.note) {
+               warning << "MIDI note not found in model - programmers should investigate this" << endmsg;
+               /* use the actual new note */
+               change.note = new_note;
+       }
+
+       return change;
+}
+
+int
+MidiModel::DiffCommand::set_state(const XMLNode& diff_command)
+{
+       if (diff_command.name() != string(DIFF_COMMAND_ELEMENT)) {
+               return 1;
+       }
+
+       _changes.clear();
+
+       XMLNode* changed_notes = diff_command.child(DIFF_NOTES_ELEMENT);
+       XMLNodeList notes = changed_notes->children();
+
+       transform (notes.begin(), notes.end(), back_inserter(_changes),
+                  sigc::mem_fun(*this, &DiffCommand::unmarshal_change));
+       
+       return 0;
+}
+
+XMLNode&
+MidiModel::DiffCommand::get_state ()
+{
+       XMLNode* diff_command = new XMLNode(DIFF_COMMAND_ELEMENT);
+       diff_command->add_property("midi-source", _model->midi_source()->id().to_s());
+
+       XMLNode* changes = diff_command->add_child(DIFF_NOTES_ELEMENT);
+       for_each(_changes.begin(), _changes.end(), sigc::compose(
+                        sigc::mem_fun(*changes, &XMLNode::add_child_nocopy),
+                        sigc::mem_fun(*this, &DiffCommand::marshal_change)));
+
+       return *diff_command;
+}
+
 /** 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
@@ -322,3 +649,14 @@ MidiModel::get_state()
        return *node;
 }
 
+boost::shared_ptr<Evoral::Note<MidiModel::TimeType> >
+MidiModel::find_note (boost::shared_ptr<Evoral::Note<TimeType> > other) 
+{
+       Notes::iterator i = find (notes().begin(), notes().end(), other);
+
+       if (i == notes().end()) {
+               return boost::shared_ptr<Evoral::Note<TimeType> > ();
+       }
+       
+       return *i;
+}
index c782882c0f737a0ac2cd1e98cccf7fa9bf5205ca..32a4a50cb44f75279d6a9fc1eea64675e1c46abf 100644 (file)
@@ -2932,6 +2932,16 @@ Session::restore_history (string snapshot_name)
                         } else {
                                 error << "FIXME: Failed to downcast MidiSource for DeltaCommand" << endmsg;
                         }
+                   } else if (n->name() == "DiffCommand") {
+                        PBD::ID  id(n->property("midi-source")->value());
+                        boost::shared_ptr<MidiSource> midi_source = 
+                                boost::dynamic_pointer_cast<MidiSource, Source>(source_by_id(id));
+                        if(midi_source) {
+                                ut->add_command(new MidiModel::DiffCommand(midi_source->model(), *n));                          
+                        } else {
+                                error << "FIXME: Failed to downcast MidiSource for DeltaCommand" << endmsg;
+                        }
+
                    } else {
                            error << string_compose(_("Couldn't figure out how to make a Command out of a %1 XMLNode."), n->name()) << endmsg;
                    }
index b891c2096aad07a415e39bc36f11e26b723355b9..e92094ae788375bcd092077ab4ba46a4d5980dcb 100644 (file)
@@ -72,7 +72,6 @@ private:
        MIDIEvent<Time> _off_event;
 };
 
-
 } // namespace Evoral
 
 template<typename Time>
index cc3d4fee629893438852ccab6fa751879472fb24..9440cde2733cb14d13b1a61dc6c03d0968fc8b82 100644 (file)
@@ -78,21 +78,20 @@ Note<Time>::~Note()
 {
 }
 
-
 template<typename Time>
 const Note<Time>&
-Note<Time>::operator=(const Note<Time>& copy)
+Note<Time>::operator=(const Note<Time>& other)
 {
-       _on_event = copy._on_event;
-       _off_event = copy._off_event;
+       _on_event = other._on_event;
+       _off_event = other._off_event;
        
-       assert(time() == copy.time());
-       assert(end_time() == copy.end_time());
-       assert(note() == copy.note());
-       assert(velocity() == copy.velocity());
-       assert(length() == copy.length());
+       assert(time() == other.time());
+       assert(end_time() == other.end_time());
+       assert(note() == other.note());
+       assert(velocity() == other.velocity());
+       assert(length() == other.length());
        assert(_on_event.channel() == _off_event.channel());
-       assert(channel() == copy.channel());
+       assert(channel() == other.channel());
        
        return *this;
 }