+using namespace PBD;
+
+MidiModel::MidiModel (boost::shared_ptr<MidiSource> s)
+ : AutomatableSequence<TimeType>(s->session())
+{
+ set_midi_source (s);
+}
+
+/** Start a new NoteDiff 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::NoteDiffCommand*
+MidiModel::new_note_diff_command (const string name)
+{
+ boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
+ assert (ms);
+
+ return new NoteDiffCommand (ms->model(), name);
+}
+
+/** Start a new SysExDiff command */
+MidiModel::SysExDiffCommand*
+MidiModel::new_sysex_diff_command (const string name)
+{
+ boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
+ assert (ms);
+
+ return new SysExDiffCommand (ms->model(), name);
+}
+
+/** Start a new PatchChangeDiff command */
+MidiModel::PatchChangeDiffCommand*
+MidiModel::new_patch_change_diff_command (const string name)
+{
+ boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
+ assert (ms);
+
+ return new PatchChangeDiffCommand (ms->model(), name);
+}
+
+
+/** 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(Session& session, Command* cmd)
+{
+ session.begin_reversible_command(cmd->name());
+ (*cmd)();
+ session.commit_reversible_command(cmd);
+ set_edited(true);
+}
+
+/** Apply a command as part of a larger reversible transaction
+ *
+ * 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_as_subcommand(Session& session, Command* cmd)
+{
+ (*cmd)();
+ session.add_command(cmd);
+ set_edited(true);
+}
+
+/************** DIFF COMMAND ********************/
+
+#define NOTE_DIFF_COMMAND_ELEMENT "NoteDiffCommand"
+#define DIFF_NOTES_ELEMENT "ChangedNotes"
+#define ADDED_NOTES_ELEMENT "AddedNotes"
+#define REMOVED_NOTES_ELEMENT "RemovedNotes"
+#define SIDE_EFFECT_REMOVALS_ELEMENT "SideEffectRemovals"
+#define SYSEX_DIFF_COMMAND_ELEMENT "SysExDiffCommand"
+#define DIFF_SYSEXES_ELEMENT "ChangedSysExes"
+#define PATCH_CHANGE_DIFF_COMMAND_ELEMENT "PatchChangeDiffCommand"
+#define ADDED_PATCH_CHANGES_ELEMENT "AddedPatchChanges"
+#define REMOVED_PATCH_CHANGES_ELEMENT "RemovedPatchChanges"
+#define DIFF_PATCH_CHANGES_ELEMENT "ChangedPatchChanges"
+
+MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
+ : Command (name)
+ , _model (m)
+ , _name (name)
+{
+ assert(_model);
+}
+
+MidiModel::NoteDiffCommand::NoteDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
+ : DiffCommand (m, "")
+{
+ assert (_model);
+ set_state (node, Stateful::loading_state_version);
+}
+
+void
+MidiModel::NoteDiffCommand::add (const NotePtr note)
+{
+ _removed_notes.remove(note);
+ _added_notes.push_back(note);
+}
+
+void
+MidiModel::NoteDiffCommand::remove (const NotePtr note)
+{
+ _added_notes.remove(note);
+ _removed_notes.push_back(note);
+}
+
+void
+MidiModel::NoteDiffCommand::side_effect_remove (const NotePtr note)
+{
+ side_effect_removals.insert (note);
+}
+
+void
+MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop,
+ uint8_t new_value)
+{
+ assert (note);
+
+ NoteChange change;
+
+ switch (prop) {
+ case NoteNumber:
+ if (new_value == note->note()) {
+ return;
+ }
+ change.old_value = note->note();
+ break;
+ case Velocity:
+ if (new_value == note->velocity()) {
+ return;
+ }
+ change.old_value = note->velocity();
+ break;
+ case Channel:
+ if (new_value == note->channel()) {
+ return;
+ }
+ change.old_value = note->channel();
+ break;
+
+
+ case StartTime:
+ fatal << "MidiModel::DiffCommand::change() with integer argument called for start time" << endmsg;
+ /*NOTREACHED*/
+ break;
+ case Length:
+ fatal << "MidiModel::DiffCommand::change() with integer argument called for length" << endmsg;
+ /*NOTREACHED*/
+ break;
+ }
+
+ change.note = note;
+ change.property = prop;
+ change.new_value = new_value;
+
+ _changes.push_back (change);
+}
+
+void
+MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop,
+ TimeType new_time)
+{
+ assert (note);
+
+ NoteChange change;
+
+ switch (prop) {
+ case NoteNumber:
+ case Channel:
+ case Velocity:
+ fatal << "MidiModel::NoteDiffCommand::change() with time argument called for note, channel or velocity" << endmsg;
+ break;
+
+ case StartTime:
+ if (Evoral::musical_time_equal (note->time(), new_time)) {
+ return;
+ }
+ change.old_time = note->time();
+ break;
+ case Length:
+ if (Evoral::musical_time_equal (note->length(), new_time)) {
+ return;
+ }
+ change.old_time = note->length();
+ break;
+ }
+
+ change.note = note;
+ change.property = prop;
+ change.new_time = new_time;
+
+ _changes.push_back (change);
+}
+
+MidiModel::NoteDiffCommand &
+MidiModel::NoteDiffCommand::operator+= (const NoteDiffCommand& other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ if (_model != other._model) {
+ return *this;
+ }
+
+ _added_notes.insert (_added_notes.end(), other._added_notes.begin(), other._added_notes.end());
+ _removed_notes.insert (_removed_notes.end(), other._removed_notes.begin(), other._removed_notes.end());
+ side_effect_removals.insert (other.side_effect_removals.begin(), other.side_effect_removals.end());
+ _changes.insert (_changes.end(), other._changes.begin(), other._changes.end());
+
+ return *this;
+}
+
+void
+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;
+ 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);
+ 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_time);
+ 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);
+ break;
+
+ /* no remove-then-add required for these properties, since we do not index them
+ */
+
+ case Velocity:
+ i->note->set_velocity (i->new_value);
+ break;
+
+ case Length:
+ i->note->set_length (i->new_time);
+ 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;
+
+ 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);
+ 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_time);
+ 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);
+ break;
+
+ /* no remove-then-add required for these properties, since we do not index them
+ */
+
+ case Velocity:
+ i->note->set_velocity (i->old_value);
+ break;
+
+ case Length:
+ i->note->set_length (i->old_time);
+ 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;
+ unsigned int time;
+ unsigned int 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 = 0;
+ }
+
+ if ((prop = xml_note->property("length")) != 0) {
+ istringstream length_str(prop->value());
+ length_str >> length;
+ } else {
+ warning << "note information missing length" << endmsg;
+ length = 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_time;
+ } else {
+ old_value_str << (unsigned int) change.old_value;
+ }
+ 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_time;
+ } else {
+ new_value_str << (unsigned int) change.new_value;
+ }
+ xml_change->add_property ("new", new_value_str.str());
+ }
+
+ ostringstream id_str;
+ id_str << change.note->id();
+ xml_change->add_property ("id", id_str.str());
+
+ return *xml_change;
+}
+
+MidiModel::NoteDiffCommand::NoteChange
+MidiModel::NoteDiffCommand::unmarshal_change (XMLNode *xml_change)
+{
+ XMLProperty* prop;
+ NoteChange change;
+
+ 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 ("id")) == 0) {
+ error << _("No NoteID found for note property change - ignored") << endmsg;
+ return change;
+ }
+
+ gint note_id = atoi (prop->value().c_str());
+
+ if ((prop = xml_change->property ("old")) != 0) {
+ istringstream old_str (prop->value());
+ if (change.property == StartTime || change.property == Length) {
+ old_str >> change.old_time;
+ } else {
+ int integer_value_so_that_istream_does_the_right_thing;
+ old_str >> integer_value_so_that_istream_does_the_right_thing;
+ change.old_value = integer_value_so_that_istream_does_the_right_thing;
+ }
+ } else {
+ fatal << "!!!" << endmsg;
+ /*NOTREACHED*/
+ }
+
+ if ((prop = xml_change->property ("new")) != 0) {
+ istringstream new_str (prop->value());
+ if (change.property == StartTime || change.property == Length) {
+ new_str >> change.new_time;
+ } else {
+ int integer_value_so_that_istream_does_the_right_thing;
+ new_str >> integer_value_so_that_istream_does_the_right_thing;
+ change.new_value = integer_value_so_that_istream_does_the_right_thing;
+ }
+ } else {
+ fatal << "!!!" << endmsg;
+ /*NOTREACHED*/
+ }
+
+ /* we must point at the instance of the note that is actually in the model.
+ so go look for it ...
+ */
+
+ change.note = _model->find_note (note_id);
+
+ if (!change.note) {
+ warning << "MIDI note #" << note_id << " not found in model - programmers should investigate this" << endmsg;
+ return change;
+ }
+
+ return change;
+}
+
+int
+MidiModel::NoteDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
+{
+ if (diff_command.name() != string (NOTE_DIFF_COMMAND_ELEMENT)) {
+ return 1;
+ }
+
+ /* additions */
+
+ _added_notes.clear();
+ XMLNode* added_notes = diff_command.child(ADDED_NOTES_ELEMENT);
+ if (added_notes) {
+ XMLNodeList notes = added_notes->children();
+ transform(notes.begin(), notes.end(), back_inserter(_added_notes),
+ boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
+ }
+
+
+ /* removals */
+
+ _removed_notes.clear();
+ XMLNode* removed_notes = diff_command.child(REMOVED_NOTES_ELEMENT);
+ if (removed_notes) {
+ XMLNodeList notes = removed_notes->children();
+ transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
+ boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
+ }
+
+
+ /* changes */
+
+ _changes.clear();
+
+ XMLNode* changed_notes = diff_command.child(DIFF_NOTES_ELEMENT);
+
+ if (changed_notes) {
+ XMLNodeList notes = changed_notes->children();
+ transform (notes.begin(), notes.end(), back_inserter(_changes),
+ boost::bind (&NoteDiffCommand::unmarshal_change, this, _1));
+
+ }
+
+ /* side effect removals caused by changes */
+
+ side_effect_removals.clear();
+
+ XMLNode* side_effect_notes = diff_command.child(SIDE_EFFECT_REMOVALS_ELEMENT);
+
+ if (side_effect_notes) {
+ XMLNodeList notes = side_effect_notes->children();
+ for (XMLNodeList::iterator n = notes.begin(); n != notes.end(); ++n) {
+ side_effect_removals.insert (unmarshal_note (*n));
+ }
+ }
+
+ return 0;
+}
+
+XMLNode&
+MidiModel::NoteDiffCommand::get_state ()
+{
+ XMLNode* diff_command = new XMLNode (NOTE_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(),
+ boost::bind (
+ boost::bind (&XMLNode::add_child_nocopy, changes, _1),
+ boost::bind (&NoteDiffCommand::marshal_change, this, _1)));
+
+ XMLNode* added_notes = diff_command->add_child(ADDED_NOTES_ELEMENT);
+ for_each(_added_notes.begin(), _added_notes.end(),
+ boost::bind(
+ boost::bind (&XMLNode::add_child_nocopy, added_notes, _1),
+ boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
+
+ XMLNode* removed_notes = diff_command->add_child(REMOVED_NOTES_ELEMENT);
+ for_each(_removed_notes.begin(), _removed_notes.end(),
+ boost::bind (
+ boost::bind (&XMLNode::add_child_nocopy, removed_notes, _1),
+ boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
+
+ /* if this command had side-effects, store that state too
+ */
+
+ if (!side_effect_removals.empty()) {
+ XMLNode* side_effect_notes = diff_command->add_child(SIDE_EFFECT_REMOVALS_ELEMENT);
+ for_each(side_effect_removals.begin(), side_effect_removals.end(),
+ boost::bind (
+ boost::bind (&XMLNode::add_child_nocopy, side_effect_notes, _1),
+ boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
+ }
+
+ return *diff_command;
+}
+
+MidiModel::SysExDiffCommand::SysExDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
+ : DiffCommand (m, "")
+{
+ assert (_model);
+ set_state (node, Stateful::loading_state_version);
+}
+
+void
+MidiModel::SysExDiffCommand::change (boost::shared_ptr<Evoral::Event<TimeType> > s, TimeType new_time)
+{
+ Change change;
+
+ change.sysex = s;
+ change.property = Time;
+ change.old_time = s->time ();
+ change.new_time = new_time;
+
+ _changes.push_back (change);
+}
+
+void
+MidiModel::SysExDiffCommand::operator() ()
+{
+ {
+ MidiModel::WriteLock lock (_model->edit_lock ());
+
+ for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+ switch (i->property) {
+ case Time:
+ i->sysex->set_time (i->new_time);
+ }
+ }
+ }
+
+ _model->ContentsChanged (); /* EMIT SIGNAL */
+}
+
+void
+MidiModel::SysExDiffCommand::undo ()
+{
+ {
+ MidiModel::WriteLock lock (_model->edit_lock ());
+
+ for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+ switch (i->property) {
+ case Time:
+ i->sysex->set_time (i->old_time);
+ break;
+ }
+ }
+
+ }
+
+ _model->ContentsChanged(); /* EMIT SIGNAL */
+}
+
+XMLNode&
+MidiModel::SysExDiffCommand::marshal_change (const Change& 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 << change.old_time;
+ xml_change->add_property ("old", old_value_str.str());
+ }