2 * Copyright (C) 2007-2016 David Robillard <d@drobilla.net>
3 * Copyright (C) 2008-2012 Hans Baier <hansfbaier@googlemail.com>
4 * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
6 * Copyright (C) 2014-2016 Robin Gareus <robin@gareus.org>
7 * Copyright (C) 2015-2017 Nick Mainsbridge <mainsbridge@gmail.com>
8 * Copyright (C) 2015 André Nusser <andre.nusser@googlemail.com>
9 * Copyright (C) 2016 Julien "_FrnchFrgg_" RIVAUD <frnchfrgg@free.fr>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
32 #include "pbd/compose.h"
33 #include "pbd/enumwriter.h"
34 #include "pbd/error.h"
36 #include "evoral/Control.h"
38 #include "midi++/events.h"
40 #include "ardour/automation_control.h"
41 #include "ardour/evoral_types_convert.h"
42 #include "ardour/midi_automation_list_binder.h"
43 #include "ardour/midi_model.h"
44 #include "ardour/midi_source.h"
45 #include "ardour/midi_state_tracker.h"
46 #include "ardour/session.h"
47 #include "ardour/types.h"
52 DEFINE_ENUM_CONVERT(ARDOUR::MidiModel::NoteDiffCommand::Property);
53 DEFINE_ENUM_CONVERT(ARDOUR::MidiModel::SysExDiffCommand::Property);
54 DEFINE_ENUM_CONVERT(ARDOUR::MidiModel::PatchChangeDiffCommand::Property);
58 using namespace ARDOUR;
61 MidiModel::MidiModel (boost::shared_ptr<MidiSource> s)
62 : AutomatableSequence<TimeType>(s->session())
67 MidiModel::NoteDiffCommand*
68 MidiModel::new_note_diff_command (const string& name)
70 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
73 return new NoteDiffCommand (ms->model(), name);
76 MidiModel::SysExDiffCommand*
77 MidiModel::new_sysex_diff_command (const string& name)
79 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
82 return new SysExDiffCommand (ms->model(), name);
85 MidiModel::PatchChangeDiffCommand*
86 MidiModel::new_patch_change_diff_command (const string& name)
88 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
91 return new PatchChangeDiffCommand (ms->model(), name);
96 MidiModel::apply_command(Session& session, Command* cmd)
98 session.begin_reversible_command (cmd->name());
100 session.commit_reversible_command (cmd);
105 MidiModel::apply_command_as_subcommand(Session& session, Command* cmd)
108 session.add_command (cmd);
112 /* ************* DIFF COMMAND ********************/
114 #define NOTE_DIFF_COMMAND_ELEMENT "NoteDiffCommand"
115 #define DIFF_NOTES_ELEMENT "ChangedNotes"
116 #define ADDED_NOTES_ELEMENT "AddedNotes"
117 #define REMOVED_NOTES_ELEMENT "RemovedNotes"
118 #define SIDE_EFFECT_REMOVALS_ELEMENT "SideEffectRemovals"
119 #define SYSEX_DIFF_COMMAND_ELEMENT "SysExDiffCommand"
120 #define DIFF_SYSEXES_ELEMENT "ChangedSysExes"
121 #define PATCH_CHANGE_DIFF_COMMAND_ELEMENT "PatchChangeDiffCommand"
122 #define ADDED_PATCH_CHANGES_ELEMENT "AddedPatchChanges"
123 #define REMOVED_PATCH_CHANGES_ELEMENT "RemovedPatchChanges"
124 #define DIFF_PATCH_CHANGES_ELEMENT "ChangedPatchChanges"
126 MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
134 MidiModel::NoteDiffCommand::NoteDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
135 : DiffCommand (m, "")
138 set_state (node, Stateful::loading_state_version);
142 MidiModel::NoteDiffCommand::add (const NotePtr note)
144 _removed_notes.remove(note);
145 _added_notes.push_back(note);
149 MidiModel::NoteDiffCommand::remove (const NotePtr note)
151 _added_notes.remove(note);
152 _removed_notes.push_back(note);
156 MidiModel::NoteDiffCommand::side_effect_remove (const NotePtr note)
158 side_effect_removals.insert (note);
162 MidiModel::NoteDiffCommand::get_value (const NotePtr note, Property prop)
166 return Variant(note->note());
168 return Variant(note->velocity());
170 return Variant(note->channel());
172 return Variant(note->time());
174 return Variant(note->length());
181 MidiModel::NoteDiffCommand::value_type(Property prop)
190 return Variant::BEATS;
193 return Variant::NOTHING;
197 MidiModel::NoteDiffCommand::change (const NotePtr note,
199 const Variant& new_value)
203 const NoteChange change = {
204 prop, note, 0, get_value(note, prop), new_value
207 if (change.old_value == new_value) {
211 _changes.push_back (change);
214 MidiModel::NoteDiffCommand &
215 MidiModel::NoteDiffCommand::operator+= (const NoteDiffCommand& other)
217 if (this == &other) {
221 if (_model != other._model) {
225 _added_notes.insert (_added_notes.end(), other._added_notes.begin(), other._added_notes.end());
226 _removed_notes.insert (_removed_notes.end(), other._removed_notes.begin(), other._removed_notes.end());
227 side_effect_removals.insert (other.side_effect_removals.begin(), other.side_effect_removals.end());
228 _changes.insert (_changes.end(), other._changes.begin(), other._changes.end());
234 MidiModel::NoteDiffCommand::operator() ()
237 MidiModel::WriteLock lock(_model->edit_lock());
239 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
240 if (!_model->add_note_unlocked(*i)) {
241 /* failed to add it, so don't leave it in the removed list, to
242 avoid apparent errors on undo.
244 _removed_notes.remove (*i);
248 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
249 _model->remove_note_unlocked(*i);
252 /* notes we modify in a way that requires remove-then-add to maintain ordering */
253 set<NotePtr> temporary_removals;
255 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
256 Property prop = i->property;
259 /* note found during deserialization, so try
260 again now that the model state is different.
262 i->note = _model->find_note (i->note_id);
268 if (temporary_removals.find (i->note) == temporary_removals.end()) {
269 _model->remove_note_unlocked (i->note);
270 temporary_removals.insert (i->note);
272 i->note->set_note (i->new_value.get_int());
276 if (temporary_removals.find (i->note) == temporary_removals.end()) {
277 _model->remove_note_unlocked (i->note);
278 temporary_removals.insert (i->note);
280 i->note->set_time (i->new_value.get_beats());
284 if (temporary_removals.find (i->note) == temporary_removals.end()) {
285 _model->remove_note_unlocked (i->note);
286 temporary_removals.insert (i->note);
288 i->note->set_channel (i->new_value.get_int());
291 /* no remove-then-add required for these properties, since we do not index them
295 i->note->set_velocity (i->new_value.get_int());
299 i->note->set_length (i->new_value.get_beats());
305 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
306 NoteDiffCommand side_effects (model(), "side effects");
307 if (_model->add_note_unlocked (*i, &side_effects)) {
308 /* The note was re-added ok */
309 *this += side_effects;
311 /* The note that we removed earlier could not be re-added. This change record
312 must say that the note was removed. We'll keep the changes we made, though,
313 as if the note is re-added by the undo the changes must also be undone.
315 _removed_notes.push_back (*i);
319 if (!side_effect_removals.empty()) {
321 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
322 cerr << "\t" << *i << ' ' << **i << endl;
327 _model->ContentsChanged(); /* EMIT SIGNAL */
331 MidiModel::NoteDiffCommand::undo ()
334 MidiModel::WriteLock lock(_model->edit_lock());
336 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
337 _model->remove_note_unlocked(*i);
340 /* Apply changes first; this is important in the case of a note change which
341 resulted in the note being removed by the overlap checker. If the overlap
342 checker removes a note, it will be in _removed_notes. We are going to re-add
343 it below, but first we must undo the changes we made so that the overlap
344 checker doesn't refuse the re-add.
347 /* notes we modify in a way that requires remove-then-add to maintain ordering */
348 set<NotePtr> temporary_removals;
351 /* lazily discover any affected notes that were not discovered when
352 * loading the history because of deletions, etc.
355 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
357 i->note = _model->find_note (i->note_id);
362 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
363 Property prop = i->property;
367 if (temporary_removals.find (i->note) == temporary_removals.end() &&
368 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
370 /* We only need to mark this note for re-add if (a) we haven't
371 already marked it and (b) it isn't on the _removed_notes
372 list (which means that it has already been removed and it
373 will be re-added anyway)
376 _model->remove_note_unlocked (i->note);
377 temporary_removals.insert (i->note);
379 i->note->set_note (i->old_value.get_int());
383 if (temporary_removals.find (i->note) == temporary_removals.end() &&
384 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
388 _model->remove_note_unlocked (i->note);
389 temporary_removals.insert (i->note);
391 i->note->set_time (i->old_value.get_beats());
395 if (temporary_removals.find (i->note) == temporary_removals.end() &&
396 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()) {
400 _model->remove_note_unlocked (i->note);
401 temporary_removals.insert (i->note);
403 i->note->set_channel (i->old_value.get_int());
406 /* no remove-then-add required for these properties, since we do not index them
410 i->note->set_velocity (i->old_value.get_int());
414 i->note->set_length (i->old_value.get_beats());
419 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
420 _model->add_note_unlocked(*i);
423 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
424 _model->add_note_unlocked (*i);
427 /* finally add back notes that were removed by the "do". we don't care
428 about side effects here since the model should be back to its original
429 state once this is done.
432 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
433 _model->add_note_unlocked (*i);
437 _model->ContentsChanged(); /* EMIT SIGNAL */
441 MidiModel::NoteDiffCommand::marshal_note(const NotePtr note)
443 XMLNode* xml_note = new XMLNode("note");
445 xml_note->set_property ("id", note->id ());
446 xml_note->set_property ("note", note->note ());
447 xml_note->set_property ("channel", note->channel ());
448 xml_note->set_property ("time", note->time ());
449 xml_note->set_property ("length", note->length ());
450 xml_note->set_property ("velocity", note->velocity ());
455 Evoral::Sequence<MidiModel::TimeType>::NotePtr
456 MidiModel::NoteDiffCommand::unmarshal_note (XMLNode *xml_note)
458 Evoral::event_id_t id;
459 if (!xml_note->get_property ("id", id)) {
460 error << "note information missing ID value" << endmsg;
465 if (!xml_note->get_property("note", note)) {
466 warning << "note information missing note value" << endmsg;
471 if (!xml_note->get_property("channel", channel)) {
472 warning << "note information missing channel" << endmsg;
476 MidiModel::TimeType time;
477 if (!xml_note->get_property("time", time)) {
478 warning << "note information missing time" << endmsg;
479 time = MidiModel::TimeType();
482 MidiModel::TimeType length;
483 if (!xml_note->get_property("length", length)) {
484 warning << "note information missing length" << endmsg;
485 length = MidiModel::TimeType(1);
489 if (!xml_note->get_property("velocity", velocity)) {
490 warning << "note information missing velocity" << endmsg;
494 NotePtr note_ptr(new Evoral::Note<TimeType>(channel, time, length, note, velocity));
495 note_ptr->set_id (id);
501 MidiModel::NoteDiffCommand::marshal_change (const NoteChange& change)
503 XMLNode* xml_change = new XMLNode("Change");
505 /* first, the change itself */
507 xml_change->set_property ("property", change.property);
509 if (change.property == StartTime || change.property == Length) {
510 xml_change->set_property ("old", change.old_value.get_beats ());
512 xml_change->set_property ("old", change.old_value.get_int ());
515 if (change.property == StartTime || change.property == Length) {
516 xml_change->set_property ("new", change.new_value.get_beats ());
518 xml_change->set_property ("new", change.new_value.get_int ());
522 xml_change->set_property ("id", change.note->id());
523 } else if (change.note_id) {
524 warning << _("Change has no note, using note ID") << endmsg;
525 xml_change->set_property ("id", change.note_id);
527 error << _("Change has no note or note ID") << endmsg;
533 MidiModel::NoteDiffCommand::NoteChange
534 MidiModel::NoteDiffCommand::unmarshal_change (XMLNode *xml_change)
539 if (!xml_change->get_property("property", change.property)) {
540 fatal << "!!!" << endmsg;
541 abort(); /*NOTREACHED*/
545 if (!xml_change->get_property ("id", note_id)) {
546 error << _("No NoteID found for note property change - ignored") << endmsg;
551 Temporal::Beats old_time;
552 if ((change.property == StartTime || change.property == Length) &&
553 xml_change->get_property ("old", old_time)) {
554 change.old_value = old_time;
555 } else if (xml_change->get_property ("old", old_val)) {
556 change.old_value = old_val;
558 fatal << "!!!" << endmsg;
559 abort(); /*NOTREACHED*/
563 Temporal::Beats new_time;
564 if ((change.property == StartTime || change.property == Length) &&
565 xml_change->get_property ("new", new_time)) {
566 change.new_value = new_time;
567 } else if (xml_change->get_property ("new", new_val)) {
568 change.new_value = new_val;
570 fatal << "!!!" << endmsg;
571 abort(); /*NOTREACHED*/
574 /* we must point at the instance of the note that is actually in the model.
575 so go look for it ... it may not be there (it could have been
576 deleted in a later operation, so store the note id so that we can
577 look it up again later).
580 change.note = _model->find_note (note_id);
581 change.note_id = note_id;
587 MidiModel::NoteDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
589 if (diff_command.name() != string (NOTE_DIFF_COMMAND_ELEMENT)) {
595 _added_notes.clear();
596 XMLNode* added_notes = diff_command.child(ADDED_NOTES_ELEMENT);
598 XMLNodeList notes = added_notes->children();
599 transform(notes.begin(), notes.end(), back_inserter(_added_notes),
600 boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
606 _removed_notes.clear();
607 XMLNode* removed_notes = diff_command.child(REMOVED_NOTES_ELEMENT);
609 XMLNodeList notes = removed_notes->children();
610 transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
611 boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
619 XMLNode* changed_notes = diff_command.child(DIFF_NOTES_ELEMENT);
622 XMLNodeList notes = changed_notes->children();
623 transform (notes.begin(), notes.end(), back_inserter(_changes),
624 boost::bind (&NoteDiffCommand::unmarshal_change, this, _1));
628 /* side effect removals caused by changes */
630 side_effect_removals.clear();
632 XMLNode* side_effect_notes = diff_command.child(SIDE_EFFECT_REMOVALS_ELEMENT);
634 if (side_effect_notes) {
635 XMLNodeList notes = side_effect_notes->children();
636 for (XMLNodeList::iterator n = notes.begin(); n != notes.end(); ++n) {
637 side_effect_removals.insert (unmarshal_note (*n));
645 MidiModel::NoteDiffCommand::get_state ()
647 XMLNode* diff_command = new XMLNode (NOTE_DIFF_COMMAND_ELEMENT);
648 diff_command->set_property("midi-source", _model->midi_source()->id().to_s());
650 XMLNode* changes = diff_command->add_child(DIFF_NOTES_ELEMENT);
651 for_each(_changes.begin(), _changes.end(),
653 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
654 boost::bind (&NoteDiffCommand::marshal_change, this, _1)));
656 XMLNode* added_notes = diff_command->add_child(ADDED_NOTES_ELEMENT);
657 for_each(_added_notes.begin(), _added_notes.end(),
659 boost::bind (&XMLNode::add_child_nocopy, added_notes, _1),
660 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
662 XMLNode* removed_notes = diff_command->add_child(REMOVED_NOTES_ELEMENT);
663 for_each(_removed_notes.begin(), _removed_notes.end(),
665 boost::bind (&XMLNode::add_child_nocopy, removed_notes, _1),
666 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
668 /* if this command had side-effects, store that state too
671 if (!side_effect_removals.empty()) {
672 XMLNode* side_effect_notes = diff_command->add_child(SIDE_EFFECT_REMOVALS_ELEMENT);
673 for_each(side_effect_removals.begin(), side_effect_removals.end(),
675 boost::bind (&XMLNode::add_child_nocopy, side_effect_notes, _1),
676 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
679 return *diff_command;
682 MidiModel::SysExDiffCommand::SysExDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
683 : DiffCommand (m, "")
686 set_state (node, Stateful::loading_state_version);
690 MidiModel::SysExDiffCommand::change (boost::shared_ptr<Evoral::Event<TimeType> > s, TimeType new_time)
695 change.property = Time;
696 change.old_time = s->time ();
697 change.new_time = new_time;
699 _changes.push_back (change);
703 MidiModel::SysExDiffCommand::operator() ()
706 MidiModel::WriteLock lock (_model->edit_lock ());
708 for (list<SysExPtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
709 _model->remove_sysex_unlocked (*i);
712 /* find any sysex events that were missing when unmarshalling */
714 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
716 i->sysex = _model->find_sysex (i->sysex_id);
721 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
722 switch (i->property) {
724 i->sysex->set_time (i->new_time);
729 _model->ContentsChanged (); /* EMIT SIGNAL */
733 MidiModel::SysExDiffCommand::undo ()
736 MidiModel::WriteLock lock (_model->edit_lock ());
738 for (list<SysExPtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
739 _model->add_sysex_unlocked (*i);
742 /* find any sysex events that were missing when unmarshalling */
744 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
746 i->sysex = _model->find_sysex (i->sysex_id);
751 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
752 switch (i->property) {
754 i->sysex->set_time (i->old_time);
761 _model->ContentsChanged(); /* EMIT SIGNAL */
765 MidiModel::SysExDiffCommand::remove (SysExPtr sysex)
767 _removed.push_back(sysex);
771 MidiModel::SysExDiffCommand::marshal_change (const Change& change)
773 XMLNode* xml_change = new XMLNode ("Change");
775 /* first, the change itself */
777 xml_change->set_property ("property", change.property);
778 xml_change->set_property ("old", change.old_time);
779 xml_change->set_property ("new", change.new_time);
780 xml_change->set_property ("id", change.sysex->id());
785 MidiModel::SysExDiffCommand::Change
786 MidiModel::SysExDiffCommand::unmarshal_change (XMLNode *xml_change)
790 if (!xml_change->get_property ("property", change.property)) {
791 fatal << "!!!" << endmsg;
792 abort(); /*NOTREACHED*/
796 if (!xml_change->get_property ("id", sysex_id)) {
797 error << _("No SysExID found for sys-ex property change - ignored") << endmsg;
801 if (!xml_change->get_property ("old", change.old_time)) {
802 fatal << "!!!" << endmsg;
803 abort(); /*NOTREACHED*/
806 if (!xml_change->get_property ("new", change.new_time)) {
807 fatal << "!!!" << endmsg;
808 abort(); /*NOTREACHED*/
811 /* we must point at the instance of the sysex that is actually in the model.
812 so go look for it ...
815 change.sysex = _model->find_sysex (sysex_id);
816 change.sysex_id = sysex_id;
822 MidiModel::SysExDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
824 if (diff_command.name() != string (SYSEX_DIFF_COMMAND_ELEMENT)) {
832 XMLNode* changed_sysexes = diff_command.child (DIFF_SYSEXES_ELEMENT);
834 if (changed_sysexes) {
835 XMLNodeList sysexes = changed_sysexes->children();
836 transform (sysexes.begin(), sysexes.end(), back_inserter (_changes),
837 boost::bind (&SysExDiffCommand::unmarshal_change, this, _1));
845 MidiModel::SysExDiffCommand::get_state ()
847 XMLNode* diff_command = new XMLNode (SYSEX_DIFF_COMMAND_ELEMENT);
848 diff_command->set_property ("midi-source", _model->midi_source()->id().to_s());
850 XMLNode* changes = diff_command->add_child(DIFF_SYSEXES_ELEMENT);
851 for_each (_changes.begin(), _changes.end(),
853 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
854 boost::bind (&SysExDiffCommand::marshal_change, this, _1)));
856 return *diff_command;
859 MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const string& name)
860 : DiffCommand (m, name)
865 MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode & node)
866 : DiffCommand (m, "")
869 set_state (node, Stateful::loading_state_version);
873 MidiModel::PatchChangeDiffCommand::add (PatchChangePtr p)
875 _added.push_back (p);
879 MidiModel::PatchChangeDiffCommand::remove (PatchChangePtr p)
881 _removed.push_back (p);
885 MidiModel::PatchChangeDiffCommand::change_time (PatchChangePtr patch, TimeType t)
890 c.old_time = patch->time ();
893 _changes.push_back (c);
897 MidiModel::PatchChangeDiffCommand::change_channel (PatchChangePtr patch, uint8_t channel)
900 c.property = Channel;
902 c.old_channel = patch->channel ();
903 c.new_channel = channel;
904 c.patch_id = patch->id();
906 _changes.push_back (c);
910 MidiModel::PatchChangeDiffCommand::change_program (PatchChangePtr patch, uint8_t program)
913 c.property = Program;
915 c.old_program = patch->program ();
916 c.new_program = program;
917 c.patch_id = patch->id();
919 _changes.push_back (c);
923 MidiModel::PatchChangeDiffCommand::change_bank (PatchChangePtr patch, int bank)
928 c.old_bank = patch->bank ();
931 _changes.push_back (c);
935 MidiModel::PatchChangeDiffCommand::operator() ()
938 MidiModel::WriteLock lock (_model->edit_lock ());
940 for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) {
941 _model->add_patch_change_unlocked (*i);
944 for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
945 _model->remove_patch_change_unlocked (*i);
948 /* find any patch change events that were missing when unmarshalling */
950 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
952 i->patch = _model->find_patch_change (i->patch_id);
957 set<PatchChangePtr> temporary_removals;
959 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
960 switch (i->property) {
962 if (temporary_removals.find (i->patch) == temporary_removals.end()) {
963 _model->remove_patch_change_unlocked (i->patch);
964 temporary_removals.insert (i->patch);
966 i->patch->set_time (i->new_time);
970 i->patch->set_channel (i->new_channel);
974 i->patch->set_program (i->new_program);
978 i->patch->set_bank (i->new_bank);
983 for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
984 _model->add_patch_change_unlocked (*i);
988 _model->ContentsChanged (); /* EMIT SIGNAL */
992 MidiModel::PatchChangeDiffCommand::undo ()
995 MidiModel::WriteLock lock (_model->edit_lock());
997 for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) {
998 _model->remove_patch_change_unlocked (*i);
1001 for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
1002 _model->add_patch_change_unlocked (*i);
1005 /* find any patch change events that were missing when unmarshalling */
1007 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
1009 i->patch = _model->find_patch_change (i->patch_id);
1014 set<PatchChangePtr> temporary_removals;
1016 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
1017 switch (i->property) {
1019 if (temporary_removals.find (i->patch) == temporary_removals.end()) {
1020 _model->remove_patch_change_unlocked (i->patch);
1021 temporary_removals.insert (i->patch);
1023 i->patch->set_time (i->old_time);
1027 i->patch->set_channel (i->old_channel);
1031 i->patch->set_program (i->old_program);
1035 i->patch->set_bank (i->old_bank);
1040 for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
1041 _model->add_patch_change_unlocked (*i);
1046 _model->ContentsChanged (); /* EMIT SIGNAL */
1050 MidiModel::PatchChangeDiffCommand::marshal_patch_change (constPatchChangePtr p)
1052 XMLNode* n = new XMLNode ("patch-change");
1054 n->set_property ("id", p->id ());
1055 n->set_property ("time", p->time ());
1056 n->set_property ("channel", p->channel ());
1057 n->set_property ("program", p->program ());
1058 n->set_property ("bank", p->bank ());
1064 MidiModel::PatchChangeDiffCommand::marshal_change (const Change& c)
1066 XMLNode* n = new XMLNode (X_("Change"));
1068 n->set_property (X_("property"), c.property);
1070 if (c.property == Time) {
1071 n->set_property (X_("old"), c.old_time);
1072 } else if (c.property == Channel) {
1073 n->set_property (X_("old"), c.old_channel);
1074 } else if (c.property == Program) {
1075 n->set_property (X_("old"), c.old_program);
1076 } else if (c.property == Bank) {
1077 n->set_property (X_("old"), c.old_bank);
1080 if (c.property == Time) {
1081 n->set_property (X_ ("new"), c.new_time);
1082 } else if (c.property == Channel) {
1083 n->set_property (X_ ("new"), c.new_channel);
1084 } else if (c.property == Program) {
1085 n->set_property (X_ ("new"), c.new_program);
1086 } else if (c.property == Bank) {
1087 n->set_property (X_ ("new"), c.new_bank);
1090 n->set_property ("id", c.patch->id ());
1095 MidiModel::PatchChangePtr
1096 MidiModel::PatchChangeDiffCommand::unmarshal_patch_change (XMLNode* n)
1098 Evoral::event_id_t id = 0;
1099 if (!n->get_property ("id", id)) {
1103 Temporal::Beats time = Temporal::Beats();
1104 if (!n->get_property ("time", time)) {
1108 uint8_t channel = 0;
1109 if (!n->get_property ("channel", channel)) {
1114 if (!n->get_property ("program", program)) {
1119 if (!n->get_property ("bank", bank)) {
1123 PatchChangePtr p (new Evoral::PatchChange<TimeType> (time, channel, program, bank));
1128 MidiModel::PatchChangeDiffCommand::Change
1129 MidiModel::PatchChangeDiffCommand::unmarshal_change (XMLNode* n)
1132 Evoral::event_id_t id;
1134 if (!n->get_property ("property", c.property) || !n->get_property ("id", id)) {
1138 if ((c.property == Time && !n->get_property ("old", c.old_time)) ||
1139 (c.property == Channel && !n->get_property ("old", c.old_channel)) ||
1140 (c.property == Program && !n->get_property ("old", c.old_program)) ||
1141 (c.property == Bank && !n->get_property ("old", c.old_bank))) {
1145 if ((c.property == Time && !n->get_property ("new", c.new_time)) ||
1146 (c.property == Channel && !n->get_property ("new", c.new_channel)) ||
1147 (c.property == Program && !n->get_property ("new", c.new_program)) ||
1148 (c.property == Bank && !n->get_property ("new", c.new_bank))) {
1152 c.patch = _model->find_patch_change (id);
1159 MidiModel::PatchChangeDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
1161 if (diff_command.name() != PATCH_CHANGE_DIFF_COMMAND_ELEMENT) {
1166 XMLNode* added = diff_command.child (ADDED_PATCH_CHANGES_ELEMENT);
1168 XMLNodeList p = added->children ();
1169 transform (p.begin(), p.end(), back_inserter (_added), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1));
1173 XMLNode* removed = diff_command.child (REMOVED_PATCH_CHANGES_ELEMENT);
1175 XMLNodeList p = removed->children ();
1176 transform (p.begin(), p.end(), back_inserter (_removed), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1));
1180 XMLNode* changed = diff_command.child (DIFF_PATCH_CHANGES_ELEMENT);
1182 XMLNodeList p = changed->children ();
1183 transform (p.begin(), p.end(), back_inserter (_changes), boost::bind (&PatchChangeDiffCommand::unmarshal_change, this, _1));
1190 MidiModel::PatchChangeDiffCommand::get_state ()
1192 XMLNode* diff_command = new XMLNode (PATCH_CHANGE_DIFF_COMMAND_ELEMENT);
1193 diff_command->set_property("midi-source", _model->midi_source()->id().to_s());
1195 XMLNode* added = diff_command->add_child (ADDED_PATCH_CHANGES_ELEMENT);
1196 for_each (_added.begin(), _added.end(),
1198 boost::bind (&XMLNode::add_child_nocopy, added, _1),
1199 boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1)
1203 XMLNode* removed = diff_command->add_child (REMOVED_PATCH_CHANGES_ELEMENT);
1204 for_each (_removed.begin(), _removed.end(),
1206 boost::bind (&XMLNode::add_child_nocopy, removed, _1),
1207 boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1)
1211 XMLNode* changes = diff_command->add_child (DIFF_PATCH_CHANGES_ELEMENT);
1212 for_each (_changes.begin(), _changes.end(),
1214 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
1215 boost::bind (&PatchChangeDiffCommand::marshal_change, this, _1)
1219 return *diff_command;
1222 /** Write all of the model to a MidiSource (i.e. save the model).
1223 * This is different from manually using read to write to a source in that
1224 * note off events are written regardless of the track mode. This is so the
1225 * user can switch a recorded track (with note durations from some instrument)
1226 * to percussive, save, reload, then switch it back to sustained without
1227 * destroying the original note durations.
1229 * Similarly, control events are written without interpolation (as with the
1233 MidiModel::write_to (boost::shared_ptr<MidiSource> source,
1234 const Glib::Threads::Mutex::Lock& source_lock)
1236 ReadLock lock(read_lock());
1238 const bool old_percussive = percussive();
1239 set_percussive(false);
1241 source->drop_model(source_lock);
1242 source->mark_streaming_midi_write_started (source_lock, note_mode());
1244 for (Evoral::Sequence<TimeType>::const_iterator i = begin(TimeType(), true); i != end(); ++i) {
1245 source->append_event_beats(source_lock, *i);
1248 set_percussive(old_percussive);
1249 source->mark_streaming_write_completed(source_lock);
1256 /** very similar to ::write_to() but writes to the model's own
1257 existing midi_source, without making it call MidiSource::drop_model().
1258 the caller is a MidiSource that needs to catch up with the state
1262 MidiModel::sync_to_source (const Glib::Threads::Mutex::Lock& source_lock)
1264 ReadLock lock(read_lock());
1266 const bool old_percussive = percussive();
1267 set_percussive(false);
1269 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1271 error << "MIDI model has no source to sync to" << endmsg;
1275 /* Invalidate and store active notes, which will be picked up by the iterator
1276 on the next roll if time progresses linearly. */
1277 ms->invalidate(source_lock);
1279 ms->mark_streaming_midi_write_started (source_lock, note_mode());
1281 for (Evoral::Sequence<TimeType>::const_iterator i = begin(TimeType(), true); i != end(); ++i) {
1282 ms->append_event_beats(source_lock, *i);
1285 set_percussive (old_percussive);
1286 ms->mark_streaming_write_completed (source_lock);
1293 /** Write part or all of the model to a MidiSource (i.e. save the model).
1294 * This is different from manually using read to write to a source in that
1295 * note off events are written regardless of the track mode. This is so the
1296 * user can switch a recorded track (with note durations from some instrument)
1297 * to percussive, save, reload, then switch it back to sustained without
1298 * destroying the original note durations.
1301 MidiModel::write_section_to (boost::shared_ptr<MidiSource> source,
1302 const Glib::Threads::Mutex::Lock& source_lock,
1303 TimeType begin_time,
1307 ReadLock lock(read_lock());
1308 MidiStateTracker mst;
1310 const bool old_percussive = percussive();
1311 set_percussive(false);
1313 source->drop_model(source_lock);
1314 source->mark_streaming_midi_write_started (source_lock, note_mode());
1316 for (Evoral::Sequence<TimeType>::const_iterator i = begin(TimeType(), true); i != end(); ++i) {
1317 if (i->time() >= begin_time && i->time() < end_time) {
1319 Evoral::Event<TimeType> mev (*i, true); /* copy the event */
1321 if (offset_events) {
1322 mev.set_time(mev.time() - begin_time);
1325 if (mev.is_note_off()) {
1327 if (!mst.active (mev.note(), mev.channel())) {
1328 /* the matching note-on was outside the
1329 time range we were given, so just
1330 ignore this note-off.
1335 source->append_event_beats (source_lock, mev);
1336 mst.remove (mev.note(), mev.channel());
1338 } else if (mev.is_note_on()) {
1339 mst.add (mev.note(), mev.channel());
1340 source->append_event_beats(source_lock, mev);
1342 source->append_event_beats(source_lock, mev);
1347 if (offset_events) {
1348 end_time -= begin_time;
1350 mst.resolve_notes (*source, source_lock, end_time);
1352 set_percussive(old_percussive);
1353 source->mark_streaming_write_completed(source_lock);
1361 MidiModel::get_state()
1363 XMLNode *node = new XMLNode("MidiModel");
1367 Evoral::Sequence<MidiModel::TimeType>::NotePtr
1368 MidiModel::find_note (NotePtr other)
1370 Notes::iterator l = notes().lower_bound(other);
1372 if (l != notes().end()) {
1373 for (; (*l)->time() == other->time(); ++l) {
1374 /* NB: compare note contents, not note pointers.
1375 If "other" was a ptr to a note already in
1376 the model, we wouldn't be looking for it,
1379 if (**l == *other) {
1388 Evoral::Sequence<MidiModel::TimeType>::NotePtr
1389 MidiModel::find_note (gint note_id)
1391 /* used only for looking up notes when reloading history from disk,
1392 so we don't care about performance *too* much.
1395 for (Notes::iterator l = notes().begin(); l != notes().end(); ++l) {
1396 if ((*l)->id() == note_id) {
1404 MidiModel::PatchChangePtr
1405 MidiModel::find_patch_change (Evoral::event_id_t id)
1407 for (PatchChanges::iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) {
1408 if ((*i)->id() == id) {
1413 return PatchChangePtr ();
1416 boost::shared_ptr<Evoral::Event<MidiModel::TimeType> >
1417 MidiModel::find_sysex (gint sysex_id)
1419 /* used only for looking up notes when reloading history from disk,
1420 so we don't care about performance *too* much.
1423 for (SysExes::iterator l = sysexes().begin(); l != sysexes().end(); ++l) {
1424 if ((*l)->id() == sysex_id) {
1429 return boost::shared_ptr<Evoral::Event<TimeType> > ();
1432 /** Lock and invalidate the source.
1433 * This should be used by commands and editing things
1435 MidiModel::WriteLock
1436 MidiModel::edit_lock()
1438 boost::shared_ptr<MidiSource> ms = _midi_source.lock();
1439 Glib::Threads::Mutex::Lock* source_lock = 0;
1442 /* Take source lock and invalidate iterator to release its lock on model.
1443 Add currently active notes to _active_notes so we can restore them
1444 if playback resumes at the same point after the edit. */
1445 source_lock = new Glib::Threads::Mutex::Lock(ms->mutex());
1446 ms->invalidate(*source_lock);
1449 return WriteLock(new WriteLockImpl(source_lock, _lock, _control_lock));
1453 MidiModel::resolve_overlaps_unlocked (const NotePtr note, void* arg)
1455 using namespace Evoral;
1457 if (_writing || insert_merge_policy() == InsertMergeRelax) {
1461 NoteDiffCommand* cmd = static_cast<NoteDiffCommand*>(arg);
1463 TimeType sa = note->time();
1464 TimeType ea = note->end_time();
1466 const Pitches& p (pitches (note->channel()));
1467 NotePtr search_note(new Note<TimeType>(0, TimeType(), TimeType(), note->note()));
1468 set<NotePtr> to_be_deleted;
1469 bool set_note_length = false;
1470 bool set_note_time = false;
1471 TimeType note_time = note->time();
1472 TimeType note_length = note->length();
1474 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 checking overlaps for note %2 @ %3\n", this, (int)note->note(), note->time()));
1476 for (Pitches::const_iterator i = p.lower_bound (search_note);
1477 i != p.end() && (*i)->note() == note->note(); ++i) {
1479 TimeType sb = (*i)->time();
1480 TimeType eb = (*i)->end_time();
1481 OverlapType overlap = OverlapNone;
1483 if ((sb > sa) && (eb <= ea)) {
1484 overlap = OverlapInternal;
1485 } else if ((eb > sa) && (eb <= ea)) {
1486 overlap = OverlapStart;
1487 } else if ((sb > sa) && (sb < ea)) {
1488 overlap = OverlapEnd;
1489 } else if ((sa >= sb) && (sa <= eb) && (ea <= eb)) {
1490 overlap = OverlapExternal;
1496 DEBUG_TRACE (DEBUG::Sequence, string_compose (
1497 "\toverlap is %1 for (%2,%3) vs (%4,%5)\n",
1498 enum_2_string(overlap), sa, ea, sb, eb));
1500 if (insert_merge_policy() == InsertMergeReject) {
1501 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 just reject\n", this));
1507 cerr << "OverlapStart\n";
1508 /* existing note covers start of new note */
1509 switch (insert_merge_policy()) {
1510 case InsertMergeReplace:
1511 to_be_deleted.insert (*i);
1513 case InsertMergeTruncateExisting:
1515 cmd->change (*i, NoteDiffCommand::Length, (note->time() - (*i)->time()));
1517 (*i)->set_length (note->time() - (*i)->time());
1519 case InsertMergeTruncateAddition:
1520 set_note_time = true;
1521 set_note_length = true;
1522 note_time = (*i)->time() + (*i)->length();
1523 note_length = min (note_length, (*i)->length() - ((*i)->end_time() - note->time()));
1525 case InsertMergeExtend:
1527 cmd->change ((*i), NoteDiffCommand::Length, note->end_time() - (*i)->time());
1529 (*i)->set_length (note->end_time() - (*i)->time());
1530 return -1; /* do not add the new note */
1533 abort(); /*NOTREACHED*/
1540 cerr << "OverlapEnd\n";
1541 /* existing note covers end of new note */
1542 switch (insert_merge_policy()) {
1543 case InsertMergeReplace:
1544 to_be_deleted.insert (*i);
1547 case InsertMergeTruncateExisting:
1548 /* resetting the start time of the existing note
1549 is a problem because of time ordering.
1553 case InsertMergeTruncateAddition:
1554 set_note_length = true;
1555 note_length = min (note_length, ((*i)->time() - note->time()));
1558 case InsertMergeExtend:
1559 /* we can't reset the time of the existing note because
1560 that will corrupt time ordering. So remove the
1561 existing note and change the position/length
1562 of the new note (which has not been added yet)
1564 to_be_deleted.insert (*i);
1565 set_note_length = true;
1566 note_length = min (note_length, (*i)->end_time() - note->time());
1569 abort(); /*NOTREACHED*/
1575 case OverlapExternal:
1576 cerr << "OverlapExt\n";
1577 /* existing note overlaps all the new note */
1578 switch (insert_merge_policy()) {
1579 case InsertMergeReplace:
1580 to_be_deleted.insert (*i);
1582 case InsertMergeTruncateExisting:
1583 case InsertMergeTruncateAddition:
1584 case InsertMergeExtend:
1585 /* cannot add in this case */
1588 abort(); /*NOTREACHED*/
1594 case OverlapInternal:
1595 cerr << "OverlapInt\n";
1596 /* new note fully overlaps an existing note */
1597 switch (insert_merge_policy()) {
1598 case InsertMergeReplace:
1599 case InsertMergeTruncateExisting:
1600 case InsertMergeTruncateAddition:
1601 case InsertMergeExtend:
1602 /* delete the existing note, the new one will cover it */
1603 to_be_deleted.insert (*i);
1606 abort(); /*NOTREACHED*/
1613 abort(); /*NOTREACHED*/
1619 for (set<NotePtr>::iterator i = to_be_deleted.begin(); i != to_be_deleted.end(); ++i) {
1620 remove_note_unlocked (*i);
1623 cmd->side_effect_remove (*i);
1627 if (set_note_time) {
1629 cmd->change (note, NoteDiffCommand::StartTime, note_time);
1631 note->set_time (note_time);
1634 if (set_note_length) {
1636 cmd->change (note, NoteDiffCommand::Length, note_length);
1638 note->set_length (note_length);
1645 MidiModel::insert_merge_policy () const
1647 /* XXX ultimately this should be a per-track or even per-model policy */
1648 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1651 return ms->session().config.get_insert_merge_policy ();
1655 MidiModel::set_midi_source (boost::shared_ptr<MidiSource> s)
1657 boost::shared_ptr<MidiSource> old = _midi_source.lock ();
1660 Source::Lock lm(old->mutex());
1661 old->invalidate (lm);
1664 _midi_source_connections.drop_connections ();
1668 s->InterpolationChanged.connect_same_thread (
1669 _midi_source_connections, boost::bind (&MidiModel::source_interpolation_changed, this, _1, _2)
1672 s->AutomationStateChanged.connect_same_thread (
1673 _midi_source_connections, boost::bind (&MidiModel::source_automation_state_changed, this, _1, _2)
1677 /** The source has signalled that the interpolation style for a parameter has changed. In order to
1678 * keep MidiSource and ControlList interpolation state the same, we pass this change onto the
1679 * appropriate ControlList.
1681 * The idea is that MidiSource and the MidiModel's ControlList states are kept in sync, and one
1682 * or the other is listened to by the GUI.
1685 MidiModel::source_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1687 Glib::Threads::Mutex::Lock lm (_control_lock);
1688 control(p)->list()->set_interpolation (s);
1691 /** A ControlList has signalled that its interpolation style has changed. Again, in order to keep
1692 * MidiSource and ControlList interpolation state in sync, we pass this change onto our MidiSource.
1695 MidiModel::control_list_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1697 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1700 ms->set_interpolation_of (p, s);
1704 MidiModel::source_automation_state_changed (Evoral::Parameter p, AutoState s)
1706 Glib::Threads::Mutex::Lock lm (_control_lock);
1707 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (control(p)->list ());
1708 al->set_automation_state (s);
1712 MidiModel::automation_list_automation_state_changed (Evoral::Parameter p, AutoState s)
1714 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1716 ms->set_automation_state_of (p, s);
1719 boost::shared_ptr<Evoral::Control>
1720 MidiModel::control_factory (Evoral::Parameter const & p)
1722 boost::shared_ptr<Evoral::Control> c = Automatable::control_factory (p);
1724 /* Set up newly created control's lists to the appropriate interpolation and
1725 automation state from our source.
1728 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1731 c->list()->set_interpolation (ms->interpolation_of (p));
1733 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (c->list ());
1736 al->set_automation_state (ms->automation_state_of (p));
1741 boost::shared_ptr<const MidiSource>
1742 MidiModel::midi_source ()
1744 return _midi_source.lock ();
1747 /** Moves notes, patch changes, controllers and sys-ex to insert silence at the start of the model.
1748 * Adds commands to the session's current undo stack to reflect the movements.
1751 MidiModel::insert_silence_at_start (TimeType t)
1753 boost::shared_ptr<MidiSource> s = _midi_source.lock ();
1758 if (!notes().empty ()) {
1759 NoteDiffCommand* c = new_note_diff_command ("insert silence");
1761 for (Notes::const_iterator i = notes().begin(); i != notes().end(); ++i) {
1762 c->change (*i, NoteDiffCommand::StartTime, (*i)->time() + t);
1765 apply_command_as_subcommand (s->session(), c);
1770 if (!patch_changes().empty ()) {
1771 PatchChangeDiffCommand* c = new_patch_change_diff_command ("insert silence");
1773 for (PatchChanges::const_iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) {
1774 c->change_time (*i, (*i)->time() + t);
1777 apply_command_as_subcommand (s->session(), c);
1782 for (Controls::iterator i = controls().begin(); i != controls().end(); ++i) {
1783 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
1784 XMLNode& before = ac->alist()->get_state ();
1785 i->second->list()->shift (0, t.to_double());
1786 XMLNode& after = ac->alist()->get_state ();
1787 s->session().add_command (new MementoCommand<AutomationList> (new MidiAutomationListBinder (s, i->first), &before, &after));
1792 if (!sysexes().empty()) {
1793 SysExDiffCommand* c = new_sysex_diff_command ("insert silence");
1795 for (SysExes::iterator i = sysexes().begin(); i != sysexes().end(); ++i) {
1796 c->change (*i, (*i)->time() + t);
1799 apply_command_as_subcommand (s->session(), c);
1802 ContentsShifted (t.to_double());
1806 MidiModel::transpose (NoteDiffCommand* c, const NotePtr note_ptr, int semitones)
1808 int new_note = note_ptr->note() + semitones;
1812 } else if (new_note > 127) {
1816 c->change (note_ptr, NoteDiffCommand::NoteNumber, (uint8_t) new_note);
1820 MidiModel::control_list_marked_dirty ()
1822 AutomatableSequence<Temporal::Beats>::control_list_marked_dirty ();
1824 ContentsChanged (); /* EMIT SIGNAL */