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/evoral_types_convert.h"
37 #include "ardour/midi_automation_list_binder.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_source.h"
40 #include "ardour/midi_state_tracker.h"
41 #include "ardour/session.h"
42 #include "ardour/types.h"
47 DEFINE_ENUM_CONVERT(ARDOUR::MidiModel::NoteDiffCommand::Property);
48 DEFINE_ENUM_CONVERT(ARDOUR::MidiModel::SysExDiffCommand::Property);
49 DEFINE_ENUM_CONVERT(ARDOUR::MidiModel::PatchChangeDiffCommand::Property);
53 using namespace ARDOUR;
56 MidiModel::MidiModel (boost::shared_ptr<MidiSource> s)
57 : AutomatableSequence<TimeType>(s->session())
62 /** Start a new NoteDiff command.
64 * This has no side-effects on the model or Session, the returned command
65 * can be held on to for as long as the caller wishes, or discarded without
66 * formality, until apply_command is called and ownership is taken.
68 MidiModel::NoteDiffCommand*
69 MidiModel::new_note_diff_command (const string& name)
71 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
74 return new NoteDiffCommand (ms->model(), name);
77 /** Start a new SysExDiff command */
78 MidiModel::SysExDiffCommand*
79 MidiModel::new_sysex_diff_command (const string& name)
81 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
84 return new SysExDiffCommand (ms->model(), name);
87 /** Start a new PatchChangeDiff command */
88 MidiModel::PatchChangeDiffCommand*
89 MidiModel::new_patch_change_diff_command (const string& name)
91 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
94 return new PatchChangeDiffCommand (ms->model(), name);
100 * Ownership of cmd is taken, it must not be deleted by the caller.
101 * The command will constitute one item on the undo stack.
104 MidiModel::apply_command(Session& session, Command* cmd)
106 session.begin_reversible_command (cmd->name());
108 session.commit_reversible_command (cmd);
112 /** Apply a command as part of a larger reversible transaction
114 * Ownership of cmd is taken, it must not be deleted by the caller.
115 * The command will constitute one item on the undo stack.
118 MidiModel::apply_command_as_subcommand(Session& session, Command* cmd)
121 session.add_command (cmd);
125 /************** DIFF COMMAND ********************/
127 #define NOTE_DIFF_COMMAND_ELEMENT "NoteDiffCommand"
128 #define DIFF_NOTES_ELEMENT "ChangedNotes"
129 #define ADDED_NOTES_ELEMENT "AddedNotes"
130 #define REMOVED_NOTES_ELEMENT "RemovedNotes"
131 #define SIDE_EFFECT_REMOVALS_ELEMENT "SideEffectRemovals"
132 #define SYSEX_DIFF_COMMAND_ELEMENT "SysExDiffCommand"
133 #define DIFF_SYSEXES_ELEMENT "ChangedSysExes"
134 #define PATCH_CHANGE_DIFF_COMMAND_ELEMENT "PatchChangeDiffCommand"
135 #define ADDED_PATCH_CHANGES_ELEMENT "AddedPatchChanges"
136 #define REMOVED_PATCH_CHANGES_ELEMENT "RemovedPatchChanges"
137 #define DIFF_PATCH_CHANGES_ELEMENT "ChangedPatchChanges"
139 MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
147 MidiModel::NoteDiffCommand::NoteDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
148 : DiffCommand (m, "")
151 set_state (node, Stateful::loading_state_version);
155 MidiModel::NoteDiffCommand::add (const NotePtr note)
157 _removed_notes.remove(note);
158 _added_notes.push_back(note);
162 MidiModel::NoteDiffCommand::remove (const NotePtr note)
164 _added_notes.remove(note);
165 _removed_notes.push_back(note);
169 MidiModel::NoteDiffCommand::side_effect_remove (const NotePtr note)
171 side_effect_removals.insert (note);
175 MidiModel::NoteDiffCommand::get_value (const NotePtr note, Property prop)
179 return Variant(note->note());
181 return Variant(note->velocity());
183 return Variant(note->channel());
185 return Variant(note->time());
187 return Variant(note->length());
194 MidiModel::NoteDiffCommand::value_type(Property prop)
203 return Variant::BEATS;
206 return Variant::NOTHING;
210 MidiModel::NoteDiffCommand::change (const NotePtr note,
212 const Variant& new_value)
216 const NoteChange change = {
217 prop, note, 0, get_value(note, prop), new_value
220 if (change.old_value == new_value) {
224 _changes.push_back (change);
227 MidiModel::NoteDiffCommand &
228 MidiModel::NoteDiffCommand::operator+= (const NoteDiffCommand& other)
230 if (this == &other) {
234 if (_model != other._model) {
238 _added_notes.insert (_added_notes.end(), other._added_notes.begin(), other._added_notes.end());
239 _removed_notes.insert (_removed_notes.end(), other._removed_notes.begin(), other._removed_notes.end());
240 side_effect_removals.insert (other.side_effect_removals.begin(), other.side_effect_removals.end());
241 _changes.insert (_changes.end(), other._changes.begin(), other._changes.end());
247 MidiModel::NoteDiffCommand::operator() ()
250 MidiModel::WriteLock lock(_model->edit_lock());
252 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
253 if (!_model->add_note_unlocked(*i)) {
254 /* failed to add it, so don't leave it in the removed list, to
255 avoid apparent errors on undo.
257 _removed_notes.remove (*i);
261 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
262 _model->remove_note_unlocked(*i);
265 /* notes we modify in a way that requires remove-then-add to maintain ordering */
266 set<NotePtr> temporary_removals;
268 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
269 Property prop = i->property;
272 /* note found during deserialization, so try
273 again now that the model state is different.
275 i->note = _model->find_note (i->note_id);
281 if (temporary_removals.find (i->note) == temporary_removals.end()) {
282 _model->remove_note_unlocked (i->note);
283 temporary_removals.insert (i->note);
285 i->note->set_note (i->new_value.get_int());
289 if (temporary_removals.find (i->note) == temporary_removals.end()) {
290 _model->remove_note_unlocked (i->note);
291 temporary_removals.insert (i->note);
293 i->note->set_time (i->new_value.get_beats());
297 if (temporary_removals.find (i->note) == temporary_removals.end()) {
298 _model->remove_note_unlocked (i->note);
299 temporary_removals.insert (i->note);
301 i->note->set_channel (i->new_value.get_int());
304 /* no remove-then-add required for these properties, since we do not index them
308 i->note->set_velocity (i->new_value.get_int());
312 i->note->set_length (i->new_value.get_beats());
318 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
319 NoteDiffCommand side_effects (model(), "side effects");
320 if (_model->add_note_unlocked (*i, &side_effects)) {
321 /* The note was re-added ok */
322 *this += side_effects;
324 /* The note that we removed earlier could not be re-added. This change record
325 must say that the note was removed. We'll keep the changes we made, though,
326 as if the note is re-added by the undo the changes must also be undone.
328 _removed_notes.push_back (*i);
332 if (!side_effect_removals.empty()) {
334 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
335 cerr << "\t" << *i << ' ' << **i << endl;
340 _model->ContentsChanged(); /* EMIT SIGNAL */
344 MidiModel::NoteDiffCommand::undo ()
347 MidiModel::WriteLock lock(_model->edit_lock());
349 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
350 _model->remove_note_unlocked(*i);
353 /* Apply changes first; this is important in the case of a note change which
354 resulted in the note being removed by the overlap checker. If the overlap
355 checker removes a note, it will be in _removed_notes. We are going to re-add
356 it below, but first we must undo the changes we made so that the overlap
357 checker doesn't refuse the re-add.
360 /* notes we modify in a way that requires remove-then-add to maintain ordering */
361 set<NotePtr> temporary_removals;
364 /* lazily discover any affected notes that were not discovered when
365 * loading the history because of deletions, etc.
368 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
370 i->note = _model->find_note (i->note_id);
375 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
376 Property prop = i->property;
380 if (temporary_removals.find (i->note) == temporary_removals.end() &&
381 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
383 /* We only need to mark this note for re-add if (a) we haven't
384 already marked it and (b) it isn't on the _removed_notes
385 list (which means that it has already been removed and it
386 will be re-added anyway)
389 _model->remove_note_unlocked (i->note);
390 temporary_removals.insert (i->note);
392 i->note->set_note (i->old_value.get_int());
396 if (temporary_removals.find (i->note) == temporary_removals.end() &&
397 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
401 _model->remove_note_unlocked (i->note);
402 temporary_removals.insert (i->note);
404 i->note->set_time (i->old_value.get_beats());
408 if (temporary_removals.find (i->note) == temporary_removals.end() &&
409 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
413 _model->remove_note_unlocked (i->note);
414 temporary_removals.insert (i->note);
416 i->note->set_channel (i->old_value.get_int());
419 /* no remove-then-add required for these properties, since we do not index them
423 i->note->set_velocity (i->old_value.get_int());
427 i->note->set_length (i->old_value.get_beats());
432 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
433 _model->add_note_unlocked(*i);
436 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
437 _model->add_note_unlocked (*i);
440 /* finally add back notes that were removed by the "do". we don't care
441 about side effects here since the model should be back to its original
442 state once this is done.
445 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
446 _model->add_note_unlocked (*i);
450 _model->ContentsChanged(); /* EMIT SIGNAL */
454 MidiModel::NoteDiffCommand::marshal_note(const NotePtr note)
456 XMLNode* xml_note = new XMLNode("note");
458 xml_note->set_property ("id", note->id ());
459 xml_note->set_property ("note", note->note ());
460 xml_note->set_property ("channel", note->channel ());
461 xml_note->set_property ("time", note->time ());
462 xml_note->set_property ("length", note->length ());
463 xml_note->set_property ("velocity", note->velocity ());
468 Evoral::Sequence<MidiModel::TimeType>::NotePtr
469 MidiModel::NoteDiffCommand::unmarshal_note (XMLNode *xml_note)
471 Evoral::event_id_t id;
472 if (!xml_note->get_property ("id", id)) {
473 error << "note information missing ID value" << endmsg;
478 if (!xml_note->get_property("note", note)) {
479 warning << "note information missing note value" << endmsg;
484 if (!xml_note->get_property("channel", channel)) {
485 warning << "note information missing channel" << endmsg;
489 MidiModel::TimeType time;
490 if (!xml_note->get_property("time", time)) {
491 warning << "note information missing time" << endmsg;
492 time = MidiModel::TimeType();
495 MidiModel::TimeType length;
496 if (!xml_note->get_property("length", length)) {
497 warning << "note information missing length" << endmsg;
498 length = MidiModel::TimeType(1);
502 if (!xml_note->get_property("velocity", velocity)) {
503 warning << "note information missing velocity" << endmsg;
507 NotePtr note_ptr(new Evoral::Note<TimeType>(channel, time, length, note, velocity));
508 note_ptr->set_id (id);
514 MidiModel::NoteDiffCommand::marshal_change (const NoteChange& change)
516 XMLNode* xml_change = new XMLNode("Change");
518 /* first, the change itself */
520 xml_change->set_property ("property", change.property);
522 if (change.property == StartTime || change.property == Length) {
523 xml_change->set_property ("old", change.old_value.get_beats ());
525 xml_change->set_property ("old", change.old_value.get_int ());
528 if (change.property == StartTime || change.property == Length) {
529 xml_change->set_property ("new", change.new_value.get_beats ());
531 xml_change->set_property ("new", change.new_value.get_int ());
535 xml_change->set_property ("id", change.note->id());
536 } else if (change.note_id) {
537 warning << _("Change has no note, using note ID") << endmsg;
538 xml_change->set_property ("id", change.note_id);
540 error << _("Change has no note or note ID") << endmsg;
546 MidiModel::NoteDiffCommand::NoteChange
547 MidiModel::NoteDiffCommand::unmarshal_change (XMLNode *xml_change)
552 if (!xml_change->get_property("property", change.property)) {
553 fatal << "!!!" << endmsg;
554 abort(); /*NOTREACHED*/
558 if (!xml_change->get_property ("id", note_id)) {
559 error << _("No NoteID found for note property change - ignored") << endmsg;
564 Temporal::Beats old_time;
565 if ((change.property == StartTime || change.property == Length) &&
566 xml_change->get_property ("old", old_time)) {
567 change.old_value = old_time;
568 } else if (xml_change->get_property ("old", old_val)) {
569 change.old_value = old_val;
571 fatal << "!!!" << endmsg;
572 abort(); /*NOTREACHED*/
576 Temporal::Beats new_time;
577 if ((change.property == StartTime || change.property == Length) &&
578 xml_change->get_property ("new", new_time)) {
579 change.new_value = new_time;
580 } else if (xml_change->get_property ("new", new_val)) {
581 change.new_value = new_val;
583 fatal << "!!!" << endmsg;
584 abort(); /*NOTREACHED*/
587 /* we must point at the instance of the note that is actually in the model.
588 so go look for it ... it may not be there (it could have been
589 deleted in a later operation, so store the note id so that we can
590 look it up again later).
593 change.note = _model->find_note (note_id);
594 change.note_id = note_id;
600 MidiModel::NoteDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
602 if (diff_command.name() != string (NOTE_DIFF_COMMAND_ELEMENT)) {
608 _added_notes.clear();
609 XMLNode* added_notes = diff_command.child(ADDED_NOTES_ELEMENT);
611 XMLNodeList notes = added_notes->children();
612 transform(notes.begin(), notes.end(), back_inserter(_added_notes),
613 boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
619 _removed_notes.clear();
620 XMLNode* removed_notes = diff_command.child(REMOVED_NOTES_ELEMENT);
622 XMLNodeList notes = removed_notes->children();
623 transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
624 boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
632 XMLNode* changed_notes = diff_command.child(DIFF_NOTES_ELEMENT);
635 XMLNodeList notes = changed_notes->children();
636 transform (notes.begin(), notes.end(), back_inserter(_changes),
637 boost::bind (&NoteDiffCommand::unmarshal_change, this, _1));
641 /* side effect removals caused by changes */
643 side_effect_removals.clear();
645 XMLNode* side_effect_notes = diff_command.child(SIDE_EFFECT_REMOVALS_ELEMENT);
647 if (side_effect_notes) {
648 XMLNodeList notes = side_effect_notes->children();
649 for (XMLNodeList::iterator n = notes.begin(); n != notes.end(); ++n) {
650 side_effect_removals.insert (unmarshal_note (*n));
658 MidiModel::NoteDiffCommand::get_state ()
660 XMLNode* diff_command = new XMLNode (NOTE_DIFF_COMMAND_ELEMENT);
661 diff_command->set_property("midi-source", _model->midi_source()->id().to_s());
663 XMLNode* changes = diff_command->add_child(DIFF_NOTES_ELEMENT);
664 for_each(_changes.begin(), _changes.end(),
666 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
667 boost::bind (&NoteDiffCommand::marshal_change, this, _1)));
669 XMLNode* added_notes = diff_command->add_child(ADDED_NOTES_ELEMENT);
670 for_each(_added_notes.begin(), _added_notes.end(),
672 boost::bind (&XMLNode::add_child_nocopy, added_notes, _1),
673 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
675 XMLNode* removed_notes = diff_command->add_child(REMOVED_NOTES_ELEMENT);
676 for_each(_removed_notes.begin(), _removed_notes.end(),
678 boost::bind (&XMLNode::add_child_nocopy, removed_notes, _1),
679 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
681 /* if this command had side-effects, store that state too
684 if (!side_effect_removals.empty()) {
685 XMLNode* side_effect_notes = diff_command->add_child(SIDE_EFFECT_REMOVALS_ELEMENT);
686 for_each(side_effect_removals.begin(), side_effect_removals.end(),
688 boost::bind (&XMLNode::add_child_nocopy, side_effect_notes, _1),
689 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
692 return *diff_command;
695 MidiModel::SysExDiffCommand::SysExDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
696 : DiffCommand (m, "")
699 set_state (node, Stateful::loading_state_version);
703 MidiModel::SysExDiffCommand::change (boost::shared_ptr<Evoral::Event<TimeType> > s, TimeType new_time)
708 change.property = Time;
709 change.old_time = s->time ();
710 change.new_time = new_time;
712 _changes.push_back (change);
716 MidiModel::SysExDiffCommand::operator() ()
719 MidiModel::WriteLock lock (_model->edit_lock ());
721 for (list<SysExPtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
722 _model->remove_sysex_unlocked (*i);
725 /* find any sysex events that were missing when unmarshalling */
727 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
729 i->sysex = _model->find_sysex (i->sysex_id);
734 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
735 switch (i->property) {
737 i->sysex->set_time (i->new_time);
742 _model->ContentsChanged (); /* EMIT SIGNAL */
746 MidiModel::SysExDiffCommand::undo ()
749 MidiModel::WriteLock lock (_model->edit_lock ());
751 for (list<SysExPtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
752 _model->add_sysex_unlocked (*i);
755 /* find any sysex events that were missing when unmarshalling */
757 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
759 i->sysex = _model->find_sysex (i->sysex_id);
764 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
765 switch (i->property) {
767 i->sysex->set_time (i->old_time);
774 _model->ContentsChanged(); /* EMIT SIGNAL */
778 MidiModel::SysExDiffCommand::remove (SysExPtr sysex)
780 _removed.push_back(sysex);
784 MidiModel::SysExDiffCommand::marshal_change (const Change& change)
786 XMLNode* xml_change = new XMLNode ("Change");
788 /* first, the change itself */
790 xml_change->set_property ("property", change.property);
791 xml_change->set_property ("old", change.old_time);
792 xml_change->set_property ("new", change.new_time);
793 xml_change->set_property ("id", change.sysex->id());
798 MidiModel::SysExDiffCommand::Change
799 MidiModel::SysExDiffCommand::unmarshal_change (XMLNode *xml_change)
803 if (!xml_change->get_property ("property", change.property)) {
804 fatal << "!!!" << endmsg;
805 abort(); /*NOTREACHED*/
809 if (!xml_change->get_property ("id", sysex_id)) {
810 error << _("No SysExID found for sys-ex property change - ignored") << endmsg;
814 if (!xml_change->get_property ("old", change.old_time)) {
815 fatal << "!!!" << endmsg;
816 abort(); /*NOTREACHED*/
819 if (!xml_change->get_property ("new", change.new_time)) {
820 fatal << "!!!" << endmsg;
821 abort(); /*NOTREACHED*/
824 /* we must point at the instance of the sysex that is actually in the model.
825 so go look for it ...
828 change.sysex = _model->find_sysex (sysex_id);
829 change.sysex_id = sysex_id;
835 MidiModel::SysExDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
837 if (diff_command.name() != string (SYSEX_DIFF_COMMAND_ELEMENT)) {
845 XMLNode* changed_sysexes = diff_command.child (DIFF_SYSEXES_ELEMENT);
847 if (changed_sysexes) {
848 XMLNodeList sysexes = changed_sysexes->children();
849 transform (sysexes.begin(), sysexes.end(), back_inserter (_changes),
850 boost::bind (&SysExDiffCommand::unmarshal_change, this, _1));
858 MidiModel::SysExDiffCommand::get_state ()
860 XMLNode* diff_command = new XMLNode (SYSEX_DIFF_COMMAND_ELEMENT);
861 diff_command->set_property ("midi-source", _model->midi_source()->id().to_s());
863 XMLNode* changes = diff_command->add_child(DIFF_SYSEXES_ELEMENT);
864 for_each (_changes.begin(), _changes.end(),
866 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
867 boost::bind (&SysExDiffCommand::marshal_change, this, _1)));
869 return *diff_command;
872 MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const string& name)
873 : DiffCommand (m, name)
878 MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode & node)
879 : DiffCommand (m, "")
882 set_state (node, Stateful::loading_state_version);
886 MidiModel::PatchChangeDiffCommand::add (PatchChangePtr p)
888 _added.push_back (p);
892 MidiModel::PatchChangeDiffCommand::remove (PatchChangePtr p)
894 _removed.push_back (p);
898 MidiModel::PatchChangeDiffCommand::change_time (PatchChangePtr patch, TimeType t)
903 c.old_time = patch->time ();
906 _changes.push_back (c);
910 MidiModel::PatchChangeDiffCommand::change_channel (PatchChangePtr patch, uint8_t channel)
913 c.property = Channel;
915 c.old_channel = patch->channel ();
916 c.new_channel = channel;
917 c.patch_id = patch->id();
919 _changes.push_back (c);
923 MidiModel::PatchChangeDiffCommand::change_program (PatchChangePtr patch, uint8_t program)
926 c.property = Program;
928 c.old_program = patch->program ();
929 c.new_program = program;
930 c.patch_id = patch->id();
932 _changes.push_back (c);
936 MidiModel::PatchChangeDiffCommand::change_bank (PatchChangePtr patch, int bank)
941 c.old_bank = patch->bank ();
944 _changes.push_back (c);
948 MidiModel::PatchChangeDiffCommand::operator() ()
951 MidiModel::WriteLock lock (_model->edit_lock ());
953 for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) {
954 _model->add_patch_change_unlocked (*i);
957 for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
958 _model->remove_patch_change_unlocked (*i);
961 /* find any patch change events that were missing when unmarshalling */
963 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
965 i->patch = _model->find_patch_change (i->patch_id);
970 set<PatchChangePtr> temporary_removals;
972 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
973 switch (i->property) {
975 if (temporary_removals.find (i->patch) == temporary_removals.end()) {
976 _model->remove_patch_change_unlocked (i->patch);
977 temporary_removals.insert (i->patch);
979 i->patch->set_time (i->new_time);
983 i->patch->set_channel (i->new_channel);
987 i->patch->set_program (i->new_program);
991 i->patch->set_bank (i->new_bank);
996 for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
997 _model->add_patch_change_unlocked (*i);
1001 _model->ContentsChanged (); /* EMIT SIGNAL */
1005 MidiModel::PatchChangeDiffCommand::undo ()
1008 MidiModel::WriteLock lock (_model->edit_lock());
1010 for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) {
1011 _model->remove_patch_change_unlocked (*i);
1014 for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
1015 _model->add_patch_change_unlocked (*i);
1018 /* find any patch change events that were missing when unmarshalling */
1020 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
1022 i->patch = _model->find_patch_change (i->patch_id);
1027 set<PatchChangePtr> temporary_removals;
1029 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
1030 switch (i->property) {
1032 if (temporary_removals.find (i->patch) == temporary_removals.end()) {
1033 _model->remove_patch_change_unlocked (i->patch);
1034 temporary_removals.insert (i->patch);
1036 i->patch->set_time (i->old_time);
1040 i->patch->set_channel (i->old_channel);
1044 i->patch->set_program (i->old_program);
1048 i->patch->set_bank (i->old_bank);
1053 for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
1054 _model->add_patch_change_unlocked (*i);
1059 _model->ContentsChanged (); /* EMIT SIGNAL */
1063 MidiModel::PatchChangeDiffCommand::marshal_patch_change (constPatchChangePtr p)
1065 XMLNode* n = new XMLNode ("patch-change");
1067 n->set_property ("id", p->id ());
1068 n->set_property ("time", p->time ());
1069 n->set_property ("channel", p->channel ());
1070 n->set_property ("program", p->program ());
1071 n->set_property ("bank", p->bank ());
1077 MidiModel::PatchChangeDiffCommand::marshal_change (const Change& c)
1079 XMLNode* n = new XMLNode (X_("Change"));
1081 n->set_property (X_("property"), c.property);
1083 if (c.property == Time) {
1084 n->set_property (X_("old"), c.old_time);
1085 } else if (c.property == Channel) {
1086 n->set_property (X_("old"), c.old_channel);
1087 } else if (c.property == Program) {
1088 n->set_property (X_("old"), c.old_program);
1089 } else if (c.property == Bank) {
1090 n->set_property (X_("old"), c.old_bank);
1093 if (c.property == Time) {
1094 n->set_property (X_ ("new"), c.new_time);
1095 } else if (c.property == Channel) {
1096 n->set_property (X_ ("new"), c.new_channel);
1097 } else if (c.property == Program) {
1098 n->set_property (X_ ("new"), c.new_program);
1099 } else if (c.property == Bank) {
1100 n->set_property (X_ ("new"), c.new_bank);
1103 n->set_property ("id", c.patch->id ());
1108 MidiModel::PatchChangePtr
1109 MidiModel::PatchChangeDiffCommand::unmarshal_patch_change (XMLNode* n)
1111 Evoral::event_id_t id = 0;
1112 if (!n->get_property ("id", id)) {
1116 Temporal::Beats time = Temporal::Beats();
1117 if (!n->get_property ("time", time)) {
1121 uint8_t channel = 0;
1122 if (!n->get_property ("channel", channel)) {
1127 if (!n->get_property ("program", program)) {
1132 if (!n->get_property ("bank", bank)) {
1136 PatchChangePtr p (new Evoral::PatchChange<TimeType> (time, channel, program, bank));
1141 MidiModel::PatchChangeDiffCommand::Change
1142 MidiModel::PatchChangeDiffCommand::unmarshal_change (XMLNode* n)
1145 Evoral::event_id_t id;
1147 if (!n->get_property ("property", c.property) || !n->get_property ("id", id)) {
1151 if ((c.property == Time && !n->get_property ("old", c.old_time)) ||
1152 (c.property == Channel && !n->get_property ("old", c.old_channel)) ||
1153 (c.property == Program && !n->get_property ("old", c.old_program)) ||
1154 (c.property == Bank && !n->get_property ("old", c.old_bank))) {
1158 if ((c.property == Time && !n->get_property ("new", c.new_time)) ||
1159 (c.property == Channel && !n->get_property ("new", c.new_channel)) ||
1160 (c.property == Program && !n->get_property ("new", c.new_program)) ||
1161 (c.property == Bank && !n->get_property ("new", c.new_bank))) {
1165 c.patch = _model->find_patch_change (id);
1172 MidiModel::PatchChangeDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
1174 if (diff_command.name() != PATCH_CHANGE_DIFF_COMMAND_ELEMENT) {
1179 XMLNode* added = diff_command.child (ADDED_PATCH_CHANGES_ELEMENT);
1181 XMLNodeList p = added->children ();
1182 transform (p.begin(), p.end(), back_inserter (_added), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1));
1186 XMLNode* removed = diff_command.child (REMOVED_PATCH_CHANGES_ELEMENT);
1188 XMLNodeList p = removed->children ();
1189 transform (p.begin(), p.end(), back_inserter (_removed), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1));
1193 XMLNode* changed = diff_command.child (DIFF_PATCH_CHANGES_ELEMENT);
1195 XMLNodeList p = changed->children ();
1196 transform (p.begin(), p.end(), back_inserter (_changes), boost::bind (&PatchChangeDiffCommand::unmarshal_change, this, _1));
1203 MidiModel::PatchChangeDiffCommand::get_state ()
1205 XMLNode* diff_command = new XMLNode (PATCH_CHANGE_DIFF_COMMAND_ELEMENT);
1206 diff_command->set_property("midi-source", _model->midi_source()->id().to_s());
1208 XMLNode* added = diff_command->add_child (ADDED_PATCH_CHANGES_ELEMENT);
1209 for_each (_added.begin(), _added.end(),
1211 boost::bind (&XMLNode::add_child_nocopy, added, _1),
1212 boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1)
1216 XMLNode* removed = diff_command->add_child (REMOVED_PATCH_CHANGES_ELEMENT);
1217 for_each (_removed.begin(), _removed.end(),
1219 boost::bind (&XMLNode::add_child_nocopy, removed, _1),
1220 boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1)
1224 XMLNode* changes = diff_command->add_child (DIFF_PATCH_CHANGES_ELEMENT);
1225 for_each (_changes.begin(), _changes.end(),
1227 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
1228 boost::bind (&PatchChangeDiffCommand::marshal_change, this, _1)
1232 return *diff_command;
1235 /** Write all of the model to a MidiSource (i.e. save the model).
1236 * This is different from manually using read to write to a source in that
1237 * note off events are written regardless of the track mode. This is so the
1238 * user can switch a recorded track (with note durations from some instrument)
1239 * to percussive, save, reload, then switch it back to sustained without
1240 * destroying the original note durations.
1242 * Similarly, control events are written without interpolation (as with the
1246 MidiModel::write_to (boost::shared_ptr<MidiSource> source,
1247 const Glib::Threads::Mutex::Lock& source_lock)
1249 ReadLock lock(read_lock());
1251 const bool old_percussive = percussive();
1252 set_percussive(false);
1254 source->drop_model(source_lock);
1255 source->mark_streaming_midi_write_started (source_lock, note_mode());
1257 for (Evoral::Sequence<TimeType>::const_iterator i = begin(TimeType(), true); i != end(); ++i) {
1258 source->append_event_beats(source_lock, *i);
1261 set_percussive(old_percussive);
1262 source->mark_streaming_write_completed(source_lock);
1269 /** very similar to ::write_to() but writes to the model's own
1270 existing midi_source, without making it call MidiSource::drop_model().
1271 the caller is a MidiSource that needs to catch up with the state
1275 MidiModel::sync_to_source (const Glib::Threads::Mutex::Lock& source_lock)
1277 ReadLock lock(read_lock());
1279 const bool old_percussive = percussive();
1280 set_percussive(false);
1282 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1284 error << "MIDI model has no source to sync to" << endmsg;
1288 /* Invalidate and store active notes, which will be picked up by the iterator
1289 on the next roll if time progresses linearly. */
1290 ms->invalidate(source_lock);
1292 ms->mark_streaming_midi_write_started (source_lock, note_mode());
1294 for (Evoral::Sequence<TimeType>::const_iterator i = begin(TimeType(), true); i != end(); ++i) {
1295 ms->append_event_beats(source_lock, *i);
1298 set_percussive (old_percussive);
1299 ms->mark_streaming_write_completed (source_lock);
1306 /** Write part or all of the model to a MidiSource (i.e. save the model).
1307 * This is different from manually using read to write to a source in that
1308 * note off events are written regardless of the track mode. This is so the
1309 * user can switch a recorded track (with note durations from some instrument)
1310 * to percussive, save, reload, then switch it back to sustained without
1311 * destroying the original note durations.
1314 MidiModel::write_section_to (boost::shared_ptr<MidiSource> source,
1315 const Glib::Threads::Mutex::Lock& source_lock,
1316 TimeType begin_time,
1320 ReadLock lock(read_lock());
1321 MidiStateTracker mst;
1323 const bool old_percussive = percussive();
1324 set_percussive(false);
1326 source->drop_model(source_lock);
1327 source->mark_streaming_midi_write_started (source_lock, note_mode());
1329 for (Evoral::Sequence<TimeType>::const_iterator i = begin(TimeType(), true); i != end(); ++i) {
1330 if (i->time() >= begin_time && i->time() < end_time) {
1332 Evoral::Event<TimeType> mev (*i, true); /* copy the event */
1334 if (offset_events) {
1335 mev.set_time(mev.time() - begin_time);
1338 if (mev.is_note_off()) {
1340 if (!mst.active (mev.note(), mev.channel())) {
1341 /* the matching note-on was outside the
1342 time range we were given, so just
1343 ignore this note-off.
1348 source->append_event_beats (source_lock, mev);
1349 mst.remove (mev.note(), mev.channel());
1351 } else if (mev.is_note_on()) {
1352 mst.add (mev.note(), mev.channel());
1353 source->append_event_beats(source_lock, mev);
1355 source->append_event_beats(source_lock, mev);
1360 if (offset_events) {
1361 end_time -= begin_time;
1363 mst.resolve_notes (*source, source_lock, end_time);
1365 set_percussive(old_percussive);
1366 source->mark_streaming_write_completed(source_lock);
1374 MidiModel::get_state()
1376 XMLNode *node = new XMLNode("MidiModel");
1380 Evoral::Sequence<MidiModel::TimeType>::NotePtr
1381 MidiModel::find_note (NotePtr other)
1383 Notes::iterator l = notes().lower_bound(other);
1385 if (l != notes().end()) {
1386 for (; (*l)->time() == other->time(); ++l) {
1387 /* NB: compare note contents, not note pointers.
1388 If "other" was a ptr to a note already in
1389 the model, we wouldn't be looking for it,
1392 if (**l == *other) {
1401 Evoral::Sequence<MidiModel::TimeType>::NotePtr
1402 MidiModel::find_note (gint note_id)
1404 /* used only for looking up notes when reloading history from disk,
1405 so we don't care about performance *too* much.
1408 for (Notes::iterator l = notes().begin(); l != notes().end(); ++l) {
1409 if ((*l)->id() == note_id) {
1417 MidiModel::PatchChangePtr
1418 MidiModel::find_patch_change (Evoral::event_id_t id)
1420 for (PatchChanges::iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) {
1421 if ((*i)->id() == id) {
1426 return PatchChangePtr ();
1429 boost::shared_ptr<Evoral::Event<MidiModel::TimeType> >
1430 MidiModel::find_sysex (gint sysex_id)
1432 /* used only for looking up notes when reloading history from disk,
1433 so we don't care about performance *too* much.
1436 for (SysExes::iterator l = sysexes().begin(); l != sysexes().end(); ++l) {
1437 if ((*l)->id() == sysex_id) {
1442 return boost::shared_ptr<Evoral::Event<TimeType> > ();
1445 /** Lock and invalidate the source.
1446 * This should be used by commands and editing things
1448 MidiModel::WriteLock
1449 MidiModel::edit_lock()
1451 boost::shared_ptr<MidiSource> ms = _midi_source.lock();
1452 Glib::Threads::Mutex::Lock* source_lock = 0;
1455 /* Take source lock and invalidate iterator to release its lock on model.
1456 Add currently active notes to _active_notes so we can restore them
1457 if playback resumes at the same point after the edit. */
1458 source_lock = new Glib::Threads::Mutex::Lock(ms->mutex());
1459 ms->invalidate(*source_lock);
1462 return WriteLock(new WriteLockImpl(source_lock, _lock, _control_lock));
1466 MidiModel::resolve_overlaps_unlocked (const NotePtr note, void* arg)
1468 using namespace Evoral;
1470 if (_writing || insert_merge_policy() == InsertMergeRelax) {
1474 NoteDiffCommand* cmd = static_cast<NoteDiffCommand*>(arg);
1476 TimeType sa = note->time();
1477 TimeType ea = note->end_time();
1479 const Pitches& p (pitches (note->channel()));
1480 NotePtr search_note(new Note<TimeType>(0, TimeType(), TimeType(), note->note()));
1481 set<NotePtr> to_be_deleted;
1482 bool set_note_length = false;
1483 bool set_note_time = false;
1484 TimeType note_time = note->time();
1485 TimeType note_length = note->length();
1487 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 checking overlaps for note %2 @ %3\n", this, (int)note->note(), note->time()));
1489 for (Pitches::const_iterator i = p.lower_bound (search_note);
1490 i != p.end() && (*i)->note() == note->note(); ++i) {
1492 TimeType sb = (*i)->time();
1493 TimeType eb = (*i)->end_time();
1494 OverlapType overlap = OverlapNone;
1496 if ((sb > sa) && (eb <= ea)) {
1497 overlap = OverlapInternal;
1498 } else if ((eb > sa) && (eb <= ea)) {
1499 overlap = OverlapStart;
1500 } else if ((sb > sa) && (sb < ea)) {
1501 overlap = OverlapEnd;
1502 } else if ((sa >= sb) && (sa <= eb) && (ea <= eb)) {
1503 overlap = OverlapExternal;
1509 DEBUG_TRACE (DEBUG::Sequence, string_compose (
1510 "\toverlap is %1 for (%2,%3) vs (%4,%5)\n",
1511 enum_2_string(overlap), sa, ea, sb, eb));
1513 if (insert_merge_policy() == InsertMergeReject) {
1514 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 just reject\n", this));
1520 cerr << "OverlapStart\n";
1521 /* existing note covers start of new note */
1522 switch (insert_merge_policy()) {
1523 case InsertMergeReplace:
1524 to_be_deleted.insert (*i);
1526 case InsertMergeTruncateExisting:
1528 cmd->change (*i, NoteDiffCommand::Length, (note->time() - (*i)->time()));
1530 (*i)->set_length (note->time() - (*i)->time());
1532 case InsertMergeTruncateAddition:
1533 set_note_time = true;
1534 set_note_length = true;
1535 note_time = (*i)->time() + (*i)->length();
1536 note_length = min (note_length, (*i)->length() - ((*i)->end_time() - note->time()));
1538 case InsertMergeExtend:
1540 cmd->change ((*i), NoteDiffCommand::Length, note->end_time() - (*i)->time());
1542 (*i)->set_length (note->end_time() - (*i)->time());
1543 return -1; /* do not add the new note */
1546 abort(); /*NOTREACHED*/
1553 cerr << "OverlapEnd\n";
1554 /* existing note covers end of new note */
1555 switch (insert_merge_policy()) {
1556 case InsertMergeReplace:
1557 to_be_deleted.insert (*i);
1560 case InsertMergeTruncateExisting:
1561 /* resetting the start time of the existing note
1562 is a problem because of time ordering.
1566 case InsertMergeTruncateAddition:
1567 set_note_length = true;
1568 note_length = min (note_length, ((*i)->time() - note->time()));
1571 case InsertMergeExtend:
1572 /* we can't reset the time of the existing note because
1573 that will corrupt time ordering. So remove the
1574 existing note and change the position/length
1575 of the new note (which has not been added yet)
1577 to_be_deleted.insert (*i);
1578 set_note_length = true;
1579 note_length = min (note_length, (*i)->end_time() - note->time());
1582 abort(); /*NOTREACHED*/
1588 case OverlapExternal:
1589 cerr << "OverlapExt\n";
1590 /* existing note overlaps all the new note */
1591 switch (insert_merge_policy()) {
1592 case InsertMergeReplace:
1593 to_be_deleted.insert (*i);
1595 case InsertMergeTruncateExisting:
1596 case InsertMergeTruncateAddition:
1597 case InsertMergeExtend:
1598 /* cannot add in this case */
1601 abort(); /*NOTREACHED*/
1607 case OverlapInternal:
1608 cerr << "OverlapInt\n";
1609 /* new note fully overlaps an existing note */
1610 switch (insert_merge_policy()) {
1611 case InsertMergeReplace:
1612 case InsertMergeTruncateExisting:
1613 case InsertMergeTruncateAddition:
1614 case InsertMergeExtend:
1615 /* delete the existing note, the new one will cover it */
1616 to_be_deleted.insert (*i);
1619 abort(); /*NOTREACHED*/
1626 abort(); /*NOTREACHED*/
1632 for (set<NotePtr>::iterator i = to_be_deleted.begin(); i != to_be_deleted.end(); ++i) {
1633 remove_note_unlocked (*i);
1636 cmd->side_effect_remove (*i);
1640 if (set_note_time) {
1642 cmd->change (note, NoteDiffCommand::StartTime, note_time);
1644 note->set_time (note_time);
1647 if (set_note_length) {
1649 cmd->change (note, NoteDiffCommand::Length, note_length);
1651 note->set_length (note_length);
1658 MidiModel::insert_merge_policy () const
1660 /* XXX ultimately this should be a per-track or even per-model policy */
1661 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1664 return ms->session().config.get_insert_merge_policy ();
1668 MidiModel::set_midi_source (boost::shared_ptr<MidiSource> s)
1670 boost::shared_ptr<MidiSource> old = _midi_source.lock ();
1673 Source::Lock lm(old->mutex());
1674 old->invalidate (lm);
1677 _midi_source_connections.drop_connections ();
1681 s->InterpolationChanged.connect_same_thread (
1682 _midi_source_connections, boost::bind (&MidiModel::source_interpolation_changed, this, _1, _2)
1685 s->AutomationStateChanged.connect_same_thread (
1686 _midi_source_connections, boost::bind (&MidiModel::source_automation_state_changed, this, _1, _2)
1690 /** The source has signalled that the interpolation style for a parameter has changed. In order to
1691 * keep MidiSource and ControlList interpolation state the same, we pass this change onto the
1692 * appropriate ControlList.
1694 * The idea is that MidiSource and the MidiModel's ControlList states are kept in sync, and one
1695 * or the other is listened to by the GUI.
1698 MidiModel::source_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1700 Glib::Threads::Mutex::Lock lm (_control_lock);
1701 control(p)->list()->set_interpolation (s);
1704 /** A ControlList has signalled that its interpolation style has changed. Again, in order to keep
1705 * MidiSource and ControlList interpolation state in sync, we pass this change onto our MidiSource.
1708 MidiModel::control_list_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1710 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1713 ms->set_interpolation_of (p, s);
1717 MidiModel::source_automation_state_changed (Evoral::Parameter p, AutoState s)
1719 Glib::Threads::Mutex::Lock lm (_control_lock);
1720 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (control(p)->list ());
1721 al->set_automation_state (s);
1725 MidiModel::automation_list_automation_state_changed (Evoral::Parameter p, AutoState s)
1727 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1729 ms->set_automation_state_of (p, s);
1732 boost::shared_ptr<Evoral::Control>
1733 MidiModel::control_factory (Evoral::Parameter const & p)
1735 boost::shared_ptr<Evoral::Control> c = Automatable::control_factory (p);
1737 /* Set up newly created control's lists to the appropriate interpolation and
1738 automation state from our source.
1741 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1744 c->list()->set_interpolation (ms->interpolation_of (p));
1746 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (c->list ());
1749 al->set_automation_state (ms->automation_state_of (p));
1754 boost::shared_ptr<const MidiSource>
1755 MidiModel::midi_source ()
1757 return _midi_source.lock ();
1760 /** Moves notes, patch changes, controllers and sys-ex to insert silence at the start of the model.
1761 * Adds commands to the session's current undo stack to reflect the movements.
1764 MidiModel::insert_silence_at_start (TimeType t)
1766 boost::shared_ptr<MidiSource> s = _midi_source.lock ();
1771 if (!notes().empty ()) {
1772 NoteDiffCommand* c = new_note_diff_command ("insert silence");
1774 for (Notes::const_iterator i = notes().begin(); i != notes().end(); ++i) {
1775 c->change (*i, NoteDiffCommand::StartTime, (*i)->time() + t);
1778 apply_command_as_subcommand (s->session(), c);
1783 if (!patch_changes().empty ()) {
1784 PatchChangeDiffCommand* c = new_patch_change_diff_command ("insert silence");
1786 for (PatchChanges::const_iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) {
1787 c->change_time (*i, (*i)->time() + t);
1790 apply_command_as_subcommand (s->session(), c);
1795 for (Controls::iterator i = controls().begin(); i != controls().end(); ++i) {
1796 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
1797 XMLNode& before = ac->alist()->get_state ();
1798 i->second->list()->shift (0, t.to_double());
1799 XMLNode& after = ac->alist()->get_state ();
1800 s->session().add_command (new MementoCommand<AutomationList> (new MidiAutomationListBinder (s, i->first), &before, &after));
1805 if (!sysexes().empty()) {
1806 SysExDiffCommand* c = new_sysex_diff_command ("insert silence");
1808 for (SysExes::iterator i = sysexes().begin(); i != sysexes().end(); ++i) {
1809 c->change (*i, (*i)->time() + t);
1812 apply_command_as_subcommand (s->session(), c);
1815 ContentsShifted (t.to_double());
1819 MidiModel::transpose (NoteDiffCommand* c, const NotePtr note_ptr, int semitones)
1821 int new_note = note_ptr->note() + semitones;
1825 } else if (new_note > 127) {
1829 c->change (note_ptr, NoteDiffCommand::NoteNumber, (uint8_t) new_note);
1833 MidiModel::control_list_marked_dirty ()
1835 AutomatableSequence<Temporal::Beats>::control_list_marked_dirty ();
1837 ContentsChanged (); /* EMIT SIGNAL */