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/midi_region.h"
35 #include "ardour/midi_source.h"
36 #include "ardour/midi_model.h"
37 #include "ardour/midi_patch_manager.h"
38 #include "ardour/session.h"
40 #include "evoral/Parameter.hpp"
41 #include "evoral/MIDIParameters.hpp"
42 #include "evoral/MIDIEvent.hpp"
43 #include "evoral/Control.hpp"
44 #include "evoral/midi_util.h"
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas_patch_change.h"
53 #include "editor_drag.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
57 #include "midi_channel_dialog.h"
58 #include "midi_cut_buffer.h"
59 #include "midi_list_editor.h"
60 #include "midi_region_view.h"
61 #include "midi_streamview.h"
62 #include "midi_time_axis.h"
63 #include "midi_util.h"
64 #include "midi_velocity_dialog.h"
65 #include "mouse_cursors.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 "patch_change_dialog.h"
74 #include "verbose_cursor.h"
78 using namespace ARDOUR;
80 using namespace Editing;
81 using namespace ArdourCanvas;
82 using Gtkmm2ext::Keyboard;
84 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
86 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
88 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
89 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
90 : RegionView (parent, tv, r, spu, basic_color)
91 , _last_channel_selection(0xFFFF)
92 , _current_range_min(0)
93 , _current_range_max(0)
95 , _note_group(new ArdourCanvas::Group(*group))
96 , _note_diff_command (0)
98 , _step_edit_cursor (0)
99 , _step_edit_cursor_width (1.0)
100 , _step_edit_cursor_position (0.0)
101 , _channel_selection_scoped_note (0)
102 , _temporary_note_group (0)
105 , _sort_needed (true)
106 , _optimization_iterator (_events.end())
108 , _no_sound_notes (false)
111 , pre_enter_cursor (0)
112 , pre_press_cursor (0)
114 _note_group->raise_to_top();
115 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
117 /* Look up MIDNAM details from our MidiTimeAxisView */
118 MidiTimeAxisView& mtv = dynamic_cast<MidiTimeAxisView&> (tv);
119 midi_patch_settings_changed (mtv.midi_patch_model (), mtv.midi_patch_custom_device_node ());
121 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
122 connect_to_diskstream ();
124 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
127 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
128 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
129 TimeAxisViewItem::Visibility visibility)
130 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
131 , _last_channel_selection(0xFFFF)
132 , _current_range_min(0)
133 , _current_range_max(0)
135 , _note_group(new ArdourCanvas::Group(*parent))
136 , _note_diff_command (0)
138 , _step_edit_cursor (0)
139 , _step_edit_cursor_width (1.0)
140 , _step_edit_cursor_position (0.0)
141 , _channel_selection_scoped_note (0)
142 , _temporary_note_group (0)
145 , _sort_needed (true)
146 , _optimization_iterator (_events.end())
148 , _no_sound_notes (false)
151 , pre_enter_cursor (0)
152 , pre_press_cursor (0)
154 _note_group->raise_to_top();
155 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
157 /* Look up MIDNAM details from our MidiTimeAxisView */
158 MidiTimeAxisView& mtv = dynamic_cast<MidiTimeAxisView&> (tv);
159 midi_patch_settings_changed (mtv.midi_patch_model (), mtv.midi_patch_custom_device_node ());
161 connect_to_diskstream ();
163 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
167 MidiRegionView::parameter_changed (std::string const & p)
169 if (p == "diplay-first-midi-bank-as-zero") {
170 if (_enable_display) {
176 MidiRegionView::MidiRegionView (const MidiRegionView& other)
177 : sigc::trackable(other)
179 , _last_channel_selection(0xFFFF)
180 , _current_range_min(0)
181 , _current_range_max(0)
183 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
184 , _note_diff_command (0)
186 , _step_edit_cursor (0)
187 , _step_edit_cursor_width (1.0)
188 , _step_edit_cursor_position (0.0)
189 , _channel_selection_scoped_note (0)
190 , _temporary_note_group (0)
193 , _sort_needed (true)
194 , _optimization_iterator (_events.end())
196 , _no_sound_notes (false)
199 , pre_enter_cursor (0)
200 , pre_press_cursor (0)
205 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
206 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
211 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
212 : RegionView (other, boost::shared_ptr<Region> (region))
213 , _last_channel_selection(0xFFFF)
214 , _current_range_min(0)
215 , _current_range_max(0)
217 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
218 , _note_diff_command (0)
220 , _step_edit_cursor (0)
221 , _step_edit_cursor_width (1.0)
222 , _step_edit_cursor_position (0.0)
223 , _channel_selection_scoped_note (0)
224 , _temporary_note_group (0)
227 , _sort_needed (true)
228 , _optimization_iterator (_events.end())
230 , _no_sound_notes (false)
233 , pre_enter_cursor (0)
234 , pre_press_cursor (0)
239 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
240 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
246 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
248 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
250 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
251 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
255 midi_region()->midi_source(0)->load_model();
258 _model = midi_region()->midi_source(0)->model();
259 _enable_display = false;
261 RegionView::init (basic_color, false);
263 compute_colors (basic_color);
265 set_height (trackview.current_height());
268 region_sync_changed ();
269 region_resized (ARDOUR::bounds_change);
272 reset_width_dependent_items (_pixel_width);
276 _enable_display = true;
279 display_model (_model);
283 group->raise_to_top();
284 group->signal_event().connect(
285 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
287 midi_view()->signal_channel_mode_changed().connect(
288 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
290 midi_view()->signal_midi_patch_settings_changed().connect(
291 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
293 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
294 boost::bind (&MidiRegionView::snap_changed, this),
297 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
298 connect_to_diskstream ();
300 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
303 const boost::shared_ptr<ARDOUR::MidiRegion>
304 MidiRegionView::midi_region() const
306 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
310 MidiRegionView::connect_to_diskstream ()
312 midi_view()->midi_track()->DataRecorded.connect(
313 *this, invalidator(*this),
314 boost::bind (&MidiRegionView::data_recorded, this, _1),
319 MidiRegionView::canvas_event(GdkEvent* ev)
322 case GDK_ENTER_NOTIFY:
323 case GDK_LEAVE_NOTIFY:
324 _last_event_x = ev->crossing.x;
325 _last_event_y = ev->crossing.y;
327 case GDK_MOTION_NOTIFY:
328 _last_event_x = ev->motion.x;
329 _last_event_y = ev->motion.y;
335 if (ev->type == GDK_2BUTTON_PRESS) {
336 return trackview.editor().toggle_internal_editing_from_double_click (ev);
339 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
340 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
341 (trackview.editor().current_mouse_mode() == MouseZoom)) {
342 // handle non-draw modes elsewhere
348 return scroll (&ev->scroll);
351 return key_press (&ev->key);
353 case GDK_KEY_RELEASE:
354 return key_release (&ev->key);
356 case GDK_BUTTON_PRESS:
357 return button_press (&ev->button);
359 case GDK_BUTTON_RELEASE:
360 return button_release (&ev->button);
362 case GDK_ENTER_NOTIFY:
363 return enter_notify (&ev->crossing);
365 case GDK_LEAVE_NOTIFY:
366 return leave_notify (&ev->crossing);
368 case GDK_MOTION_NOTIFY:
369 return motion (&ev->motion);
379 MidiRegionView::remove_ghost_note ()
386 MidiRegionView::enter_notify (GdkEventCrossing* ev)
388 trackview.editor().MouseModeChanged.connect (
389 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
392 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
393 create_ghost_note (ev->x, ev->y);
396 if (!trackview.editor().internal_editing()) {
397 Keyboard::magic_widget_drop_focus();
399 Keyboard::magic_widget_grab_focus();
403 // if current operation is non-operational in a midi region, change the cursor to so indicate
404 if (trackview.editor().current_mouse_mode() == MouseGain) {
405 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
406 pre_enter_cursor = editor->get_canvas_cursor();
407 editor->set_canvas_cursor(editor->cursors()->timebar);
414 MidiRegionView::leave_notify (GdkEventCrossing*)
416 _mouse_mode_connection.disconnect ();
418 trackview.editor().verbose_cursor()->hide ();
419 remove_ghost_note ();
421 if (pre_enter_cursor) {
422 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
423 editor->set_canvas_cursor(pre_enter_cursor);
430 MidiRegionView::mouse_mode_changed ()
432 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
433 create_ghost_note (_last_event_x, _last_event_y);
435 remove_ghost_note ();
436 trackview.editor().verbose_cursor()->hide ();
439 if (!trackview.editor().internal_editing()) {
440 Keyboard::magic_widget_drop_focus();
442 Keyboard::magic_widget_grab_focus();
448 MidiRegionView::button_press (GdkEventButton* ev)
450 if (ev->button != 1) {
454 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
455 MouseMode m = editor->current_mouse_mode();
457 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
458 pre_press_cursor = editor->get_canvas_cursor ();
459 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
462 if (_mouse_state != SelectTouchDragging) {
464 _pressed_button = ev->button;
465 _mouse_state = Pressed;
470 _pressed_button = ev->button;
476 MidiRegionView::button_release (GdkEventButton* ev)
478 double event_x, event_y;
480 if (ev->button != 1) {
487 group->w2i(event_x, event_y);
488 group->ungrab(ev->time);
490 PublicEditor& editor = trackview.editor ();
492 if (pre_press_cursor) {
493 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
494 pre_press_cursor = 0;
497 switch (_mouse_state) {
498 case Pressed: // Clicked
500 switch (editor.current_mouse_mode()) {
502 /* no motion occured - simple click */
511 if (Keyboard::is_insert_note_event(ev)) {
513 double event_x, event_y;
517 group->w2i(event_x, event_y);
520 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
526 /* Shorten the length by 1 tick so that we can add a new note at the next
527 grid snap without it overlapping this one.
529 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
531 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
539 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
545 /* Shorten the length by 1 tick so that we can add a new note at the next
546 grid snap without it overlapping this one.
548 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
550 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
561 case SelectRectDragging:
563 editor.drags()->end_grab ((GdkEvent *) ev);
565 create_ghost_note (ev->x, ev->y);
577 MidiRegionView::motion (GdkEventMotion* ev)
579 PublicEditor& editor = trackview.editor ();
581 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
582 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
583 _mouse_state != AddDragging) {
585 create_ghost_note (ev->x, ev->y);
587 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
588 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
590 update_ghost_note (ev->x, ev->y);
592 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
594 remove_ghost_note ();
595 editor.verbose_cursor()->hide ();
597 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
599 update_ghost_note (ev->x, ev->y);
602 /* any motion immediately hides velocity text that may have been visible */
604 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
605 (*i)->hide_velocity ();
608 switch (_mouse_state) {
611 if (_pressed_button == 1) {
613 MouseMode m = editor.current_mouse_mode();
615 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
617 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
618 _mouse_state = AddDragging;
619 remove_ghost_note ();
620 editor.verbose_cursor()->hide ();
622 } else if (m == MouseObject) {
623 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
625 _mouse_state = SelectRectDragging;
627 } else if (m == MouseRange) {
628 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
629 _mouse_state = SelectVerticalDragging;
636 case SelectRectDragging:
637 case SelectVerticalDragging:
639 editor.drags()->motion_handler ((GdkEvent *) ev, false);
642 case SelectTouchDragging:
654 MidiRegionView::scroll (GdkEventScroll* ev)
656 if (_selection.empty()) {
660 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
661 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
662 it still works for zoom.
667 trackview.editor().verbose_cursor()->hide ();
669 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
670 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
672 if (ev->direction == GDK_SCROLL_UP) {
673 change_velocities (true, fine, false, together);
674 } else if (ev->direction == GDK_SCROLL_DOWN) {
675 change_velocities (false, fine, false, together);
681 MidiRegionView::key_press (GdkEventKey* ev)
683 /* since GTK bindings are generally activated on press, and since
684 detectable auto-repeat is the name of the game and only sends
685 repeated presses, carry out key actions at key press, not release.
688 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
690 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
691 _mouse_state = SelectTouchDragging;
694 } else if (ev->keyval == GDK_Escape && unmodified) {
698 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
700 bool start = (ev->keyval == GDK_comma);
701 bool end = (ev->keyval == GDK_period);
702 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
703 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
705 change_note_lengths (fine, shorter, 0.0, start, end);
709 } else if (ev->keyval == GDK_Delete && unmodified) {
714 } else if (ev->keyval == GDK_Tab) {
716 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
717 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
719 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
723 } else if (ev->keyval == GDK_ISO_Left_Tab) {
725 /* Shift-TAB generates ISO Left Tab, for some reason */
727 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
728 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
730 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
736 } else if (ev->keyval == GDK_Up) {
738 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
739 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
740 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
742 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
743 change_velocities (true, fine, allow_smush, together);
745 transpose (true, fine, allow_smush);
749 } else if (ev->keyval == GDK_Down) {
751 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
752 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
753 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
755 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
756 change_velocities (false, fine, allow_smush, together);
758 transpose (false, fine, allow_smush);
762 } else if (ev->keyval == GDK_Left && unmodified) {
767 } else if (ev->keyval == GDK_Right && unmodified) {
772 } else if (ev->keyval == GDK_c && unmodified) {
776 } else if (ev->keyval == GDK_v && unmodified) {
785 MidiRegionView::key_release (GdkEventKey* ev)
787 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
795 MidiRegionView::channel_edit ()
797 if (_selection.empty()) {
801 /* pick a note somewhat at random (since Selection is a set<>) to
802 * provide the "current" channel for the dialog.
805 uint8_t current_channel = (*_selection.begin())->note()->channel ();
806 MidiChannelDialog channel_dialog (current_channel);
807 int ret = channel_dialog.run ();
810 case Gtk::RESPONSE_OK:
816 uint8_t new_channel = channel_dialog.active_channel ();
818 start_note_diff_command (_("channel edit"));
820 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
821 Selection::iterator next = i;
823 change_note_channel (*i, new_channel);
831 MidiRegionView::velocity_edit ()
833 if (_selection.empty()) {
837 /* pick a note somewhat at random (since Selection is a set<>) to
838 * provide the "current" velocity for the dialog.
841 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
842 MidiVelocityDialog velocity_dialog (current_velocity);
843 int ret = velocity_dialog.run ();
846 case Gtk::RESPONSE_OK:
852 uint8_t new_velocity = velocity_dialog.velocity ();
854 start_note_diff_command (_("velocity edit"));
856 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
857 Selection::iterator next = i;
859 change_note_velocity (*i, new_velocity, false);
867 MidiRegionView::show_list_editor ()
870 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
872 _list_editor->present ();
875 /** Add a note to the model, and the view, at a canvas (click) coordinate.
876 * \param t time in frames relative to the position of the region
877 * \param y vertical position in pixels
878 * \param length duration of the note in beats
879 * \param snap_t true to snap t to the grid, otherwise false.
882 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
884 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
885 MidiStreamView* const view = mtv->midi_view();
887 double note = view->y_to_note(y);
890 assert(note <= 127.0);
892 // Start of note in frames relative to region start
894 framecnt_t grid_frames;
895 t = snap_frame_to_grid_underneath (t, grid_frames);
899 assert (length != 0);
901 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
902 region_frames_to_region_beats(t + _region->start()),
904 (uint8_t)note, 0x40));
906 if (_model->contains (new_note)) {
910 view->update_note_range(new_note->note());
912 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
914 _model->apply_command(*trackview.session(), cmd);
916 play_midi_note (new_note);
920 MidiRegionView::clear_events()
925 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
926 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
931 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
936 _patch_changes.clear();
938 _optimization_iterator = _events.end();
942 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
946 content_connection.disconnect ();
947 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
951 if (_enable_display) {
957 MidiRegionView::start_note_diff_command (string name)
959 if (!_note_diff_command) {
960 _note_diff_command = _model->new_note_diff_command (name);
965 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
967 if (_note_diff_command) {
968 _note_diff_command->add (note);
971 _marked_for_selection.insert(note);
974 _marked_for_velocity.insert(note);
979 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
981 if (_note_diff_command && ev->note()) {
982 _note_diff_command->remove(ev->note());
987 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
988 MidiModel::NoteDiffCommand::Property property,
991 if (_note_diff_command) {
992 _note_diff_command->change (ev->note(), property, val);
997 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
998 MidiModel::NoteDiffCommand::Property property,
999 Evoral::MusicalTime val)
1001 if (_note_diff_command) {
1002 _note_diff_command->change (ev->note(), property, val);
1007 MidiRegionView::apply_diff (bool as_subcommand)
1011 if (!_note_diff_command) {
1015 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1016 // Mark all selected notes for selection when model reloads
1017 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1018 _marked_for_selection.insert((*i)->note());
1022 if (as_subcommand) {
1023 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1025 _model->apply_command (*trackview.session(), _note_diff_command);
1028 _note_diff_command = 0;
1029 midi_view()->midi_track()->playlist_modified();
1031 if (add_or_remove) {
1032 _marked_for_selection.clear();
1035 _marked_for_velocity.clear();
1039 MidiRegionView::abort_command()
1041 delete _note_diff_command;
1042 _note_diff_command = 0;
1047 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1049 if (_optimization_iterator != _events.end()) {
1050 ++_optimization_iterator;
1053 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1054 return *_optimization_iterator;
1057 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1058 if ((*_optimization_iterator)->note() == note) {
1059 return *_optimization_iterator;
1067 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1069 MidiModel::Notes notes;
1070 _model->get_notes (notes, op, val, chan_mask);
1072 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1073 CanvasNoteEvent* cne = find_canvas_note (*n);
1081 MidiRegionView::redisplay_model()
1083 // Don't redisplay the model if we're currently recording and displaying that
1084 if (_active_notes) {
1092 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1093 (*i)->invalidate ();
1096 MidiModel::ReadLock lock(_model->read_lock());
1098 MidiModel::Notes& notes (_model->notes());
1099 _optimization_iterator = _events.begin();
1101 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1103 boost::shared_ptr<NoteType> note (*n);
1104 CanvasNoteEvent* cne;
1107 if (note_in_region_range (note, visible)) {
1109 if ((cne = find_canvas_note (note)) != 0) {
1116 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1118 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1130 add_note (note, visible);
1135 if ((cne = find_canvas_note (note)) != 0) {
1143 /* remove note items that are no longer valid */
1145 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1146 if (!(*i)->valid ()) {
1148 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1149 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1151 gr->remove_note (*i);
1156 i = _events.erase (i);
1163 _patch_changes.clear();
1167 display_patch_changes ();
1169 _marked_for_selection.clear ();
1170 _marked_for_velocity.clear ();
1172 /* we may have caused _events to contain things out of order (e.g. if a note
1173 moved earlier or later). we don't generally need them in time order, but
1174 make a note that a sort is required for those cases that require it.
1177 _sort_needed = true;
1181 MidiRegionView::display_patch_changes ()
1183 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1184 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1186 for (uint8_t i = 0; i < 16; ++i) {
1187 if (chn_mask & (1<<i)) {
1188 display_patch_changes_on_channel (i);
1190 /* TODO gray-out patch instad of not displaying it */
1195 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1197 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1199 if ((*i)->channel() != channel) {
1203 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1205 boost::shared_ptr<MIDI::Name::Patch> patch =
1206 MIDI::Name::MidiPatchManager::instance().find_patch(
1207 _model_name, _custom_device_mode, channel, patch_key);
1210 add_canvas_patch_change (*i, patch->name());
1213 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1214 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1215 add_canvas_patch_change (*i, buf);
1221 MidiRegionView::display_sysexes()
1223 bool have_periodic_system_messages = false;
1224 bool display_periodic_messages = true;
1226 if (!Config->get_never_display_periodic_midi()) {
1228 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1229 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1230 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1233 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1234 have_periodic_system_messages = true;
1240 if (have_periodic_system_messages) {
1241 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1243 /* get an approximate value for the number of samples per video frame */
1245 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1247 /* if we are zoomed out beyond than the cutoff (i.e. more
1248 * frames per pixel than frames per 4 video frames), don't
1249 * show periodic sysex messages.
1252 if (zoom > (video_frame*4)) {
1253 display_periodic_messages = false;
1257 display_periodic_messages = false;
1260 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1262 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1263 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1265 Evoral::MusicalTime time = (*i)->time();
1269 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1270 if (!display_periodic_messages) {
1278 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1279 str << int((*i)->buffer()[b]);
1280 if (b != (*i)->size() -1) {
1284 string text = str.str();
1286 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1288 double height = midi_stream_view()->contents_height();
1290 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1291 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1293 // Show unless message is beyond the region bounds
1294 if (time - _region->start() >= _region->length() || time < _region->start()) {
1300 _sys_exes.push_back(sysex);
1304 MidiRegionView::~MidiRegionView ()
1306 in_destructor = true;
1308 trackview.editor().verbose_cursor()->hide ();
1310 note_delete_connection.disconnect ();
1312 delete _list_editor;
1314 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1316 if (_active_notes) {
1320 _selection_cleared_connection.disconnect ();
1326 delete _note_diff_command;
1327 delete _step_edit_cursor;
1328 delete _temporary_note_group;
1332 MidiRegionView::region_resized (const PropertyChange& what_changed)
1334 RegionView::region_resized(what_changed);
1336 if (what_changed.contains (ARDOUR::Properties::position)) {
1337 set_duration(_region->length(), 0);
1338 if (_enable_display) {
1345 MidiRegionView::reset_width_dependent_items (double pixel_width)
1347 RegionView::reset_width_dependent_items(pixel_width);
1348 assert(_pixel_width == pixel_width);
1350 if (_enable_display) {
1354 move_step_edit_cursor (_step_edit_cursor_position);
1355 set_step_edit_cursor_width (_step_edit_cursor_width);
1359 MidiRegionView::set_height (double height)
1361 static const double FUDGE = 2.0;
1362 const double old_height = _height;
1363 RegionView::set_height(height);
1364 _height = height - FUDGE;
1366 apply_note_range(midi_stream_view()->lowest_note(),
1367 midi_stream_view()->highest_note(),
1368 height != old_height + FUDGE);
1371 name_pixbuf->raise_to_top();
1374 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1375 (*x)->set_height (midi_stream_view()->contents_height());
1378 if (_step_edit_cursor) {
1379 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1384 /** Apply the current note range from the stream view
1385 * by repositioning/hiding notes as necessary
1388 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1390 if (!_enable_display) {
1394 if (!force && _current_range_min == min && _current_range_max == max) {
1398 _current_range_min = min;
1399 _current_range_max = max;
1401 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1402 CanvasNoteEvent* event = *i;
1403 boost::shared_ptr<NoteType> note (event->note());
1405 if (note->note() < _current_range_min ||
1406 note->note() > _current_range_max) {
1412 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1414 const double y1 = midi_stream_view()->note_to_y(note->note());
1415 const double y2 = y1 + floor(midi_stream_view()->note_height());
1417 cnote->property_y1() = y1;
1418 cnote->property_y2() = y2;
1420 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1422 const double diamond_size = update_hit (chit);
1424 chit->set_height (diamond_size);
1430 MidiRegionView::add_ghost (TimeAxisView& tv)
1434 double unit_position = _region->position () / samples_per_unit;
1435 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1436 MidiGhostRegion* ghost;
1438 if (mtv && mtv->midi_view()) {
1439 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1440 to allow having midi notes on top of note lines and waveforms.
1442 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1444 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1447 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1448 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1449 ghost->add_note(note);
1453 ghost->set_height ();
1454 ghost->set_duration (_region->length() / samples_per_unit);
1455 ghosts.push_back (ghost);
1457 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1463 /** Begin tracking note state for successive calls to add_event
1466 MidiRegionView::begin_write()
1468 assert(!_active_notes);
1469 _active_notes = new CanvasNote*[128];
1470 for (unsigned i=0; i < 128; ++i) {
1471 _active_notes[i] = 0;
1476 /** Destroy note state for add_event
1479 MidiRegionView::end_write()
1481 delete[] _active_notes;
1483 _marked_for_selection.clear();
1484 _marked_for_velocity.clear();
1488 /** Resolve an active MIDI note (while recording).
1491 MidiRegionView::resolve_note(uint8_t note, double end_time)
1493 if (midi_view()->note_mode() != Sustained) {
1497 if (_active_notes && _active_notes[note]) {
1499 /* XXX is end_time really region-centric? I think so, because
1500 this is a new region that we're recording, so source zero is
1501 the same as region zero
1503 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1505 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1506 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1507 _active_notes[note] = 0;
1512 /** Extend active notes to rightmost edge of region (if length is changed)
1515 MidiRegionView::extend_active_notes()
1517 if (!_active_notes) {
1521 for (unsigned i=0; i < 128; ++i) {
1522 if (_active_notes[i]) {
1523 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1530 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1532 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1536 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1538 if (!route_ui || !route_ui->midi_track()) {
1542 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1548 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1550 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1554 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1556 if (!route_ui || !route_ui->midi_track()) {
1560 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1562 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1571 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1573 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1574 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1576 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1577 (note->note() <= midi_stream_view()->highest_note());
1582 /** Update a canvas note's size from its model note.
1583 * @param ev Canvas note to update.
1584 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1587 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1589 boost::shared_ptr<NoteType> note = ev->note();
1590 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1591 const double y1 = midi_stream_view()->note_to_y(note->note());
1593 ev->property_x1() = x;
1594 ev->property_y1() = y1;
1596 /* trim note display to not overlap the end of its region */
1598 if (note->length() > 0) {
1599 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1600 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1602 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1605 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1607 if (note->length() == 0) {
1608 if (_active_notes) {
1609 assert(note->note() < 128);
1610 // If this note is already active there's a stuck note,
1611 // finish the old note rectangle
1612 if (_active_notes[note->note()]) {
1613 CanvasNote* const old_rect = _active_notes[note->note()];
1614 boost::shared_ptr<NoteType> old_note = old_rect->note();
1615 old_rect->property_x2() = x;
1616 old_rect->property_outline_what() = (guint32) 0xF;
1618 _active_notes[note->note()] = ev;
1620 /* outline all but right edge */
1621 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1623 /* outline all edges */
1624 ev->property_outline_what() = (guint32) 0xF;
1627 if (update_ghost_regions) {
1628 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1629 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1631 gr->update_note (ev);
1638 MidiRegionView::update_hit (CanvasHit* ev)
1640 boost::shared_ptr<NoteType> note = ev->note();
1642 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1643 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1644 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1645 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1649 return diamond_size;
1652 /** Add a MIDI note to the view (with length).
1654 * If in sustained mode, notes with length 0 will be considered active
1655 * notes, and resolve_note should be called when the corresponding note off
1656 * event arrives, to properly display the note.
1659 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1661 CanvasNoteEvent* event = 0;
1663 assert(note->time() >= 0);
1664 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1666 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1668 if (midi_view()->note_mode() == Sustained) {
1670 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1672 update_note (ev_rect);
1676 MidiGhostRegion* gr;
1678 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1679 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1680 gr->add_note(ev_rect);
1684 } else if (midi_view()->note_mode() == Percussive) {
1686 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1688 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1690 update_hit (ev_diamond);
1699 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1700 note_selected(event, true);
1703 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1704 event->show_velocity();
1707 event->on_channel_selection_change(_last_channel_selection);
1708 _events.push_back(event);
1717 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1718 MidiStreamView* const view = mtv->midi_view();
1720 view->update_note_range (note->note());
1724 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1725 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1727 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1729 /* potentially extend region to hold new note */
1731 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1732 framepos_t region_end = _region->last_frame();
1734 if (end_frame > region_end) {
1735 _region->set_length (end_frame - _region->position());
1738 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1739 MidiStreamView* const view = mtv->midi_view();
1741 view->update_note_range(new_note->note());
1743 _marked_for_selection.clear ();
1746 start_note_diff_command (_("step add"));
1747 note_diff_add_note (new_note, true, false);
1750 // last_step_edit_note = new_note;
1754 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1756 change_note_lengths (false, false, beats, false, true);
1760 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1762 assert (patch->time() >= 0);
1764 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1765 const double x = trackview.editor().frame_to_pixel (region_frames);
1767 double const height = midi_stream_view()->contents_height();
1769 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1770 new CanvasPatchChange(*this, *_note_group,
1775 _custom_device_mode,
1779 // Show unless patch change is beyond the region bounds
1780 if (region_frames < 0 || region_frames >= _region->length()) {
1781 patch_change->hide();
1783 patch_change->show();
1786 _patch_changes.push_back (patch_change);
1790 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1792 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1793 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1797 if (i != _model->patch_changes().end()) {
1798 key.msb = (*i)->bank_msb ();
1799 key.lsb = (*i)->bank_lsb ();
1800 key.program_number = (*i)->program ();
1802 key.msb = key.lsb = key.program_number = 0;
1805 assert (key.is_sane());
1810 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1812 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1814 if (pc.patch()->program() != new_patch.program_number) {
1815 c->change_program (pc.patch (), new_patch.program_number);
1818 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1819 if (pc.patch()->bank() != new_bank) {
1820 c->change_bank (pc.patch (), new_bank);
1823 _model->apply_command (*trackview.session(), c);
1825 _patch_changes.clear ();
1826 display_patch_changes ();
1830 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1832 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1834 if (old_change->time() != new_change.time()) {
1835 c->change_time (old_change, new_change.time());
1838 if (old_change->channel() != new_change.channel()) {
1839 c->change_channel (old_change, new_change.channel());
1842 if (old_change->program() != new_change.program()) {
1843 c->change_program (old_change, new_change.program());
1846 if (old_change->bank() != new_change.bank()) {
1847 c->change_bank (old_change, new_change.bank());
1850 _model->apply_command (*trackview.session(), c);
1852 _patch_changes.clear ();
1853 display_patch_changes ();
1856 /** Add a patch change to the region.
1857 * @param t Time in frames relative to region position
1858 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1859 * MidiTimeAxisView::get_channel_for_add())
1862 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1864 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1866 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1867 c->add (MidiModel::PatchChangePtr (
1868 new Evoral::PatchChange<Evoral::MusicalTime> (
1869 absolute_frames_to_source_beats (_region->position() + t),
1870 mtv->get_channel_for_add(), patch.program(), patch.bank()
1875 _model->apply_command (*trackview.session(), c);
1877 _patch_changes.clear ();
1878 display_patch_changes ();
1882 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1884 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1885 c->change_time (pc.patch (), t);
1886 _model->apply_command (*trackview.session(), c);
1888 _patch_changes.clear ();
1889 display_patch_changes ();
1893 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1895 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1896 c->remove (pc->patch ());
1897 _model->apply_command (*trackview.session(), c);
1899 _patch_changes.clear ();
1900 display_patch_changes ();
1904 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1906 if (patch.patch()->program() < 127) {
1907 MIDI::Name::PatchPrimaryKey key;
1908 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1909 key.program_number++;
1910 change_patch_change (patch, key);
1915 MidiRegionView::next_patch (CanvasPatchChange& patch)
1917 if (patch.patch()->program() > 0) {
1918 MIDI::Name::PatchPrimaryKey key;
1919 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1920 key.program_number--;
1921 change_patch_change (patch, key);
1926 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1928 if (patch.patch()->program() < 127) {
1929 MIDI::Name::PatchPrimaryKey key;
1930 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1933 change_patch_change (patch, key);
1938 change_patch_change (patch, key);
1945 MidiRegionView::next_bank (CanvasPatchChange& patch)
1947 if (patch.patch()->program() > 0) {
1948 MIDI::Name::PatchPrimaryKey key;
1949 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1950 if (key.lsb < 127) {
1952 change_patch_change (patch, key);
1954 if (key.msb < 127) {
1957 change_patch_change (patch, key);
1964 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1966 if (_selection.empty()) {
1970 _selection.erase (cne);
1974 MidiRegionView::delete_selection()
1976 if (_selection.empty()) {
1980 start_note_diff_command (_("delete selection"));
1982 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1983 if ((*i)->selected()) {
1984 _note_diff_command->remove((*i)->note());
1994 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1996 start_note_diff_command (_("delete note"));
1997 _note_diff_command->remove (n);
2000 trackview.editor().verbose_cursor()->hide ();
2004 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2006 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2008 Selection::iterator tmp = i;
2011 (*i)->set_selected (false);
2012 (*i)->hide_velocity ();
2013 _selection.erase (i);
2021 /* this does not change the status of this regionview w.r.t the editor
2026 SelectionCleared (this); /* EMIT SIGNAL */
2031 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2033 clear_selection_except (ev);
2035 /* don't bother with checking to see if we should remove this
2036 regionview from the editor selection, since we're about to add
2037 another note, and thus put/keep this regionview in the editor
2041 if (!ev->selected()) {
2042 add_to_selection (ev);
2047 MidiRegionView::select_all_notes ()
2051 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2052 add_to_selection (*i);
2057 MidiRegionView::select_range (framepos_t start, framepos_t end)
2061 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2062 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2063 if (t >= start && t <= end) {
2064 add_to_selection (*i);
2070 MidiRegionView::invert_selection ()
2072 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2073 if ((*i)->selected()) {
2074 remove_from_selection(*i);
2076 add_to_selection (*i);
2082 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2084 uint8_t low_note = 127;
2085 uint8_t high_note = 0;
2086 MidiModel::Notes& notes (_model->notes());
2087 _optimization_iterator = _events.begin();
2093 if (extend && _selection.empty()) {
2099 /* scan existing selection to get note range */
2101 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2102 if ((*i)->note()->note() < low_note) {
2103 low_note = (*i)->note()->note();
2105 if ((*i)->note()->note() > high_note) {
2106 high_note = (*i)->note()->note();
2110 low_note = min (low_note, notenum);
2111 high_note = max (high_note, notenum);
2114 _no_sound_notes = true;
2116 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2118 boost::shared_ptr<NoteType> note (*n);
2119 CanvasNoteEvent* cne;
2120 bool select = false;
2122 if (((1 << note->channel()) & channel_mask) != 0) {
2124 if ((note->note() >= low_note && note->note() <= high_note)) {
2127 } else if (note->note() == notenum) {
2133 if ((cne = find_canvas_note (note)) != 0) {
2134 // extend is false because we've taken care of it,
2135 // since it extends by time range, not pitch.
2136 note_selected (cne, add, false);
2140 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2144 _no_sound_notes = false;
2148 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2150 MidiModel::Notes& notes (_model->notes());
2151 _optimization_iterator = _events.begin();
2153 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2155 boost::shared_ptr<NoteType> note (*n);
2156 CanvasNoteEvent* cne;
2158 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2159 if ((cne = find_canvas_note (note)) != 0) {
2160 if (cne->selected()) {
2161 note_deselected (cne);
2163 note_selected (cne, true, false);
2171 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2174 clear_selection_except (ev);
2175 if (!_selection.empty()) {
2176 PublicEditor& editor (trackview.editor());
2177 editor.get_selection().add (this);
2183 if (!ev->selected()) {
2184 add_to_selection (ev);
2188 /* find end of latest note selected, select all between that and the start of "ev" */
2190 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2191 Evoral::MusicalTime latest = 0;
2193 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2194 if ((*i)->note()->end_time() > latest) {
2195 latest = (*i)->note()->end_time();
2197 if ((*i)->note()->time() < earliest) {
2198 earliest = (*i)->note()->time();
2202 if (ev->note()->end_time() > latest) {
2203 latest = ev->note()->end_time();
2206 if (ev->note()->time() < earliest) {
2207 earliest = ev->note()->time();
2210 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2212 /* find notes entirely within OR spanning the earliest..latest range */
2214 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2215 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2216 add_to_selection (*i);
2224 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2226 remove_from_selection (ev);
2230 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2240 // TODO: Make this faster by storing the last updated selection rect, and only
2241 // adjusting things that are in the area that appears/disappeared.
2242 // We probably need a tree to be able to find events in O(log(n)) time.
2244 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2246 /* check if any corner of the note is inside the rect
2249 1) this is computing "touched by", not "contained by" the rect.
2250 2) this does not require that events be sorted in time.
2253 const double ix1 = (*i)->x1();
2254 const double ix2 = (*i)->x2();
2255 const double iy1 = (*i)->y1();
2256 const double iy2 = (*i)->y2();
2258 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2259 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2260 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2261 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2264 if (!(*i)->selected()) {
2265 add_to_selection (*i);
2267 } else if ((*i)->selected() && !extend) {
2268 // Not inside rectangle
2269 remove_from_selection (*i);
2275 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2281 // TODO: Make this faster by storing the last updated selection rect, and only
2282 // adjusting things that are in the area that appears/disappeared.
2283 // We probably need a tree to be able to find events in O(log(n)) time.
2285 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2287 /* check if any corner of the note is inside the rect
2290 1) this is computing "touched by", not "contained by" the rect.
2291 2) this does not require that events be sorted in time.
2294 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2295 // within y- (note-) range
2296 if (!(*i)->selected()) {
2297 add_to_selection (*i);
2299 } else if ((*i)->selected() && !extend) {
2300 // Not inside rectangle
2301 remove_from_selection (*i);
2307 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2309 Selection::iterator i = _selection.find (ev);
2311 if (i != _selection.end()) {
2312 _selection.erase (i);
2315 ev->set_selected (false);
2316 ev->hide_velocity ();
2318 if (_selection.empty()) {
2319 PublicEditor& editor (trackview.editor());
2320 editor.get_selection().remove (this);
2325 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2327 bool add_mrv_selection = false;
2329 if (_selection.empty()) {
2330 add_mrv_selection = true;
2333 if (_selection.insert (ev).second) {
2334 ev->set_selected (true);
2335 play_midi_note ((ev)->note());
2338 if (add_mrv_selection) {
2339 PublicEditor& editor (trackview.editor());
2340 editor.get_selection().add (this);
2345 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2347 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2348 PossibleChord to_play;
2349 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2351 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2352 if ((*i)->note()->time() < earliest) {
2353 earliest = (*i)->note()->time();
2357 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2358 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2359 to_play.push_back ((*i)->note());
2361 (*i)->move_event(dx, dy);
2364 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2366 if (to_play.size() > 1) {
2368 PossibleChord shifted;
2370 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2371 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2372 moved_note->set_note (moved_note->note() + cumulative_dy);
2373 shifted.push_back (moved_note);
2376 play_midi_chord (shifted);
2378 } else if (!to_play.empty()) {
2380 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2381 moved_note->set_note (moved_note->note() + cumulative_dy);
2382 play_midi_note (moved_note);
2388 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2390 assert (!_selection.empty());
2392 uint8_t lowest_note_in_selection = 127;
2393 uint8_t highest_note_in_selection = 0;
2394 uint8_t highest_note_difference = 0;
2396 // find highest and lowest notes first
2398 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2399 uint8_t pitch = (*i)->note()->note();
2400 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2401 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2405 cerr << "dnote: " << (int) dnote << endl;
2406 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2407 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2408 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2409 << int(highest_note_in_selection) << endl;
2410 cerr << "selection size: " << _selection.size() << endl;
2411 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2414 // Make sure the note pitch does not exceed the MIDI standard range
2415 if (highest_note_in_selection + dnote > 127) {
2416 highest_note_difference = highest_note_in_selection - 127;
2419 start_note_diff_command (_("move notes"));
2421 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2423 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2424 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2430 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2432 uint8_t original_pitch = (*i)->note()->note();
2433 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2435 // keep notes in standard midi range
2436 clamp_to_0_127(new_pitch);
2438 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2439 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2441 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2446 // care about notes being moved beyond the upper/lower bounds on the canvas
2447 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2448 highest_note_in_selection > midi_stream_view()->highest_note()) {
2449 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2453 /** @param x Pixel relative to the region position.
2454 * @return Snapped frame relative to the region position.
2457 MidiRegionView::snap_pixel_to_frame(double x)
2459 PublicEditor& editor (trackview.editor());
2460 return snap_frame_to_frame (editor.pixel_to_frame (x));
2463 /** @param x Pixel relative to the region position.
2464 * @return Snapped pixel relative to the region position.
2467 MidiRegionView::snap_to_pixel(double x)
2469 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2473 MidiRegionView::get_position_pixels()
2475 framepos_t region_frame = get_position();
2476 return trackview.editor().frame_to_pixel(region_frame);
2480 MidiRegionView::get_end_position_pixels()
2482 framepos_t frame = get_position() + get_duration ();
2483 return trackview.editor().frame_to_pixel(frame);
2487 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2489 /* the time converter will return the frame corresponding to `beats'
2490 relative to the start of the source. The start of the source
2491 is an implied position given by region->position - region->start
2493 const framepos_t source_start = _region->position() - _region->start();
2494 return source_start + _source_relative_time_converter.to (beats);
2498 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2500 /* the `frames' argument needs to be converted into a frame count
2501 relative to the start of the source before being passed in to the
2504 const framepos_t source_start = _region->position() - _region->start();
2505 return _source_relative_time_converter.from (frames - source_start);
2509 MidiRegionView::region_beats_to_region_frames(double beats) const
2511 return _region_relative_time_converter.to(beats);
2515 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2517 return _region_relative_time_converter.from(frames);
2521 MidiRegionView::begin_resizing (bool /*at_front*/)
2523 _resize_data.clear();
2525 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2526 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2528 // only insert CanvasNotes into the map
2530 NoteResizeData *resize_data = new NoteResizeData();
2531 resize_data->canvas_note = note;
2533 // create a new SimpleRect from the note which will be the resize preview
2534 SimpleRect *resize_rect = new SimpleRect(
2535 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2537 // calculate the colors: get the color settings
2538 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2539 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2542 // make the resize preview notes more transparent and bright
2543 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2545 // calculate color based on note velocity
2546 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2547 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2551 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2552 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2554 resize_data->resize_rect = resize_rect;
2555 _resize_data.push_back(resize_data);
2560 /** Update resizing notes while user drags.
2561 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2562 * @param at_front which end of the note (true == note on, false == note off)
2563 * @param delta_x change in mouse position since the start of the drag
2564 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2565 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2566 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2567 * as the \a primary note.
2570 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2572 bool cursor_set = false;
2574 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2575 SimpleRect* resize_rect = (*i)->resize_rect;
2576 CanvasNote* canvas_note = (*i)->canvas_note;
2581 current_x = canvas_note->x1() + delta_x;
2583 current_x = primary->x1() + delta_x;
2587 current_x = canvas_note->x2() + delta_x;
2589 current_x = primary->x2() + delta_x;
2594 resize_rect->property_x1() = snap_to_pixel(current_x);
2595 resize_rect->property_x2() = canvas_note->x2();
2597 resize_rect->property_x2() = snap_to_pixel(current_x);
2598 resize_rect->property_x1() = canvas_note->x1();
2604 beats = snap_pixel_to_frame (current_x);
2605 beats = region_frames_to_region_beats (beats);
2610 if (beats < canvas_note->note()->end_time()) {
2611 len = canvas_note->note()->time() - beats;
2612 len += canvas_note->note()->length();
2617 if (beats >= canvas_note->note()->time()) {
2618 len = beats - canvas_note->note()->time();
2625 snprintf (buf, sizeof (buf), "%.3g beats", len);
2626 show_verbose_cursor (buf, 0, 0);
2635 /** Finish resizing notes when the user releases the mouse button.
2636 * Parameters the same as for \a update_resizing().
2639 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2641 start_note_diff_command (_("resize notes"));
2643 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2644 CanvasNote* canvas_note = (*i)->canvas_note;
2645 SimpleRect* resize_rect = (*i)->resize_rect;
2647 /* Get the new x position for this resize, which is in pixels relative
2648 * to the region position.
2655 current_x = canvas_note->x1() + delta_x;
2657 current_x = primary->x1() + delta_x;
2661 current_x = canvas_note->x2() + delta_x;
2663 current_x = primary->x2() + delta_x;
2667 /* Convert that to a frame within the source */
2668 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2670 /* and then to beats */
2671 current_x = region_frames_to_region_beats (current_x);
2673 if (at_front && current_x < canvas_note->note()->end_time()) {
2674 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2676 double len = canvas_note->note()->time() - current_x;
2677 len += canvas_note->note()->length();
2680 /* XXX convert to beats */
2681 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2686 double len = current_x - canvas_note->note()->time();
2689 /* XXX convert to beats */
2690 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2698 _resize_data.clear();
2703 MidiRegionView::abort_resizing ()
2705 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2706 delete (*i)->resize_rect;
2710 _resize_data.clear ();
2714 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2716 uint8_t new_velocity;
2719 new_velocity = event->note()->velocity() + velocity;
2720 clamp_to_0_127(new_velocity);
2722 new_velocity = velocity;
2725 event->set_selected (event->selected()); // change color
2727 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2731 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2736 new_note = event->note()->note() + note;
2741 clamp_to_0_127 (new_note);
2742 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2746 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2748 bool change_start = false;
2749 bool change_length = false;
2750 Evoral::MusicalTime new_start = 0;
2751 Evoral::MusicalTime new_length = 0;
2753 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2755 front_delta: if positive - move the start of the note later in time (shortening it)
2756 if negative - move the start of the note earlier in time (lengthening it)
2758 end_delta: if positive - move the end of the note later in time (lengthening it)
2759 if negative - move the end of the note earlier in time (shortening it)
2763 if (front_delta < 0) {
2765 if (event->note()->time() < -front_delta) {
2768 new_start = event->note()->time() + front_delta; // moves earlier
2771 /* start moved toward zero, so move the end point out to where it used to be.
2772 Note that front_delta is negative, so this increases the length.
2775 new_length = event->note()->length() - front_delta;
2776 change_start = true;
2777 change_length = true;
2781 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2783 if (new_pos < event->note()->end_time()) {
2784 new_start = event->note()->time() + front_delta;
2785 /* start moved toward the end, so move the end point back to where it used to be */
2786 new_length = event->note()->length() - front_delta;
2787 change_start = true;
2788 change_length = true;
2795 bool can_change = true;
2796 if (end_delta < 0) {
2797 if (event->note()->length() < -end_delta) {
2803 new_length = event->note()->length() + end_delta;
2804 change_length = true;
2809 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2812 if (change_length) {
2813 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2818 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2820 uint8_t new_channel;
2824 if (event->note()->channel() < -chn) {
2827 new_channel = event->note()->channel() + chn;
2830 new_channel = event->note()->channel() + chn;
2833 new_channel = (uint8_t) chn;
2836 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2840 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2842 Evoral::MusicalTime new_time;
2846 if (event->note()->time() < -delta) {
2849 new_time = event->note()->time() + delta;
2852 new_time = event->note()->time() + delta;
2858 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2862 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2864 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2868 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2873 if (_selection.empty()) {
2888 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2889 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2895 start_note_diff_command (_("change velocities"));
2897 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2898 Selection::iterator next = i;
2902 if (i == _selection.begin()) {
2903 change_note_velocity (*i, delta, true);
2904 value = (*i)->note()->velocity() + delta;
2906 change_note_velocity (*i, value, false);
2910 change_note_velocity (*i, delta, true);
2918 if (!_selection.empty()) {
2920 snprintf (buf, sizeof (buf), "Vel %d",
2921 (int) (*_selection.begin())->note()->velocity());
2922 show_verbose_cursor (buf, 10, 10);
2928 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2930 if (_selection.empty()) {
2947 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2949 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2953 if ((int8_t) (*i)->note()->note() + delta > 127) {
2960 start_note_diff_command (_("transpose"));
2962 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2963 Selection::iterator next = i;
2965 change_note_note (*i, delta, true);
2973 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2979 /* grab the current grid distance */
2981 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2983 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2984 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
2994 start_note_diff_command (_("change note lengths"));
2996 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2997 Selection::iterator next = i;
3000 /* note the negation of the delta for start */
3002 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3011 MidiRegionView::nudge_notes (bool forward)
3013 if (_selection.empty()) {
3017 /* pick a note as the point along the timeline to get the nudge distance.
3018 its not necessarily the earliest note, so we may want to pull the notes out
3019 into a vector and sort before using the first one.
3022 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3024 framecnt_t distance;
3026 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3028 /* grid is off - use nudge distance */
3030 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3036 framepos_t next_pos = ref_point;
3039 if (max_framepos - 1 < next_pos) {
3043 if (next_pos == 0) {
3049 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3050 distance = ref_point - next_pos;
3053 if (distance == 0) {
3057 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3063 start_note_diff_command (_("nudge"));
3065 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3066 Selection::iterator next = i;
3068 change_note_time (*i, delta, true);
3076 MidiRegionView::change_channel(uint8_t channel)
3078 start_note_diff_command(_("change channel"));
3079 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3080 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3088 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3090 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3092 pre_enter_cursor = editor->get_canvas_cursor ();
3094 if (_mouse_state == SelectTouchDragging) {
3095 note_selected (ev, true);
3098 show_verbose_cursor (ev->note ());
3102 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3104 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3106 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3107 (*i)->hide_velocity ();
3110 editor->verbose_cursor()->hide ();
3112 if (pre_enter_cursor) {
3113 editor->set_canvas_cursor (pre_enter_cursor);
3114 pre_enter_cursor = 0;
3119 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3122 /* XXX should get patch name if we can */
3123 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3124 show_verbose_cursor (s.str(), 10, 20);
3128 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3130 trackview.editor().verbose_cursor()->hide ();
3134 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3136 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3137 Editing::MouseMode mm = editor->current_mouse_mode();
3138 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3140 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3141 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3142 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3143 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3145 if (pre_enter_cursor && can_set_cursor) {
3146 editor->set_canvas_cursor (pre_enter_cursor);
3152 MidiRegionView::set_frame_color()
3156 TimeAxisViewItem::set_frame_color ();
3163 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3164 } else if (high_enough_for_name) {
3165 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3170 if (!rect_visible) {
3171 f = UINT_RGBA_CHANGE_A (f, 0);
3174 frame->property_fill_color_rgba() = f;
3178 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3180 if (mode == ForceChannel) {
3181 mask = 0xFFFF; // Show all notes as active (below)
3184 // Update notes for selection
3185 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3186 (*i)->on_channel_selection_change(mask);
3189 _last_channel_selection = mask;
3191 _patch_changes.clear ();
3192 display_patch_changes ();
3196 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3198 _model_name = model;
3199 _custom_device_mode = custom_device_mode;
3204 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3206 if (_selection.empty()) {
3210 PublicEditor& editor (trackview.editor());
3214 /* XXX what to do ? */
3218 editor.get_cut_buffer().add (selection_as_cut_buffer());
3226 start_note_diff_command();
3228 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3235 note_diff_remove_note (*i);
3245 MidiRegionView::selection_as_cut_buffer () const
3249 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3250 NoteType* n = (*i)->note().get();
3251 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3254 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3260 /** This method handles undo */
3262 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3268 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3270 trackview.session()->begin_reversible_command (_("paste"));
3272 start_note_diff_command (_("paste"));
3274 Evoral::MusicalTime beat_delta;
3275 Evoral::MusicalTime paste_pos_beats;
3276 Evoral::MusicalTime duration;
3277 Evoral::MusicalTime end_point = 0;
3279 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3280 paste_pos_beats = absolute_frames_to_source_beats (pos);
3281 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3282 paste_pos_beats = 0;
3284 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",
3285 (*mcb.notes().begin())->time(),
3286 (*mcb.notes().rbegin())->end_time(),
3287 duration, pos, _region->position(),
3288 paste_pos_beats, beat_delta));
3292 for (int n = 0; n < (int) times; ++n) {
3294 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3296 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3297 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3299 /* make all newly added notes selected */
3301 note_diff_add_note (copied_note, true);
3302 end_point = copied_note->end_time();
3305 paste_pos_beats += duration;
3308 /* if we pasted past the current end of the region, extend the region */
3310 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3311 framepos_t region_end = _region->position() + _region->length() - 1;
3313 if (end_frame > region_end) {
3315 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3317 _region->clear_changes ();
3318 _region->set_length (end_frame - _region->position());
3319 trackview.session()->add_command (new StatefulDiffCommand (_region));
3324 trackview.session()->commit_reversible_command ();
3327 struct EventNoteTimeEarlyFirstComparator {
3328 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3329 return a->note()->time() < b->note()->time();
3334 MidiRegionView::time_sort_events ()
3336 if (!_sort_needed) {
3340 EventNoteTimeEarlyFirstComparator cmp;
3343 _sort_needed = false;
3347 MidiRegionView::goto_next_note (bool add_to_selection)
3349 bool use_next = false;
3351 if (_events.back()->selected()) {
3355 time_sort_events ();
3357 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3358 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3360 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3361 if ((*i)->selected()) {
3364 } else if (use_next) {
3365 if (channel_mask & (1 << (*i)->note()->channel())) {
3366 if (!add_to_selection) {
3369 note_selected (*i, true, false);
3376 /* use the first one */
3378 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3379 unique_select (_events.front());
3384 MidiRegionView::goto_previous_note (bool add_to_selection)
3386 bool use_next = false;
3388 if (_events.front()->selected()) {
3392 time_sort_events ();
3394 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3395 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3397 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3398 if ((*i)->selected()) {
3401 } else if (use_next) {
3402 if (channel_mask & (1 << (*i)->note()->channel())) {
3403 if (!add_to_selection) {
3406 note_selected (*i, true, false);
3413 /* use the last one */
3415 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3416 unique_select (*(_events.rbegin()));
3421 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3423 bool had_selected = false;
3425 time_sort_events ();
3427 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3428 if ((*i)->selected()) {
3429 selected.insert ((*i)->note());
3430 had_selected = true;
3434 if (allow_all_if_none_selected && !had_selected) {
3435 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3436 selected.insert ((*i)->note());
3442 MidiRegionView::update_ghost_note (double x, double y)
3444 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3449 _note_group->w2i (x, y);
3451 PublicEditor& editor = trackview.editor ();
3453 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3454 framecnt_t grid_frames;
3455 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3457 /* use region_frames... because we are converting a delta within the region
3461 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3467 /* note that this sets the time of the ghost note in beats relative to
3468 the start of the source; that is how all note times are stored.
3470 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3471 _ghost_note->note()->set_length (length);
3472 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3473 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3475 /* the ghost note does not appear in ghost regions, so pass false in here */
3476 update_note (_ghost_note, false);
3478 show_verbose_cursor (_ghost_note->note ());
3482 MidiRegionView::create_ghost_note (double x, double y)
3484 remove_ghost_note ();
3486 boost::shared_ptr<NoteType> g (new NoteType);
3487 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3488 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3489 update_ghost_note (x, y);
3490 _ghost_note->show ();
3495 show_verbose_cursor (_ghost_note->note ());
3499 MidiRegionView::snap_changed ()
3505 create_ghost_note (_last_ghost_x, _last_ghost_y);
3509 MidiRegionView::drop_down_keys ()
3511 _mouse_state = None;
3515 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3517 double note = midi_stream_view()->y_to_note(y);
3519 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3521 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3523 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3524 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3525 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3526 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3531 bool add_mrv_selection = false;
3533 if (_selection.empty()) {
3534 add_mrv_selection = true;
3537 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3538 if (_selection.insert (*i).second) {
3539 (*i)->set_selected (true);
3543 if (add_mrv_selection) {
3544 PublicEditor& editor (trackview.editor());
3545 editor.get_selection().add (this);
3550 MidiRegionView::color_handler ()
3552 RegionView::color_handler ();
3554 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3555 (*i)->set_selected ((*i)->selected()); // will change color
3558 /* XXX probably more to do here */
3562 MidiRegionView::enable_display (bool yn)
3564 RegionView::enable_display (yn);
3571 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3573 if (_step_edit_cursor == 0) {
3574 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3576 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3577 _step_edit_cursor->property_y1() = 0;
3578 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3579 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3580 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3583 move_step_edit_cursor (pos);
3584 _step_edit_cursor->show ();
3588 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3590 _step_edit_cursor_position = pos;
3592 if (_step_edit_cursor) {
3593 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3594 _step_edit_cursor->property_x1() = pixel;
3595 set_step_edit_cursor_width (_step_edit_cursor_width);
3600 MidiRegionView::hide_step_edit_cursor ()
3602 if (_step_edit_cursor) {
3603 _step_edit_cursor->hide ();
3608 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3610 _step_edit_cursor_width = beats;
3612 if (_step_edit_cursor) {
3613 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3617 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3618 * @param w Source that the data will end up in.
3621 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3623 if (!_active_notes) {
3624 /* we aren't actively being recorded to */
3628 boost::shared_ptr<MidiSource> src = w.lock ();
3629 if (!src || src != midi_region()->midi_source()) {
3630 /* recorded data was not destined for our source */
3634 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3636 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3638 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3640 framepos_t back = max_framepos;
3642 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3643 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3644 assert (ev.buffer ());
3646 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3647 frames from the start of the source, and so time_beats is in terms of the
3651 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3653 if (ev.type() == MIDI_CMD_NOTE_ON) {
3655 boost::shared_ptr<NoteType> note (
3656 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3659 add_note (note, true);
3661 /* fix up our note range */
3662 if (ev.note() < _current_range_min) {
3663 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3664 } else if (ev.note() > _current_range_max) {
3665 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3668 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3669 resolve_note (ev.note (), time_beats);
3675 midi_stream_view()->check_record_layers (region(), back);
3679 MidiRegionView::trim_front_starting ()
3681 /* Reparent the note group to the region view's parent, so that it doesn't change
3682 when the region view is trimmed.
3684 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3685 _temporary_note_group->move (group->property_x(), group->property_y());
3686 _note_group->reparent (*_temporary_note_group);
3690 MidiRegionView::trim_front_ending ()
3692 _note_group->reparent (*group);
3693 delete _temporary_note_group;
3694 _temporary_note_group = 0;
3696 if (_region->start() < 0) {
3697 /* Trim drag made start time -ve; fix this */
3698 midi_region()->fix_negative_start ();
3703 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3705 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), _model_name, _custom_device_mode, Gtk::Stock::APPLY);
3706 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3710 change_patch_change (pc->patch(), d.patch ());
3715 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3718 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3719 Evoral::midi_note_name (n->note()).c_str(),
3721 (int) n->channel() + 1,
3722 (int) n->velocity());
3724 show_verbose_cursor (buf, 10, 20);
3728 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3732 trackview.editor().get_pointer_position (wx, wy);
3737 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3739 double x1, y1, x2, y2;
3740 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3742 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3743 wy -= (y2 - y1) + 2 * yoffset;
3746 trackview.editor().verbose_cursor()->set (text, wx, wy);
3747 trackview.editor().verbose_cursor()->show ();
3750 /** @param p A session framepos.
3751 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3752 * @return p snapped to the grid subdivision underneath it.
3755 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3757 PublicEditor& editor = trackview.editor ();
3760 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3766 grid_frames = region_beats_to_region_frames (grid_beats);
3768 /* Hack so that we always snap to the note that we are over, instead of snapping
3769 to the next one if we're more than halfway through the one we're over.
3771 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3772 p -= grid_frames / 2;
3775 return snap_frame_to_frame (p);
3778 /** Called when the selection has been cleared in any MidiRegionView.
3779 * @param rv MidiRegionView that the selection was cleared in.
3782 MidiRegionView::selection_cleared (MidiRegionView* rv)
3788 /* Clear our selection in sympathy; but don't signal the fact */
3789 clear_selection (false);