2 Copyright (C) 2007 Paul Davis
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.
26 #include "pbd/error.h"
27 #include "pbd/enumwriter.h"
28 #include "midi++/events.h"
30 #include "ardour/midi_model.h"
31 #include "ardour/midi_source.h"
32 #include "ardour/midi_state_tracker.h"
33 #include "ardour/smf_source.h"
34 #include "ardour/types.h"
35 #include "ardour/session.h"
36 #include "ardour/midi_automation_list_binder.h"
39 using namespace ARDOUR;
42 MidiModel::MidiModel (boost::shared_ptr<MidiSource> s)
43 : AutomatableSequence<TimeType>(s->session())
48 /** Start a new NoteDiff command.
50 * This has no side-effects on the model or Session, the returned command
51 * can be held on to for as long as the caller wishes, or discarded without
52 * formality, until apply_command is called and ownership is taken.
54 MidiModel::NoteDiffCommand*
55 MidiModel::new_note_diff_command (const string name)
57 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
60 return new NoteDiffCommand (ms->model(), name);
65 * Ownership of cmd is taken, it must not be deleted by the caller.
66 * The command will constitute one item on the undo stack.
69 MidiModel::apply_command(Session& session, Command* cmd)
71 session.begin_reversible_command(cmd->name());
73 session.commit_reversible_command(cmd);
77 /** Apply a command as part of a larger reversible transaction
79 * Ownership of cmd is taken, it must not be deleted by the caller.
80 * The command will constitute one item on the undo stack.
83 MidiModel::apply_command_as_subcommand(Session& session, Command* cmd)
86 session.add_command(cmd);
90 /************** DIFF COMMAND ********************/
92 #define NOTE_DIFF_COMMAND_ELEMENT "NoteDiffCommand"
93 #define DIFF_NOTES_ELEMENT "ChangedNotes"
94 #define ADDED_NOTES_ELEMENT "AddedNotes"
95 #define REMOVED_NOTES_ELEMENT "RemovedNotes"
96 #define SIDE_EFFECT_REMOVALS_ELEMENT "SideEffectRemovals"
98 MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
106 MidiModel::NoteDiffCommand::NoteDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node)
107 : DiffCommand (m, "")
110 set_state (node, Stateful::loading_state_version);
114 MidiModel::NoteDiffCommand::add (const NotePtr note)
116 _removed_notes.remove(note);
117 _added_notes.push_back(note);
121 MidiModel::NoteDiffCommand::remove (const NotePtr note)
123 _added_notes.remove(note);
124 _removed_notes.push_back(note);
128 MidiModel::NoteDiffCommand::side_effect_remove (const NotePtr note)
130 side_effect_removals.insert (note);
134 MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop,
141 if (new_value == note->note()) {
144 change.old_value = note->note();
147 if (new_value == note->velocity()) {
150 change.old_value = note->velocity();
153 if (new_value == note->channel()) {
156 change.old_value = note->channel();
161 fatal << "MidiModel::DiffCommand::change() with integer argument called for start time" << endmsg;
165 fatal << "MidiModel::DiffCommand::change() with integer argument called for length" << endmsg;
171 change.property = prop;
172 change.new_value = new_value;
174 _changes.push_back (change);
178 MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop,
187 fatal << "MidiModel::NoteDiffCommand::change() with time argument called for note, channel or velocity" << endmsg;
191 if (Evoral::musical_time_equal (note->time(), new_time)) {
194 change.old_time = note->time();
197 if (Evoral::musical_time_equal (note->length(), new_time)) {
200 change.old_time = note->length();
205 change.property = prop;
206 change.new_time = new_time;
208 _changes.push_back (change);
211 MidiModel::NoteDiffCommand &
212 MidiModel::NoteDiffCommand::operator+= (const NoteDiffCommand& other)
214 if (this == &other) {
218 if (_model != other._model) {
222 _added_notes.insert (_added_notes.end(), other._added_notes.begin(), other._added_notes.end());
223 _removed_notes.insert (_removed_notes.end(), other._removed_notes.begin(), other._removed_notes.end());
224 side_effect_removals.insert (other.side_effect_removals.begin(), other.side_effect_removals.end());
225 _changes.insert (_changes.end(), other._changes.begin(), other._changes.end());
231 MidiModel::NoteDiffCommand::operator() ()
234 MidiModel::WriteLock lock(_model->edit_lock());
236 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
237 if (!_model->add_note_unlocked(*i)) {
238 /* failed to add it, so don't leave it in the removed list, to
239 avoid apparent errors on undo.
241 _removed_notes.remove (*i);
245 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
246 _model->remove_note_unlocked(*i);
249 /* notes we modify in a way that requires remove-then-add to maintain ordering */
250 set<NotePtr> temporary_removals;
252 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
253 Property prop = i->property;
256 if (temporary_removals.find (i->note) == temporary_removals.end()) {
257 _model->remove_note_unlocked (i->note);
258 temporary_removals.insert (i->note);
260 i->note->set_note (i->new_value);
264 if (temporary_removals.find (i->note) == temporary_removals.end()) {
265 _model->remove_note_unlocked (i->note);
266 temporary_removals.insert (i->note);
269 i->note->set_time (i->new_time);
273 if (temporary_removals.find (i->note) == temporary_removals.end()) {
274 _model->remove_note_unlocked (i->note);
275 temporary_removals.insert (i->note);
277 i->note->set_channel (i->new_value);
280 /* no remove-then-add required for these properties, since we do not index them
284 i->note->set_velocity (i->new_value);
288 i->note->set_length (i->new_time);
295 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
296 NoteDiffCommand side_effects (model(), "side effects");
297 _model->add_note_unlocked (*i, &side_effects);
298 *this += side_effects;
301 if (!side_effect_removals.empty()) {
303 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
304 cerr << "\t" << *i << ' ' << **i << endl;
309 _model->ContentsChanged(); /* EMIT SIGNAL */
313 MidiModel::NoteDiffCommand::undo ()
316 MidiModel::WriteLock lock(_model->edit_lock());
318 for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
319 _model->remove_note_unlocked(*i);
322 for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
323 _model->add_note_unlocked(*i);
326 /* notes we modify in a way that requires remove-then-add to maintain ordering */
327 set<NotePtr> temporary_removals;
329 for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
330 Property prop = i->property;
333 if (temporary_removals.find (i->note) == temporary_removals.end()) {
334 _model->remove_note_unlocked (i->note);
335 temporary_removals.insert (i->note);
337 i->note->set_note (i->old_value);
340 i->note->set_velocity (i->old_value);
343 if (temporary_removals.find (i->note) == temporary_removals.end()) {
344 _model->remove_note_unlocked (i->note);
345 temporary_removals.insert (i->note);
347 i->note->set_time (i->old_time);
350 i->note->set_length (i->old_time);
353 if (temporary_removals.find (i->note) == temporary_removals.end()) {
354 _model->remove_note_unlocked (i->note);
355 temporary_removals.insert (i->note);
357 i->note->set_channel (i->old_value);
362 for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
363 _model->add_note_unlocked (*i);
366 /* finally add back notes that were removed by the "do". we don't care
367 about side effects here since the model should be back to its original
368 state once this is done.
371 for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
372 _model->add_note_unlocked (*i);
376 _model->ContentsChanged(); /* EMIT SIGNAL */
380 MidiModel::NoteDiffCommand::marshal_note(const NotePtr note)
382 XMLNode* xml_note = new XMLNode("note");
385 ostringstream id_str(ios::ate);
386 id_str << int(note->id());
387 xml_note->add_property("id", id_str.str());
391 ostringstream note_str(ios::ate);
392 note_str << int(note->note());
393 xml_note->add_property("note", note_str.str());
397 ostringstream channel_str(ios::ate);
398 channel_str << int(note->channel());
399 xml_note->add_property("channel", channel_str.str());
403 ostringstream time_str(ios::ate);
404 time_str << note->time();
405 xml_note->add_property("time", time_str.str());
409 ostringstream length_str(ios::ate);
410 length_str << note->length();
411 xml_note->add_property("length", length_str.str());
415 ostringstream velocity_str(ios::ate);
416 velocity_str << (unsigned int) note->velocity();
417 xml_note->add_property("velocity", velocity_str.str());
423 Evoral::Sequence<MidiModel::TimeType>::NotePtr
424 MidiModel::NoteDiffCommand::unmarshal_note (XMLNode *xml_note)
428 unsigned int channel;
431 unsigned int velocity;
434 if ((prop = xml_note->property("id")) != 0) {
435 istringstream id_str(prop->value());
438 error << "note information missing ID value" << endmsg;
442 if ((prop = xml_note->property("note")) != 0) {
443 istringstream note_str(prop->value());
446 warning << "note information missing note value" << endmsg;
450 if ((prop = xml_note->property("channel")) != 0) {
451 istringstream channel_str(prop->value());
452 channel_str >> channel;
454 warning << "note information missing channel" << endmsg;
458 if ((prop = xml_note->property("time")) != 0) {
459 istringstream time_str(prop->value());
462 warning << "note information missing time" << endmsg;
466 if ((prop = xml_note->property("length")) != 0) {
467 istringstream length_str(prop->value());
468 length_str >> length;
470 warning << "note information missing length" << endmsg;
474 if ((prop = xml_note->property("velocity")) != 0) {
475 istringstream velocity_str(prop->value());
476 velocity_str >> velocity;
478 warning << "note information missing velocity" << endmsg;
482 NotePtr note_ptr(new Evoral::Note<TimeType>(channel, time, length, note, velocity));
483 note_ptr->set_id (id);
489 MidiModel::NoteDiffCommand::marshal_change (const NoteChange& change)
491 XMLNode* xml_change = new XMLNode("Change");
493 /* first, the change itself */
495 xml_change->add_property ("property", enum_2_string (change.property));
498 ostringstream old_value_str (ios::ate);
499 if (change.property == StartTime || change.property == Length) {
500 old_value_str << change.old_time;
502 old_value_str << (unsigned int) change.old_value;
504 xml_change->add_property ("old", old_value_str.str());
508 ostringstream new_value_str (ios::ate);
509 if (change.property == StartTime || change.property == Length) {
510 new_value_str << change.new_time;
512 new_value_str << (unsigned int) change.new_value;
514 xml_change->add_property ("new", new_value_str.str());
517 ostringstream id_str;
518 id_str << change.note->id();
519 xml_change->add_property ("id", id_str.str());
524 MidiModel::NoteDiffCommand::NoteChange
525 MidiModel::NoteDiffCommand::unmarshal_change (XMLNode *xml_change)
530 if ((prop = xml_change->property("property")) != 0) {
531 change.property = (Property) string_2_enum (prop->value(), change.property);
533 fatal << "!!!" << endmsg;
537 if ((prop = xml_change->property ("id")) == 0) {
538 error << _("No NoteID found for note property change - ignored") << endmsg;
542 gint note_id = atoi (prop->value().c_str());
544 if ((prop = xml_change->property ("old")) != 0) {
545 istringstream old_str (prop->value());
546 if (change.property == StartTime || change.property == Length) {
547 old_str >> change.old_time;
549 int integer_value_so_that_istream_does_the_right_thing;
550 old_str >> integer_value_so_that_istream_does_the_right_thing;
551 change.old_value = integer_value_so_that_istream_does_the_right_thing;
554 fatal << "!!!" << endmsg;
558 if ((prop = xml_change->property ("new")) != 0) {
559 istringstream new_str (prop->value());
560 if (change.property == StartTime || change.property == Length) {
561 new_str >> change.new_time;
563 int integer_value_so_that_istream_does_the_right_thing;
564 new_str >> integer_value_so_that_istream_does_the_right_thing;
565 change.new_value = integer_value_so_that_istream_does_the_right_thing;
568 fatal << "!!!" << endmsg;
572 /* we must point at the instance of the note that is actually in the model.
573 so go look for it ...
576 change.note = _model->find_note (note_id);
579 warning << "MIDI note #" << note_id << " not found in model - programmers should investigate this" << endmsg;
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->add_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;
683 /** Write all of the model to a MidiSource (i.e. save the model).
684 * This is different from manually using read to write to a source in that
685 * note off events are written regardless of the track mode. This is so the
686 * user can switch a recorded track (with note durations from some instrument)
687 * to percussive, save, reload, then switch it back to sustained without
688 * destroying the original note durations.
690 * Similarly, control events are written without interpolation (as with the
694 MidiModel::write_to (boost::shared_ptr<MidiSource> source)
696 ReadLock lock(read_lock());
698 const bool old_percussive = percussive();
699 set_percussive(false);
701 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
704 source->drop_model();
705 source->mark_streaming_midi_write_started (note_mode(), ms->timeline_position ());
707 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
708 source->append_event_unlocked_beats(*i);
711 set_percussive(old_percussive);
712 source->mark_streaming_write_completed();
719 /** very similar to ::write_to() but writes to the model's own
720 existing midi_source, without making it call MidiSource::drop_model().
721 the caller is a MidiSource that needs to catch up with the state
725 MidiModel::sync_to_source ()
727 ReadLock lock(read_lock());
729 const bool old_percussive = percussive();
730 set_percussive(false);
732 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
735 ms->mark_streaming_midi_write_started (note_mode(), ms->timeline_position());
737 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
738 ms->append_event_unlocked_beats(*i);
741 set_percussive (old_percussive);
742 ms->mark_streaming_write_completed ();
749 /** Write part or all of the model to a MidiSource (i.e. save the model).
750 * This is different from manually using read to write to a source in that
751 * note off events are written regardless of the track mode. This is so the
752 * user can switch a recorded track (with note durations from some instrument)
753 * to percussive, save, reload, then switch it back to sustained without
754 * destroying the original note durations.
757 MidiModel::write_section_to (boost::shared_ptr<MidiSource> source, Evoral::MusicalTime begin_time, Evoral::MusicalTime end_time)
759 ReadLock lock(read_lock());
760 MidiStateTracker mst;
761 Evoral::MusicalTime extra_note_on_time = end_time;
763 const bool old_percussive = percussive();
764 set_percussive(false);
766 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
769 source->drop_model();
770 source->mark_streaming_midi_write_started (note_mode(), ms->timeline_position());
772 for (Evoral::Sequence<TimeType>::const_iterator i = begin(0, true); i != end(); ++i) {
773 const Evoral::Event<Evoral::MusicalTime>& ev (*i);
775 if (ev.time() >= begin_time && ev.time() < end_time) {
777 const Evoral::MIDIEvent<Evoral::MusicalTime>* mev =
778 static_cast<const Evoral::MIDIEvent<Evoral::MusicalTime>* > (&ev);
785 if (mev->is_note_off()) {
787 if (!mst.active (mev->note(), mev->channel())) {
789 /* add a note-on at the start of the range we're writing
790 to the file. velocity is just an arbitary reasonable value.
793 Evoral::MIDIEvent<Evoral::MusicalTime> on (mev->event_type(), extra_note_on_time, 3, 0, true);
794 on.set_type (mev->type());
795 on.set_note (mev->note());
796 on.set_channel (mev->channel());
797 on.set_velocity (mev->velocity());
799 cerr << "Add note on for odd note off, note = " << (int) on.note() << endl;
800 source->append_event_unlocked_beats (on);
801 mst.add (on.note(), on.channel());
803 extra_note_on_time += 1.0/128.0;
806 cerr << "MIDI Note off (note = " << (int) mev->note() << endl;
807 source->append_event_unlocked_beats (*i);
808 mst.remove (mev->note(), mev->channel());
811 } else if (mev->is_note_on()) {
812 cerr << "MIDI Note on (note = " << (int) mev->note() << endl;
813 mst.add (mev->note(), mev->channel());
814 source->append_event_unlocked_beats(*i);
817 cerr << "MIDI other event type\n";
818 source->append_event_unlocked_beats(*i);
823 mst.resolve_notes (*source, end_time);
825 set_percussive(old_percussive);
826 source->mark_streaming_write_completed();
834 MidiModel::get_state()
836 XMLNode *node = new XMLNode("MidiModel");
840 Evoral::Sequence<MidiModel::TimeType>::NotePtr
841 MidiModel::find_note (NotePtr other)
843 Notes::iterator l = notes().lower_bound(other);
845 if (l != notes().end()) {
846 for (; (*l)->time() == other->time(); ++l) {
847 /* NB: compare note contents, not note pointers.
848 If "other" was a ptr to a note already in
849 the model, we wouldn't be looking for it,
861 Evoral::Sequence<MidiModel::TimeType>::NotePtr
862 MidiModel::find_note (gint note_id)
864 /* used only for looking up notes when reloading history from disk,
865 so we don't care about performance *too* much.
868 for (Notes::iterator l = notes().begin(); l != notes().end(); ++l) {
869 if ((*l)->id() == note_id) {
877 /** Lock and invalidate the source.
878 * This should be used by commands and editing things
881 MidiModel::edit_lock()
883 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
886 Glib::Mutex::Lock* source_lock = new Glib::Mutex::Lock (ms->mutex());
887 ms->invalidate(); // Release cached iterator's read lock on model
888 return WriteLock(new WriteLockImpl(source_lock, _lock, _control_lock));
891 /** Lock just the model, the source lock must already be held.
892 * This should only be called from libardour/evoral places
895 MidiModel::write_lock()
897 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
900 assert (!ms->mutex().trylock ());
901 return WriteLock(new WriteLockImpl(NULL, _lock, _control_lock));
905 MidiModel::resolve_overlaps_unlocked (const NotePtr note, void* arg)
907 using namespace Evoral;
909 if (_writing || insert_merge_policy() == InsertMergeRelax) {
913 NoteDiffCommand* cmd = static_cast<NoteDiffCommand*>(arg);
915 TimeType sa = note->time();
916 TimeType ea = note->end_time();
918 const Pitches& p (pitches (note->channel()));
919 NotePtr search_note(new Note<TimeType>(0, 0, 0, note->note()));
920 set<NotePtr> to_be_deleted;
921 bool set_note_length = false;
922 bool set_note_time = false;
923 TimeType note_time = note->time();
924 TimeType note_length = note->length();
926 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 checking overlaps for note %2 @ %3\n", this, (int)note->note(), note->time()));
928 for (Pitches::const_iterator i = p.lower_bound (search_note);
929 i != p.end() && (*i)->note() == note->note(); ++i) {
931 TimeType sb = (*i)->time();
932 TimeType eb = (*i)->end_time();
933 OverlapType overlap = OverlapNone;
936 if ((sb > sa) && (eb <= ea)) {
937 overlap = OverlapInternal;
938 } else if ((eb >= sa) && (eb <= ea)) {
939 overlap = OverlapStart;
940 } else if ((sb > sa) && (sb <= ea)) {
941 overlap = OverlapEnd;
942 } else if ((sa >= sb) && (sa <= eb) && (ea <= eb)) {
943 overlap = OverlapExternal;
949 DEBUG_TRACE (DEBUG::Sequence, string_compose ("\toverlap is %1 for (%2,%3) vs (%4,%5)\n", enum_2_string(overlap),
952 if (insert_merge_policy() == InsertMergeReject) {
953 DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 just reject\n", this));
959 cerr << "OverlapStart\n";
960 /* existing note covers start of new note */
961 switch (insert_merge_policy()) {
962 case InsertMergeReplace:
963 to_be_deleted.insert (*i);
965 case InsertMergeTruncateExisting:
967 cmd->change (*i, NoteDiffCommand::Length, (note->time() - (*i)->time()));
969 (*i)->set_length (note->time() - (*i)->time());
971 case InsertMergeTruncateAddition:
972 set_note_time = true;
973 set_note_length = true;
974 note_time = (*i)->time() + (*i)->length();
975 note_length = min (note_length, (*i)->length() - ((*i)->end_time() - note->time()));
977 case InsertMergeExtend:
979 cmd->change ((*i), NoteDiffCommand::Length, note->end_time() - (*i)->time());
981 (*i)->set_length (note->end_time() - (*i)->time());
982 return -1; /* do not add the new note */
992 cerr << "OverlapEnd\n";
993 /* existing note covers end of new note */
994 switch (insert_merge_policy()) {
995 case InsertMergeReplace:
996 to_be_deleted.insert (*i);
999 case InsertMergeTruncateExisting:
1000 /* resetting the start time of the existing note
1001 is a problem because of time ordering.
1005 case InsertMergeTruncateAddition:
1006 set_note_length = true;
1007 note_length = min (note_length, ((*i)->time() - note->time()));
1010 case InsertMergeExtend:
1011 /* we can't reset the time of the existing note because
1012 that will corrupt time ordering. So remove the
1013 existing note and change the position/length
1014 of the new note (which has not been added yet)
1016 to_be_deleted.insert (*i);
1017 set_note_length = true;
1018 note_length = min (note_length, (*i)->end_time() - note->time());
1027 case OverlapExternal:
1028 cerr << "OverlapExt\n";
1029 /* existing note overlaps all the new note */
1030 switch (insert_merge_policy()) {
1031 case InsertMergeReplace:
1032 to_be_deleted.insert (*i);
1034 case InsertMergeTruncateExisting:
1035 case InsertMergeTruncateAddition:
1036 case InsertMergeExtend:
1037 /* cannot add in this case */
1046 case OverlapInternal:
1047 cerr << "OverlapInt\n";
1048 /* new note fully overlaps an existing note */
1049 switch (insert_merge_policy()) {
1050 case InsertMergeReplace:
1051 case InsertMergeTruncateExisting:
1052 case InsertMergeTruncateAddition:
1053 case InsertMergeExtend:
1054 /* delete the existing note, the new one will cover it */
1055 to_be_deleted.insert (*i);
1071 for (set<NotePtr>::iterator i = to_be_deleted.begin(); i != to_be_deleted.end(); ++i) {
1072 remove_note_unlocked (*i);
1075 cmd->side_effect_remove (*i);
1079 if (set_note_time) {
1081 cmd->change (note, NoteDiffCommand::StartTime, note_time);
1083 note->set_time (note_time);
1086 if (set_note_length) {
1088 cmd->change (note, NoteDiffCommand::Length, note_length);
1090 note->set_length (note_length);
1097 MidiModel::insert_merge_policy () const
1099 /* XXX ultimately this should be a per-track or even per-model policy */
1100 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1103 return ms->session().config.get_insert_merge_policy ();
1107 MidiModel::set_midi_source (boost::shared_ptr<MidiSource> s)
1109 boost::shared_ptr<MidiSource> old = _midi_source.lock ();
1115 _midi_source_connections.drop_connections ();
1119 s->InterpolationChanged.connect_same_thread (
1120 _midi_source_connections, boost::bind (&MidiModel::source_interpolation_changed, this, _1, _2)
1123 s->AutomationStateChanged.connect_same_thread (
1124 _midi_source_connections, boost::bind (&MidiModel::source_automation_state_changed, this, _1, _2)
1128 /** The source has signalled that the interpolation style for a parameter has changed. In order to
1129 * keep MidiSource and ControlList interpolation state the same, we pass this change onto the
1130 * appropriate ControlList.
1132 * The idea is that MidiSource and the MidiModel's ControlList states are kept in sync, and one
1133 * or the other is listened to by the GUI.
1136 MidiModel::source_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1138 Glib::Mutex::Lock lm (_control_lock);
1139 control(p)->list()->set_interpolation (s);
1142 /** A ControlList has signalled that its interpolation style has changed. Again, in order to keep
1143 * MidiSource and ControlList interpolation state in sync, we pass this change onto our MidiSource.
1146 MidiModel::control_list_interpolation_changed (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
1148 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1151 ms->set_interpolation_of (p, s);
1155 MidiModel::source_automation_state_changed (Evoral::Parameter p, AutoState s)
1157 Glib::Mutex::Lock lm (_control_lock);
1158 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (control(p)->list ());
1159 al->set_automation_state (s);
1163 MidiModel::automation_list_automation_state_changed (Evoral::Parameter p, AutoState s)
1165 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1167 ms->set_automation_state_of (p, s);
1170 boost::shared_ptr<Evoral::Control>
1171 MidiModel::control_factory (Evoral::Parameter const & p)
1173 boost::shared_ptr<Evoral::Control> c = Automatable::control_factory (p);
1175 /* Set up newly created control's lists to the appropriate interpolation and
1176 automation state from our source.
1179 boost::shared_ptr<MidiSource> ms = _midi_source.lock ();
1182 c->list()->set_interpolation (ms->interpolation_of (p));
1184 boost::shared_ptr<AutomationList> al = boost::dynamic_pointer_cast<AutomationList> (c->list ());
1187 al->set_automation_state (ms->automation_state_of (p));
1192 boost::shared_ptr<const MidiSource>
1193 MidiModel::midi_source ()
1195 return _midi_source.lock ();
1198 /** Moves notes, controllers and sys-ex to insert silence at the start of the model.
1199 * Adds commands to the session's current undo stack to reflect the movements.
1202 MidiModel::insert_silence_at_start (TimeType t)
1206 NoteDiffCommand* c = new_note_diff_command ("insert silence");
1208 for (Notes::const_iterator i = notes().begin(); i != notes().end(); ++i) {
1209 c->change (*i, NoteDiffCommand::StartTime, (*i)->time() + t);
1212 boost::shared_ptr<MidiSource> s = _midi_source.lock ();
1215 apply_command_as_subcommand (s->session(), c);
1219 for (Controls::iterator i = controls().begin(); i != controls().end(); ++i) {
1220 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
1221 XMLNode& before = ac->alist()->get_state ();
1222 i->second->list()->shift (0, t);
1223 XMLNode& after = ac->alist()->get_state ();
1224 s->session().add_command (new MementoCommand<AutomationList> (new MidiAutomationListBinder (s, i->first), &before, &after));