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/types.h"
46 using namespace ARDOUR;
49 MidiModel::MidiModel (boost::shared_ptr<MidiSource> s)
50 : AutomatableSequence<TimeType>(s->session())
55 /** Start a new NoteDiff command.
57 * This has no side-effects on the model or Session, the returned command
58 * can be held on to for as long as the caller wishes, or discarded without
59 * formality, until apply_command is called and ownership is taken.
61 MidiModel::NoteDiffCommand*
62 MidiModel::new_note_diff_command (const string name)
64 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
67 return new NoteDiffCommand (ms->model(), name);
70 /** Start a new SysExDiff command */
71 MidiModel::SysExDiffCommand*
72 MidiModel::new_sysex_diff_command (const string name)
74 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
77 return new SysExDiffCommand (ms->model(), name);
80 /** Start a new PatchChangeDiff command */
81 MidiModel::PatchChangeDiffCommand*
82 MidiModel::new_patch_change_diff_command (const string name)
84 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
87 return new PatchChangeDiffCommand (ms->model(), name);
93 * Ownership of cmd is taken, it must not be deleted by the caller.
94 * The command will constitute one item on the undo stack.
97 MidiModel::apply_command(Session& session, Command* cmd)
99 session.begin_reversible_command(cmd->name());
101 session.commit_reversible_command(cmd);
105 /** Apply a command as part of a larger reversible transaction
107 * Ownership of cmd is taken, it must not be deleted by the caller.
108 * The command will constitute one item on the undo stack.
111 MidiModel::apply_command_as_subcommand(Session& session, Command* cmd)
114 session.add_command(cmd);
118 /************** DIFF COMMAND ********************/
120 #define NOTE_DIFF_COMMAND_ELEMENT "NoteDiffCommand"
121 #define DIFF_NOTES_ELEMENT "ChangedNotes"
122 #define ADDED_NOTES_ELEMENT "AddedNotes"
123 #define REMOVED_NOTES_ELEMENT "RemovedNotes"
124 #define SIDE_EFFECT_REMOVALS_ELEMENT "SideEffectRemovals"
125 #define SYSEX_DIFF_COMMAND_ELEMENT "SysExDiffCommand"
126 #define DIFF_SYSEXES_ELEMENT "ChangedSysExes"
127 #define PATCH_CHANGE_DIFF_COMMAND_ELEMENT "PatchChangeDiffCommand"
128 #define ADDED_PATCH_CHANGES_ELEMENT "AddedPatchChanges"
129 #define REMOVED_PATCH_CHANGES_ELEMENT "RemovedPatchChanges"
130 #define DIFF_PATCH_CHANGES_ELEMENT "ChangedPatchChanges"
132 MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
140 MidiModel::NoteDiffCommand::NoteDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
141 : DiffCommand (m, "")
144 set_state (node, Stateful::loading_state_version);
148 MidiModel::NoteDiffCommand::add (const NotePtr note)
150 _removed_notes.remove(note);
151 _added_notes.push_back(note);
155 MidiModel::NoteDiffCommand::remove (const NotePtr note)
157 _added_notes.remove(note);
158 _removed_notes.push_back(note);
162 MidiModel::NoteDiffCommand::side_effect_remove (const NotePtr note)
164 side_effect_removals.insert (note);
168 MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop,
177 if (new_value == note->note()) {
180 change.old_value = note->note();
183 if (new_value == note->velocity()) {
186 change.old_value = note->velocity();
189 if (new_value == note->channel()) {
192 change.old_value = note->channel();
197 fatal << "MidiModel::DiffCommand::change() with integer argument called for start time" << endmsg;
201 fatal << "MidiModel::DiffCommand::change() with integer argument called for length" << endmsg;
207 change.property = prop;
208 change.new_value = new_value;
210 _changes.push_back (change);
214 MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop,
225 fatal << "MidiModel::NoteDiffCommand::change() with time argument called for note, channel or velocity" << endmsg;
229 if (Evoral::musical_time_equal (note->time(), new_time)) {
232 change.old_time = note->time();
235 if (Evoral::musical_time_equal (note->length(), new_time)) {
238 change.old_time = note->length();
243 change.property = prop;
244 change.new_time = new_time;
246 _changes.push_back (change);
249 MidiModel::NoteDiffCommand &
250 MidiModel::NoteDiffCommand::operator+= (const NoteDiffCommand& other)
252 if (this == &other) {
256 if (_model != other._model) {
260 _added_notes.insert (_added_notes.end(), other._added_notes.begin(), other._added_notes.end());
261 _removed_notes.insert (_removed_notes.end(), other._removed_notes.begin(), other._removed_notes.end());
262 side_effect_removals.insert (other.side_effect_removals.begin(), other.side_effect_removals.end());
263 _changes.insert (_changes.end(), other._changes.begin(), other._changes.end());
269 MidiModel::NoteDiffCommand::operator() ()
272 MidiModel::WriteLock lock(_model->edit_lock());
274 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
275 if (!_model->add_note_unlocked(*i)) {
276 /* failed to add it, so don't leave it in the removed list, to
277 avoid apparent errors on undo.
279 _removed_notes.remove (*i);
283 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
284 _model->remove_note_unlocked(*i);
287 /* notes we modify in a way that requires remove-then-add to maintain ordering */
288 set<NotePtr> temporary_removals;
290 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
291 Property prop = i->property;
294 if (temporary_removals.find (i->note) == temporary_removals.end()) {
295 _model->remove_note_unlocked (i->note);
296 temporary_removals.insert (i->note);
298 i->note->set_note (i->new_value);
302 if (temporary_removals.find (i->note) == temporary_removals.end()) {
303 _model->remove_note_unlocked (i->note);
304 temporary_removals.insert (i->note);
306 i->note->set_time (i->new_time);
310 if (temporary_removals.find (i->note) == temporary_removals.end()) {
311 _model->remove_note_unlocked (i->note);
312 temporary_removals.insert (i->note);
314 i->note->set_channel (i->new_value);
317 /* no remove-then-add required for these properties, since we do not index them
321 i->note->set_velocity (i->new_value);
325 i->note->set_length (i->new_time);
331 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
332 NoteDiffCommand side_effects (model(), "side effects");
333 if (_model->add_note_unlocked (*i, &side_effects)) {
334 /* The note was re-added ok */
335 *this += side_effects;
337 /* The note that we removed earlier could not be re-added. This change record
338 must say that the note was removed. We'll keep the changes we made, though,
339 as if the note is re-added by the undo the changes must also be undone.
341 _removed_notes.push_back (*i);
345 if (!side_effect_removals.empty()) {
347 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
348 cerr << "\t" << *i << ' ' << **i << endl;
353 _model->ContentsChanged(); /* EMIT SIGNAL */
357 MidiModel::NoteDiffCommand::undo ()
360 MidiModel::WriteLock lock(_model->edit_lock());
362 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
363 _model->remove_note_unlocked(*i);
366 /* Apply changes first; this is important in the case of a note change which
367 resulted in the note being removed by the overlap checker. If the overlap
368 checker removes a note, it will be in _removed_notes. We are going to re-add
369 it below, but first we must undo the changes we made so that the overlap
370 checker doesn't refuse the re-add.
373 /* notes we modify in a way that requires remove-then-add to maintain ordering */
374 set<NotePtr> temporary_removals;
376 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
377 Property prop = i->property;
382 temporary_removals.find (i->note) == temporary_removals.end() &&
383 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()
386 /* We only need to mark this note for re-add if (a) we haven't
387 already marked it and (b) it isn't on the _removed_notes
388 list (which means that it has already been removed and it
389 will be re-added anyway)
392 _model->remove_note_unlocked (i->note);
393 temporary_removals.insert (i->note);
395 i->note->set_note (i->old_value);
400 temporary_removals.find (i->note) == temporary_removals.end() &&
401 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()
406 _model->remove_note_unlocked (i->note);
407 temporary_removals.insert (i->note);
409 i->note->set_time (i->old_time);
414 temporary_removals.find (i->note) == temporary_removals.end() &&
415 find (_removed_notes.begin(), _removed_notes.end(), i->note) == _removed_notes.end()
420 _model->remove_note_unlocked (i->note);
421 temporary_removals.insert (i->note);
423 i->note->set_channel (i->old_value);
426 /* no remove-then-add required for these properties, since we do not index them
430 i->note->set_velocity (i->old_value);
434 i->note->set_length (i->old_time);
439 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
440 _model->add_note_unlocked(*i);
443 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
444 _model->add_note_unlocked (*i);
447 /* finally add back notes that were removed by the "do". we don't care
448 about side effects here since the model should be back to its original
449 state once this is done.
452 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
453 _model->add_note_unlocked (*i);
457 _model->ContentsChanged(); /* EMIT SIGNAL */
461 MidiModel::NoteDiffCommand::marshal_note(const NotePtr note)
463 XMLNode* xml_note = new XMLNode("note");
466 ostringstream id_str(ios::ate);
467 id_str << int(note->id());
468 xml_note->add_property("id", id_str.str());
472 ostringstream note_str(ios::ate);
473 note_str << int(note->note());
474 xml_note->add_property("note", note_str.str());
478 ostringstream channel_str(ios::ate);
479 channel_str << int(note->channel());
480 xml_note->add_property("channel", channel_str.str());
484 ostringstream time_str(ios::ate);
485 time_str << note->time();
486 xml_note->add_property("time", time_str.str());
490 ostringstream length_str(ios::ate);
491 length_str << note->length();
492 xml_note->add_property("length", length_str.str());
496 ostringstream velocity_str(ios::ate);
497 velocity_str << (unsigned int) note->velocity();
498 xml_note->add_property("velocity", velocity_str.str());
504 Evoral::Sequence<MidiModel::TimeType>::NotePtr
505 MidiModel::NoteDiffCommand::unmarshal_note (XMLNode *xml_note)
509 unsigned int channel;
510 MidiModel::TimeType time;
511 MidiModel::TimeType length;
512 unsigned int velocity;
515 if ((prop = xml_note->property("id")) != 0) {
516 istringstream id_str(prop->value());
519 error << "note information missing ID value" << endmsg;
523 if ((prop = xml_note->property("note")) != 0) {
524 istringstream note_str(prop->value());
527 warning << "note information missing note value" << endmsg;
531 if ((prop = xml_note->property("channel")) != 0) {
532 istringstream channel_str(prop->value());
533 channel_str >> channel;
535 warning << "note information missing channel" << endmsg;
539 if ((prop = xml_note->property("time")) != 0) {
540 istringstream time_str(prop->value());
543 warning << "note information missing time" << endmsg;
547 if ((prop = xml_note->property("length")) != 0) {
548 istringstream length_str(prop->value());
549 length_str >> length;
551 warning << "note information missing length" << endmsg;
555 if ((prop = xml_note->property("velocity")) != 0) {
556 istringstream velocity_str(prop->value());
557 velocity_str >> velocity;
559 warning << "note information missing velocity" << endmsg;
563 NotePtr note_ptr(new Evoral::Note<TimeType>(channel, time, length, note, velocity));
564 note_ptr->set_id (id);
570 MidiModel::NoteDiffCommand::marshal_change (const NoteChange& change)
572 XMLNode* xml_change = new XMLNode("Change");
574 /* first, the change itself */
576 xml_change->add_property ("property", enum_2_string (change.property));
579 ostringstream old_value_str (ios::ate);
580 if (change.property == StartTime || change.property == Length) {
581 old_value_str << change.old_time;
583 old_value_str << (unsigned int) change.old_value;
585 xml_change->add_property ("old", old_value_str.str());
589 ostringstream new_value_str (ios::ate);
590 if (change.property == StartTime || change.property == Length) {
591 new_value_str << change.new_time;
593 new_value_str << (unsigned int) change.new_value;
595 xml_change->add_property ("new", new_value_str.str());
598 ostringstream id_str;
599 id_str << change.note->id();
600 xml_change->add_property ("id", id_str.str());
605 MidiModel::NoteDiffCommand::NoteChange
606 MidiModel::NoteDiffCommand::unmarshal_change (XMLNode *xml_change)
611 if ((prop = xml_change->property("property")) != 0) {
612 change.property = (Property) string_2_enum (prop->value(), change.property);
614 fatal << "!!!" << endmsg;
618 if ((prop = xml_change->property ("id")) == 0) {
619 error << _("No NoteID found for note property change - ignored") << endmsg;
623 gint note_id = atoi (prop->value().c_str());
625 if ((prop = xml_change->property ("old")) != 0) {
626 istringstream old_str (prop->value());
627 if (change.property == StartTime || change.property == Length) {
628 old_str >> change.old_time;
630 int integer_value_so_that_istream_does_the_right_thing;
631 old_str >> integer_value_so_that_istream_does_the_right_thing;
632 change.old_value = integer_value_so_that_istream_does_the_right_thing;
635 fatal << "!!!" << endmsg;
639 if ((prop = xml_change->property ("new")) != 0) {
640 istringstream new_str (prop->value());
641 if (change.property == StartTime || change.property == Length) {
642 new_str >> change.new_time;
644 int integer_value_so_that_istream_does_the_right_thing;
645 new_str >> integer_value_so_that_istream_does_the_right_thing;
646 change.new_value = integer_value_so_that_istream_does_the_right_thing;
649 fatal << "!!!" << endmsg;
653 /* we must point at the instance of the note that is actually in the model.
654 so go look for it ...
657 change.note = _model->find_note (note_id);
660 warning << "MIDI note #" << note_id << " not found in model - programmers should investigate this" << endmsg;
668 MidiModel::NoteDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
670 if (diff_command.name() != string (NOTE_DIFF_COMMAND_ELEMENT)) {
676 _added_notes.clear();
677 XMLNode* added_notes = diff_command.child(ADDED_NOTES_ELEMENT);
679 XMLNodeList notes = added_notes->children();
680 transform(notes.begin(), notes.end(), back_inserter(_added_notes),
681 boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
687 _removed_notes.clear();
688 XMLNode* removed_notes = diff_command.child(REMOVED_NOTES_ELEMENT);
690 XMLNodeList notes = removed_notes->children();
691 transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
692 boost::bind (&NoteDiffCommand::unmarshal_note, this, _1));
700 XMLNode* changed_notes = diff_command.child(DIFF_NOTES_ELEMENT);
703 XMLNodeList notes = changed_notes->children();
704 transform (notes.begin(), notes.end(), back_inserter(_changes),
705 boost::bind (&NoteDiffCommand::unmarshal_change, this, _1));
709 /* side effect removals caused by changes */
711 side_effect_removals.clear();
713 XMLNode* side_effect_notes = diff_command.child(SIDE_EFFECT_REMOVALS_ELEMENT);
715 if (side_effect_notes) {
716 XMLNodeList notes = side_effect_notes->children();
717 for (XMLNodeList::iterator n = notes.begin(); n != notes.end(); ++n) {
718 side_effect_removals.insert (unmarshal_note (*n));
726 MidiModel::NoteDiffCommand::get_state ()
728 XMLNode* diff_command = new XMLNode (NOTE_DIFF_COMMAND_ELEMENT);
729 diff_command->add_property("midi-source", _model->midi_source()->id().to_s());
731 XMLNode* changes = diff_command->add_child(DIFF_NOTES_ELEMENT);
732 for_each(_changes.begin(), _changes.end(),
734 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
735 boost::bind (&NoteDiffCommand::marshal_change, this, _1)));
737 XMLNode* added_notes = diff_command->add_child(ADDED_NOTES_ELEMENT);
738 for_each(_added_notes.begin(), _added_notes.end(),
740 boost::bind (&XMLNode::add_child_nocopy, added_notes, _1),
741 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
743 XMLNode* removed_notes = diff_command->add_child(REMOVED_NOTES_ELEMENT);
744 for_each(_removed_notes.begin(), _removed_notes.end(),
746 boost::bind (&XMLNode::add_child_nocopy, removed_notes, _1),
747 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
749 /* if this command had side-effects, store that state too
752 if (!side_effect_removals.empty()) {
753 XMLNode* side_effect_notes = diff_command->add_child(SIDE_EFFECT_REMOVALS_ELEMENT);
754 for_each(side_effect_removals.begin(), side_effect_removals.end(),
756 boost::bind (&XMLNode::add_child_nocopy, side_effect_notes, _1),
757 boost::bind (&NoteDiffCommand::marshal_note, this, _1)));
760 return *diff_command;
763 MidiModel::SysExDiffCommand::SysExDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
764 : DiffCommand (m, "")
767 set_state (node, Stateful::loading_state_version);
771 MidiModel::SysExDiffCommand::change (boost::shared_ptr<Evoral::Event<TimeType> > s, TimeType new_time)
776 change.property = Time;
777 change.old_time = s->time ();
778 change.new_time = new_time;
780 _changes.push_back (change);
784 MidiModel::SysExDiffCommand::operator() ()
787 MidiModel::WriteLock lock (_model->edit_lock ());
789 for (list<SysExPtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
790 _model->remove_sysex_unlocked (*i);
793 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
794 switch (i->property) {
796 i->sysex->set_time (i->new_time);
801 _model->ContentsChanged (); /* EMIT SIGNAL */
805 MidiModel::SysExDiffCommand::undo ()
808 MidiModel::WriteLock lock (_model->edit_lock ());
810 for (list<SysExPtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
811 _model->add_sysex_unlocked (*i);
814 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
815 switch (i->property) {
817 i->sysex->set_time (i->old_time);
824 _model->ContentsChanged(); /* EMIT SIGNAL */
828 MidiModel::SysExDiffCommand::remove (SysExPtr sysex)
830 _removed.push_back(sysex);
834 MidiModel::SysExDiffCommand::marshal_change (const Change& change)
836 XMLNode* xml_change = new XMLNode ("Change");
838 /* first, the change itself */
840 xml_change->add_property ("property", enum_2_string (change.property));
843 ostringstream old_value_str (ios::ate);
844 old_value_str << change.old_time;
845 xml_change->add_property ("old", old_value_str.str());
849 ostringstream new_value_str (ios::ate);
850 new_value_str << change.new_time;
851 xml_change->add_property ("new", new_value_str.str());
854 ostringstream id_str;
855 id_str << change.sysex->id();
856 xml_change->add_property ("id", id_str.str());
861 MidiModel::SysExDiffCommand::Change
862 MidiModel::SysExDiffCommand::unmarshal_change (XMLNode *xml_change)
867 if ((prop = xml_change->property ("property")) != 0) {
868 change.property = (Property) string_2_enum (prop->value(), change.property);
870 fatal << "!!!" << endmsg;
874 if ((prop = xml_change->property ("id")) == 0) {
875 error << _("No SysExID found for sys-ex property change - ignored") << endmsg;
879 gint sysex_id = atoi (prop->value().c_str());
881 if ((prop = xml_change->property ("old")) != 0) {
882 istringstream old_str (prop->value());
883 old_str >> change.old_time;
885 fatal << "!!!" << endmsg;
889 if ((prop = xml_change->property ("new")) != 0) {
890 istringstream new_str (prop->value());
891 new_str >> change.new_time;
893 fatal << "!!!" << endmsg;
897 /* we must point at the instance of the sysex that is actually in the model.
898 so go look for it ...
901 change.sysex = _model->find_sysex (sysex_id);
904 warning << "Sys-ex #" << sysex_id << " not found in model - programmers should investigate this" << endmsg;
912 MidiModel::SysExDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
914 if (diff_command.name() != string (SYSEX_DIFF_COMMAND_ELEMENT)) {
922 XMLNode* changed_sysexes = diff_command.child (DIFF_SYSEXES_ELEMENT);
924 if (changed_sysexes) {
925 XMLNodeList sysexes = changed_sysexes->children();
926 transform (sysexes.begin(), sysexes.end(), back_inserter (_changes),
927 boost::bind (&SysExDiffCommand::unmarshal_change, this, _1));
935 MidiModel::SysExDiffCommand::get_state ()
937 XMLNode* diff_command = new XMLNode (SYSEX_DIFF_COMMAND_ELEMENT);
938 diff_command->add_property ("midi-source", _model->midi_source()->id().to_s());
940 XMLNode* changes = diff_command->add_child(DIFF_SYSEXES_ELEMENT);
941 for_each (_changes.begin(), _changes.end(),
943 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
944 boost::bind (&SysExDiffCommand::marshal_change, this, _1)));
946 return *diff_command;
949 MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const string& name)
950 : DiffCommand (m, name)
955 MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode & node)
956 : DiffCommand (m, "")
959 set_state (node, Stateful::loading_state_version);
963 MidiModel::PatchChangeDiffCommand::add (PatchChangePtr p)
965 _added.push_back (p);
969 MidiModel::PatchChangeDiffCommand::remove (PatchChangePtr p)
971 _removed.push_back (p);
975 MidiModel::PatchChangeDiffCommand::change_time (PatchChangePtr patch, TimeType t)
980 c.old_time = patch->time ();
983 _changes.push_back (c);
987 MidiModel::PatchChangeDiffCommand::change_channel (PatchChangePtr patch, uint8_t channel)
990 c.property = Channel;
992 c.old_channel = patch->channel ();
993 c.new_channel = channel;
995 _changes.push_back (c);
999 MidiModel::PatchChangeDiffCommand::change_program (PatchChangePtr patch, uint8_t program)
1002 c.property = Program;
1004 c.old_program = patch->program ();
1005 c.new_program = program;
1007 _changes.push_back (c);
1011 MidiModel::PatchChangeDiffCommand::change_bank (PatchChangePtr patch, int bank)
1016 c.old_bank = patch->bank ();
1019 _changes.push_back (c);
1023 MidiModel::PatchChangeDiffCommand::operator() ()
1026 MidiModel::WriteLock lock (_model->edit_lock ());
1028 for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) {
1029 _model->add_patch_change_unlocked (*i);
1032 for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
1033 _model->remove_patch_change_unlocked (*i);
1036 set<PatchChangePtr> temporary_removals;
1038 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
1039 switch (i->property) {
1041 if (temporary_removals.find (i->patch) == temporary_removals.end()) {
1042 _model->remove_patch_change_unlocked (i->patch);
1043 temporary_removals.insert (i->patch);
1045 i->patch->set_time (i->new_time);
1049 i->patch->set_channel (i->new_channel);
1053 i->patch->set_program (i->new_program);
1057 i->patch->set_bank (i->new_bank);
1062 for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
1063 _model->add_patch_change_unlocked (*i);
1067 _model->ContentsChanged (); /* EMIT SIGNAL */
1071 MidiModel::PatchChangeDiffCommand::undo ()
1074 MidiModel::WriteLock lock (_model->edit_lock());
1076 for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) {
1077 _model->remove_patch_change_unlocked (*i);
1080 for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) {
1081 _model->add_patch_change_unlocked (*i);
1084 set<PatchChangePtr> temporary_removals;
1086 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
1087 switch (i->property) {
1089 if (temporary_removals.find (i->patch) == temporary_removals.end()) {
1090 _model->remove_patch_change_unlocked (i->patch);
1091 temporary_removals.insert (i->patch);
1093 i->patch->set_time (i->old_time);
1097 i->patch->set_channel (i->old_channel);
1101 i->patch->set_program (i->old_program);
1105 i->patch->set_bank (i->old_bank);
1110 for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
1111 _model->add_patch_change_unlocked (*i);
1116 _model->ContentsChanged (); /* EMIT SIGNAL */
1120 MidiModel::PatchChangeDiffCommand::marshal_patch_change (constPatchChangePtr p)
1122 XMLNode* n = new XMLNode ("patch-change");
1125 ostringstream s (ios::ate);
1126 s << int (p->id ());
1127 n->add_property ("id", s.str());
1131 ostringstream s (ios::ate);
1133 n->add_property ("time", s.str ());
1137 ostringstream s (ios::ate);
1138 s << int (p->channel ());
1139 n->add_property ("channel", s.str ());
1143 ostringstream s (ios::ate);
1144 s << int (p->program ());
1145 n->add_property ("program", s.str ());
1149 ostringstream s (ios::ate);
1150 s << int (p->bank ());
1151 n->add_property ("bank", s.str ());
1158 MidiModel::PatchChangeDiffCommand::marshal_change (const Change& c)
1160 XMLNode* n = new XMLNode (X_("Change"));
1162 n->add_property (X_("property"), enum_2_string (c.property));
1165 ostringstream s (ios::ate);
1166 if (c.property == Time) {
1168 } else if (c.property == Channel) {
1170 } else if (c.property == Program) {
1171 s << int (c.old_program);
1172 } else if (c.property == Bank) {
1176 n->add_property (X_("old"), s.str ());
1180 ostringstream s (ios::ate);
1182 if (c.property == Time) {
1184 } else if (c.property == Channel) {
1186 } else if (c.property == Program) {
1187 s << int (c.new_program);
1188 } else if (c.property == Bank) {
1192 n->add_property (X_("new"), s.str ());
1197 s << c.patch->id ();
1198 n->add_property ("id", s.str ());
1204 MidiModel::PatchChangePtr
1205 MidiModel::PatchChangeDiffCommand::unmarshal_patch_change (XMLNode* n)
1208 Evoral::event_id_t id;
1209 Evoral::MusicalTime time = 0;
1210 uint8_t channel = 0;
1211 uint8_t program = 0;
1214 if ((prop = n->property ("id")) != 0) {
1215 istringstream s (prop->value());
1219 if ((prop = n->property ("time")) != 0) {
1220 istringstream s (prop->value ());
1224 if ((prop = n->property ("channel")) != 0) {
1225 istringstream s (prop->value ());
1229 if ((prop = n->property ("program")) != 0) {
1230 istringstream s (prop->value ());
1234 if ((prop = n->property ("bank")) != 0) {
1235 istringstream s (prop->value ());
1239 PatchChangePtr p (new Evoral::PatchChange<TimeType> (time, channel, program, bank));
1244 MidiModel::PatchChangeDiffCommand::Change
1245 MidiModel::PatchChangeDiffCommand::unmarshal_change (XMLNode* n)
1250 prop = n->property ("property");
1252 c.property = (Property) string_2_enum (prop->value(), c.property);
1254 prop = n->property ("id");
1256 Evoral::event_id_t const id = atoi (prop->value().c_str());
1258 prop = n->property ("old");
1261 istringstream s (prop->value ());
1262 if (c.property == Time) {
1264 } else if (c.property == Channel) {
1266 } else if (c.property == Program) {
1268 } else if (c.property == Bank) {
1273 prop = n->property ("new");
1276 istringstream s (prop->value ());
1277 if (c.property == Time) {
1279 } else if (c.property == Channel) {
1281 } else if (c.property == Program) {
1283 } else if (c.property == Bank) {
1288 c.patch = _model->find_patch_change (id);
1295 MidiModel::PatchChangeDiffCommand::set_state (const XMLNode& diff_command, int /*version*/)
1297 if (diff_command.name() != PATCH_CHANGE_DIFF_COMMAND_ELEMENT) {
1302 XMLNode* added = diff_command.child (ADDED_PATCH_CHANGES_ELEMENT);
1304 XMLNodeList p = added->children ();
1305 transform (p.begin(), p.end(), back_inserter (_added), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1));
1309 XMLNode* removed = diff_command.child (REMOVED_PATCH_CHANGES_ELEMENT);
1311 XMLNodeList p = removed->children ();
1312 transform (p.begin(), p.end(), back_inserter (_removed), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1));
1316 XMLNode* changed = diff_command.child (DIFF_PATCH_CHANGES_ELEMENT);
1318 XMLNodeList p = changed->children ();
1319 transform (p.begin(), p.end(), back_inserter (_changes), boost::bind (&PatchChangeDiffCommand::unmarshal_change, this, _1));
1326 MidiModel::PatchChangeDiffCommand::get_state ()
1328 XMLNode* diff_command = new XMLNode (PATCH_CHANGE_DIFF_COMMAND_ELEMENT);
1329 diff_command->add_property("midi-source", _model->midi_source()->id().to_s());
1331 XMLNode* added = diff_command->add_child (ADDED_PATCH_CHANGES_ELEMENT);
1332 for_each (_added.begin(), _added.end(),
1334 boost::bind (&XMLNode::add_child_nocopy, added, _1),
1335 boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1)
1339 XMLNode* removed = diff_command->add_child (REMOVED_PATCH_CHANGES_ELEMENT);
1340 for_each (_removed.begin(), _removed.end(),
1342 boost::bind (&XMLNode::add_child_nocopy, removed, _1),
1343 boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1)
1347 XMLNode* changes = diff_command->add_child (DIFF_PATCH_CHANGES_ELEMENT);
1348 for_each (_changes.begin(), _changes.end(),
1350 boost::bind (&XMLNode::add_child_nocopy, changes, _1),
1351 boost::bind (&PatchChangeDiffCommand::marshal_change, this, _1)
1355 return *diff_command;
1358 /** Write all of the model to a MidiSource (i.e. save the model).
1359 * This is different from manually using read to write to a source in that
1360 * note off events are written regardless of the track mode. This is so the
1361 * user can switch a recorded track (with note durations from some instrument)
1362 * to percussive, save, reload, then switch it back to sustained without
1363 * destroying the original note durations.
1365 * Similarly, control events are written without interpolation (as with the
1369 MidiModel::write_to (boost::shared_ptr<MidiSource> source)
1371 ReadLock lock(read_lock());
1373 const bool old_percussive = percussive();
1374 set_percussive(false);
1376 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1379 source->drop_model();
1380 source->mark_streaming_midi_write_started (note_mode());
1382 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
1383 source->append_event_unlocked_beats(*i);
1386 set_percussive(old_percussive);
1387 source->mark_streaming_write_completed();
1394 /** very similar to ::write_to() but writes to the model's own
1395 existing midi_source, without making it call MidiSource::drop_model().
1396 the caller is a MidiSource that needs to catch up with the state
1400 MidiModel::sync_to_source ()
1402 ReadLock lock(read_lock());
1404 const bool old_percussive = percussive();
1405 set_percussive(false);
1407 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1410 ms->mark_streaming_midi_write_started (note_mode());
1412 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
1413 ms->append_event_unlocked_beats(*i);
1416 set_percussive (old_percussive);
1417 ms->mark_streaming_write_completed ();
1424 /** Write part or all of the model to a MidiSource (i.e. save the model).
1425 * This is different from manually using read to write to a source in that
1426 * note off events are written regardless of the track mode. This is so the
1427 * user can switch a recorded track (with note durations from some instrument)
1428 * to percussive, save, reload, then switch it back to sustained without
1429 * destroying the original note durations.
1432 MidiModel::write_section_to (boost::shared_ptr<MidiSource> source, Evoral::MusicalTime begin_time, Evoral::MusicalTime end_time)
1434 ReadLock lock(read_lock());
1435 MidiStateTracker mst;
1437 const bool old_percussive = percussive();
1438 set_percussive(false);
1440 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1443 source->drop_model();
1444 source->mark_streaming_midi_write_started (note_mode());
1446 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
1447 const Evoral::Event<Evoral::MusicalTime>& ev (*i);
1449 if (ev.time() >= begin_time && ev.time() < end_time) {
1451 const Evoral::MIDIEvent<Evoral::MusicalTime>* mev =
1452 static_cast<const Evoral::MIDIEvent<Evoral::MusicalTime>* > (&ev);
1459 if (mev->is_note_off()) {
1461 if (!mst.active (mev->note(), mev->channel())) {
1462 /* the matching note-on was outside the
1463 time range we were given, so just
1464 ignore this note-off.
1469 source->append_event_unlocked_beats (*i);
1470 mst.remove (mev->note(), mev->channel());
1472 } else if (mev->is_note_on()) {
1473 mst.add (mev->note(), mev->channel());
1474 source->append_event_unlocked_beats(*i);
1476 source->append_event_unlocked_beats(*i);
1481 mst.resolve_notes (*source, end_time);
1483 set_percussive(old_percussive);
1484 source->mark_streaming_write_completed();
1492 MidiModel::get_state()
1494 XMLNode *node = new XMLNode("MidiModel");
1498 Evoral::Sequence<MidiModel::TimeType>::NotePtr
1499 MidiModel::find_note (NotePtr other)
1501 Notes::iterator l = notes().lower_bound(other);
1503 if (l != notes().end()) {
1504 for (; (*l)->time() == other->time(); ++l) {
1505 /* NB: compare note contents, not note pointers.
1506 If "other" was a ptr to a note already in
1507 the model, we wouldn't be looking for it,
1510 if (**l == *other) {
1519 Evoral::Sequence<MidiModel::TimeType>::NotePtr
1520 MidiModel::find_note (gint note_id)
1522 /* used only for looking up notes when reloading history from disk,
1523 so we don't care about performance *too* much.
1526 for (Notes::iterator l = notes().begin(); l != notes().end(); ++l) {
1527 if ((*l)->id() == note_id) {
1535 MidiModel::PatchChangePtr
1536 MidiModel::find_patch_change (Evoral::event_id_t id)
1538 for (PatchChanges::iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) {
1539 if ((*i)->id() == id) {
1544 return PatchChangePtr ();
1547 boost::shared_ptr<Evoral::Event<MidiModel::TimeType> >
1548 MidiModel::find_sysex (gint sysex_id)
1550 /* used only for looking up notes when reloading history from disk,
1551 so we don't care about performance *too* much.
1554 for (SysExes::iterator l = sysexes().begin(); l != sysexes().end(); ++l) {
1555 if ((*l)->id() == sysex_id) {
1560 return boost::shared_ptr<Evoral::Event<TimeType> > ();
1563 /** Lock and invalidate the source.
1564 * This should be used by commands and editing things
1566 MidiModel::WriteLock
1567 MidiModel::edit_lock()
1569 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1572 Glib::Threads::Mutex::Lock* source_lock = new Glib::Threads::Mutex::Lock (ms->mutex());
1573 ms->invalidate(); // Release cached iterator's read lock on model
1574 return WriteLock(new WriteLockImpl(source_lock, _lock, _control_lock));
1577 /** Lock just the model, the source lock must already be held.
1578 * This should only be called from libardour/evoral places
1580 MidiModel::WriteLock
1581 MidiModel::write_lock()
1583 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1586 assert (!ms->mutex().trylock ());
1587 return WriteLock(new WriteLockImpl(NULL, _lock, _control_lock));
1591 MidiModel::resolve_overlaps_unlocked (const NotePtr note, void* arg)
1593 using namespace Evoral;
1595 if (_writing || insert_merge_policy() == InsertMergeRelax) {
1599 NoteDiffCommand* cmd = static_cast<NoteDiffCommand*>(arg);
1601 TimeType sa = note->time();
1602 TimeType ea = note->end_time();
1604 const Pitches& p (pitches (note->channel()));
1605 NotePtr search_note(new Note<TimeType>(0, 0, 0, note->note()));
1606 set<NotePtr> to_be_deleted;
1607 bool set_note_length = false;
1608 bool set_note_time = false;
1609 TimeType note_time = note->time();
1610 TimeType note_length = note->length();
1612 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 checking overlaps for note %2 @ %3\n", this, (int)note->note(), note->time()));
1614 for (Pitches::const_iterator i = p.lower_bound (search_note);
1615 i != p.end() && (*i)->note() == note->note(); ++i) {
1617 TimeType sb = (*i)->time();
1618 TimeType eb = (*i)->end_time();
1619 OverlapType overlap = OverlapNone;
1621 if ((sb > sa) && (eb <= ea)) {
1622 overlap = OverlapInternal;
1623 } else if ((eb >= sa) && (eb <= ea)) {
1624 overlap = OverlapStart;
1625 } else if ((sb > sa) && (sb <= ea)) {
1626 overlap = OverlapEnd;
1627 } else if ((sa >= sb) && (sa <= eb) && (ea <= eb)) {
1628 overlap = OverlapExternal;
1634 DEBUG_TRACE (DEBUG::Sequence, string_compose (
1635 "\toverlap is %1 for (%2,%3) vs (%4,%5)\n",
1636 enum_2_string(overlap), sa, ea, sb, eb));
1638 if (insert_merge_policy() == InsertMergeReject) {
1639 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 just reject\n", this));
1645 cerr << "OverlapStart\n";
1646 /* existing note covers start of new note */
1647 switch (insert_merge_policy()) {
1648 case InsertMergeReplace:
1649 to_be_deleted.insert (*i);
1651 case InsertMergeTruncateExisting:
1653 cmd->change (*i, NoteDiffCommand::Length, (note->time() - (*i)->time()));
1655 (*i)->set_length (note->time() - (*i)->time());
1657 case InsertMergeTruncateAddition:
1658 set_note_time = true;
1659 set_note_length = true;
1660 note_time = (*i)->time() + (*i)->length();
1661 note_length = min (note_length, (*i)->length() - ((*i)->end_time() - note->time()));
1663 case InsertMergeExtend:
1665 cmd->change ((*i), NoteDiffCommand::Length, note->end_time() - (*i)->time());
1667 (*i)->set_length (note->end_time() - (*i)->time());
1668 return -1; /* do not add the new note */
1678 cerr << "OverlapEnd\n";
1679 /* existing note covers end of new note */
1680 switch (insert_merge_policy()) {
1681 case InsertMergeReplace:
1682 to_be_deleted.insert (*i);
1685 case InsertMergeTruncateExisting:
1686 /* resetting the start time of the existing note
1687 is a problem because of time ordering.
1691 case InsertMergeTruncateAddition:
1692 set_note_length = true;
1693 note_length = min (note_length, ((*i)->time() - note->time()));
1696 case InsertMergeExtend:
1697 /* we can't reset the time of the existing note because
1698 that will corrupt time ordering. So remove the
1699 existing note and change the position/length
1700 of the new note (which has not been added yet)
1702 to_be_deleted.insert (*i);
1703 set_note_length = true;
1704 note_length = min (note_length, (*i)->end_time() - note->time());
1713 case OverlapExternal:
1714 cerr << "OverlapExt\n";
1715 /* existing note overlaps all the new note */
1716 switch (insert_merge_policy()) {
1717 case InsertMergeReplace:
1718 to_be_deleted.insert (*i);
1720 case InsertMergeTruncateExisting:
1721 case InsertMergeTruncateAddition:
1722 case InsertMergeExtend:
1723 /* cannot add in this case */
1732 case OverlapInternal:
1733 cerr << "OverlapInt\n";
1734 /* new note fully overlaps an existing note */
1735 switch (insert_merge_policy()) {
1736 case InsertMergeReplace:
1737 case InsertMergeTruncateExisting:
1738 case InsertMergeTruncateAddition:
1739 case InsertMergeExtend:
1740 /* delete the existing note, the new one will cover it */
1741 to_be_deleted.insert (*i);
1757 for (set<NotePtr>::iterator i = to_be_deleted.begin(); i != to_be_deleted.end(); ++i) {
1758 remove_note_unlocked (*i);
1761 cmd->side_effect_remove (*i);
1765 if (set_note_time) {
1767 cmd->change (note, NoteDiffCommand::StartTime, note_time);
1769 note->set_time (note_time);
1772 if (set_note_length) {
1774 cmd->change (note, NoteDiffCommand::Length, note_length);
1776 note->set_length (note_length);
1783 MidiModel::insert_merge_policy () const
1785 /* XXX ultimately this should be a per-track or even per-model policy */
1786 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1789 return ms->session().config.get_insert_merge_policy ();
1793 MidiModel::set_midi_source (boost::shared_ptr<MidiSource> s)
1795 boost::shared_ptr<MidiSource> old = _midi_source.lock ();
1801 _midi_source_connections.drop_connections ();
1805 s->InterpolationChanged.connect_same_thread (
1806 _midi_source_connections, boost::bind (&MidiModel::source_interpolation_changed, this, _1, _2)
1809 s->AutomationStateChanged.connect_same_thread (
1810 _midi_source_connections, boost::bind (&MidiModel::source_automation_state_changed, this, _1, _2)
1814 /** The source has signalled that the interpolation style for a parameter has changed. In order to
1815 * keep MidiSource and ControlList interpolation state the same, we pass this change onto the
1816 * appropriate ControlList.
1818 * The idea is that MidiSource and the MidiModel's ControlList states are kept in sync, and one
1819 * or the other is listened to by the GUI.
1822 MidiModel::source_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1824 Glib::Threads::Mutex::Lock lm (_control_lock);
1825 control(p)->list()->set_interpolation (s);
1828 /** A ControlList has signalled that its interpolation style has changed. Again, in order to keep
1829 * MidiSource and ControlList interpolation state in sync, we pass this change onto our MidiSource.
1832 MidiModel::control_list_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1834 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1837 ms->set_interpolation_of (p, s);
1841 MidiModel::source_automation_state_changed (Evoral::Parameter p, AutoState s)
1843 Glib::Threads::Mutex::Lock lm (_control_lock);
1844 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (control(p)->list ());
1845 al->set_automation_state (s);
1849 MidiModel::automation_list_automation_state_changed (Evoral::Parameter p, AutoState s)
1851 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1853 ms->set_automation_state_of (p, s);
1856 boost::shared_ptr<Evoral::Control>
1857 MidiModel::control_factory (Evoral::Parameter const & p)
1859 boost::shared_ptr<Evoral::Control> c = Automatable::control_factory (p);
1861 /* Set up newly created control's lists to the appropriate interpolation and
1862 automation state from our source.
1865 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1868 c->list()->set_interpolation (ms->interpolation_of (p));
1870 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (c->list ());
1873 al->set_automation_state (ms->automation_state_of (p));
1878 boost::shared_ptr<const MidiSource>
1879 MidiModel::midi_source ()
1881 return _midi_source.lock ();
1884 /** Moves notes, patch changes, controllers and sys-ex to insert silence at the start of the model.
1885 * Adds commands to the session's current undo stack to reflect the movements.
1888 MidiModel::insert_silence_at_start (TimeType t)
1890 boost::shared_ptr<MidiSource> s = _midi_source.lock ();
1895 if (!notes().empty ()) {
1896 NoteDiffCommand* c = new_note_diff_command ("insert silence");
1898 for (Notes::const_iterator i = notes().begin(); i != notes().end(); ++i) {
1899 c->change (*i, NoteDiffCommand::StartTime, (*i)->time() + t);
1902 apply_command_as_subcommand (s->session(), c);
1907 if (!patch_changes().empty ()) {
1908 PatchChangeDiffCommand* c = new_patch_change_diff_command ("insert silence");
1910 for (PatchChanges::const_iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) {
1911 c->change_time (*i, (*i)->time() + t);
1914 apply_command_as_subcommand (s->session(), c);
1919 for (Controls::iterator i = controls().begin(); i != controls().end(); ++i) {
1920 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
1921 XMLNode& before = ac->alist()->get_state ();
1922 i->second->list()->shift (0, t);
1923 XMLNode& after = ac->alist()->get_state ();
1924 s->session().add_command (new MementoCommand<AutomationList> (new MidiAutomationListBinder (s, i->first), &before, &after));
1929 if (!sysexes().empty()) {
1930 SysExDiffCommand* c = new_sysex_diff_command ("insert silence");
1932 for (SysExes::iterator i = sysexes().begin(); i != sysexes().end(); ++i) {
1933 c->change (*i, (*i)->time() + t);
1936 apply_command_as_subcommand (s->session(), c);
1940 /** Transpose notes in a time range by a given number of semitones. Notes
1941 * will be clamped at 0 and 127 if the transposition would make them exceed
1944 * @param from Start time.
1945 * @param end End time.
1946 * @param semitones Number of semitones to transpose by (+ve is higher, -ve is lower).
1949 MidiModel::transpose (TimeType from, TimeType to, int semitones)
1951 boost::shared_ptr<const MidiSource> s = midi_source ();
1953 NoteDiffCommand* c = new_note_diff_command (_("transpose"));
1955 for (Notes::iterator i = notes().begin(); i != notes().end(); ++i) {
1957 if ((*i)->time() >= to) {
1962 } else if ((*i)->time() >= from) {
1964 int new_note = (*i)->note() + semitones;
1968 } else if (new_note > 127) {
1972 c->change (*i, NoteDiffCommand::NoteNumber, (uint8_t) new_note);
1977 apply_command (s->session (), c);
1981 MidiModel::control_list_marked_dirty ()
1983 AutomatableSequence<Evoral::MusicalTime>::control_list_marked_dirty ();
1985 ContentsChanged (); /* EMIT SIGNAL */