2 Copyright (C) 2001-2011 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 <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "automation_region_view.h"
49 #include "automation_time_axis.h"
50 #include "canvas-hit.h"
51 #include "canvas-note.h"
52 #include "canvas_patch_change.h"
55 #include "editor_drag.h"
56 #include "ghostregion.h"
57 #include "gui_thread.h"
59 #include "midi_channel_dialog.h"
60 #include "midi_cut_buffer.h"
61 #include "midi_list_editor.h"
62 #include "midi_region_view.h"
63 #include "midi_streamview.h"
64 #include "midi_time_axis.h"
65 #include "midi_util.h"
66 #include "note_player.h"
67 #include "public_editor.h"
68 #include "rgb_macros.h"
69 #include "selection.h"
70 #include "simpleline.h"
71 #include "streamview.h"
73 #include "mouse_cursors.h"
74 #include "patch_change_dialog.h"
75 #include "verbose_cursor.h"
79 using namespace ARDOUR;
81 using namespace Editing;
82 using namespace ArdourCanvas;
83 using Gtkmm2ext::Keyboard;
85 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
87 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
89 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
90 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
91 : RegionView (parent, tv, r, spu, basic_color)
93 , _last_channel_selection(0xFFFF)
94 , _current_range_min(0)
95 , _current_range_max(0)
97 , _note_group(new ArdourCanvas::Group(*group))
98 , _note_diff_command (0)
100 , _step_edit_cursor (0)
101 , _step_edit_cursor_width (1.0)
102 , _step_edit_cursor_position (0.0)
103 , _channel_selection_scoped_note (0)
104 , _temporary_note_group (0)
107 , _sort_needed (true)
108 , _optimization_iterator (_events.end())
110 , _no_sound_notes (false)
113 , _pre_enter_cursor (0)
115 _note_group->raise_to_top();
116 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
118 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
119 connect_to_diskstream ();
121 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
124 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
125 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
126 TimeAxisViewItem::Visibility visibility)
127 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
129 , _last_channel_selection(0xFFFF)
130 , _current_range_min(0)
131 , _current_range_max(0)
133 , _note_group(new ArdourCanvas::Group(*parent))
134 , _note_diff_command (0)
136 , _step_edit_cursor (0)
137 , _step_edit_cursor_width (1.0)
138 , _step_edit_cursor_position (0.0)
139 , _channel_selection_scoped_note (0)
140 , _temporary_note_group (0)
143 , _sort_needed (true)
144 , _optimization_iterator (_events.end())
146 , _no_sound_notes (false)
149 , _pre_enter_cursor (0)
151 _note_group->raise_to_top();
152 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
154 connect_to_diskstream ();
156 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
160 MidiRegionView::parameter_changed (std::string const & p)
162 if (p == "diplay-first-midi-bank-as-zero") {
163 if (_enable_display) {
169 MidiRegionView::MidiRegionView (const MidiRegionView& other)
170 : sigc::trackable(other)
173 , _last_channel_selection(0xFFFF)
174 , _current_range_min(0)
175 , _current_range_max(0)
177 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
178 , _note_diff_command (0)
180 , _step_edit_cursor (0)
181 , _step_edit_cursor_width (1.0)
182 , _step_edit_cursor_position (0.0)
183 , _channel_selection_scoped_note (0)
184 , _temporary_note_group (0)
187 , _sort_needed (true)
188 , _optimization_iterator (_events.end())
190 , _no_sound_notes (false)
193 , _pre_enter_cursor (0)
198 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
199 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
204 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
205 : RegionView (other, boost::shared_ptr<Region> (region))
207 , _last_channel_selection(0xFFFF)
208 , _current_range_min(0)
209 , _current_range_max(0)
211 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
212 , _note_diff_command (0)
214 , _step_edit_cursor (0)
215 , _step_edit_cursor_width (1.0)
216 , _step_edit_cursor_position (0.0)
217 , _channel_selection_scoped_note (0)
218 , _temporary_note_group (0)
221 , _sort_needed (true)
222 , _optimization_iterator (_events.end())
224 , _no_sound_notes (false)
227 , _pre_enter_cursor (0)
232 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
233 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
239 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
241 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
243 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
244 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
248 midi_region()->midi_source(0)->load_model();
251 _model = midi_region()->midi_source(0)->model();
252 _enable_display = false;
254 RegionView::init (basic_color, false);
256 compute_colors (basic_color);
258 set_height (trackview.current_height());
261 region_sync_changed ();
262 region_resized (ARDOUR::bounds_change);
265 reset_width_dependent_items (_pixel_width);
269 _enable_display = true;
272 display_model (_model);
276 group->raise_to_top();
277 group->signal_event().connect(
278 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
280 midi_view()->signal_channel_mode_changed().connect(
281 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
283 midi_view()->signal_midi_patch_settings_changed().connect(
284 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
286 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
287 ui_bind(&MidiRegionView::snap_changed, this),
290 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
291 connect_to_diskstream ();
293 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
296 const boost::shared_ptr<ARDOUR::MidiRegion>
297 MidiRegionView::midi_region() const
299 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
303 MidiRegionView::connect_to_diskstream ()
305 midi_view()->midi_track()->DataRecorded.connect(
306 *this, invalidator(*this),
307 ui_bind(&MidiRegionView::data_recorded, this, _1),
312 MidiRegionView::canvas_event(GdkEvent* ev)
315 case GDK_ENTER_NOTIFY:
316 case GDK_LEAVE_NOTIFY:
317 _last_event_x = ev->crossing.x;
318 _last_event_y = ev->crossing.y;
320 case GDK_MOTION_NOTIFY:
321 _last_event_x = ev->motion.x;
322 _last_event_y = ev->motion.y;
328 if (!trackview.editor().internal_editing()) {
334 return scroll (&ev->scroll);
337 return key_press (&ev->key);
339 case GDK_KEY_RELEASE:
340 return key_release (&ev->key);
342 case GDK_BUTTON_PRESS:
343 return button_press (&ev->button);
345 case GDK_2BUTTON_PRESS:
348 case GDK_BUTTON_RELEASE:
349 return button_release (&ev->button);
351 case GDK_ENTER_NOTIFY:
352 return enter_notify (&ev->crossing);
354 case GDK_LEAVE_NOTIFY:
355 return leave_notify (&ev->crossing);
357 case GDK_MOTION_NOTIFY:
358 return motion (&ev->motion);
368 MidiRegionView::remove_ghost_note ()
375 MidiRegionView::enter_notify (GdkEventCrossing* ev)
377 trackview.editor().MouseModeChanged.connect (
378 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
381 if (trackview.editor().current_mouse_mode() == MouseRange && _mouse_state != AddDragging) {
382 create_ghost_note (ev->x, ev->y);
385 if (!trackview.editor().internal_editing()) {
386 Keyboard::magic_widget_drop_focus();
388 Keyboard::magic_widget_grab_focus();
396 MidiRegionView::leave_notify (GdkEventCrossing*)
398 _mouse_mode_connection.disconnect ();
400 trackview.editor().verbose_cursor()->hide ();
401 remove_ghost_note ();
407 MidiRegionView::mouse_mode_changed ()
409 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
410 create_ghost_note (_last_event_x, _last_event_y);
412 remove_ghost_note ();
413 trackview.editor().verbose_cursor()->hide ();
416 if (!trackview.editor().internal_editing()) {
417 Keyboard::magic_widget_drop_focus();
419 Keyboard::magic_widget_grab_focus();
425 MidiRegionView::button_press (GdkEventButton* ev)
427 if (ev->button != 1) {
431 if (_mouse_state != SelectTouchDragging) {
433 _pressed_button = ev->button;
434 _mouse_state = Pressed;
439 _pressed_button = ev->button;
445 MidiRegionView::button_release (GdkEventButton* ev)
447 double event_x, event_y;
449 if (ev->button != 1) {
456 group->w2i(event_x, event_y);
457 group->ungrab(ev->time);
459 PublicEditor& editor = trackview.editor ();
461 switch (_mouse_state) {
462 case Pressed: // Clicked
464 switch (editor.current_mouse_mode()) {
470 if (Keyboard::is_insert_note_event(ev)) {
472 double event_x, event_y;
476 group->w2i(event_x, event_y);
479 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
485 /* Shorten the length by 1 tick so that we can add a new note at the next
486 grid snap without it overlapping this one.
488 beats -= 1.0 / Timecode::BBT_Time::ticks_per_bar_division;
490 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
498 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
504 /* Shorten the length by 1 tick so that we can add a new note at the next
505 grid snap without it overlapping this one.
507 beats -= 1.0 / Timecode::BBT_Time::ticks_per_bar_division;
509 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
520 case SelectRectDragging:
522 editor.drags()->end_grab ((GdkEvent *) ev);
524 create_ghost_note (ev->x, ev->y);
536 MidiRegionView::motion (GdkEventMotion* ev)
538 PublicEditor& editor = trackview.editor ();
540 if (!_ghost_note && editor.current_mouse_mode() != MouseRange
541 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
542 && _mouse_state != AddDragging) {
544 create_ghost_note (ev->x, ev->y);
545 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
546 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
548 update_ghost_note (ev->x, ev->y);
549 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
551 remove_ghost_note ();
553 editor.verbose_cursor()->hide ();
554 } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
555 update_ghost_note (ev->x, ev->y);
558 /* any motion immediately hides velocity text that may have been visible */
560 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
561 (*i)->hide_velocity ();
564 switch (_mouse_state) {
567 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
568 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
570 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
571 _mouse_state = SelectRectDragging;
574 } else if (editor.internal_editing()) {
576 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
577 _mouse_state = AddDragging;
579 remove_ghost_note ();
581 editor.verbose_cursor()->hide ();
588 case SelectRectDragging:
590 editor.drags()->motion_handler ((GdkEvent *) ev, false);
593 case SelectTouchDragging:
605 MidiRegionView::scroll (GdkEventScroll* ev)
607 if (_selection.empty()) {
611 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
612 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
613 it still works for zoom.
618 trackview.editor().verbose_cursor()->hide ();
620 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
622 if (ev->direction == GDK_SCROLL_UP) {
623 change_velocities (true, fine, false);
624 } else if (ev->direction == GDK_SCROLL_DOWN) {
625 change_velocities (false, fine, false);
631 MidiRegionView::key_press (GdkEventKey* ev)
633 /* since GTK bindings are generally activated on press, and since
634 detectable auto-repeat is the name of the game and only sends
635 repeated presses, carry out key actions at key press, not release.
638 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
640 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
641 _mouse_state = SelectTouchDragging;
644 } else if (ev->keyval == GDK_Escape && unmodified) {
648 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
650 bool start = (ev->keyval == GDK_comma);
651 bool end = (ev->keyval == GDK_period);
652 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
653 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
655 change_note_lengths (fine, shorter, 0.0, start, end);
659 } else if (ev->keyval == GDK_Delete && unmodified) {
664 } else if (ev->keyval == GDK_Tab) {
666 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
667 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
669 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
673 } else if (ev->keyval == GDK_ISO_Left_Tab) {
675 /* Shift-TAB generates ISO Left Tab, for some reason */
677 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
678 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
680 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
686 } else if (ev->keyval == GDK_Up) {
688 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
689 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
691 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
692 change_velocities (true, fine, allow_smush);
694 transpose (true, fine, allow_smush);
698 } else if (ev->keyval == GDK_Down) {
700 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
701 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
703 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
704 change_velocities (false, fine, allow_smush);
706 transpose (false, fine, allow_smush);
710 } else if (ev->keyval == GDK_Left && unmodified) {
715 } else if (ev->keyval == GDK_Right && unmodified) {
720 } else if (ev->keyval == GDK_c && unmodified) {
729 MidiRegionView::key_release (GdkEventKey* ev)
731 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
739 MidiRegionView::channel_edit ()
742 uint8_t current_channel = 0;
744 if (_selection.empty()) {
748 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
750 current_channel = (*i)->note()->channel ();
755 MidiChannelDialog channel_dialog (current_channel);
756 int ret = channel_dialog.run ();
759 case Gtk::RESPONSE_OK:
765 uint8_t new_channel = channel_dialog.active_channel ();
767 start_note_diff_command (_("channel edit"));
769 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
770 Selection::iterator next = i;
772 change_note_channel (*i, new_channel);
780 MidiRegionView::show_list_editor ()
783 _list_editor = new MidiListEditor (trackview.session(), midi_region());
785 _list_editor->present ();
788 /** Add a note to the model, and the view, at a canvas (click) coordinate.
789 * \param t time in frames relative to the position of the region
790 * \param y vertical position in pixels
791 * \param length duration of the note in beats
792 * \param snap_t true to snap t to the grid, otherwise false.
795 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
797 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
798 MidiStreamView* const view = mtv->midi_view();
800 double note = view->y_to_note(y);
803 assert(note <= 127.0);
805 // Start of note in frames relative to region start
807 framecnt_t grid_frames;
808 t = snap_frame_to_grid_underneath (t, grid_frames);
812 assert (length != 0);
814 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
815 region_frames_to_region_beats(t + _region->start()),
817 (uint8_t)note, 0x40));
819 if (_model->contains (new_note)) {
823 view->update_note_range(new_note->note());
825 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
827 _model->apply_command(*trackview.session(), cmd);
829 play_midi_note (new_note);
833 MidiRegionView::clear_events()
838 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
839 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
844 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
849 _patch_changes.clear();
851 _optimization_iterator = _events.end();
855 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
859 content_connection.disconnect ();
860 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
864 if (_enable_display) {
870 MidiRegionView::start_note_diff_command (string name)
872 if (!_note_diff_command) {
873 _note_diff_command = _model->new_note_diff_command (name);
878 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
880 if (_note_diff_command) {
881 _note_diff_command->add (note);
884 _marked_for_selection.insert(note);
887 _marked_for_velocity.insert(note);
892 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
894 if (_note_diff_command && ev->note()) {
895 _note_diff_command->remove(ev->note());
900 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
901 MidiModel::NoteDiffCommand::Property property,
904 if (_note_diff_command) {
905 _note_diff_command->change (ev->note(), property, val);
910 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
911 MidiModel::NoteDiffCommand::Property property,
912 Evoral::MusicalTime val)
914 if (_note_diff_command) {
915 _note_diff_command->change (ev->note(), property, val);
920 MidiRegionView::apply_diff (bool as_subcommand)
924 if (!_note_diff_command) {
928 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
929 // Mark all selected notes for selection when model reloads
930 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
931 _marked_for_selection.insert((*i)->note());
936 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
938 _model->apply_command (*trackview.session(), _note_diff_command);
941 _note_diff_command = 0;
942 midi_view()->midi_track()->playlist_modified();
945 _marked_for_selection.clear();
948 _marked_for_velocity.clear();
952 MidiRegionView::abort_command()
954 delete _note_diff_command;
955 _note_diff_command = 0;
960 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
962 if (_optimization_iterator != _events.end()) {
963 ++_optimization_iterator;
966 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
967 return *_optimization_iterator;
970 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
971 if ((*_optimization_iterator)->note() == note) {
972 return *_optimization_iterator;
980 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
982 MidiModel::Notes notes;
983 _model->get_notes (notes, op, val, chan_mask);
985 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
986 CanvasNoteEvent* cne = find_canvas_note (*n);
994 MidiRegionView::redisplay_model()
996 // Don't redisplay the model if we're currently recording and displaying that
1005 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1006 (*i)->invalidate ();
1009 MidiModel::ReadLock lock(_model->read_lock());
1011 MidiModel::Notes& notes (_model->notes());
1012 _optimization_iterator = _events.begin();
1014 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1016 boost::shared_ptr<NoteType> note (*n);
1017 CanvasNoteEvent* cne;
1020 if (note_in_region_range (note, visible)) {
1022 if ((cne = find_canvas_note (note)) != 0) {
1029 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1031 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1043 add_note (note, visible);
1048 if ((cne = find_canvas_note (note)) != 0) {
1056 /* remove note items that are no longer valid */
1058 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1059 if (!(*i)->valid ()) {
1061 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1062 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1064 gr->remove_note (*i);
1069 i = _events.erase (i);
1076 _patch_changes.clear();
1080 display_patch_changes ();
1082 _marked_for_selection.clear ();
1083 _marked_for_velocity.clear ();
1085 /* we may have caused _events to contain things out of order (e.g. if a note
1086 moved earlier or later). we don't generally need them in time order, but
1087 make a note that a sort is required for those cases that require it.
1090 _sort_needed = true;
1094 MidiRegionView::display_patch_changes ()
1096 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1097 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1099 for (uint8_t i = 0; i < 16; ++i) {
1100 if (chn_mask & (1<<i)) {
1101 display_patch_changes_on_channel (i);
1103 /* TODO gray-out patch instad of not displaying it */
1108 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1110 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1112 if ((*i)->channel() != channel) {
1116 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1118 boost::shared_ptr<MIDI::Name::Patch> patch =
1119 MIDI::Name::MidiPatchManager::instance().find_patch(
1120 _model_name, _custom_device_mode, channel, patch_key);
1123 add_canvas_patch_change (*i, patch->name());
1126 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1127 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1128 add_canvas_patch_change (*i, buf);
1134 MidiRegionView::display_sysexes()
1136 bool have_periodic_system_messages = false;
1137 bool display_periodic_messages = true;
1139 if (!Config->get_never_display_periodic_midi()) {
1141 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1142 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1143 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1146 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1147 have_periodic_system_messages = true;
1153 if (have_periodic_system_messages) {
1154 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1156 /* get an approximate value for the number of samples per video frame */
1158 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1160 /* if we are zoomed out beyond than the cutoff (i.e. more
1161 * frames per pixel than frames per 4 video frames), don't
1162 * show periodic sysex messages.
1165 if (zoom > (video_frame*4)) {
1166 display_periodic_messages = false;
1170 display_periodic_messages = false;
1173 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1175 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1176 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1178 Evoral::MusicalTime time = (*i)->time();
1182 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1183 if (!display_periodic_messages) {
1191 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1192 str << int((*i)->buffer()[b]);
1193 if (b != (*i)->size() -1) {
1197 string text = str.str();
1199 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1201 double height = midi_stream_view()->contents_height();
1203 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1204 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1206 // Show unless message is beyond the region bounds
1207 if (time - _region->start() >= _region->length() || time < _region->start()) {
1213 _sys_exes.push_back(sysex);
1217 MidiRegionView::~MidiRegionView ()
1219 in_destructor = true;
1221 trackview.editor().verbose_cursor()->hide ();
1223 note_delete_connection.disconnect ();
1225 delete _list_editor;
1227 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1229 if (_active_notes) {
1237 delete _note_diff_command;
1238 delete _step_edit_cursor;
1239 delete _temporary_note_group;
1243 MidiRegionView::region_resized (const PropertyChange& what_changed)
1245 RegionView::region_resized(what_changed);
1247 if (what_changed.contains (ARDOUR::Properties::position)) {
1248 set_duration(_region->length(), 0);
1249 if (_enable_display) {
1256 MidiRegionView::reset_width_dependent_items (double pixel_width)
1258 RegionView::reset_width_dependent_items(pixel_width);
1259 assert(_pixel_width == pixel_width);
1261 if (_enable_display) {
1265 move_step_edit_cursor (_step_edit_cursor_position);
1266 set_step_edit_cursor_width (_step_edit_cursor_width);
1270 MidiRegionView::set_height (double height)
1272 static const double FUDGE = 2.0;
1273 const double old_height = _height;
1274 RegionView::set_height(height);
1275 _height = height - FUDGE;
1277 apply_note_range(midi_stream_view()->lowest_note(),
1278 midi_stream_view()->highest_note(),
1279 height != old_height + FUDGE);
1282 name_pixbuf->raise_to_top();
1285 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1286 (*x)->set_height (midi_stream_view()->contents_height());
1289 if (_step_edit_cursor) {
1290 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1295 /** Apply the current note range from the stream view
1296 * by repositioning/hiding notes as necessary
1299 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1301 if (!_enable_display) {
1305 if (!force && _current_range_min == min && _current_range_max == max) {
1309 _current_range_min = min;
1310 _current_range_max = max;
1312 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1313 CanvasNoteEvent* event = *i;
1314 boost::shared_ptr<NoteType> note (event->note());
1316 if (note->note() < _current_range_min ||
1317 note->note() > _current_range_max) {
1323 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1325 const double y1 = midi_stream_view()->note_to_y(note->note());
1326 const double y2 = y1 + floor(midi_stream_view()->note_height());
1328 cnote->property_y1() = y1;
1329 cnote->property_y2() = y2;
1331 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1333 const double diamond_size = update_hit (chit);
1335 chit->set_height (diamond_size);
1341 MidiRegionView::add_ghost (TimeAxisView& tv)
1345 double unit_position = _region->position () / samples_per_unit;
1346 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1347 MidiGhostRegion* ghost;
1349 if (mtv && mtv->midi_view()) {
1350 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1351 to allow having midi notes on top of note lines and waveforms.
1353 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1355 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1358 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1359 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1360 ghost->add_note(note);
1364 ghost->set_height ();
1365 ghost->set_duration (_region->length() / samples_per_unit);
1366 ghosts.push_back (ghost);
1368 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1374 /** Begin tracking note state for successive calls to add_event
1377 MidiRegionView::begin_write()
1379 assert(!_active_notes);
1380 _active_notes = new CanvasNote*[128];
1381 for (unsigned i=0; i < 128; ++i) {
1382 _active_notes[i] = 0;
1387 /** Destroy note state for add_event
1390 MidiRegionView::end_write()
1392 delete[] _active_notes;
1394 _marked_for_selection.clear();
1395 _marked_for_velocity.clear();
1399 /** Resolve an active MIDI note (while recording).
1402 MidiRegionView::resolve_note(uint8_t note, double end_time)
1404 if (midi_view()->note_mode() != Sustained) {
1408 if (_active_notes && _active_notes[note]) {
1410 /* XXX is end_time really region-centric? I think so, because
1411 this is a new region that we're recording, so source zero is
1412 the same as region zero
1414 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1416 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1417 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1418 _active_notes[note] = 0;
1423 /** Extend active notes to rightmost edge of region (if length is changed)
1426 MidiRegionView::extend_active_notes()
1428 if (!_active_notes) {
1432 for (unsigned i=0; i < 128; ++i) {
1433 if (_active_notes[i]) {
1434 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1441 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1443 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1447 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1449 if (!route_ui || !route_ui->midi_track()) {
1453 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1459 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1461 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1465 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1467 if (!route_ui || !route_ui->midi_track()) {
1471 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1473 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1482 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1484 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1485 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1487 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1488 (note->note() <= midi_stream_view()->highest_note());
1493 /** Update a canvas note's size from its model note.
1494 * @param ev Canvas note to update.
1495 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1498 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1500 boost::shared_ptr<NoteType> note = ev->note();
1501 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1502 const double y1 = midi_stream_view()->note_to_y(note->note());
1504 ev->property_x1() = x;
1505 ev->property_y1() = y1;
1507 /* trim note display to not overlap the end of its region */
1509 if (note->length() > 0) {
1510 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1511 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1513 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1516 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1518 if (note->length() == 0) {
1519 if (_active_notes) {
1520 assert(note->note() < 128);
1521 // If this note is already active there's a stuck note,
1522 // finish the old note rectangle
1523 if (_active_notes[note->note()]) {
1524 CanvasNote* const old_rect = _active_notes[note->note()];
1525 boost::shared_ptr<NoteType> old_note = old_rect->note();
1526 old_rect->property_x2() = x;
1527 old_rect->property_outline_what() = (guint32) 0xF;
1529 _active_notes[note->note()] = ev;
1531 /* outline all but right edge */
1532 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1534 /* outline all edges */
1535 ev->property_outline_what() = (guint32) 0xF;
1538 if (update_ghost_regions) {
1539 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1540 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1542 gr->update_note (ev);
1549 MidiRegionView::update_hit (CanvasHit* ev)
1551 boost::shared_ptr<NoteType> note = ev->note();
1553 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1554 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1555 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1556 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1560 return diamond_size;
1563 /** Add a MIDI note to the view (with length).
1565 * If in sustained mode, notes with length 0 will be considered active
1566 * notes, and resolve_note should be called when the corresponding note off
1567 * event arrives, to properly display the note.
1570 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1572 CanvasNoteEvent* event = 0;
1574 assert(note->time() >= 0);
1575 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1577 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1579 if (midi_view()->note_mode() == Sustained) {
1581 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1583 update_note (ev_rect);
1587 MidiGhostRegion* gr;
1589 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1590 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1591 gr->add_note(ev_rect);
1595 } else if (midi_view()->note_mode() == Percussive) {
1597 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1599 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1601 update_hit (ev_diamond);
1610 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1611 note_selected(event, true);
1614 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1615 event->show_velocity();
1618 event->on_channel_selection_change(_last_channel_selection);
1619 _events.push_back(event);
1628 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1629 MidiStreamView* const view = mtv->midi_view();
1631 view->update_note_range (note->note());
1635 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1636 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1638 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1640 /* potentially extend region to hold new note */
1642 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1643 framepos_t region_end = _region->last_frame();
1645 if (end_frame > region_end) {
1646 _region->set_length (end_frame - _region->position());
1649 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1650 MidiStreamView* const view = mtv->midi_view();
1652 view->update_note_range(new_note->note());
1654 _marked_for_selection.clear ();
1657 start_note_diff_command (_("step add"));
1658 note_diff_add_note (new_note, true, false);
1661 // last_step_edit_note = new_note;
1665 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1667 change_note_lengths (false, false, beats, false, true);
1671 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1673 assert (patch->time() >= 0);
1675 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1676 const double x = trackview.editor().frame_to_pixel (region_frames);
1678 double const height = midi_stream_view()->contents_height();
1680 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1681 new CanvasPatchChange(*this, *_note_group,
1686 _custom_device_mode,
1690 // Show unless patch change is beyond the region bounds
1691 if (region_frames < 0 || region_frames >= _region->length()) {
1692 patch_change->hide();
1694 patch_change->show();
1697 _patch_changes.push_back (patch_change);
1701 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1703 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1704 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1708 if (i != _model->patch_changes().end()) {
1709 key.msb = (*i)->bank_msb ();
1710 key.lsb = (*i)->bank_lsb ();
1711 key.program_number = (*i)->program ();
1713 key.msb = key.lsb = key.program_number = 0;
1716 assert (key.is_sane());
1721 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1723 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1725 if (pc.patch()->program() != new_patch.program_number) {
1726 c->change_program (pc.patch (), new_patch.program_number);
1729 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1730 if (pc.patch()->bank() != new_bank) {
1731 c->change_bank (pc.patch (), new_bank);
1734 _model->apply_command (*trackview.session(), c);
1736 _patch_changes.clear ();
1737 display_patch_changes ();
1741 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1743 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1745 if (old_change->time() != new_change.time()) {
1746 c->change_time (old_change, new_change.time());
1749 if (old_change->channel() != new_change.channel()) {
1750 c->change_channel (old_change, new_change.channel());
1753 if (old_change->program() != new_change.program()) {
1754 c->change_program (old_change, new_change.program());
1757 if (old_change->bank() != new_change.bank()) {
1758 c->change_bank (old_change, new_change.bank());
1761 _model->apply_command (*trackview.session(), c);
1763 _patch_changes.clear ();
1764 display_patch_changes ();
1767 /** Add a patch change to the region.
1768 * @param t Time in frames relative to region position
1769 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1770 * MidiTimeAxisView::get_channel_for_add())
1773 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1775 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1777 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1778 c->add (MidiModel::PatchChangePtr (
1779 new Evoral::PatchChange<Evoral::MusicalTime> (
1780 absolute_frames_to_source_beats (_region->position() + t),
1781 mtv->get_channel_for_add(), patch.program(), patch.bank()
1786 _model->apply_command (*trackview.session(), c);
1788 _patch_changes.clear ();
1789 display_patch_changes ();
1793 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1795 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1796 c->change_time (pc.patch (), t);
1797 _model->apply_command (*trackview.session(), c);
1799 _patch_changes.clear ();
1800 display_patch_changes ();
1804 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1806 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1807 c->remove (pc->patch ());
1808 _model->apply_command (*trackview.session(), c);
1810 _patch_changes.clear ();
1811 display_patch_changes ();
1815 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1817 if (patch.patch()->program() < 127) {
1818 MIDI::Name::PatchPrimaryKey key;
1819 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1820 key.program_number++;
1821 change_patch_change (patch, key);
1826 MidiRegionView::next_patch (CanvasPatchChange& patch)
1828 if (patch.patch()->program() > 0) {
1829 MIDI::Name::PatchPrimaryKey key;
1830 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1831 key.program_number--;
1832 change_patch_change (patch, key);
1837 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1839 if (patch.patch()->program() < 127) {
1840 MIDI::Name::PatchPrimaryKey key;
1841 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1844 change_patch_change (patch, key);
1849 change_patch_change (patch, key);
1856 MidiRegionView::next_bank (CanvasPatchChange& patch)
1858 if (patch.patch()->program() > 0) {
1859 MIDI::Name::PatchPrimaryKey key;
1860 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1861 if (key.lsb < 127) {
1863 change_patch_change (patch, key);
1865 if (key.msb < 127) {
1868 change_patch_change (patch, key);
1875 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1877 if (_selection.empty()) {
1881 _selection.erase (cne);
1885 MidiRegionView::delete_selection()
1887 if (_selection.empty()) {
1891 start_note_diff_command (_("delete selection"));
1893 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1894 if ((*i)->selected()) {
1895 _note_diff_command->remove((*i)->note());
1905 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1907 start_note_diff_command (_("delete note"));
1908 _note_diff_command->remove (n);
1911 trackview.editor().verbose_cursor()->hide ();
1915 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1917 bool changed = false;
1919 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1921 Selection::iterator tmp = i;
1924 (*i)->set_selected (false);
1925 (*i)->hide_velocity ();
1926 _selection.erase (i);
1935 /* this does not change the status of this regionview w.r.t the editor
1939 if (changed && signal) {
1940 SelectionCleared (this); /* EMIT SIGNAL */
1945 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1947 clear_selection_except (ev);
1949 /* don't bother with checking to see if we should remove this
1950 regionview from the editor selection, since we're about to add
1951 another note, and thus put/keep this regionview in the editor
1955 if (!ev->selected()) {
1956 add_to_selection (ev);
1961 MidiRegionView::select_all_notes ()
1965 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1966 add_to_selection (*i);
1971 MidiRegionView::select_range (framepos_t start, framepos_t end)
1975 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1976 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
1977 if (t >= start && t <= end) {
1978 add_to_selection (*i);
1984 MidiRegionView::invert_selection ()
1986 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1987 if ((*i)->selected()) {
1988 remove_from_selection(*i);
1990 add_to_selection (*i);
1996 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1998 uint8_t low_note = 127;
1999 uint8_t high_note = 0;
2000 MidiModel::Notes& notes (_model->notes());
2001 _optimization_iterator = _events.begin();
2007 if (extend && _selection.empty()) {
2013 /* scan existing selection to get note range */
2015 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2016 if ((*i)->note()->note() < low_note) {
2017 low_note = (*i)->note()->note();
2019 if ((*i)->note()->note() > high_note) {
2020 high_note = (*i)->note()->note();
2024 low_note = min (low_note, notenum);
2025 high_note = max (high_note, notenum);
2028 _no_sound_notes = true;
2030 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2032 boost::shared_ptr<NoteType> note (*n);
2033 CanvasNoteEvent* cne;
2034 bool select = false;
2036 if (((1 << note->channel()) & channel_mask) != 0) {
2038 if ((note->note() >= low_note && note->note() <= high_note)) {
2041 } else if (note->note() == notenum) {
2047 if ((cne = find_canvas_note (note)) != 0) {
2048 // extend is false because we've taken care of it,
2049 // since it extends by time range, not pitch.
2050 note_selected (cne, add, false);
2054 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2058 _no_sound_notes = false;
2062 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2064 MidiModel::Notes& notes (_model->notes());
2065 _optimization_iterator = _events.begin();
2067 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2069 boost::shared_ptr<NoteType> note (*n);
2070 CanvasNoteEvent* cne;
2072 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2073 if ((cne = find_canvas_note (note)) != 0) {
2074 if (cne->selected()) {
2075 note_deselected (cne);
2077 note_selected (cne, true, false);
2085 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2088 clear_selection_except (ev);
2089 if (!_selection.empty()) {
2090 PublicEditor& editor (trackview.editor());
2091 editor.get_selection().add (this);
2097 if (!ev->selected()) {
2098 add_to_selection (ev);
2102 /* find end of latest note selected, select all between that and the start of "ev" */
2104 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2105 Evoral::MusicalTime latest = 0;
2107 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2108 if ((*i)->note()->end_time() > latest) {
2109 latest = (*i)->note()->end_time();
2111 if ((*i)->note()->time() < earliest) {
2112 earliest = (*i)->note()->time();
2116 if (ev->note()->end_time() > latest) {
2117 latest = ev->note()->end_time();
2120 if (ev->note()->time() < earliest) {
2121 earliest = ev->note()->time();
2124 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2126 /* find notes entirely within OR spanning the earliest..latest range */
2128 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2129 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2130 add_to_selection (*i);
2138 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2140 remove_from_selection (ev);
2144 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2154 // TODO: Make this faster by storing the last updated selection rect, and only
2155 // adjusting things that are in the area that appears/disappeared.
2156 // We probably need a tree to be able to find events in O(log(n)) time.
2158 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2160 /* check if any corner of the note is inside the rect
2163 1) this is computing "touched by", not "contained by" the rect.
2164 2) this does not require that events be sorted in time.
2167 const double ix1 = (*i)->x1();
2168 const double ix2 = (*i)->x2();
2169 const double iy1 = (*i)->y1();
2170 const double iy2 = (*i)->y2();
2172 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2173 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2174 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2175 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2178 if (!(*i)->selected()) {
2179 add_to_selection (*i);
2181 } else if ((*i)->selected() && !extend) {
2182 // Not inside rectangle
2183 remove_from_selection (*i);
2189 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2191 Selection::iterator i = _selection.find (ev);
2193 if (i != _selection.end()) {
2194 _selection.erase (i);
2197 ev->set_selected (false);
2198 ev->hide_velocity ();
2200 if (_selection.empty()) {
2201 PublicEditor& editor (trackview.editor());
2202 editor.get_selection().remove (this);
2207 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2209 bool add_mrv_selection = false;
2211 if (_selection.empty()) {
2212 add_mrv_selection = true;
2215 if (_selection.insert (ev).second) {
2216 ev->set_selected (true);
2217 play_midi_note ((ev)->note());
2220 if (add_mrv_selection) {
2221 PublicEditor& editor (trackview.editor());
2222 editor.get_selection().add (this);
2227 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2229 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2230 PossibleChord to_play;
2231 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2233 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2234 if ((*i)->note()->time() < earliest) {
2235 earliest = (*i)->note()->time();
2239 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2240 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2241 to_play.push_back ((*i)->note());
2243 (*i)->move_event(dx, dy);
2246 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2248 if (to_play.size() > 1) {
2250 PossibleChord shifted;
2252 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2253 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2254 moved_note->set_note (moved_note->note() + cumulative_dy);
2255 shifted.push_back (moved_note);
2258 play_midi_chord (shifted);
2260 } else if (!to_play.empty()) {
2262 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2263 moved_note->set_note (moved_note->note() + cumulative_dy);
2264 play_midi_note (moved_note);
2270 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2272 assert (!_selection.empty());
2274 uint8_t lowest_note_in_selection = 127;
2275 uint8_t highest_note_in_selection = 0;
2276 uint8_t highest_note_difference = 0;
2278 // find highest and lowest notes first
2280 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2281 uint8_t pitch = (*i)->note()->note();
2282 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2283 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2287 cerr << "dnote: " << (int) dnote << endl;
2288 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2289 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2290 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2291 << int(highest_note_in_selection) << endl;
2292 cerr << "selection size: " << _selection.size() << endl;
2293 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2296 // Make sure the note pitch does not exceed the MIDI standard range
2297 if (highest_note_in_selection + dnote > 127) {
2298 highest_note_difference = highest_note_in_selection - 127;
2301 start_note_diff_command (_("move notes"));
2303 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2305 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2306 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2312 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2314 uint8_t original_pitch = (*i)->note()->note();
2315 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2317 // keep notes in standard midi range
2318 clamp_to_0_127(new_pitch);
2320 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2321 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2323 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2328 // care about notes being moved beyond the upper/lower bounds on the canvas
2329 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2330 highest_note_in_selection > midi_stream_view()->highest_note()) {
2331 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2335 /** @param x Pixel relative to the region position.
2336 * @return Snapped frame relative to the region position.
2339 MidiRegionView::snap_pixel_to_frame(double x)
2341 PublicEditor& editor (trackview.editor());
2342 return snap_frame_to_frame (editor.pixel_to_frame (x));
2345 /** @param x Pixel relative to the region position.
2346 * @return Snapped pixel relative to the region position.
2349 MidiRegionView::snap_to_pixel(double x)
2351 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2355 MidiRegionView::get_position_pixels()
2357 framepos_t region_frame = get_position();
2358 return trackview.editor().frame_to_pixel(region_frame);
2362 MidiRegionView::get_end_position_pixels()
2364 framepos_t frame = get_position() + get_duration ();
2365 return trackview.editor().frame_to_pixel(frame);
2369 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2371 /* the time converter will return the frame corresponding to `beats'
2372 relative to the start of the source. The start of the source
2373 is an implied position given by region->position - region->start
2375 const framepos_t source_start = _region->position() - _region->start();
2376 return source_start + _source_relative_time_converter.to (beats);
2380 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2382 /* the `frames' argument needs to be converted into a frame count
2383 relative to the start of the source before being passed in to the
2386 const framepos_t source_start = _region->position() - _region->start();
2387 return _source_relative_time_converter.from (frames - source_start);
2391 MidiRegionView::region_beats_to_region_frames(double beats) const
2393 return _region_relative_time_converter.to(beats);
2397 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2399 return _region_relative_time_converter.from(frames);
2403 MidiRegionView::begin_resizing (bool /*at_front*/)
2405 _resize_data.clear();
2407 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2408 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2410 // only insert CanvasNotes into the map
2412 NoteResizeData *resize_data = new NoteResizeData();
2413 resize_data->canvas_note = note;
2415 // create a new SimpleRect from the note which will be the resize preview
2416 SimpleRect *resize_rect = new SimpleRect(
2417 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2419 // calculate the colors: get the color settings
2420 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2421 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2424 // make the resize preview notes more transparent and bright
2425 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2427 // calculate color based on note velocity
2428 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2429 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2433 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2434 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2436 resize_data->resize_rect = resize_rect;
2437 _resize_data.push_back(resize_data);
2442 /** Update resizing notes while user drags.
2443 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2444 * @param at_front which end of the note (true == note on, false == note off)
2445 * @param delta_x change in mouse position since the start of the drag
2446 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2447 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2448 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2449 * as the \a primary note.
2452 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2454 bool cursor_set = false;
2456 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2457 SimpleRect* resize_rect = (*i)->resize_rect;
2458 CanvasNote* canvas_note = (*i)->canvas_note;
2463 current_x = canvas_note->x1() + delta_x;
2465 current_x = primary->x1() + delta_x;
2469 current_x = canvas_note->x2() + delta_x;
2471 current_x = primary->x2() + delta_x;
2476 resize_rect->property_x1() = snap_to_pixel(current_x);
2477 resize_rect->property_x2() = canvas_note->x2();
2479 resize_rect->property_x2() = snap_to_pixel(current_x);
2480 resize_rect->property_x1() = canvas_note->x1();
2486 beats = snap_pixel_to_frame (current_x);
2487 beats = region_frames_to_region_beats (beats);
2492 if (beats < canvas_note->note()->end_time()) {
2493 len = canvas_note->note()->time() - beats;
2494 len += canvas_note->note()->length();
2499 if (beats >= canvas_note->note()->time()) {
2500 len = beats - canvas_note->note()->time();
2507 snprintf (buf, sizeof (buf), "%.3g beats", len);
2508 show_verbose_cursor (buf, 0, 0);
2517 /** Finish resizing notes when the user releases the mouse button.
2518 * Parameters the same as for \a update_resizing().
2521 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2523 start_note_diff_command (_("resize notes"));
2525 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2526 CanvasNote* canvas_note = (*i)->canvas_note;
2527 SimpleRect* resize_rect = (*i)->resize_rect;
2529 /* Get the new x position for this resize, which is in pixels relative
2530 * to the region position.
2537 current_x = canvas_note->x1() + delta_x;
2539 current_x = primary->x1() + delta_x;
2543 current_x = canvas_note->x2() + delta_x;
2545 current_x = primary->x2() + delta_x;
2549 /* Convert that to a frame within the source */
2550 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2552 /* and then to beats */
2553 current_x = region_frames_to_region_beats (current_x);
2555 if (at_front && current_x < canvas_note->note()->end_time()) {
2556 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2558 double len = canvas_note->note()->time() - current_x;
2559 len += canvas_note->note()->length();
2562 /* XXX convert to beats */
2563 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2568 double len = current_x - canvas_note->note()->time();
2571 /* XXX convert to beats */
2572 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2580 _resize_data.clear();
2585 MidiRegionView::abort_resizing ()
2587 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2588 delete (*i)->resize_rect;
2592 _resize_data.clear ();
2596 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2598 uint8_t new_velocity;
2601 new_velocity = event->note()->velocity() + velocity;
2602 clamp_to_0_127(new_velocity);
2604 new_velocity = velocity;
2607 event->set_selected (event->selected()); // change color
2609 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2613 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2618 new_note = event->note()->note() + note;
2623 clamp_to_0_127 (new_note);
2624 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2628 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2630 bool change_start = false;
2631 bool change_length = false;
2632 Evoral::MusicalTime new_start = 0;
2633 Evoral::MusicalTime new_length = 0;
2635 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2637 front_delta: if positive - move the start of the note later in time (shortening it)
2638 if negative - move the start of the note earlier in time (lengthening it)
2640 end_delta: if positive - move the end of the note later in time (lengthening it)
2641 if negative - move the end of the note earlier in time (shortening it)
2645 if (front_delta < 0) {
2647 if (event->note()->time() < -front_delta) {
2650 new_start = event->note()->time() + front_delta; // moves earlier
2653 /* start moved toward zero, so move the end point out to where it used to be.
2654 Note that front_delta is negative, so this increases the length.
2657 new_length = event->note()->length() - front_delta;
2658 change_start = true;
2659 change_length = true;
2663 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2665 if (new_pos < event->note()->end_time()) {
2666 new_start = event->note()->time() + front_delta;
2667 /* start moved toward the end, so move the end point back to where it used to be */
2668 new_length = event->note()->length() - front_delta;
2669 change_start = true;
2670 change_length = true;
2677 bool can_change = true;
2678 if (end_delta < 0) {
2679 if (event->note()->length() < -end_delta) {
2685 new_length = event->note()->length() + end_delta;
2686 change_length = true;
2691 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2694 if (change_length) {
2695 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2700 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2702 uint8_t new_channel;
2706 if (event->note()->channel() < -chn) {
2709 new_channel = event->note()->channel() + chn;
2712 new_channel = event->note()->channel() + chn;
2715 new_channel = (uint8_t) chn;
2718 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2722 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2724 Evoral::MusicalTime new_time;
2728 if (event->note()->time() < -delta) {
2731 new_time = event->note()->time() + delta;
2734 new_time = event->note()->time() + delta;
2740 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2744 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2746 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2750 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2754 if (_selection.empty()) {
2769 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2770 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2776 start_note_diff_command (_("change velocities"));
2778 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2779 Selection::iterator next = i;
2781 change_note_velocity (*i, delta, true);
2787 if (!_selection.empty()) {
2789 snprintf (buf, sizeof (buf), "Vel %d",
2790 (int) (*_selection.begin())->note()->velocity());
2791 show_verbose_cursor (buf, 10, 10);
2797 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2799 if (_selection.empty()) {
2816 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2818 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2822 if ((int8_t) (*i)->note()->note() + delta > 127) {
2829 start_note_diff_command (_("transpose"));
2831 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2832 Selection::iterator next = i;
2834 change_note_note (*i, delta, true);
2842 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2848 /* grab the current grid distance */
2850 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2852 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2853 cerr << "Grid type not available as beats - TO BE FIXED\n";
2863 start_note_diff_command (_("change note lengths"));
2865 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2866 Selection::iterator next = i;
2869 /* note the negation of the delta for start */
2871 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2880 MidiRegionView::nudge_notes (bool forward)
2882 if (_selection.empty()) {
2886 /* pick a note as the point along the timeline to get the nudge distance.
2887 its not necessarily the earliest note, so we may want to pull the notes out
2888 into a vector and sort before using the first one.
2891 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2893 framepos_t distance;
2895 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2897 /* grid is off - use nudge distance */
2899 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2905 framepos_t next_pos = ref_point;
2908 if (max_framepos - 1 < next_pos) {
2912 if (next_pos == 0) {
2918 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2919 distance = ref_point - next_pos;
2922 if (distance == 0) {
2926 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2932 start_note_diff_command (_("nudge"));
2934 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2935 Selection::iterator next = i;
2937 change_note_time (*i, delta, true);
2945 MidiRegionView::change_channel(uint8_t channel)
2947 start_note_diff_command(_("change channel"));
2948 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2949 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2957 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2959 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2961 _pre_enter_cursor = editor->get_canvas_cursor ();
2963 if (_mouse_state == SelectTouchDragging) {
2964 note_selected (ev, true);
2967 show_verbose_cursor (ev->note ());
2971 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2973 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2975 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2976 (*i)->hide_velocity ();
2979 editor->verbose_cursor()->hide ();
2981 if (_pre_enter_cursor) {
2982 editor->set_canvas_cursor (_pre_enter_cursor);
2983 _pre_enter_cursor = 0;
2988 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2991 /* XXX should get patch name if we can */
2992 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
2993 show_verbose_cursor (s.str(), 10, 20);
2997 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
2999 trackview.editor().verbose_cursor()->hide ();
3003 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3005 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3007 if (x_fraction > 0.0 && x_fraction < 0.25) {
3008 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3009 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3010 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3012 if (_pre_enter_cursor && can_set_cursor) {
3013 editor->set_canvas_cursor (_pre_enter_cursor);
3019 MidiRegionView::set_frame_color()
3023 TimeAxisViewItem::set_frame_color ();
3030 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3031 } else if (high_enough_for_name) {
3032 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3037 if (!rect_visible) {
3038 f = UINT_RGBA_CHANGE_A (f, 0);
3041 frame->property_fill_color_rgba() = f;
3045 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3049 case FilterChannels:
3050 _force_channel = -1;
3053 _force_channel = mask;
3054 mask = 0xFFFF; // Show all notes as active (below)
3057 // Update notes for selection
3058 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3059 (*i)->on_channel_selection_change(mask);
3062 _last_channel_selection = mask;
3064 _patch_changes.clear ();
3065 display_patch_changes ();
3069 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3071 _model_name = model;
3072 _custom_device_mode = custom_device_mode;
3077 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3079 if (_selection.empty()) {
3083 PublicEditor& editor (trackview.editor());
3087 /* XXX what to do ? */
3091 editor.get_cut_buffer().add (selection_as_cut_buffer());
3099 start_note_diff_command();
3101 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3108 note_diff_remove_note (*i);
3118 MidiRegionView::selection_as_cut_buffer () const
3122 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3123 NoteType* n = (*i)->note().get();
3124 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3127 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3133 /** This method handles undo */
3135 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3141 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3143 trackview.session()->begin_reversible_command (_("paste"));
3145 start_note_diff_command (_("paste"));
3147 Evoral::MusicalTime beat_delta;
3148 Evoral::MusicalTime paste_pos_beats;
3149 Evoral::MusicalTime duration;
3150 Evoral::MusicalTime end_point = 0;
3152 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3153 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3154 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3155 paste_pos_beats = 0;
3157 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n",
3158 (*mcb.notes().begin())->time(),
3159 (*mcb.notes().rbegin())->end_time(),
3160 duration, pos, _region->position(),
3161 paste_pos_beats, beat_delta));
3165 for (int n = 0; n < (int) times; ++n) {
3167 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3169 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3170 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3172 /* make all newly added notes selected */
3174 note_diff_add_note (copied_note, true);
3175 end_point = copied_note->end_time();
3178 paste_pos_beats += duration;
3181 /* if we pasted past the current end of the region, extend the region */
3183 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3184 framepos_t region_end = _region->position() + _region->length() - 1;
3186 if (end_frame > region_end) {
3188 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3190 _region->clear_changes ();
3191 _region->set_length (end_frame);
3192 trackview.session()->add_command (new StatefulDiffCommand (_region));
3197 trackview.session()->commit_reversible_command ();
3200 struct EventNoteTimeEarlyFirstComparator {
3201 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3202 return a->note()->time() < b->note()->time();
3207 MidiRegionView::time_sort_events ()
3209 if (!_sort_needed) {
3213 EventNoteTimeEarlyFirstComparator cmp;
3216 _sort_needed = false;
3220 MidiRegionView::goto_next_note (bool add_to_selection)
3222 bool use_next = false;
3224 if (_events.back()->selected()) {
3228 time_sort_events ();
3230 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3231 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3233 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3234 if ((*i)->selected()) {
3237 } else if (use_next) {
3238 if (channel_mask & (1 << (*i)->note()->channel())) {
3239 if (!add_to_selection) {
3242 note_selected (*i, true, false);
3249 /* use the first one */
3251 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3252 unique_select (_events.front());
3257 MidiRegionView::goto_previous_note (bool add_to_selection)
3259 bool use_next = false;
3261 if (_events.front()->selected()) {
3265 time_sort_events ();
3267 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3268 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3270 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3271 if ((*i)->selected()) {
3274 } else if (use_next) {
3275 if (channel_mask & (1 << (*i)->note()->channel())) {
3276 if (!add_to_selection) {
3279 note_selected (*i, true, false);
3286 /* use the last one */
3288 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3289 unique_select (*(_events.rbegin()));
3294 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3296 bool had_selected = false;
3298 time_sort_events ();
3300 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3301 if ((*i)->selected()) {
3302 selected.insert ((*i)->note());
3303 had_selected = true;
3307 if (allow_all_if_none_selected && !had_selected) {
3308 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3309 selected.insert ((*i)->note());
3315 MidiRegionView::update_ghost_note (double x, double y)
3317 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3322 _note_group->w2i (x, y);
3324 PublicEditor& editor = trackview.editor ();
3326 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3327 framecnt_t grid_frames;
3328 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3330 /* use region_frames... because we are converting a delta within the region
3334 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3340 /* note that this sets the time of the ghost note in beats relative to
3341 the start of the source; that is how all note times are stored.
3343 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3344 _ghost_note->note()->set_length (length);
3345 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3346 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3348 /* the ghost note does not appear in ghost regions, so pass false in here */
3349 update_note (_ghost_note, false);
3351 show_verbose_cursor (_ghost_note->note ());
3355 MidiRegionView::create_ghost_note (double x, double y)
3357 remove_ghost_note ();
3359 boost::shared_ptr<NoteType> g (new NoteType);
3360 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3361 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3362 update_ghost_note (x, y);
3363 _ghost_note->show ();
3368 show_verbose_cursor (_ghost_note->note ());
3372 MidiRegionView::snap_changed ()
3378 create_ghost_note (_last_ghost_x, _last_ghost_y);
3382 MidiRegionView::drop_down_keys ()
3384 _mouse_state = None;
3388 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3390 double note = midi_stream_view()->y_to_note(y);
3392 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3394 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3396 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3397 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3398 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3399 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3404 bool add_mrv_selection = false;
3406 if (_selection.empty()) {
3407 add_mrv_selection = true;
3410 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3411 if (_selection.insert (*i).second) {
3412 (*i)->set_selected (true);
3416 if (add_mrv_selection) {
3417 PublicEditor& editor (trackview.editor());
3418 editor.get_selection().add (this);
3423 MidiRegionView::color_handler ()
3425 RegionView::color_handler ();
3427 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3428 (*i)->set_selected ((*i)->selected()); // will change color
3431 /* XXX probably more to do here */
3435 MidiRegionView::enable_display (bool yn)
3437 RegionView::enable_display (yn);
3444 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3446 if (_step_edit_cursor == 0) {
3447 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3449 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3450 _step_edit_cursor->property_y1() = 0;
3451 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3452 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3453 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3456 move_step_edit_cursor (pos);
3457 _step_edit_cursor->show ();
3461 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3463 _step_edit_cursor_position = pos;
3465 if (_step_edit_cursor) {
3466 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3467 _step_edit_cursor->property_x1() = pixel;
3468 set_step_edit_cursor_width (_step_edit_cursor_width);
3473 MidiRegionView::hide_step_edit_cursor ()
3475 if (_step_edit_cursor) {
3476 _step_edit_cursor->hide ();
3481 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3483 _step_edit_cursor_width = beats;
3485 if (_step_edit_cursor) {
3486 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3490 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3491 * @param w Source that the data will end up in.
3494 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3496 if (!_active_notes) {
3497 /* we aren't actively being recorded to */
3501 boost::shared_ptr<MidiSource> src = w.lock ();
3502 if (!src || src != midi_region()->midi_source()) {
3503 /* recorded data was not destined for our source */
3507 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3509 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3511 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3513 framepos_t back = max_framepos;
3515 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3516 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3517 assert (ev.buffer ());
3519 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3520 frames from the start of the source, and so time_beats is in terms of the
3524 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3526 if (ev.type() == MIDI_CMD_NOTE_ON) {
3528 boost::shared_ptr<NoteType> note (
3529 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3532 add_note (note, true);
3534 /* fix up our note range */
3535 if (ev.note() < _current_range_min) {
3536 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3537 } else if (ev.note() > _current_range_max) {
3538 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3541 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3542 resolve_note (ev.note (), time_beats);
3548 midi_stream_view()->check_record_layers (region(), back);
3552 MidiRegionView::trim_front_starting ()
3554 /* Reparent the note group to the region view's parent, so that it doesn't change
3555 when the region view is trimmed.
3557 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3558 _temporary_note_group->move (group->property_x(), group->property_y());
3559 _note_group->reparent (*_temporary_note_group);
3563 MidiRegionView::trim_front_ending ()
3565 _note_group->reparent (*group);
3566 delete _temporary_note_group;
3567 _temporary_note_group = 0;
3569 if (_region->start() < 0) {
3570 /* Trim drag made start time -ve; fix this */
3571 midi_region()->fix_negative_start ();
3576 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3578 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3579 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3583 change_patch_change (pc->patch(), d.patch ());
3588 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3591 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3592 Evoral::midi_note_name (n->note()).c_str(),
3594 (int) n->channel() + 1,
3595 (int) n->velocity());
3597 show_verbose_cursor (buf, 10, 20);
3601 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3605 trackview.editor().get_pointer_position (wx, wy);
3610 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3612 double x1, y1, x2, y2;
3613 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3615 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3616 wy -= (y2 - y1) + 2 * yoffset;
3619 trackview.editor().verbose_cursor()->set (text, wx, wy);
3620 trackview.editor().verbose_cursor()->show ();
3623 /** @param p A session framepos.
3624 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3625 * @return p snapped to the grid subdivision underneath it.
3628 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3630 PublicEditor& editor = trackview.editor ();
3633 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3639 grid_frames = region_beats_to_region_frames (grid_beats);
3641 /* Hack so that we always snap to the note that we are over, instead of snapping
3642 to the next one if we're more than halfway through the one we're over.
3644 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3645 p -= grid_frames / 2;
3648 return snap_frame_to_frame (p);
3651 /** Called when the selection has been cleared in any MidiRegionView.
3652 * @param rv MidiRegionView that the selection was cleared in.
3655 MidiRegionView::selection_cleared (MidiRegionView* rv)
3661 /* Clear our selection in sympathy; but don't signal the fact */
3662 clear_selection (false);