+MidiModel::NoteDiffCommand::operator() ()
+{
+ {
+ MidiModel::WriteLock lock(_model->edit_lock());
+
+ for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
+ if (!_model->add_note_unlocked(*i)) {
+ /* failed to add it, so don't leave it in the removed list, to
+ avoid apparent errors on undo.
+ */
+ _removed_notes.remove (*i);
+ }
+ }
+
+ for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
+ _model->remove_note_unlocked(*i);
+ }
+
+ /* notes we modify in a way that requires remove-then-add to maintain ordering */
+ set<NotePtr> temporary_removals;
+
+ for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+ Property prop = i->property;
+
+ if (!i->note) {
+ /* note found during deserialization, so try
+ again now that the model state is different.
+ */
+ i->note = _model->find_note (i->note_id);
+ assert (i->note);
+ }
+
+ switch (prop) {
+ case NoteNumber:
+ if (temporary_removals.find (i->note) == temporary_removals.end()) {
+ _model->remove_note_unlocked (i->note);
+ temporary_removals.insert (i->note);
+ }
+ i->note->set_note (i->new_value.get_int());
+ break;
+
+ case StartTime:
+ if (temporary_removals.find (i->note) == temporary_removals.end()) {
+ _model->remove_note_unlocked (i->note);
+ temporary_removals.insert (i->note);
+ }
+ i->note->set_time (i->new_value.get_beats());
+ break;
+
+ case Channel:
+ if (temporary_removals.find (i->note) == temporary_removals.end()) {
+ _model->remove_note_unlocked (i->note);
+ temporary_removals.insert (i->note);
+ }
+ i->note->set_channel (i->new_value.get_int());
+ break;
+
+ /* no remove-then-add required for these properties, since we do not index them
+ */
+
+ case Velocity:
+ i->note->set_velocity (i->new_value.get_int());
+ break;
+
+ case Length:
+ i->note->set_length (i->new_value.get_beats());
+ break;
+
+ }
+ }
+
+ for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
+ NoteDiffCommand side_effects (model(), "side effects");
+ if (_model->add_note_unlocked (*i, &side_effects)) {
+ /* The note was re-added ok */
+ *this += side_effects;
+ } else {
+ /* The note that we removed earlier could not be re-added. This change record
+ must say that the note was removed. We'll keep the changes we made, though,
+ as if the note is re-added by the undo the changes must also be undone.
+ */
+ _removed_notes.push_back (*i);
+ }
+ }
+
+ if (!side_effect_removals.empty()) {
+ cerr << "SER: \n";
+ for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
+ cerr << "\t" << *i << ' ' << **i << endl;
+ }
+ }
+ }
+
+ _model->ContentsChanged(); /* EMIT SIGNAL */
+}
+
+void
+MidiModel::NoteDiffCommand::undo ()
+{
+ {
+ MidiModel::WriteLock lock(_model->edit_lock());
+
+ for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
+ _model->remove_note_unlocked(*i);
+ }
+
+ /* Apply changes first; this is important in the case of a note change which
+ resulted in the note being removed by the overlap checker. If the overlap
+ checker removes a note, it will be in _removed_notes. We are going to re-add
+ it below, but first we must undo the changes we made so that the overlap
+ checker doesn't refuse the re-add.
+ */
+
+ /* notes we modify in a way that requires remove-then-add to maintain ordering */
+ set<NotePtr> temporary_removals;
+
+
+ /* lazily discover any affected notes that were not discovered when
+ * loading the history because of deletions, etc.
+ */
+
+ for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+ if (!i->note) {
+ i->note = _model->find_note (i->note_id);
+ assert (i->note);
+ }
+ }
+
+ for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+ Property prop = i->property;
+
+ switch (prop) {
+ case NoteNumber:
+ if (temporary_removals.find (i->note) == temporary_removals.end() &&
+ find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
+
+ /* We only need to mark this note for re-add if (a) we haven't
+ already marked it and (b) it isn't on the _removed_notes
+ list (which means that it has already been removed and it
+ will be re-added anyway)
+ */
+
+ _model->remove_note_unlocked (i->note);
+ temporary_removals.insert (i->note);
+ }
+ i->note->set_note (i->old_value.get_int());
+ break;
+
+ case StartTime:
+ if (temporary_removals.find (i->note) == temporary_removals.end() &&
+ find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
+
+ /* See above ... */
+
+ _model->remove_note_unlocked (i->note);
+ temporary_removals.insert (i->note);
+ }
+ i->note->set_time (i->old_value.get_beats());
+ break;
+
+ case Channel:
+ if (temporary_removals.find (i->note) == temporary_removals.end() &&
+ find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
+
+ /* See above ... */
+
+ _model->remove_note_unlocked (i->note);
+ temporary_removals.insert (i->note);
+ }
+ i->note->set_channel (i->old_value.get_int());
+ break;
+
+ /* no remove-then-add required for these properties, since we do not index them
+ */
+
+ case Velocity:
+ i->note->set_velocity (i->old_value.get_int());
+ break;
+
+ case Length:
+ i->note->set_length (i->old_value.get_beats());
+ break;
+ }
+ }
+
+ for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
+ _model->add_note_unlocked(*i);
+ }
+
+ for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
+ _model->add_note_unlocked (*i);
+ }
+
+ /* finally add back notes that were removed by the "do". we don't care
+ about side effects here since the model should be back to its original
+ state once this is done.
+ */
+
+ for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
+ _model->add_note_unlocked (*i);
+ }
+ }
+
+ _model->ContentsChanged(); /* EMIT SIGNAL */
+}
+
+XMLNode&
+MidiModel::NoteDiffCommand::marshal_note(const NotePtr note)
+{
+ XMLNode* xml_note = new XMLNode("note");
+
+ {
+ ostringstream id_str(ios::ate);
+ id_str << int(note->id());
+ xml_note->add_property("id", id_str.str());
+ }
+
+ {
+ ostringstream note_str(ios::ate);
+ note_str << int(note->note());
+ xml_note->add_property("note", note_str.str());
+ }
+
+ {
+ ostringstream channel_str(ios::ate);
+ channel_str << int(note->channel());
+ xml_note->add_property("channel", channel_str.str());
+ }
+
+ {
+ ostringstream time_str(ios::ate);
+ time_str << note->time();
+ xml_note->add_property("time", time_str.str());
+ }
+
+ {
+ ostringstream length_str(ios::ate);
+ length_str << note->length();
+ xml_note->add_property("length", length_str.str());
+ }
+
+ {
+ ostringstream velocity_str(ios::ate);
+ velocity_str << (unsigned int) note->velocity();
+ xml_note->add_property("velocity", velocity_str.str());
+ }
+
+ return *xml_note;
+}
+
+Evoral::Sequence<MidiModel::TimeType>::NotePtr
+MidiModel::NoteDiffCommand::unmarshal_note (XMLNode *xml_note)
+{
+ unsigned int note;
+ XMLProperty* prop;
+ unsigned int channel;
+ MidiModel::TimeType time;
+ MidiModel::TimeType length;
+ unsigned int velocity;
+ gint id;
+
+ if ((prop = xml_note->property("id")) != 0) {
+ istringstream id_str(prop->value());
+ id_str >> id;
+ } else {
+ error << "note information missing ID value" << endmsg;
+ id = -1;
+ }
+
+ 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;
+ }
+
+ 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 = MidiModel::TimeType();
+ }
+
+ if ((prop = xml_note->property("length")) != 0) {
+ istringstream length_str(prop->value());
+ length_str >> length;
+ } else {
+ warning << "note information missing length" << endmsg;
+ length = MidiModel::TimeType(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;
+ }
+
+ NotePtr note_ptr(new Evoral::Note<TimeType>(channel, time, length, note, velocity));
+ note_ptr->set_id (id);
+
+ return note_ptr;
+}
+
+XMLNode&
+MidiModel::NoteDiffCommand::marshal_change (const NoteChange& 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);
+ if (change.property == StartTime || change.property == Length) {
+ old_value_str << change.old_value.get_beats();
+ } else {
+ old_value_str << change.old_value.get_int();
+ }
+ xml_change->add_property ("old", old_value_str.str());
+ }
+
+ {
+ ostringstream new_value_str (ios::ate);
+ if (change.property == StartTime || change.property == Length) {
+ new_value_str << change.new_value.get_beats();
+ } else {
+ new_value_str << change.new_value.get_int();
+ }
+ xml_change->add_property ("new", new_value_str.str());
+ }
+
+ ostringstream id_str;
+ if (change.note) {
+ id_str << change.note->id();
+ xml_change->add_property ("id", id_str.str());
+ } else if (change.note_id) {
+ warning << _("Change has no note, using note ID") << endmsg;
+ id_str << change.note_id;
+ xml_change->add_property ("id", id_str.str());
+ } else {
+ error << _("Change has no note or note ID") << endmsg;
+ }
+
+ return *xml_change;
+}
+
+MidiModel::NoteDiffCommand::NoteChange
+MidiModel::NoteDiffCommand::unmarshal_change (XMLNode *xml_change)