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 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1191 /** @param active_channel true to display patch changes fully, false to display
1192 * them `greyed-out' (as on an inactive channel)
1195 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_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(), active_channel);
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, active_channel);
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);
1759 /** Add a new patch change flag to the canvas.
1760 * @param patch the patch change to add
1761 * @param the text to display in the flag
1762 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1765 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1767 assert (patch->time() >= 0);
1769 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1770 const double x = trackview.editor().frame_to_pixel (region_frames);
1772 double const height = midi_stream_view()->contents_height();
1774 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1775 new CanvasPatchChange(*this, *_note_group,
1780 _custom_device_mode,
1785 // Show unless patch change is beyond the region bounds
1786 if (region_frames < 0 || region_frames >= _region->length()) {
1787 patch_change->hide();
1789 patch_change->show();
1792 _patch_changes.push_back (patch_change);
1796 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1798 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1799 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1803 if (i != _model->patch_changes().end()) {
1804 key.msb = (*i)->bank_msb ();
1805 key.lsb = (*i)->bank_lsb ();
1806 key.program_number = (*i)->program ();
1808 key.msb = key.lsb = key.program_number = 0;
1811 assert (key.is_sane());
1816 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1818 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1820 if (pc.patch()->program() != new_patch.program_number) {
1821 c->change_program (pc.patch (), new_patch.program_number);
1824 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1825 if (pc.patch()->bank() != new_bank) {
1826 c->change_bank (pc.patch (), new_bank);
1829 _model->apply_command (*trackview.session(), c);
1831 _patch_changes.clear ();
1832 display_patch_changes ();
1836 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1838 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1840 if (old_change->time() != new_change.time()) {
1841 c->change_time (old_change, new_change.time());
1844 if (old_change->channel() != new_change.channel()) {
1845 c->change_channel (old_change, new_change.channel());
1848 if (old_change->program() != new_change.program()) {
1849 c->change_program (old_change, new_change.program());
1852 if (old_change->bank() != new_change.bank()) {
1853 c->change_bank (old_change, new_change.bank());
1856 _model->apply_command (*trackview.session(), c);
1858 _patch_changes.clear ();
1859 display_patch_changes ();
1862 /** Add a patch change to the region.
1863 * @param t Time in frames relative to region position
1864 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1865 * MidiTimeAxisView::get_channel_for_add())
1868 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1870 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1872 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1873 c->add (MidiModel::PatchChangePtr (
1874 new Evoral::PatchChange<Evoral::MusicalTime> (
1875 absolute_frames_to_source_beats (_region->position() + t),
1876 mtv->get_channel_for_add(), patch.program(), patch.bank()
1881 _model->apply_command (*trackview.session(), c);
1883 _patch_changes.clear ();
1884 display_patch_changes ();
1888 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1890 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1891 c->change_time (pc.patch (), t);
1892 _model->apply_command (*trackview.session(), c);
1894 _patch_changes.clear ();
1895 display_patch_changes ();
1899 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1901 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1902 c->remove (pc->patch ());
1903 _model->apply_command (*trackview.session(), c);
1905 _patch_changes.clear ();
1906 display_patch_changes ();
1910 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1912 if (patch.patch()->program() < 127) {
1913 MIDI::Name::PatchPrimaryKey key;
1914 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1915 key.program_number++;
1916 change_patch_change (patch, key);
1921 MidiRegionView::next_patch (CanvasPatchChange& patch)
1923 if (patch.patch()->program() > 0) {
1924 MIDI::Name::PatchPrimaryKey key;
1925 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1926 key.program_number--;
1927 change_patch_change (patch, key);
1932 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1934 if (patch.patch()->program() < 127) {
1935 MIDI::Name::PatchPrimaryKey key;
1936 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1939 change_patch_change (patch, key);
1944 change_patch_change (patch, key);
1951 MidiRegionView::next_bank (CanvasPatchChange& patch)
1953 if (patch.patch()->program() > 0) {
1954 MIDI::Name::PatchPrimaryKey key;
1955 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1956 if (key.lsb < 127) {
1958 change_patch_change (patch, key);
1960 if (key.msb < 127) {
1963 change_patch_change (patch, key);
1970 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1972 if (_selection.empty()) {
1976 _selection.erase (cne);
1980 MidiRegionView::delete_selection()
1982 if (_selection.empty()) {
1986 start_note_diff_command (_("delete selection"));
1988 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1989 if ((*i)->selected()) {
1990 _note_diff_command->remove((*i)->note());
2000 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2002 start_note_diff_command (_("delete note"));
2003 _note_diff_command->remove (n);
2006 trackview.editor().verbose_cursor()->hide ();
2010 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2012 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2014 Selection::iterator tmp = i;
2017 (*i)->set_selected (false);
2018 (*i)->hide_velocity ();
2019 _selection.erase (i);
2027 /* this does not change the status of this regionview w.r.t the editor
2032 SelectionCleared (this); /* EMIT SIGNAL */
2037 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2039 clear_selection_except (ev);
2041 /* don't bother with checking to see if we should remove this
2042 regionview from the editor selection, since we're about to add
2043 another note, and thus put/keep this regionview in the editor
2047 if (!ev->selected()) {
2048 add_to_selection (ev);
2053 MidiRegionView::select_all_notes ()
2057 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2058 add_to_selection (*i);
2063 MidiRegionView::select_range (framepos_t start, framepos_t end)
2067 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2068 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2069 if (t >= start && t <= end) {
2070 add_to_selection (*i);
2076 MidiRegionView::invert_selection ()
2078 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2079 if ((*i)->selected()) {
2080 remove_from_selection(*i);
2082 add_to_selection (*i);
2088 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2090 uint8_t low_note = 127;
2091 uint8_t high_note = 0;
2092 MidiModel::Notes& notes (_model->notes());
2093 _optimization_iterator = _events.begin();
2099 if (extend && _selection.empty()) {
2105 /* scan existing selection to get note range */
2107 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2108 if ((*i)->note()->note() < low_note) {
2109 low_note = (*i)->note()->note();
2111 if ((*i)->note()->note() > high_note) {
2112 high_note = (*i)->note()->note();
2116 low_note = min (low_note, notenum);
2117 high_note = max (high_note, notenum);
2120 _no_sound_notes = true;
2122 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2124 boost::shared_ptr<NoteType> note (*n);
2125 CanvasNoteEvent* cne;
2126 bool select = false;
2128 if (((1 << note->channel()) & channel_mask) != 0) {
2130 if ((note->note() >= low_note && note->note() <= high_note)) {
2133 } else if (note->note() == notenum) {
2139 if ((cne = find_canvas_note (note)) != 0) {
2140 // extend is false because we've taken care of it,
2141 // since it extends by time range, not pitch.
2142 note_selected (cne, add, false);
2146 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2150 _no_sound_notes = false;
2154 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2156 MidiModel::Notes& notes (_model->notes());
2157 _optimization_iterator = _events.begin();
2159 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2161 boost::shared_ptr<NoteType> note (*n);
2162 CanvasNoteEvent* cne;
2164 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2165 if ((cne = find_canvas_note (note)) != 0) {
2166 if (cne->selected()) {
2167 note_deselected (cne);
2169 note_selected (cne, true, false);
2177 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2180 clear_selection_except (ev);
2181 if (!_selection.empty()) {
2182 PublicEditor& editor (trackview.editor());
2183 editor.get_selection().add (this);
2189 if (!ev->selected()) {
2190 add_to_selection (ev);
2194 /* find end of latest note selected, select all between that and the start of "ev" */
2196 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2197 Evoral::MusicalTime latest = 0;
2199 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2200 if ((*i)->note()->end_time() > latest) {
2201 latest = (*i)->note()->end_time();
2203 if ((*i)->note()->time() < earliest) {
2204 earliest = (*i)->note()->time();
2208 if (ev->note()->end_time() > latest) {
2209 latest = ev->note()->end_time();
2212 if (ev->note()->time() < earliest) {
2213 earliest = ev->note()->time();
2216 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2218 /* find notes entirely within OR spanning the earliest..latest range */
2220 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2221 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2222 add_to_selection (*i);
2230 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2232 remove_from_selection (ev);
2236 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2246 // TODO: Make this faster by storing the last updated selection rect, and only
2247 // adjusting things that are in the area that appears/disappeared.
2248 // We probably need a tree to be able to find events in O(log(n)) time.
2250 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2252 /* check if any corner of the note is inside the rect
2255 1) this is computing "touched by", not "contained by" the rect.
2256 2) this does not require that events be sorted in time.
2259 const double ix1 = (*i)->x1();
2260 const double ix2 = (*i)->x2();
2261 const double iy1 = (*i)->y1();
2262 const double iy2 = (*i)->y2();
2264 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2265 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2266 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2267 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2270 if (!(*i)->selected()) {
2271 add_to_selection (*i);
2273 } else if ((*i)->selected() && !extend) {
2274 // Not inside rectangle
2275 remove_from_selection (*i);
2281 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2287 // TODO: Make this faster by storing the last updated selection rect, and only
2288 // adjusting things that are in the area that appears/disappeared.
2289 // We probably need a tree to be able to find events in O(log(n)) time.
2291 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2293 /* check if any corner of the note is inside the rect
2296 1) this is computing "touched by", not "contained by" the rect.
2297 2) this does not require that events be sorted in time.
2300 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2301 // within y- (note-) range
2302 if (!(*i)->selected()) {
2303 add_to_selection (*i);
2305 } else if ((*i)->selected() && !extend) {
2306 // Not inside rectangle
2307 remove_from_selection (*i);
2313 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2315 Selection::iterator i = _selection.find (ev);
2317 if (i != _selection.end()) {
2318 _selection.erase (i);
2321 ev->set_selected (false);
2322 ev->hide_velocity ();
2324 if (_selection.empty()) {
2325 PublicEditor& editor (trackview.editor());
2326 editor.get_selection().remove (this);
2331 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2333 bool add_mrv_selection = false;
2335 if (_selection.empty()) {
2336 add_mrv_selection = true;
2339 if (_selection.insert (ev).second) {
2340 ev->set_selected (true);
2341 play_midi_note ((ev)->note());
2344 if (add_mrv_selection) {
2345 PublicEditor& editor (trackview.editor());
2346 editor.get_selection().add (this);
2351 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2353 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2354 PossibleChord to_play;
2355 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2357 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2358 if ((*i)->note()->time() < earliest) {
2359 earliest = (*i)->note()->time();
2363 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2364 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2365 to_play.push_back ((*i)->note());
2367 (*i)->move_event(dx, dy);
2370 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2372 if (to_play.size() > 1) {
2374 PossibleChord shifted;
2376 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2377 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2378 moved_note->set_note (moved_note->note() + cumulative_dy);
2379 shifted.push_back (moved_note);
2382 play_midi_chord (shifted);
2384 } else if (!to_play.empty()) {
2386 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2387 moved_note->set_note (moved_note->note() + cumulative_dy);
2388 play_midi_note (moved_note);
2394 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2396 assert (!_selection.empty());
2398 uint8_t lowest_note_in_selection = 127;
2399 uint8_t highest_note_in_selection = 0;
2400 uint8_t highest_note_difference = 0;
2402 // find highest and lowest notes first
2404 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2405 uint8_t pitch = (*i)->note()->note();
2406 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2407 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2411 cerr << "dnote: " << (int) dnote << endl;
2412 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2413 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2414 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2415 << int(highest_note_in_selection) << endl;
2416 cerr << "selection size: " << _selection.size() << endl;
2417 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2420 // Make sure the note pitch does not exceed the MIDI standard range
2421 if (highest_note_in_selection + dnote > 127) {
2422 highest_note_difference = highest_note_in_selection - 127;
2425 start_note_diff_command (_("move notes"));
2427 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2429 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2430 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2436 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2438 uint8_t original_pitch = (*i)->note()->note();
2439 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2441 // keep notes in standard midi range
2442 clamp_to_0_127(new_pitch);
2444 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2445 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2447 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2452 // care about notes being moved beyond the upper/lower bounds on the canvas
2453 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2454 highest_note_in_selection > midi_stream_view()->highest_note()) {
2455 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2459 /** @param x Pixel relative to the region position.
2460 * @return Snapped frame relative to the region position.
2463 MidiRegionView::snap_pixel_to_frame(double x)
2465 PublicEditor& editor (trackview.editor());
2466 return snap_frame_to_frame (editor.pixel_to_frame (x));
2469 /** @param x Pixel relative to the region position.
2470 * @return Snapped pixel relative to the region position.
2473 MidiRegionView::snap_to_pixel(double x)
2475 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2479 MidiRegionView::get_position_pixels()
2481 framepos_t region_frame = get_position();
2482 return trackview.editor().frame_to_pixel(region_frame);
2486 MidiRegionView::get_end_position_pixels()
2488 framepos_t frame = get_position() + get_duration ();
2489 return trackview.editor().frame_to_pixel(frame);
2493 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2495 /* the time converter will return the frame corresponding to `beats'
2496 relative to the start of the source. The start of the source
2497 is an implied position given by region->position - region->start
2499 const framepos_t source_start = _region->position() - _region->start();
2500 return source_start + _source_relative_time_converter.to (beats);
2504 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2506 /* the `frames' argument needs to be converted into a frame count
2507 relative to the start of the source before being passed in to the
2510 const framepos_t source_start = _region->position() - _region->start();
2511 return _source_relative_time_converter.from (frames - source_start);
2515 MidiRegionView::region_beats_to_region_frames(double beats) const
2517 return _region_relative_time_converter.to(beats);
2521 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2523 return _region_relative_time_converter.from(frames);
2527 MidiRegionView::begin_resizing (bool /*at_front*/)
2529 _resize_data.clear();
2531 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2532 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2534 // only insert CanvasNotes into the map
2536 NoteResizeData *resize_data = new NoteResizeData();
2537 resize_data->canvas_note = note;
2539 // create a new SimpleRect from the note which will be the resize preview
2540 SimpleRect *resize_rect = new SimpleRect(
2541 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2543 // calculate the colors: get the color settings
2544 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2545 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2548 // make the resize preview notes more transparent and bright
2549 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2551 // calculate color based on note velocity
2552 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2553 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2557 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2558 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2560 resize_data->resize_rect = resize_rect;
2561 _resize_data.push_back(resize_data);
2566 /** Update resizing notes while user drags.
2567 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2568 * @param at_front which end of the note (true == note on, false == note off)
2569 * @param delta_x change in mouse position since the start of the drag
2570 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2571 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2572 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2573 * as the \a primary note.
2576 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2578 bool cursor_set = false;
2580 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2581 SimpleRect* resize_rect = (*i)->resize_rect;
2582 CanvasNote* canvas_note = (*i)->canvas_note;
2587 current_x = canvas_note->x1() + delta_x;
2589 current_x = primary->x1() + delta_x;
2593 current_x = canvas_note->x2() + delta_x;
2595 current_x = primary->x2() + delta_x;
2600 resize_rect->property_x1() = snap_to_pixel(current_x);
2601 resize_rect->property_x2() = canvas_note->x2();
2603 resize_rect->property_x2() = snap_to_pixel(current_x);
2604 resize_rect->property_x1() = canvas_note->x1();
2610 beats = snap_pixel_to_frame (current_x);
2611 beats = region_frames_to_region_beats (beats);
2616 if (beats < canvas_note->note()->end_time()) {
2617 len = canvas_note->note()->time() - beats;
2618 len += canvas_note->note()->length();
2623 if (beats >= canvas_note->note()->time()) {
2624 len = beats - canvas_note->note()->time();
2631 snprintf (buf, sizeof (buf), "%.3g beats", len);
2632 show_verbose_cursor (buf, 0, 0);
2641 /** Finish resizing notes when the user releases the mouse button.
2642 * Parameters the same as for \a update_resizing().
2645 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2647 start_note_diff_command (_("resize notes"));
2649 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2650 CanvasNote* canvas_note = (*i)->canvas_note;
2651 SimpleRect* resize_rect = (*i)->resize_rect;
2653 /* Get the new x position for this resize, which is in pixels relative
2654 * to the region position.
2661 current_x = canvas_note->x1() + delta_x;
2663 current_x = primary->x1() + delta_x;
2667 current_x = canvas_note->x2() + delta_x;
2669 current_x = primary->x2() + delta_x;
2673 /* Convert that to a frame within the source */
2674 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2676 /* and then to beats */
2677 current_x = region_frames_to_region_beats (current_x);
2679 if (at_front && current_x < canvas_note->note()->end_time()) {
2680 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2682 double len = canvas_note->note()->time() - current_x;
2683 len += canvas_note->note()->length();
2686 /* XXX convert to beats */
2687 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2692 double len = current_x - canvas_note->note()->time();
2695 /* XXX convert to beats */
2696 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2704 _resize_data.clear();
2709 MidiRegionView::abort_resizing ()
2711 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2712 delete (*i)->resize_rect;
2716 _resize_data.clear ();
2720 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2722 uint8_t new_velocity;
2725 new_velocity = event->note()->velocity() + velocity;
2726 clamp_to_0_127(new_velocity);
2728 new_velocity = velocity;
2731 event->set_selected (event->selected()); // change color
2733 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2737 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2742 new_note = event->note()->note() + note;
2747 clamp_to_0_127 (new_note);
2748 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2752 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2754 bool change_start = false;
2755 bool change_length = false;
2756 Evoral::MusicalTime new_start = 0;
2757 Evoral::MusicalTime new_length = 0;
2759 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2761 front_delta: if positive - move the start of the note later in time (shortening it)
2762 if negative - move the start of the note earlier in time (lengthening it)
2764 end_delta: if positive - move the end of the note later in time (lengthening it)
2765 if negative - move the end of the note earlier in time (shortening it)
2769 if (front_delta < 0) {
2771 if (event->note()->time() < -front_delta) {
2774 new_start = event->note()->time() + front_delta; // moves earlier
2777 /* start moved toward zero, so move the end point out to where it used to be.
2778 Note that front_delta is negative, so this increases the length.
2781 new_length = event->note()->length() - front_delta;
2782 change_start = true;
2783 change_length = true;
2787 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2789 if (new_pos < event->note()->end_time()) {
2790 new_start = event->note()->time() + front_delta;
2791 /* start moved toward the end, so move the end point back to where it used to be */
2792 new_length = event->note()->length() - front_delta;
2793 change_start = true;
2794 change_length = true;
2801 bool can_change = true;
2802 if (end_delta < 0) {
2803 if (event->note()->length() < -end_delta) {
2809 new_length = event->note()->length() + end_delta;
2810 change_length = true;
2815 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2818 if (change_length) {
2819 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2824 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2826 uint8_t new_channel;
2830 if (event->note()->channel() < -chn) {
2833 new_channel = event->note()->channel() + chn;
2836 new_channel = event->note()->channel() + chn;
2839 new_channel = (uint8_t) chn;
2842 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2846 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2848 Evoral::MusicalTime new_time;
2852 if (event->note()->time() < -delta) {
2855 new_time = event->note()->time() + delta;
2858 new_time = event->note()->time() + delta;
2864 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2868 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2870 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2874 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2879 if (_selection.empty()) {
2894 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2895 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2901 start_note_diff_command (_("change velocities"));
2903 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2904 Selection::iterator next = i;
2908 if (i == _selection.begin()) {
2909 change_note_velocity (*i, delta, true);
2910 value = (*i)->note()->velocity() + delta;
2912 change_note_velocity (*i, value, false);
2916 change_note_velocity (*i, delta, true);
2924 if (!_selection.empty()) {
2926 snprintf (buf, sizeof (buf), "Vel %d",
2927 (int) (*_selection.begin())->note()->velocity());
2928 show_verbose_cursor (buf, 10, 10);
2934 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2936 if (_selection.empty()) {
2953 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2955 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2959 if ((int8_t) (*i)->note()->note() + delta > 127) {
2966 start_note_diff_command (_("transpose"));
2968 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2969 Selection::iterator next = i;
2971 change_note_note (*i, delta, true);
2979 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2985 /* grab the current grid distance */
2987 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2989 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2990 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3000 start_note_diff_command (_("change note lengths"));
3002 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3003 Selection::iterator next = i;
3006 /* note the negation of the delta for start */
3008 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3017 MidiRegionView::nudge_notes (bool forward)
3019 if (_selection.empty()) {
3023 /* pick a note as the point along the timeline to get the nudge distance.
3024 its not necessarily the earliest note, so we may want to pull the notes out
3025 into a vector and sort before using the first one.
3028 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3030 framecnt_t distance;
3032 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3034 /* grid is off - use nudge distance */
3036 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3042 framepos_t next_pos = ref_point;
3045 if (max_framepos - 1 < next_pos) {
3049 if (next_pos == 0) {
3055 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3056 distance = ref_point - next_pos;
3059 if (distance == 0) {
3063 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3069 start_note_diff_command (_("nudge"));
3071 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3072 Selection::iterator next = i;
3074 change_note_time (*i, delta, true);
3082 MidiRegionView::change_channel(uint8_t channel)
3084 start_note_diff_command(_("change channel"));
3085 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3086 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3094 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3096 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3098 pre_enter_cursor = editor->get_canvas_cursor ();
3100 if (_mouse_state == SelectTouchDragging) {
3101 note_selected (ev, true);
3104 show_verbose_cursor (ev->note ());
3108 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3110 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3112 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3113 (*i)->hide_velocity ();
3116 editor->verbose_cursor()->hide ();
3118 if (pre_enter_cursor) {
3119 editor->set_canvas_cursor (pre_enter_cursor);
3120 pre_enter_cursor = 0;
3125 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3128 /* XXX should get patch name if we can */
3129 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3130 show_verbose_cursor (s.str(), 10, 20);
3134 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3136 trackview.editor().verbose_cursor()->hide ();
3140 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3142 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3143 Editing::MouseMode mm = editor->current_mouse_mode();
3144 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3146 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3147 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3148 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3149 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3151 if (pre_enter_cursor && can_set_cursor) {
3152 editor->set_canvas_cursor (pre_enter_cursor);
3158 MidiRegionView::set_frame_color()
3162 TimeAxisViewItem::set_frame_color ();
3169 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3170 } else if (high_enough_for_name) {
3171 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3176 if (!rect_visible) {
3177 f = UINT_RGBA_CHANGE_A (f, 0);
3180 frame->property_fill_color_rgba() = f;
3184 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3186 if (mode == ForceChannel) {
3187 mask = 0xFFFF; // Show all notes as active (below)
3190 // Update notes for selection
3191 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3192 (*i)->on_channel_selection_change(mask);
3195 _last_channel_selection = mask;
3197 _patch_changes.clear ();
3198 display_patch_changes ();
3202 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3204 _model_name = model;
3205 _custom_device_mode = custom_device_mode;
3210 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3212 if (_selection.empty()) {
3216 PublicEditor& editor (trackview.editor());
3220 /* XXX what to do ? */
3224 editor.get_cut_buffer().add (selection_as_cut_buffer());
3232 start_note_diff_command();
3234 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3241 note_diff_remove_note (*i);
3251 MidiRegionView::selection_as_cut_buffer () const
3255 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3256 NoteType* n = (*i)->note().get();
3257 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3260 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3266 /** This method handles undo */
3268 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3274 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3276 trackview.session()->begin_reversible_command (_("paste"));
3278 start_note_diff_command (_("paste"));
3280 Evoral::MusicalTime beat_delta;
3281 Evoral::MusicalTime paste_pos_beats;
3282 Evoral::MusicalTime duration;
3283 Evoral::MusicalTime end_point = 0;
3285 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3286 paste_pos_beats = absolute_frames_to_source_beats (pos);
3287 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3288 paste_pos_beats = 0;
3290 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",
3291 (*mcb.notes().begin())->time(),
3292 (*mcb.notes().rbegin())->end_time(),
3293 duration, pos, _region->position(),
3294 paste_pos_beats, beat_delta));
3298 for (int n = 0; n < (int) times; ++n) {
3300 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3302 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3303 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3305 /* make all newly added notes selected */
3307 note_diff_add_note (copied_note, true);
3308 end_point = copied_note->end_time();
3311 paste_pos_beats += duration;
3314 /* if we pasted past the current end of the region, extend the region */
3316 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3317 framepos_t region_end = _region->position() + _region->length() - 1;
3319 if (end_frame > region_end) {
3321 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3323 _region->clear_changes ();
3324 _region->set_length (end_frame - _region->position());
3325 trackview.session()->add_command (new StatefulDiffCommand (_region));
3330 trackview.session()->commit_reversible_command ();
3333 struct EventNoteTimeEarlyFirstComparator {
3334 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3335 return a->note()->time() < b->note()->time();
3340 MidiRegionView::time_sort_events ()
3342 if (!_sort_needed) {
3346 EventNoteTimeEarlyFirstComparator cmp;
3349 _sort_needed = false;
3353 MidiRegionView::goto_next_note (bool add_to_selection)
3355 bool use_next = false;
3357 if (_events.back()->selected()) {
3361 time_sort_events ();
3363 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3364 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3366 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3367 if ((*i)->selected()) {
3370 } else if (use_next) {
3371 if (channel_mask & (1 << (*i)->note()->channel())) {
3372 if (!add_to_selection) {
3375 note_selected (*i, true, false);
3382 /* use the first one */
3384 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3385 unique_select (_events.front());
3390 MidiRegionView::goto_previous_note (bool add_to_selection)
3392 bool use_next = false;
3394 if (_events.front()->selected()) {
3398 time_sort_events ();
3400 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3401 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3403 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3404 if ((*i)->selected()) {
3407 } else if (use_next) {
3408 if (channel_mask & (1 << (*i)->note()->channel())) {
3409 if (!add_to_selection) {
3412 note_selected (*i, true, false);
3419 /* use the last one */
3421 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3422 unique_select (*(_events.rbegin()));
3427 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3429 bool had_selected = false;
3431 time_sort_events ();
3433 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3434 if ((*i)->selected()) {
3435 selected.insert ((*i)->note());
3436 had_selected = true;
3440 if (allow_all_if_none_selected && !had_selected) {
3441 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3442 selected.insert ((*i)->note());
3448 MidiRegionView::update_ghost_note (double x, double y)
3450 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3455 _note_group->w2i (x, y);
3457 PublicEditor& editor = trackview.editor ();
3459 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3460 framecnt_t grid_frames;
3461 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3463 /* use region_frames... because we are converting a delta within the region
3467 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3473 /* note that this sets the time of the ghost note in beats relative to
3474 the start of the source; that is how all note times are stored.
3476 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3477 _ghost_note->note()->set_length (length);
3478 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3479 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3481 /* the ghost note does not appear in ghost regions, so pass false in here */
3482 update_note (_ghost_note, false);
3484 show_verbose_cursor (_ghost_note->note ());
3488 MidiRegionView::create_ghost_note (double x, double y)
3490 remove_ghost_note ();
3492 boost::shared_ptr<NoteType> g (new NoteType);
3493 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3494 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3495 update_ghost_note (x, y);
3496 _ghost_note->show ();
3501 show_verbose_cursor (_ghost_note->note ());
3505 MidiRegionView::snap_changed ()
3511 create_ghost_note (_last_ghost_x, _last_ghost_y);
3515 MidiRegionView::drop_down_keys ()
3517 _mouse_state = None;
3521 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3523 double note = midi_stream_view()->y_to_note(y);
3525 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3527 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3529 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3530 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3531 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3532 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3537 bool add_mrv_selection = false;
3539 if (_selection.empty()) {
3540 add_mrv_selection = true;
3543 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3544 if (_selection.insert (*i).second) {
3545 (*i)->set_selected (true);
3549 if (add_mrv_selection) {
3550 PublicEditor& editor (trackview.editor());
3551 editor.get_selection().add (this);
3556 MidiRegionView::color_handler ()
3558 RegionView::color_handler ();
3560 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3561 (*i)->set_selected ((*i)->selected()); // will change color
3564 /* XXX probably more to do here */
3568 MidiRegionView::enable_display (bool yn)
3570 RegionView::enable_display (yn);
3577 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3579 if (_step_edit_cursor == 0) {
3580 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3582 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3583 _step_edit_cursor->property_y1() = 0;
3584 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3585 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3586 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3589 move_step_edit_cursor (pos);
3590 _step_edit_cursor->show ();
3594 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3596 _step_edit_cursor_position = pos;
3598 if (_step_edit_cursor) {
3599 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3600 _step_edit_cursor->property_x1() = pixel;
3601 set_step_edit_cursor_width (_step_edit_cursor_width);
3606 MidiRegionView::hide_step_edit_cursor ()
3608 if (_step_edit_cursor) {
3609 _step_edit_cursor->hide ();
3614 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3616 _step_edit_cursor_width = beats;
3618 if (_step_edit_cursor) {
3619 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3623 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3624 * @param w Source that the data will end up in.
3627 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3629 if (!_active_notes) {
3630 /* we aren't actively being recorded to */
3634 boost::shared_ptr<MidiSource> src = w.lock ();
3635 if (!src || src != midi_region()->midi_source()) {
3636 /* recorded data was not destined for our source */
3640 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3642 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3644 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3646 framepos_t back = max_framepos;
3648 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3649 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3650 assert (ev.buffer ());
3652 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3653 frames from the start of the source, and so time_beats is in terms of the
3657 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3659 if (ev.type() == MIDI_CMD_NOTE_ON) {
3661 boost::shared_ptr<NoteType> note (
3662 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3665 add_note (note, true);
3667 /* fix up our note range */
3668 if (ev.note() < _current_range_min) {
3669 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3670 } else if (ev.note() > _current_range_max) {
3671 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3674 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3675 resolve_note (ev.note (), time_beats);
3681 midi_stream_view()->check_record_layers (region(), back);
3685 MidiRegionView::trim_front_starting ()
3687 /* Reparent the note group to the region view's parent, so that it doesn't change
3688 when the region view is trimmed.
3690 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3691 _temporary_note_group->move (group->property_x(), group->property_y());
3692 _note_group->reparent (*_temporary_note_group);
3696 MidiRegionView::trim_front_ending ()
3698 _note_group->reparent (*group);
3699 delete _temporary_note_group;
3700 _temporary_note_group = 0;
3702 if (_region->start() < 0) {
3703 /* Trim drag made start time -ve; fix this */
3704 midi_region()->fix_negative_start ();
3709 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3711 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), _model_name, _custom_device_mode, Gtk::Stock::APPLY);
3712 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3716 change_patch_change (pc->patch(), d.patch ());
3721 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3724 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3725 Evoral::midi_note_name (n->note()).c_str(),
3727 (int) n->channel() + 1,
3728 (int) n->velocity());
3730 show_verbose_cursor (buf, 10, 20);
3734 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3738 trackview.editor().get_pointer_position (wx, wy);
3743 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3745 double x1, y1, x2, y2;
3746 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3748 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3749 wy -= (y2 - y1) + 2 * yoffset;
3752 trackview.editor().verbose_cursor()->set (text, wx, wy);
3753 trackview.editor().verbose_cursor()->show ();
3756 /** @param p A session framepos.
3757 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3758 * @return p snapped to the grid subdivision underneath it.
3761 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3763 PublicEditor& editor = trackview.editor ();
3766 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3772 grid_frames = region_beats_to_region_frames (grid_beats);
3774 /* Hack so that we always snap to the note that we are over, instead of snapping
3775 to the next one if we're more than halfway through the one we're over.
3777 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3778 p -= grid_frames / 2;
3781 return snap_frame_to_frame (p);
3784 /** Called when the selection has been cleared in any MidiRegionView.
3785 * @param rv MidiRegionView that the selection was cleared in.
3788 MidiRegionView::selection_cleared (MidiRegionView* rv)
3794 /* Clear our selection in sympathy; but don't signal the fact */
3795 clear_selection (false);