2 Copyright (C) 2007 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include "pbd/compose.h"
28 #include "pbd/enumwriter.h"
29 #include "pbd/error.h"
31 #include "evoral/Control.hpp"
33 #include "midi++/events.h"
35 #include "ardour/automation_control.h"
36 #include "ardour/midi_automation_list_binder.h"
37 #include "ardour/midi_model.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_state_tracker.h"
40 #include "ardour/session.h"
41 #include "ardour/smf_source.h"
42 #include "ardour/types.h"
47 using namespace ARDOUR;
50 MidiModel::MidiModel (boost::shared_ptr<MidiSource> s)
51 : AutomatableSequence<TimeType>(s->session())
56 /** Start a new NoteDiff command.
58 * This has no side-effects on the model or Session, the returned command
59 * can be held on to for as long as the caller wishes, or discarded without
60 * formality, until apply_command is called and ownership is taken.
62 MidiModel::NoteDiffCommand*
63 MidiModel::new_note_diff_command (const string name)
65 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
68 return new NoteDiffCommand (ms->model(), name);
71 /** Start a new SysExDiff command */
72 MidiModel::SysExDiffCommand*
73 MidiModel::new_sysex_diff_command (const string name)
75 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
78 return new SysExDiffCommand (ms->model(), name);
81 /** Start a new PatchChangeDiff command */
82 MidiModel::PatchChangeDiffCommand*
83 MidiModel::new_patch_change_diff_command (const string name)
85 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
88 return new PatchChangeDiffCommand (ms->model(), name);
94 * Ownership of cmd is taken, it must not be deleted by the caller.
95 * The command will constitute one item on the undo stack.
98 MidiModel::apply_command(Session& session, Command* cmd)
100 session.begin_reversible_command(cmd->name());
102 session.commit_reversible_command(cmd);
106 /** Apply a command as part of a larger reversible transaction
108 * Ownership of cmd is taken, it must not be deleted by the caller.
109 * The command will constitute one item on the undo stack.
112 MidiModel::apply_command_as_subcommand(Session& session, Command* cmd)
115 session.add_command(cmd);
119 /************** DIFF COMMAND ********************/
121 #define NOTE_DIFF_COMMAND_ELEMENT "NoteDiffCommand"
122 #define DIFF_NOTES_ELEMENT "ChangedNotes"
123 #define ADDED_NOTES_ELEMENT "AddedNotes"
124 #define REMOVED_NOTES_ELEMENT "RemovedNotes"
125 #define SIDE_EFFECT_REMOVALS_ELEMENT "SideEffectRemovals"
126 #define SYSEX_DIFF_COMMAND_ELEMENT "SysExDiffCommand"
127 #define DIFF_SYSEXES_ELEMENT "ChangedSysExes"
128 #define PATCH_CHANGE_DIFF_COMMAND_ELEMENT "PatchChangeDiffCommand"
129 #define ADDED_PATCH_CHANGES_ELEMENT "AddedPatchChanges"
130 #define REMOVED_PATCH_CHANGES_ELEMENT "RemovedPatchChanges"
131 #define DIFF_PATCH_CHANGES_ELEMENT "ChangedPatchChanges"
133 MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
141 MidiModel::NoteDiffCommand::NoteDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
142 : DiffCommand (m, "")
145 set_state (node, Stateful::loading_state_version);
149 MidiModel::NoteDiffCommand::add (const NotePtr note)
151 _removed_notes.remove(note);
152 _added_notes.push_back(note);
156 MidiModel::NoteDiffCommand::remove (const NotePtr note)
158 _added_notes.remove(note);
159 _removed_notes.push_back(note);
163 MidiModel::NoteDiffCommand::side_effect_remove (const NotePtr note)
165 side_effect_removals.insert (note);
169 MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop,
178 if (new_value == note->note()) {
181 change.old_value = note->note();
184 if (new_value == note->velocity()) {
187 change.old_value = note->velocity();
190 if (new_value == note->channel()) {
193 change.old_value = note->channel();
198 fatal << "MidiModel::DiffCommand::change() with integer argument called for start time" << endmsg;
202 fatal << "MidiModel::DiffCommand::change() with integer argument called for length" << endmsg;
208 change.property = prop;
209 change.new_value = new_value;
211 _changes.push_back (change);
215 MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop,
226 fatal << "MidiModel::NoteDiffCommand::change() with time argument called for note, channel or velocity" << endmsg;
230 if (Evoral::musical_time_equal (note->time(), new_time)) {
233 change.old_time = note->time();
236 if (Evoral::musical_time_equal (note->length(), new_time)) {
239 change.old_time = note->length();
244 change.property = prop;
245 change.new_time = new_time;
247 _changes.push_back (change);
250 MidiModel::NoteDiffCommand &
251 MidiModel::NoteDiffCommand::operator+= (const NoteDiffCommand& other)
253 if (this == &other) {
257 if (_model != other._model) {
261 _added_notes.insert (_added_notes.end(), other._added_notes.begin(), other._added_notes.end());
262 _removed_notes.insert (_removed_notes.end(), other._removed_notes.begin(), other._removed_notes.end());
263 side_effect_removals.insert (other.side_effect_removals.begin(), other.side_effect_removals.end());
264 _changes.insert (_changes.end(), other._changes.begin(), other._changes.end());
270 MidiModel::NoteDiffCommand::operator() ()
273 MidiModel::WriteLock lock(_model->edit_lock());
275 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
276 if (!_model->add_note_unlocked(*i)) {
277 /* failed to add it, so don't leave it in the removed list, to
278 avoid apparent errors on undo.
280 _removed_notes.remove (*i);
284 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
285 _model->remove_note_unlocked(*i);
288 /* notes we modify in a way that requires remove-then-add to maintain ordering */
289 set<NotePtr> temporary_removals;
291 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
292 Property prop = i->property;
295 if (temporary_removals.find (i->note) == temporary_removals.end()) {
296 _model->remove_note_unlocked (i->note);
297 temporary_removals.insert (i->note);
299 i->note->set_note (i->new_value);
303 if (temporary_removals.find (i->note) == temporary_removals.end()) {
304 _model->remove_note_unlocked (i->note);
305 temporary_removals.insert (i->note);
307 i->note->set_time (i->new_time);
311 if (temporary_removals.find (i->note) == temporary_removals.end()) {
312 _model->remove_note_unlocked (i->note);
313 temporary_removals.insert (i->note);
315 i->note->set_channel (i->new_value);
318 /* no remove-then-add required for these properties, since we do not index them
322 i->note->set_velocity (i->new_value);
326 i->note->set_length (i->new_time);
332 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
333 NoteDiffCommand side_effects (model(), "side effects");
334 if (_model->add_note_unlocked (*i, &side_effects)) {
335 /* The note was re-added ok */
336 *this += side_effects;
338 /* The note that we removed earlier could not be re-added. This change record
339 must say that the note was removed. We'll keep the changes we made, though,
340 as if the note is re-added by the undo the changes must also be undone.
342 _removed_notes.push_back (*i);
346 if (!side_effect_removals.empty()) {
348 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
349 cerr << "\t" << *i << ' ' << **i << endl;
354 _model->ContentsChanged(); /* EMIT SIGNAL */
358 MidiModel::NoteDiffCommand::undo ()
361 MidiModel::WriteLock lock(_model->edit_lock());
363 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
364 _model->remove_note_unlocked(*i);
367 /* Apply changes first; this is important in the case of a note change which
368 resulted in the note being removed by the overlap checker. If the overlap
369 checker removes a note, it will be in _removed_notes. We are going to re-add
370 it below, but first we must undo the changes we made so that the overlap
371 checker doesn't refuse the re-add.
374 /* notes we modify in a way that requires remove-then-add to maintain ordering */
375 set<NotePtr> temporary_removals;
377 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
378 Property prop = i->property;
383 temporary_removals.find (i->note) == temporary_removals.end() &&
384 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()
387 /* We only need to mark this note for re-add if (a) we haven't
388 already marked it and (b) it isn't on the _removed_notes
389 list (which means that it has already been removed and it
390 will be re-added anyway)
393 _model->remove_note_unlocked (i->note);
394 temporary_removals.insert (i->note);
396 i->note->set_note (i->old_value);
401 temporary_removals.find (i->note) == temporary_removals.end() &&
402 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()
407 _model->remove_note_unlocked (i->note);
408 temporary_removals.insert (i->note);
410 i->note->set_time (i->old_time);
415 temporary_removals.find (i->note) == temporary_removals.end() &&
416 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()
421 _model->remove_note_unlocked (i->note);
422 temporary_removals.insert (i->note);
424 i->note->set_channel (i->old_value);
427 /* no remove-then-add required for these properties, since we do not index them
431 i->note->set_velocity (i->old_value);
435 i->note->set_length (i->old_time);
440 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
441 _model->add_note_unlocked(*i);
444 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
445 _model->add_note_unlocked (*i);
448 /* finally add back notes that were removed by the "do". we don't care
449 about side effects here since the model should be back to its original
450 state once this is done.
453 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
454 _model->add_note_unlocked (*i);
458 _model->ContentsChanged(); /* EMIT SIGNAL */
462 MidiModel::NoteDiffCommand::marshal_note(const NotePtr note)
464 XMLNode* xml_note = new XMLNode("note");
467 ostringstream id_str(ios::ate);
468 id_str << int(note->id());
469 xml_note->add_property("id", id_str.str());
473 ostringstream note_str(ios::ate);
474 note_str << int(note->note());
475 xml_note->add_property("note", note_str.str());
479 ostringstream channel_str(ios::ate);
480 channel_str << int(note->channel());
481 xml_note->add_property("channel", channel_str.str());
485 ostringstream time_str(ios::ate);
486 time_str << note->time();
487 xml_note->add_property("time", time_str.str());
491 ostringstream length_str(ios::ate);
492 length_str << note->length();
493 xml_note->add_property("length", length_str.str());
497 ostringstream velocity_str(ios::ate);
498 velocity_str << (unsigned int) note->velocity();
499 xml_note->add_property("velocity", velocity_str.str());
505 Evoral::Sequence<MidiModel::TimeType>::NotePtr
506 MidiModel::NoteDiffCommand::unmarshal_note (XMLNode *xml_note)
510 unsigned int channel;
511 MidiModel::TimeType time;
512 MidiModel::TimeType length;
513 unsigned int velocity;
516 if ((prop = xml_note->property("id")) != 0) {
517 istringstream id_str(prop->value());
520 error << "note information missing ID value" << endmsg;
524 if ((prop = xml_note->property("note")) != 0) {
525 istringstream note_str(prop->value());
528 warning << "note information missing note value" << endmsg;
532 if ((prop = xml_note->property("channel")) != 0) {
533 istringstream channel_str(prop->value());
534 channel_str >> channel;
536 warning << "note information missing channel" << endmsg;
540 if ((prop = xml_note->property("time")) != 0) {
541 istringstream time_str(prop->value());
544 warning << "note information missing time" << endmsg;
548 if ((prop = xml_note->property("length")) != 0) {
549 istringstream length_str(prop->value());
550 length_str >> length;
552 warning << "note information missing length" << endmsg;
556 if ((prop = xml_note->property("velocity")) != 0) {
557 istringstream velocity_str(prop->value());
558 velocity_str >> velocity;
560 warning << "note information missing velocity" << endmsg;
564 NotePtr note_ptr(new Evoral::Note<TimeType>(channel, time, length, note, velocity));
565 note_ptr->set_id (id);
571 MidiModel::NoteDiffCommand::marshal_change (const NoteChange& change)
573 XMLNode* xml_change = new XMLNode("Change");
575 /* first, the change itself */
577 xml_change->add_property ("property", enum_2_string (change.property));
580 ostringstream old_value_str (ios::ate);
581 if (change.property == StartTime || change.property == Length) {
582 old_value_str << change.old_time;
584 old_value_str << (unsigned int) change.old_value;
586 xml_change->add_property ("old", old_value_str.str());
590 ostringstream new_value_str (ios::ate);
591 if (change.property == StartTime || change.property == Length) {
592 new_value_str << change.new_time;
594 new_value_str << (unsigned int) change.new_value;
596 xml_change->add_property ("new", new_value_str.str());
599 ostringstream id_str;
600 id_str << change.note->id();
601 xml_change->add_property ("id", id_str.str());
606 MidiModel::NoteDiffCommand::NoteChange
607 MidiModel::NoteDiffCommand::unmarshal_change (XMLNode *xml_change)
612 if ((prop = xml_change->property("property")) != 0) {
613 change.property = (Property) string_2_enum (prop->value(), change.property);
615 fatal << "!!!" << endmsg;
619 if ((prop = xml_change->property ("id")) == 0) {
620 error << _("No NoteID found for note property change - ignored") << endmsg;
624 gint note_id = atoi (prop->value().c_str());
626 if ((prop = xml_change->property ("old")) != 0) {
627 istringstream old_str (prop->value());
628 if (change.property == StartTime || change.property == Length) {
629 old_str >> change.old_time;
631 int integer_value_so_that_istream_does_the_right_thing;
632 old_str >> integer_value_so_that_istream_does_the_right_thing;
633 change.old_value = integer_value_so_that_istream_does_the_right_thing;
636 fatal << "!!!" << endmsg;
640 if ((prop = xml_change->property ("new")) != 0) {
641 istringstream new_str (prop->value());
642 if (change.property == StartTime || change.property == Length) {
643 new_str >> change.new_time;
645 int integer_value_so_that_istream_does_the_right_thing;
646 new_str >> integer_value_so_that_istream_does_the_right_thing;
647 change.new_value = integer_value_so_that_istream_does_the_right_thing;
650 fatal << "!!!" << endmsg;
654 /* we must point at the instance of the note that is actually in the model.
655 so go look for it ...
658 change.note = _model->find_note (note_id);
661 warning << "MIDI note #" << note_id << " not found in model - programmers should investigate this" << endmsg;
669 MidiModel::NoteDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
671 if (diff_command.name() != string (NOTE_DIFF_COMMAND_ELEMENT)) {
677 _added_notes.clear();
678 XMLNode* added_notes = diff_command.child(ADDED_NOTES_ELEMENT);
680 XMLNodeList notes = added_notes->children();
681 transform(notes.begin(), notes.end(), back_inserter(_added_notes),
682 boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
688 _removed_notes.clear();
689 XMLNode* removed_notes = diff_command.child(REMOVED_NOTES_ELEMENT);
691 XMLNodeList notes = removed_notes->children();
692 transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
693 boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
701 XMLNode* changed_notes = diff_command.child(DIFF_NOTES_ELEMENT);
704 XMLNodeList notes = changed_notes->children();
705 transform (notes.begin(), notes.end(), back_inserter(_changes),
706 boost::bind (&NoteDiffCommand::unmarshal_change, this, _1));
710 /* side effect removals caused by changes */
712 side_effect_removals.clear();
714 XMLNode* side_effect_notes = diff_command.child(SIDE_EFFECT_REMOVALS_ELEMENT);
716 if (side_effect_notes) {
717 XMLNodeList notes = side_effect_notes->children();
718 for (XMLNodeList::iterator n = notes.begin(); n != notes.end(); ++n) {
719 side_effect_removals.insert (unmarshal_note (*n));
727 MidiModel::NoteDiffCommand::get_state ()
729 XMLNode* diff_command = new XMLNode (NOTE_DIFF_COMMAND_ELEMENT);
730 diff_command->add_property("midi-source", _model->midi_source()->id().to_s());
732 XMLNode* changes = diff_command->add_child(DIFF_NOTES_ELEMENT);
733 for_each(_changes.begin(), _changes.end(),
735 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
736 boost::bind (&NoteDiffCommand::marshal_change, this, _1)));
738 XMLNode* added_notes = diff_command->add_child(ADDED_NOTES_ELEMENT);
739 for_each(_added_notes.begin(), _added_notes.end(),
741 boost::bind (&XMLNode::add_child_nocopy, added_notes, _1),
742 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
744 XMLNode* removed_notes = diff_command->add_child(REMOVED_NOTES_ELEMENT);
745 for_each(_removed_notes.begin(), _removed_notes.end(),
747 boost::bind (&XMLNode::add_child_nocopy, removed_notes, _1),
748 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
750 /* if this command had side-effects, store that state too
753 if (!side_effect_removals.empty()) {
754 XMLNode* side_effect_notes = diff_command->add_child(SIDE_EFFECT_REMOVALS_ELEMENT);
755 for_each(side_effect_removals.begin(), side_effect_removals.end(),
757 boost::bind (&XMLNode::add_child_nocopy, side_effect_notes, _1),
758 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
761 return *diff_command;
764 MidiModel::SysExDiffCommand::SysExDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
765 : DiffCommand (m, "")
768 set_state (node, Stateful::loading_state_version);
772 MidiModel::SysExDiffCommand::change (boost::shared_ptr<Evoral::Event<TimeType> > s, TimeType new_time)
777 change.property = Time;
778 change.old_time = s->time ();
779 change.new_time = new_time;
781 _changes.push_back (change);
785 MidiModel::SysExDiffCommand::operator() ()
788 MidiModel::WriteLock lock (_model->edit_lock ());
790 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
791 switch (i->property) {
793 i->sysex->set_time (i->new_time);
798 _model->ContentsChanged (); /* EMIT SIGNAL */
802 MidiModel::SysExDiffCommand::undo ()
805 MidiModel::WriteLock lock (_model->edit_lock ());
807 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
808 switch (i->property) {
810 i->sysex->set_time (i->old_time);
817 _model->ContentsChanged(); /* EMIT SIGNAL */
821 MidiModel::SysExDiffCommand::marshal_change (const Change& change)
823 XMLNode* xml_change = new XMLNode ("Change");
825 /* first, the change itself */
827 xml_change->add_property ("property", enum_2_string (change.property));
830 ostringstream old_value_str (ios::ate);
831 old_value_str << change.old_time;
832 xml_change->add_property ("old", old_value_str.str());
836 ostringstream new_value_str (ios::ate);
837 new_value_str << change.new_time;
838 xml_change->add_property ("new", new_value_str.str());
841 ostringstream id_str;
842 id_str << change.sysex->id();
843 xml_change->add_property ("id", id_str.str());
848 MidiModel::SysExDiffCommand::Change
849 MidiModel::SysExDiffCommand::unmarshal_change (XMLNode *xml_change)
854 if ((prop = xml_change->property ("property")) != 0) {
855 change.property = (Property) string_2_enum (prop->value(), change.property);
857 fatal << "!!!" << endmsg;
861 if ((prop = xml_change->property ("id")) == 0) {
862 error << _("No SysExID found for sys-ex property change - ignored") << endmsg;
866 gint sysex_id = atoi (prop->value().c_str());
868 if ((prop = xml_change->property ("old")) != 0) {
869 istringstream old_str (prop->value());
870 old_str >> change.old_time;
872 fatal << "!!!" << endmsg;
876 if ((prop = xml_change->property ("new")) != 0) {
877 istringstream new_str (prop->value());
878 new_str >> change.new_time;
880 fatal << "!!!" << endmsg;
884 /* we must point at the instance of the sysex that is actually in the model.
885 so go look for it ...
888 change.sysex = _model->find_sysex (sysex_id);
891 warning << "Sys-ex #" << sysex_id << " not found in model - programmers should investigate this" << endmsg;
899 MidiModel::SysExDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
901 if (diff_command.name() != string (SYSEX_DIFF_COMMAND_ELEMENT)) {
909 XMLNode* changed_sysexes = diff_command.child (DIFF_SYSEXES_ELEMENT);
911 if (changed_sysexes) {
912 XMLNodeList sysexes = changed_sysexes->children();
913 transform (sysexes.begin(), sysexes.end(), back_inserter (_changes),
914 boost::bind (&SysExDiffCommand::unmarshal_change, this, _1));
922 MidiModel::SysExDiffCommand::get_state ()
924 XMLNode* diff_command = new XMLNode (SYSEX_DIFF_COMMAND_ELEMENT);
925 diff_command->add_property ("midi-source", _model->midi_source()->id().to_s());
927 XMLNode* changes = diff_command->add_child(DIFF_SYSEXES_ELEMENT);
928 for_each (_changes.begin(), _changes.end(),
930 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
931 boost::bind (&SysExDiffCommand::marshal_change, this, _1)));
933 return *diff_command;
936 MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const string& name)
937 : DiffCommand (m, name)
942 MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode & node)
943 : DiffCommand (m, "")
946 set_state (node, Stateful::loading_state_version);
950 MidiModel::PatchChangeDiffCommand::add (PatchChangePtr p)
952 _added.push_back (p);
956 MidiModel::PatchChangeDiffCommand::remove (PatchChangePtr p)
958 _removed.push_back (p);
962 MidiModel::PatchChangeDiffCommand::change_time (PatchChangePtr patch, TimeType t)
967 c.old_time = patch->time ();
970 _changes.push_back (c);
974 MidiModel::PatchChangeDiffCommand::change_channel (PatchChangePtr patch, uint8_t channel)
977 c.property = Channel;
979 c.old_channel = patch->channel ();
980 c.new_channel = channel;
982 _changes.push_back (c);
986 MidiModel::PatchChangeDiffCommand::change_program (PatchChangePtr patch, uint8_t program)
989 c.property = Program;
991 c.old_program = patch->program ();
992 c.new_program = program;
994 _changes.push_back (c);
998 MidiModel::PatchChangeDiffCommand::change_bank (PatchChangePtr patch, int bank)
1003 c.old_bank = patch->bank ();
1006 _changes.push_back (c);
1010 MidiModel::PatchChangeDiffCommand::operator() ()
1013 MidiModel::WriteLock lock (_model->edit_lock ());
1015 for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) {
1016 _model->add_patch_change_unlocked (*i);
1019 for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
1020 _model->remove_patch_change_unlocked (*i);
1023 set<PatchChangePtr> temporary_removals;
1025 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
1026 switch (i->property) {
1028 if (temporary_removals.find (i->patch) == temporary_removals.end()) {
1029 _model->remove_patch_change_unlocked (i->patch);
1030 temporary_removals.insert (i->patch);
1032 i->patch->set_time (i->new_time);
1036 i->patch->set_channel (i->new_channel);
1040 i->patch->set_program (i->new_program);
1044 i->patch->set_bank (i->new_bank);
1049 for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
1050 _model->add_patch_change_unlocked (*i);
1054 _model->ContentsChanged (); /* EMIT SIGNAL */
1058 MidiModel::PatchChangeDiffCommand::undo ()
1061 MidiModel::WriteLock lock (_model->edit_lock());
1063 for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) {
1064 _model->remove_patch_change_unlocked (*i);
1067 for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
1068 _model->add_patch_change_unlocked (*i);
1071 set<PatchChangePtr> temporary_removals;
1073 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
1074 switch (i->property) {
1076 if (temporary_removals.find (i->patch) == temporary_removals.end()) {
1077 _model->remove_patch_change_unlocked (i->patch);
1078 temporary_removals.insert (i->patch);
1080 i->patch->set_time (i->old_time);
1084 i->patch->set_channel (i->old_channel);
1088 i->patch->set_program (i->old_program);
1092 i->patch->set_bank (i->old_bank);
1097 for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
1098 _model->add_patch_change_unlocked (*i);
1103 _model->ContentsChanged (); /* EMIT SIGNAL */
1107 MidiModel::PatchChangeDiffCommand::marshal_patch_change (constPatchChangePtr p)
1109 XMLNode* n = new XMLNode ("patch-change");
1112 ostringstream s (ios::ate);
1113 s << int (p->id ());
1114 n->add_property ("id", s.str());
1118 ostringstream s (ios::ate);
1120 n->add_property ("time", s.str ());
1124 ostringstream s (ios::ate);
1125 s << int (p->channel ());
1126 n->add_property ("channel", s.str ());
1130 ostringstream s (ios::ate);
1131 s << int (p->program ());
1132 n->add_property ("program", s.str ());
1136 ostringstream s (ios::ate);
1137 s << int (p->bank ());
1138 n->add_property ("bank", s.str ());
1145 MidiModel::PatchChangeDiffCommand::marshal_change (const Change& c)
1147 XMLNode* n = new XMLNode (X_("Change"));
1149 n->add_property (X_("property"), enum_2_string (c.property));
1152 ostringstream s (ios::ate);
1153 if (c.property == Time) {
1155 } else if (c.property == Channel) {
1157 } else if (c.property == Program) {
1158 s << int (c.old_program);
1159 } else if (c.property == Bank) {
1163 n->add_property (X_("old"), s.str ());
1167 ostringstream s (ios::ate);
1169 if (c.property == Time) {
1171 } else if (c.property == Channel) {
1173 } else if (c.property == Program) {
1174 s << int (c.new_program);
1175 } else if (c.property == Bank) {
1179 n->add_property (X_("new"), s.str ());
1184 s << c.patch->id ();
1185 n->add_property ("id", s.str ());
1191 MidiModel::PatchChangePtr
1192 MidiModel::PatchChangeDiffCommand::unmarshal_patch_change (XMLNode* n)
1195 Evoral::event_id_t id;
1196 Evoral::MusicalTime time = 0;
1197 uint8_t channel = 0;
1198 uint8_t program = 0;
1201 if ((prop = n->property ("id")) != 0) {
1202 istringstream s (prop->value());
1206 if ((prop = n->property ("time")) != 0) {
1207 istringstream s (prop->value ());
1211 if ((prop = n->property ("channel")) != 0) {
1212 istringstream s (prop->value ());
1216 if ((prop = n->property ("program")) != 0) {
1217 istringstream s (prop->value ());
1221 if ((prop = n->property ("bank")) != 0) {
1222 istringstream s (prop->value ());
1226 PatchChangePtr p (new Evoral::PatchChange<TimeType> (time, channel, program, bank));
1231 MidiModel::PatchChangeDiffCommand::Change
1232 MidiModel::PatchChangeDiffCommand::unmarshal_change (XMLNode* n)
1237 prop = n->property ("property");
1239 c.property = (Property) string_2_enum (prop->value(), c.property);
1241 prop = n->property ("id");
1243 Evoral::event_id_t const id = atoi (prop->value().c_str());
1245 prop = n->property ("old");
1248 istringstream s (prop->value ());
1249 if (c.property == Time) {
1251 } else if (c.property == Channel) {
1253 } else if (c.property == Program) {
1255 } else if (c.property == Bank) {
1260 prop = n->property ("new");
1263 istringstream s (prop->value ());
1264 if (c.property == Time) {
1266 } else if (c.property == Channel) {
1268 } else if (c.property == Program) {
1270 } else if (c.property == Bank) {
1275 c.patch = _model->find_patch_change (id);
1282 MidiModel::PatchChangeDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
1284 if (diff_command.name() != PATCH_CHANGE_DIFF_COMMAND_ELEMENT) {
1289 XMLNode* added = diff_command.child (ADDED_PATCH_CHANGES_ELEMENT);
1291 XMLNodeList p = added->children ();
1292 transform (p.begin(), p.end(), back_inserter (_added), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1));
1296 XMLNode* removed = diff_command.child (REMOVED_PATCH_CHANGES_ELEMENT);
1298 XMLNodeList p = removed->children ();
1299 transform (p.begin(), p.end(), back_inserter (_removed), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1));
1303 XMLNode* changed = diff_command.child (DIFF_PATCH_CHANGES_ELEMENT);
1305 XMLNodeList p = changed->children ();
1306 transform (p.begin(), p.end(), back_inserter (_changes), boost::bind (&PatchChangeDiffCommand::unmarshal_change, this, _1));
1313 MidiModel::PatchChangeDiffCommand::get_state ()
1315 XMLNode* diff_command = new XMLNode (PATCH_CHANGE_DIFF_COMMAND_ELEMENT);
1316 diff_command->add_property("midi-source", _model->midi_source()->id().to_s());
1318 XMLNode* added = diff_command->add_child (ADDED_PATCH_CHANGES_ELEMENT);
1319 for_each (_added.begin(), _added.end(),
1321 boost::bind (&XMLNode::add_child_nocopy, added, _1),
1322 boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1)
1326 XMLNode* removed = diff_command->add_child (REMOVED_PATCH_CHANGES_ELEMENT);
1327 for_each (_removed.begin(), _removed.end(),
1329 boost::bind (&XMLNode::add_child_nocopy, removed, _1),
1330 boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1)
1334 XMLNode* changes = diff_command->add_child (DIFF_PATCH_CHANGES_ELEMENT);
1335 for_each (_changes.begin(), _changes.end(),
1337 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
1338 boost::bind (&PatchChangeDiffCommand::marshal_change, this, _1)
1342 return *diff_command;
1345 /** Write all of the model to a MidiSource (i.e. save the model).
1346 * This is different from manually using read to write to a source in that
1347 * note off events are written regardless of the track mode. This is so the
1348 * user can switch a recorded track (with note durations from some instrument)
1349 * to percussive, save, reload, then switch it back to sustained without
1350 * destroying the original note durations.
1352 * Similarly, control events are written without interpolation (as with the
1356 MidiModel::write_to (boost::shared_ptr<MidiSource> source)
1358 ReadLock lock(read_lock());
1360 const bool old_percussive = percussive();
1361 set_percussive(false);
1363 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1366 source->drop_model();
1367 source->mark_streaming_midi_write_started (note_mode());
1369 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
1370 source->append_event_unlocked_beats(*i);
1373 set_percussive(old_percussive);
1374 source->mark_streaming_write_completed();
1381 /** very similar to ::write_to() but writes to the model's own
1382 existing midi_source, without making it call MidiSource::drop_model().
1383 the caller is a MidiSource that needs to catch up with the state
1387 MidiModel::sync_to_source ()
1389 ReadLock lock(read_lock());
1391 const bool old_percussive = percussive();
1392 set_percussive(false);
1394 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1397 ms->mark_streaming_midi_write_started (note_mode());
1399 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
1400 ms->append_event_unlocked_beats(*i);
1403 set_percussive (old_percussive);
1404 ms->mark_streaming_write_completed ();
1411 /** Write part or all of the model to a MidiSource (i.e. save the model).
1412 * This is different from manually using read to write to a source in that
1413 * note off events are written regardless of the track mode. This is so the
1414 * user can switch a recorded track (with note durations from some instrument)
1415 * to percussive, save, reload, then switch it back to sustained without
1416 * destroying the original note durations.
1419 MidiModel::write_section_to (boost::shared_ptr<MidiSource> source, Evoral::MusicalTime begin_time, Evoral::MusicalTime end_time)
1421 ReadLock lock(read_lock());
1422 MidiStateTracker mst;
1424 const bool old_percussive = percussive();
1425 set_percussive(false);
1427 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1430 source->drop_model();
1431 source->mark_streaming_midi_write_started (note_mode());
1433 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
1434 const Evoral::Event<Evoral::MusicalTime>& ev (*i);
1436 if (ev.time() >= begin_time && ev.time() < end_time) {
1438 const Evoral::MIDIEvent<Evoral::MusicalTime>* mev =
1439 static_cast<const Evoral::MIDIEvent<Evoral::MusicalTime>* > (&ev);
1446 if (mev->is_note_off()) {
1448 if (!mst.active (mev->note(), mev->channel())) {
1449 /* the matching note-on was outside the
1450 time range we were given, so just
1451 ignore this note-off.
1456 source->append_event_unlocked_beats (*i);
1457 mst.remove (mev->note(), mev->channel());
1459 } else if (mev->is_note_on()) {
1460 mst.add (mev->note(), mev->channel());
1461 source->append_event_unlocked_beats(*i);
1463 source->append_event_unlocked_beats(*i);
1468 mst.resolve_notes (*source, end_time);
1470 set_percussive(old_percussive);
1471 source->mark_streaming_write_completed();
1479 MidiModel::get_state()
1481 XMLNode *node = new XMLNode("MidiModel");
1485 Evoral::Sequence<MidiModel::TimeType>::NotePtr
1486 MidiModel::find_note (NotePtr other)
1488 Notes::iterator l = notes().lower_bound(other);
1490 if (l != notes().end()) {
1491 for (; (*l)->time() == other->time(); ++l) {
1492 /* NB: compare note contents, not note pointers.
1493 If "other" was a ptr to a note already in
1494 the model, we wouldn't be looking for it,
1497 if (**l == *other) {
1506 Evoral::Sequence<MidiModel::TimeType>::NotePtr
1507 MidiModel::find_note (gint note_id)
1509 /* used only for looking up notes when reloading history from disk,
1510 so we don't care about performance *too* much.
1513 for (Notes::iterator l = notes().begin(); l != notes().end(); ++l) {
1514 if ((*l)->id() == note_id) {
1522 MidiModel::PatchChangePtr
1523 MidiModel::find_patch_change (Evoral::event_id_t id)
1525 for (PatchChanges::iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) {
1526 if ((*i)->id() == id) {
1531 return PatchChangePtr ();
1534 boost::shared_ptr<Evoral::Event<MidiModel::TimeType> >
1535 MidiModel::find_sysex (gint sysex_id)
1537 /* used only for looking up notes when reloading history from disk,
1538 so we don't care about performance *too* much.
1541 for (SysExes::iterator l = sysexes().begin(); l != sysexes().end(); ++l) {
1542 if ((*l)->id() == sysex_id) {
1547 return boost::shared_ptr<Evoral::Event<TimeType> > ();
1550 /** Lock and invalidate the source.
1551 * This should be used by commands and editing things
1553 MidiModel::WriteLock
1554 MidiModel::edit_lock()
1556 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1559 Glib::Mutex::Lock* source_lock = new Glib::Mutex::Lock (ms->mutex());
1560 ms->invalidate(); // Release cached iterator's read lock on model
1561 return WriteLock(new WriteLockImpl(source_lock, _lock, _control_lock));
1564 /** Lock just the model, the source lock must already be held.
1565 * This should only be called from libardour/evoral places
1567 MidiModel::WriteLock
1568 MidiModel::write_lock()
1570 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1573 assert (!ms->mutex().trylock ());
1574 return WriteLock(new WriteLockImpl(NULL, _lock, _control_lock));
1578 MidiModel::resolve_overlaps_unlocked (const NotePtr note, void* arg)
1580 using namespace Evoral;
1582 if (_writing || insert_merge_policy() == InsertMergeRelax) {
1586 NoteDiffCommand* cmd = static_cast<NoteDiffCommand*>(arg);
1588 TimeType sa = note->time();
1589 TimeType ea = note->end_time();
1591 const Pitches& p (pitches (note->channel()));
1592 NotePtr search_note(new Note<TimeType>(0, 0, 0, note->note()));
1593 set<NotePtr> to_be_deleted;
1594 bool set_note_length = false;
1595 bool set_note_time = false;
1596 TimeType note_time = note->time();
1597 TimeType note_length = note->length();
1599 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 checking overlaps for note %2 @ %3\n", this, (int)note->note(), note->time()));
1601 for (Pitches::const_iterator i = p.lower_bound (search_note);
1602 i != p.end() && (*i)->note() == note->note(); ++i) {
1604 TimeType sb = (*i)->time();
1605 TimeType eb = (*i)->end_time();
1606 OverlapType overlap = OverlapNone;
1608 if ((sb > sa) && (eb <= ea)) {
1609 overlap = OverlapInternal;
1610 } else if ((eb >= sa) && (eb <= ea)) {
1611 overlap = OverlapStart;
1612 } else if ((sb > sa) && (sb <= ea)) {
1613 overlap = OverlapEnd;
1614 } else if ((sa >= sb) && (sa <= eb) && (ea <= eb)) {
1615 overlap = OverlapExternal;
1621 DEBUG_TRACE (DEBUG::Sequence, string_compose (
1622 "\toverlap is %1 for (%2,%3) vs (%4,%5)\n",
1623 enum_2_string(overlap), sa, ea, sb, eb));
1625 if (insert_merge_policy() == InsertMergeReject) {
1626 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 just reject\n", this));
1632 cerr << "OverlapStart\n";
1633 /* existing note covers start of new note */
1634 switch (insert_merge_policy()) {
1635 case InsertMergeReplace:
1636 to_be_deleted.insert (*i);
1638 case InsertMergeTruncateExisting:
1640 cmd->change (*i, NoteDiffCommand::Length, (note->time() - (*i)->time()));
1642 (*i)->set_length (note->time() - (*i)->time());
1644 case InsertMergeTruncateAddition:
1645 set_note_time = true;
1646 set_note_length = true;
1647 note_time = (*i)->time() + (*i)->length();
1648 note_length = min (note_length, (*i)->length() - ((*i)->end_time() - note->time()));
1650 case InsertMergeExtend:
1652 cmd->change ((*i), NoteDiffCommand::Length, note->end_time() - (*i)->time());
1654 (*i)->set_length (note->end_time() - (*i)->time());
1655 return -1; /* do not add the new note */
1665 cerr << "OverlapEnd\n";
1666 /* existing note covers end of new note */
1667 switch (insert_merge_policy()) {
1668 case InsertMergeReplace:
1669 to_be_deleted.insert (*i);
1672 case InsertMergeTruncateExisting:
1673 /* resetting the start time of the existing note
1674 is a problem because of time ordering.
1678 case InsertMergeTruncateAddition:
1679 set_note_length = true;
1680 note_length = min (note_length, ((*i)->time() - note->time()));
1683 case InsertMergeExtend:
1684 /* we can't reset the time of the existing note because
1685 that will corrupt time ordering. So remove the
1686 existing note and change the position/length
1687 of the new note (which has not been added yet)
1689 to_be_deleted.insert (*i);
1690 set_note_length = true;
1691 note_length = min (note_length, (*i)->end_time() - note->time());
1700 case OverlapExternal:
1701 cerr << "OverlapExt\n";
1702 /* existing note overlaps all the new note */
1703 switch (insert_merge_policy()) {
1704 case InsertMergeReplace:
1705 to_be_deleted.insert (*i);
1707 case InsertMergeTruncateExisting:
1708 case InsertMergeTruncateAddition:
1709 case InsertMergeExtend:
1710 /* cannot add in this case */
1719 case OverlapInternal:
1720 cerr << "OverlapInt\n";
1721 /* new note fully overlaps an existing note */
1722 switch (insert_merge_policy()) {
1723 case InsertMergeReplace:
1724 case InsertMergeTruncateExisting:
1725 case InsertMergeTruncateAddition:
1726 case InsertMergeExtend:
1727 /* delete the existing note, the new one will cover it */
1728 to_be_deleted.insert (*i);
1744 for (set<NotePtr>::iterator i = to_be_deleted.begin(); i != to_be_deleted.end(); ++i) {
1745 remove_note_unlocked (*i);
1748 cmd->side_effect_remove (*i);
1752 if (set_note_time) {
1754 cmd->change (note, NoteDiffCommand::StartTime, note_time);
1756 note->set_time (note_time);
1759 if (set_note_length) {
1761 cmd->change (note, NoteDiffCommand::Length, note_length);
1763 note->set_length (note_length);
1770 MidiModel::insert_merge_policy () const
1772 /* XXX ultimately this should be a per-track or even per-model policy */
1773 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1776 return ms->session().config.get_insert_merge_policy ();
1780 MidiModel::set_midi_source (boost::shared_ptr<MidiSource> s)
1782 boost::shared_ptr<MidiSource> old = _midi_source.lock ();
1788 _midi_source_connections.drop_connections ();
1792 s->InterpolationChanged.connect_same_thread (
1793 _midi_source_connections, boost::bind (&MidiModel::source_interpolation_changed, this, _1, _2)
1796 s->AutomationStateChanged.connect_same_thread (
1797 _midi_source_connections, boost::bind (&MidiModel::source_automation_state_changed, this, _1, _2)
1801 /** The source has signalled that the interpolation style for a parameter has changed. In order to
1802 * keep MidiSource and ControlList interpolation state the same, we pass this change onto the
1803 * appropriate ControlList.
1805 * The idea is that MidiSource and the MidiModel's ControlList states are kept in sync, and one
1806 * or the other is listened to by the GUI.
1809 MidiModel::source_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1811 Glib::Mutex::Lock lm (_control_lock);
1812 control(p)->list()->set_interpolation (s);
1815 /** A ControlList has signalled that its interpolation style has changed. Again, in order to keep
1816 * MidiSource and ControlList interpolation state in sync, we pass this change onto our MidiSource.
1819 MidiModel::control_list_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1821 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1824 ms->set_interpolation_of (p, s);
1828 MidiModel::source_automation_state_changed (Evoral::Parameter p, AutoState s)
1830 Glib::Mutex::Lock lm (_control_lock);
1831 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (control(p)->list ());
1832 al->set_automation_state (s);
1836 MidiModel::automation_list_automation_state_changed (Evoral::Parameter p, AutoState s)
1838 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1840 ms->set_automation_state_of (p, s);
1843 boost::shared_ptr<Evoral::Control>
1844 MidiModel::control_factory (Evoral::Parameter const & p)
1846 boost::shared_ptr<Evoral::Control> c = Automatable::control_factory (p);
1848 /* Set up newly created control's lists to the appropriate interpolation and
1849 automation state from our source.
1852 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1855 c->list()->set_interpolation (ms->interpolation_of (p));
1857 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (c->list ());
1860 al->set_automation_state (ms->automation_state_of (p));
1865 boost::shared_ptr<const MidiSource>
1866 MidiModel::midi_source ()
1868 return _midi_source.lock ();
1871 /** Moves notes, patch changes, controllers and sys-ex to insert silence at the start of the model.
1872 * Adds commands to the session's current undo stack to reflect the movements.
1875 MidiModel::insert_silence_at_start (TimeType t)
1877 boost::shared_ptr<MidiSource> s = _midi_source.lock ();
1882 if (!notes().empty ()) {
1883 NoteDiffCommand* c = new_note_diff_command ("insert silence");
1885 for (Notes::const_iterator i = notes().begin(); i != notes().end(); ++i) {
1886 c->change (*i, NoteDiffCommand::StartTime, (*i)->time() + t);
1889 apply_command_as_subcommand (s->session(), c);
1894 if (!patch_changes().empty ()) {
1895 PatchChangeDiffCommand* c = new_patch_change_diff_command ("insert silence");
1897 for (PatchChanges::const_iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) {
1898 c->change_time (*i, (*i)->time() + t);
1901 apply_command_as_subcommand (s->session(), c);
1906 for (Controls::iterator i = controls().begin(); i != controls().end(); ++i) {
1907 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
1908 XMLNode& before = ac->alist()->get_state ();
1909 i->second->list()->shift (0, t);
1910 XMLNode& after = ac->alist()->get_state ();
1911 s->session().add_command (new MementoCommand<AutomationList> (new MidiAutomationListBinder (s, i->first), &before, &after));
1916 if (!sysexes().empty()) {
1917 SysExDiffCommand* c = new_sysex_diff_command ("insert silence");
1919 for (SysExes::iterator i = sysexes().begin(); i != sysexes().end(); ++i) {
1920 c->change (*i, (*i)->time() + t);
1923 apply_command_as_subcommand (s->session(), c);
1927 /** Transpose notes in a time range by a given number of semitones. Notes
1928 * will be clamped at 0 and 127 if the transposition would make them exceed
1931 * @param from Start time.
1932 * @param end End time.
1933 * @param semitones Number of semitones to transpose by (+ve is higher, -ve is lower).
1936 MidiModel::transpose (TimeType from, TimeType to, int semitones)
1938 boost::shared_ptr<const MidiSource> s = midi_source ();
1940 NoteDiffCommand* c = new_note_diff_command (_("transpose"));
1942 for (Notes::iterator i = notes().begin(); i != notes().end(); ++i) {
1944 if ((*i)->time() >= to) {
1949 } else if ((*i)->time() >= from) {
1951 int new_note = (*i)->note() + semitones;
1955 } else if (new_note > 127) {
1959 c->change (*i, NoteDiffCommand::NoteNumber, (uint8_t) new_note);
1964 apply_command (s->session (), c);
1968 MidiModel::control_list_marked_dirty ()
1970 AutomatableSequence<Evoral::MusicalTime>::control_list_marked_dirty ();
1972 ContentsChanged (); /* EMIT SIGNAL */