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)
115 _note_group->raise_to_top();
116 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
118 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
119 connect_to_diskstream ();
121 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
124 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
125 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
126 TimeAxisViewItem::Visibility visibility)
127 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
128 , _last_channel_selection(0xFFFF)
129 , _current_range_min(0)
130 , _current_range_max(0)
132 , _note_group(new ArdourCanvas::Group(*parent))
133 , _note_diff_command (0)
135 , _step_edit_cursor (0)
136 , _step_edit_cursor_width (1.0)
137 , _step_edit_cursor_position (0.0)
138 , _channel_selection_scoped_note (0)
139 , _temporary_note_group (0)
142 , _sort_needed (true)
143 , _optimization_iterator (_events.end())
145 , _no_sound_notes (false)
148 , pre_enter_cursor (0)
149 , pre_press_cursor (0)
152 _note_group->raise_to_top();
153 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
155 connect_to_diskstream ();
157 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
161 MidiRegionView::parameter_changed (std::string const & p)
163 if (p == "diplay-first-midi-bank-as-zero") {
164 if (_enable_display) {
170 MidiRegionView::MidiRegionView (const MidiRegionView& other)
171 : sigc::trackable(other)
173 , _last_channel_selection(0xFFFF)
174 , _current_range_min(0)
175 , _current_range_max(0)
177 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
178 , _note_diff_command (0)
180 , _step_edit_cursor (0)
181 , _step_edit_cursor_width (1.0)
182 , _step_edit_cursor_position (0.0)
183 , _channel_selection_scoped_note (0)
184 , _temporary_note_group (0)
187 , _sort_needed (true)
188 , _optimization_iterator (_events.end())
190 , _no_sound_notes (false)
193 , pre_enter_cursor (0)
194 , pre_press_cursor (0)
200 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
201 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
206 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
207 : RegionView (other, boost::shared_ptr<Region> (region))
208 , _last_channel_selection(0xFFFF)
209 , _current_range_min(0)
210 , _current_range_max(0)
212 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
213 , _note_diff_command (0)
215 , _step_edit_cursor (0)
216 , _step_edit_cursor_width (1.0)
217 , _step_edit_cursor_position (0.0)
218 , _channel_selection_scoped_note (0)
219 , _temporary_note_group (0)
222 , _sort_needed (true)
223 , _optimization_iterator (_events.end())
225 , _no_sound_notes (false)
228 , pre_enter_cursor (0)
229 , pre_press_cursor (0)
235 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
236 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
242 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
244 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
246 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
247 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
251 midi_region()->midi_source(0)->load_model();
254 _model = midi_region()->midi_source(0)->model();
255 _enable_display = false;
257 RegionView::init (basic_color, false);
259 compute_colors (basic_color);
261 set_height (trackview.current_height());
264 region_sync_changed ();
265 region_resized (ARDOUR::bounds_change);
270 _enable_display = true;
273 display_model (_model);
277 reset_width_dependent_items (_pixel_width);
279 group->raise_to_top();
280 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
282 midi_view()->signal_channel_mode_changed().connect(
283 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
285 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
286 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
288 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
289 boost::bind (&MidiRegionView::snap_changed, this),
292 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
293 connect_to_diskstream ();
295 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
299 MidiRegionView::instrument_info () const
301 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
302 return route_ui->route()->instrument_info();
305 const boost::shared_ptr<ARDOUR::MidiRegion>
306 MidiRegionView::midi_region() const
308 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
312 MidiRegionView::connect_to_diskstream ()
314 midi_view()->midi_track()->DataRecorded.connect(
315 *this, invalidator(*this),
316 boost::bind (&MidiRegionView::data_recorded, this, _1),
321 MidiRegionView::canvas_event(GdkEvent* ev)
326 case GDK_ENTER_NOTIFY:
327 case GDK_LEAVE_NOTIFY:
328 _last_event_x = ev->crossing.x;
329 _last_event_y = ev->crossing.y;
331 case GDK_MOTION_NOTIFY:
332 _last_event_x = ev->motion.x;
333 _last_event_y = ev->motion.y;
339 if (ev->type == GDK_2BUTTON_PRESS) {
340 return trackview.editor().toggle_internal_editing_from_double_click (ev);
343 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
344 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
345 (trackview.editor().current_mouse_mode() == MouseZoom)) {
346 // handle non-draw modes elsewhere
352 return scroll (&ev->scroll);
355 return key_press (&ev->key);
357 case GDK_KEY_RELEASE:
358 return key_release (&ev->key);
360 case GDK_BUTTON_PRESS:
361 return button_press (&ev->button);
363 case GDK_BUTTON_RELEASE:
364 r = button_release (&ev->button);
369 case GDK_ENTER_NOTIFY:
370 return enter_notify (&ev->crossing);
372 case GDK_LEAVE_NOTIFY:
373 return leave_notify (&ev->crossing);
375 case GDK_MOTION_NOTIFY:
376 return motion (&ev->motion);
386 MidiRegionView::remove_ghost_note ()
393 MidiRegionView::enter_notify (GdkEventCrossing* ev)
395 trackview.editor().MouseModeChanged.connect (
396 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
399 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
400 create_ghost_note (ev->x, ev->y);
403 if (!trackview.editor().internal_editing()) {
404 Keyboard::magic_widget_drop_focus();
406 Keyboard::magic_widget_grab_focus();
410 // if current operation is non-operational in a midi region, change the cursor to so indicate
411 if (trackview.editor().current_mouse_mode() == MouseGain) {
412 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
413 pre_enter_cursor = editor->get_canvas_cursor();
414 editor->set_canvas_cursor(editor->cursors()->timebar);
421 MidiRegionView::leave_notify (GdkEventCrossing*)
423 _mouse_mode_connection.disconnect ();
425 trackview.editor().verbose_cursor()->hide ();
426 remove_ghost_note ();
428 if (pre_enter_cursor) {
429 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
430 editor->set_canvas_cursor(pre_enter_cursor);
437 MidiRegionView::mouse_mode_changed ()
439 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
440 create_ghost_note (_last_event_x, _last_event_y);
442 remove_ghost_note ();
443 trackview.editor().verbose_cursor()->hide ();
446 if (!trackview.editor().internal_editing()) {
447 Keyboard::magic_widget_drop_focus();
449 Keyboard::magic_widget_grab_focus();
455 MidiRegionView::button_press (GdkEventButton* ev)
457 if (ev->button != 1) {
461 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
462 MouseMode m = editor->current_mouse_mode();
464 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
465 pre_press_cursor = editor->get_canvas_cursor ();
466 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
469 if (_mouse_state != SelectTouchDragging) {
471 _pressed_button = ev->button;
472 _mouse_state = Pressed;
477 _pressed_button = ev->button;
483 MidiRegionView::button_release (GdkEventButton* ev)
485 double event_x, event_y;
487 if (ev->button != 1) {
494 group->w2i(event_x, event_y);
495 group->ungrab(ev->time);
497 PublicEditor& editor = trackview.editor ();
499 if (pre_press_cursor) {
500 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
501 pre_press_cursor = 0;
504 switch (_mouse_state) {
505 case Pressed: // Clicked
507 switch (editor.current_mouse_mode()) {
509 /* no motion occured - simple click */
518 if (Keyboard::is_insert_note_event(ev)) {
520 double event_x, event_y;
524 group->w2i(event_x, event_y);
527 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
533 /* Shorten the length by 1 tick so that we can add a new note at the next
534 grid snap without it overlapping this one.
536 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
538 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
546 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
552 /* Shorten the length by 1 tick so that we can add a new note at the next
553 grid snap without it overlapping this one.
555 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
557 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
568 case SelectRectDragging:
570 editor.drags()->end_grab ((GdkEvent *) ev);
572 create_ghost_note (ev->x, ev->y);
584 MidiRegionView::motion (GdkEventMotion* ev)
586 PublicEditor& editor = trackview.editor ();
588 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
589 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
590 _mouse_state != AddDragging) {
592 create_ghost_note (ev->x, ev->y);
594 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
595 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
597 update_ghost_note (ev->x, ev->y);
599 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
601 remove_ghost_note ();
602 editor.verbose_cursor()->hide ();
604 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
606 update_ghost_note (ev->x, ev->y);
609 /* any motion immediately hides velocity text that may have been visible */
611 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
612 (*i)->hide_velocity ();
615 switch (_mouse_state) {
618 if (_pressed_button == 1) {
620 MouseMode m = editor.current_mouse_mode();
622 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
624 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
625 _mouse_state = AddDragging;
626 remove_ghost_note ();
627 editor.verbose_cursor()->hide ();
629 } else if (m == MouseObject) {
630 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
632 _mouse_state = SelectRectDragging;
634 } else if (m == MouseRange) {
635 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
636 _mouse_state = SelectVerticalDragging;
643 case SelectRectDragging:
644 case SelectVerticalDragging:
646 editor.drags()->motion_handler ((GdkEvent *) ev, false);
649 case SelectTouchDragging:
661 MidiRegionView::scroll (GdkEventScroll* ev)
663 if (_selection.empty()) {
667 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
668 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
669 it still works for zoom.
674 trackview.editor().verbose_cursor()->hide ();
676 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
677 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
679 if (ev->direction == GDK_SCROLL_UP) {
680 change_velocities (true, fine, false, together);
681 } else if (ev->direction == GDK_SCROLL_DOWN) {
682 change_velocities (false, fine, false, together);
684 /* left, right: we don't use them */
692 MidiRegionView::key_press (GdkEventKey* ev)
694 /* since GTK bindings are generally activated on press, and since
695 detectable auto-repeat is the name of the game and only sends
696 repeated presses, carry out key actions at key press, not release.
699 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
701 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
702 _mouse_state = SelectTouchDragging;
705 } else if (ev->keyval == GDK_Escape && unmodified) {
709 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
711 bool start = (ev->keyval == GDK_comma);
712 bool end = (ev->keyval == GDK_period);
713 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
714 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
716 change_note_lengths (fine, shorter, 0.0, start, end);
720 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
722 if (_selection.empty()) {
729 } else if (ev->keyval == GDK_Tab) {
731 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
732 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
734 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
738 } else if (ev->keyval == GDK_ISO_Left_Tab) {
740 /* Shift-TAB generates ISO Left Tab, for some reason */
742 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
743 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
745 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
751 } else if (ev->keyval == GDK_Up) {
753 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
754 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
755 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
757 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
758 change_velocities (true, fine, allow_smush, together);
760 transpose (true, fine, allow_smush);
764 } else if (ev->keyval == GDK_Down) {
766 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
767 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
768 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
770 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
771 change_velocities (false, fine, allow_smush, together);
773 transpose (false, fine, allow_smush);
777 } else if (ev->keyval == GDK_Left && unmodified) {
782 } else if (ev->keyval == GDK_Right && unmodified) {
787 } else if (ev->keyval == GDK_c && unmodified) {
791 } else if (ev->keyval == GDK_v && unmodified) {
800 MidiRegionView::key_release (GdkEventKey* ev)
802 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
810 MidiRegionView::channel_edit ()
812 if (_selection.empty()) {
816 /* pick a note somewhat at random (since Selection is a set<>) to
817 * provide the "current" channel for the dialog.
820 uint8_t current_channel = (*_selection.begin())->note()->channel ();
821 MidiChannelDialog channel_dialog (current_channel);
822 int ret = channel_dialog.run ();
825 case Gtk::RESPONSE_OK:
831 uint8_t new_channel = channel_dialog.active_channel ();
833 start_note_diff_command (_("channel edit"));
835 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
836 Selection::iterator next = i;
838 change_note_channel (*i, new_channel);
846 MidiRegionView::velocity_edit ()
848 if (_selection.empty()) {
852 /* pick a note somewhat at random (since Selection is a set<>) to
853 * provide the "current" velocity for the dialog.
856 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
857 MidiVelocityDialog velocity_dialog (current_velocity);
858 int ret = velocity_dialog.run ();
861 case Gtk::RESPONSE_OK:
867 uint8_t new_velocity = velocity_dialog.velocity ();
869 start_note_diff_command (_("velocity edit"));
871 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
872 Selection::iterator next = i;
874 change_note_velocity (*i, new_velocity, false);
882 MidiRegionView::show_list_editor ()
885 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
887 _list_editor->present ();
890 /** Add a note to the model, and the view, at a canvas (click) coordinate.
891 * \param t time in frames relative to the position of the region
892 * \param y vertical position in pixels
893 * \param length duration of the note in beats
894 * \param snap_t true to snap t to the grid, otherwise false.
897 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
899 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
900 MidiStreamView* const view = mtv->midi_view();
902 double note = view->y_to_note(y);
905 assert(note <= 127.0);
907 // Start of note in frames relative to region start
909 framecnt_t grid_frames;
910 t = snap_frame_to_grid_underneath (t, grid_frames);
914 assert (length != 0);
916 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
917 region_frames_to_region_beats(t + _region->start()),
919 (uint8_t)note, 0x40));
921 if (_model->contains (new_note)) {
925 view->update_note_range(new_note->note());
927 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
929 _model->apply_command(*trackview.session(), cmd);
931 play_midi_note (new_note);
935 MidiRegionView::clear_events (bool with_selection_signal)
937 clear_selection (with_selection_signal);
940 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
941 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
946 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
951 _patch_changes.clear();
953 _optimization_iterator = _events.end();
957 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
961 content_connection.disconnect ();
962 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
966 if (_enable_display) {
972 MidiRegionView::start_note_diff_command (string name)
974 if (!_note_diff_command) {
975 _note_diff_command = _model->new_note_diff_command (name);
980 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
982 if (_note_diff_command) {
983 _note_diff_command->add (note);
986 _marked_for_selection.insert(note);
989 _marked_for_velocity.insert(note);
994 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
996 if (_note_diff_command && ev->note()) {
997 _note_diff_command->remove(ev->note());
1002 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1003 MidiModel::NoteDiffCommand::Property property,
1006 if (_note_diff_command) {
1007 _note_diff_command->change (ev->note(), property, val);
1012 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1013 MidiModel::NoteDiffCommand::Property property,
1014 Evoral::MusicalTime val)
1016 if (_note_diff_command) {
1017 _note_diff_command->change (ev->note(), property, val);
1022 MidiRegionView::apply_diff (bool as_subcommand)
1026 if (!_note_diff_command) {
1030 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1031 // Mark all selected notes for selection when model reloads
1032 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1033 _marked_for_selection.insert((*i)->note());
1037 if (as_subcommand) {
1038 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1040 _model->apply_command (*trackview.session(), _note_diff_command);
1043 _note_diff_command = 0;
1044 midi_view()->midi_track()->playlist_modified();
1046 if (add_or_remove) {
1047 _marked_for_selection.clear();
1050 _marked_for_velocity.clear();
1054 MidiRegionView::abort_command()
1056 delete _note_diff_command;
1057 _note_diff_command = 0;
1062 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1064 if (_optimization_iterator != _events.end()) {
1065 ++_optimization_iterator;
1068 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1069 return *_optimization_iterator;
1072 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1073 if ((*_optimization_iterator)->note() == note) {
1074 return *_optimization_iterator;
1082 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1084 MidiModel::Notes notes;
1085 _model->get_notes (notes, op, val, chan_mask);
1087 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1088 CanvasNoteEvent* cne = find_canvas_note (*n);
1096 MidiRegionView::redisplay_model()
1098 // Don't redisplay the model if we're currently recording and displaying that
1099 if (_active_notes) {
1107 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1108 (*i)->invalidate ();
1111 MidiModel::ReadLock lock(_model->read_lock());
1113 MidiModel::Notes& notes (_model->notes());
1114 _optimization_iterator = _events.begin();
1116 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1118 boost::shared_ptr<NoteType> note (*n);
1119 CanvasNoteEvent* cne;
1122 if (note_in_region_range (note, visible)) {
1124 if ((cne = find_canvas_note (note)) != 0) {
1131 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1133 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1145 add_note (note, visible);
1150 if ((cne = find_canvas_note (note)) != 0) {
1158 /* remove note items that are no longer valid */
1160 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1161 if (!(*i)->valid ()) {
1163 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1164 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1166 gr->remove_note (*i);
1171 i = _events.erase (i);
1178 _patch_changes.clear();
1182 display_patch_changes ();
1184 _marked_for_selection.clear ();
1185 _marked_for_velocity.clear ();
1187 /* we may have caused _events to contain things out of order (e.g. if a note
1188 moved earlier or later). we don't generally need them in time order, but
1189 make a note that a sort is required for those cases that require it.
1192 _sort_needed = true;
1196 MidiRegionView::display_patch_changes ()
1198 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1199 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1201 for (uint8_t i = 0; i < 16; ++i) {
1202 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1206 /** @param active_channel true to display patch changes fully, false to display
1207 * them `greyed-out' (as on an inactive channel)
1210 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1212 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1214 if ((*i)->channel() != channel) {
1218 string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1219 add_canvas_patch_change (*i, patch_name, active_channel);
1224 MidiRegionView::display_sysexes()
1226 bool have_periodic_system_messages = false;
1227 bool display_periodic_messages = true;
1229 if (!Config->get_never_display_periodic_midi()) {
1231 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1232 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1233 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1236 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1237 have_periodic_system_messages = true;
1243 if (have_periodic_system_messages) {
1244 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1246 /* get an approximate value for the number of samples per video frame */
1248 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1250 /* if we are zoomed out beyond than the cutoff (i.e. more
1251 * frames per pixel than frames per 4 video frames), don't
1252 * show periodic sysex messages.
1255 if (zoom > (video_frame*4)) {
1256 display_periodic_messages = false;
1260 display_periodic_messages = false;
1263 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1265 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1266 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1268 Evoral::MusicalTime time = (*i)->time();
1272 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1273 if (!display_periodic_messages) {
1281 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1282 str << int((*i)->buffer()[b]);
1283 if (b != (*i)->size() -1) {
1287 string text = str.str();
1289 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1291 double height = midi_stream_view()->contents_height();
1293 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1294 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1296 // Show unless message is beyond the region bounds
1297 if (time - _region->start() >= _region->length() || time < _region->start()) {
1303 _sys_exes.push_back(sysex);
1307 MidiRegionView::~MidiRegionView ()
1309 in_destructor = true;
1311 trackview.editor().verbose_cursor()->hide ();
1313 note_delete_connection.disconnect ();
1315 delete _list_editor;
1317 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1319 if (_active_notes) {
1323 _selection_cleared_connection.disconnect ();
1326 clear_events (false);
1329 delete _note_diff_command;
1330 delete _step_edit_cursor;
1331 delete _temporary_note_group;
1335 MidiRegionView::region_resized (const PropertyChange& what_changed)
1337 RegionView::region_resized(what_changed);
1339 if (what_changed.contains (ARDOUR::Properties::position)) {
1340 set_duration(_region->length(), 0);
1341 if (_enable_display) {
1348 MidiRegionView::reset_width_dependent_items (double pixel_width)
1350 RegionView::reset_width_dependent_items(pixel_width);
1351 assert(_pixel_width == pixel_width);
1353 if (_enable_display) {
1357 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1358 if ((*x)->width() >= _pixel_width) {
1365 move_step_edit_cursor (_step_edit_cursor_position);
1366 set_step_edit_cursor_width (_step_edit_cursor_width);
1370 MidiRegionView::set_height (double height)
1372 static const double FUDGE = 2.0;
1373 const double old_height = _height;
1374 RegionView::set_height(height);
1375 _height = height - FUDGE;
1377 apply_note_range(midi_stream_view()->lowest_note(),
1378 midi_stream_view()->highest_note(),
1379 height != old_height + FUDGE);
1382 name_pixbuf->raise_to_top();
1385 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1386 (*x)->set_height (midi_stream_view()->contents_height());
1389 if (_step_edit_cursor) {
1390 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1395 /** Apply the current note range from the stream view
1396 * by repositioning/hiding notes as necessary
1399 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1401 if (!_enable_display) {
1405 if (!force && _current_range_min == min && _current_range_max == max) {
1409 _current_range_min = min;
1410 _current_range_max = max;
1412 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1413 CanvasNoteEvent* event = *i;
1414 boost::shared_ptr<NoteType> note (event->note());
1416 if (note->note() < _current_range_min ||
1417 note->note() > _current_range_max) {
1423 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1425 const double y1 = midi_stream_view()->note_to_y(note->note());
1426 const double y2 = y1 + floor(midi_stream_view()->note_height());
1428 cnote->property_y1() = y1;
1429 cnote->property_y2() = y2;
1431 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1433 const double diamond_size = update_hit (chit);
1435 chit->set_height (diamond_size);
1441 MidiRegionView::add_ghost (TimeAxisView& tv)
1445 double unit_position = _region->position () / samples_per_unit;
1446 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1447 MidiGhostRegion* ghost;
1449 if (mtv && mtv->midi_view()) {
1450 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1451 to allow having midi notes on top of note lines and waveforms.
1453 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1455 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1458 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1459 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1460 ghost->add_note(note);
1464 ghost->set_height ();
1465 ghost->set_duration (_region->length() / samples_per_unit);
1466 ghosts.push_back (ghost);
1468 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1474 /** Begin tracking note state for successive calls to add_event
1477 MidiRegionView::begin_write()
1479 assert(!_active_notes);
1480 _active_notes = new CanvasNote*[128];
1481 for (unsigned i=0; i < 128; ++i) {
1482 _active_notes[i] = 0;
1487 /** Destroy note state for add_event
1490 MidiRegionView::end_write()
1492 delete[] _active_notes;
1494 _marked_for_selection.clear();
1495 _marked_for_velocity.clear();
1499 /** Resolve an active MIDI note (while recording).
1502 MidiRegionView::resolve_note(uint8_t note, double end_time)
1504 if (midi_view()->note_mode() != Sustained) {
1508 if (_active_notes && _active_notes[note]) {
1510 /* XXX is end_time really region-centric? I think so, because
1511 this is a new region that we're recording, so source zero is
1512 the same as region zero
1514 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1516 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1517 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1518 _active_notes[note] = 0;
1523 /** Extend active notes to rightmost edge of region (if length is changed)
1526 MidiRegionView::extend_active_notes()
1528 if (!_active_notes) {
1532 for (unsigned i=0; i < 128; ++i) {
1533 if (_active_notes[i]) {
1534 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1541 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1543 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1547 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1549 if (!route_ui || !route_ui->midi_track()) {
1553 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1557 /* NotePlayer deletes itself */
1561 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1563 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1567 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1569 if (!route_ui || !route_ui->midi_track()) {
1573 delete _note_player;
1574 _note_player = new NotePlayer (route_ui->midi_track ());
1575 _note_player->add (note);
1576 _note_player->on ();
1580 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1582 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1586 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1588 if (!route_ui || !route_ui->midi_track()) {
1592 delete _note_player;
1593 _note_player = new NotePlayer (route_ui->midi_track());
1595 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1596 _note_player->add (*n);
1599 _note_player->on ();
1604 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1606 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1607 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1609 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1610 (note->note() <= midi_stream_view()->highest_note());
1615 /** Update a canvas note's size from its model note.
1616 * @param ev Canvas note to update.
1617 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1620 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1622 boost::shared_ptr<NoteType> note = ev->note();
1623 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1624 const double y1 = midi_stream_view()->note_to_y(note->note());
1626 ev->property_x1() = x;
1627 ev->property_y1() = y1;
1629 /* trim note display to not overlap the end of its region */
1631 if (note->length() > 0) {
1632 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1633 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1635 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1638 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1640 if (note->length() == 0) {
1641 if (_active_notes) {
1642 assert(note->note() < 128);
1643 // If this note is already active there's a stuck note,
1644 // finish the old note rectangle
1645 if (_active_notes[note->note()]) {
1646 CanvasNote* const old_rect = _active_notes[note->note()];
1647 boost::shared_ptr<NoteType> old_note = old_rect->note();
1648 old_rect->property_x2() = x;
1649 old_rect->property_outline_what() = (guint32) 0xF;
1651 _active_notes[note->note()] = ev;
1653 /* outline all but right edge */
1654 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1656 /* outline all edges */
1657 ev->property_outline_what() = (guint32) 0xF;
1660 if (update_ghost_regions) {
1661 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1662 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1664 gr->update_note (ev);
1671 MidiRegionView::update_hit (CanvasHit* ev)
1673 boost::shared_ptr<NoteType> note = ev->note();
1675 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1676 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1677 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1678 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1682 return diamond_size;
1685 /** Add a MIDI note to the view (with length).
1687 * If in sustained mode, notes with length 0 will be considered active
1688 * notes, and resolve_note should be called when the corresponding note off
1689 * event arrives, to properly display the note.
1692 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1694 CanvasNoteEvent* event = 0;
1696 assert(note->time() >= 0);
1697 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1699 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1701 if (midi_view()->note_mode() == Sustained) {
1703 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1705 update_note (ev_rect);
1709 MidiGhostRegion* gr;
1711 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1712 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1713 gr->add_note(ev_rect);
1717 } else if (midi_view()->note_mode() == Percussive) {
1719 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1721 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1723 update_hit (ev_diamond);
1732 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1733 note_selected(event, true);
1736 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1737 event->show_velocity();
1740 event->on_channel_selection_change(_last_channel_selection);
1741 _events.push_back(event);
1750 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1751 MidiStreamView* const view = mtv->midi_view();
1753 view->update_note_range (note->note());
1757 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1758 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1760 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1762 /* potentially extend region to hold new note */
1764 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1765 framepos_t region_end = _region->last_frame();
1767 if (end_frame > region_end) {
1768 _region->set_length (end_frame - _region->position());
1771 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1772 MidiStreamView* const view = mtv->midi_view();
1774 view->update_note_range(new_note->note());
1776 _marked_for_selection.clear ();
1779 start_note_diff_command (_("step add"));
1780 note_diff_add_note (new_note, true, false);
1783 // last_step_edit_note = new_note;
1787 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1789 change_note_lengths (false, false, beats, false, true);
1792 /** Add a new patch change flag to the canvas.
1793 * @param patch the patch change to add
1794 * @param the text to display in the flag
1795 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1798 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1800 assert (patch->time() >= 0);
1802 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1803 const double x = trackview.editor().frame_to_pixel (region_frames);
1805 double const height = midi_stream_view()->contents_height();
1807 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1808 new CanvasPatchChange(*this, *group,
1817 if (patch_change->width() < _pixel_width) {
1818 // Show unless patch change is beyond the region bounds
1819 if (region_frames < 0 || region_frames >= _region->length()) {
1820 patch_change->hide();
1822 patch_change->show();
1825 patch_change->hide ();
1828 _patch_changes.push_back (patch_change);
1831 MIDI::Name::PatchPrimaryKey
1832 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1834 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1838 MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1840 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1841 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1845 if (i != _model->patch_changes().end()) {
1846 key.bank_number = (*i)->bank();
1847 key.program_number = (*i)->program ();
1849 key.bank_number = key.program_number = 0;
1852 assert (key.is_sane());
1856 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1858 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1860 if (pc.patch()->program() != new_patch.program_number) {
1861 c->change_program (pc.patch (), new_patch.program_number);
1864 int const new_bank = new_patch.bank_number;
1865 if (pc.patch()->bank() != new_bank) {
1866 c->change_bank (pc.patch (), new_bank);
1869 _model->apply_command (*trackview.session(), c);
1871 _patch_changes.clear ();
1872 display_patch_changes ();
1876 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1878 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1880 if (old_change->time() != new_change.time()) {
1881 c->change_time (old_change, new_change.time());
1884 if (old_change->channel() != new_change.channel()) {
1885 c->change_channel (old_change, new_change.channel());
1888 if (old_change->program() != new_change.program()) {
1889 c->change_program (old_change, new_change.program());
1892 if (old_change->bank() != new_change.bank()) {
1893 c->change_bank (old_change, new_change.bank());
1896 _model->apply_command (*trackview.session(), c);
1898 _patch_changes.clear ();
1899 display_patch_changes ();
1902 /** Add a patch change to the region.
1903 * @param t Time in frames relative to region position
1904 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1905 * MidiTimeAxisView::get_channel_for_add())
1908 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1910 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1912 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1913 c->add (MidiModel::PatchChangePtr (
1914 new Evoral::PatchChange<Evoral::MusicalTime> (
1915 absolute_frames_to_source_beats (_region->position() + t),
1916 mtv->get_channel_for_add(), patch.program(), patch.bank()
1921 _model->apply_command (*trackview.session(), c);
1923 _patch_changes.clear ();
1924 display_patch_changes ();
1928 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1930 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1931 c->change_time (pc.patch (), t);
1932 _model->apply_command (*trackview.session(), c);
1934 _patch_changes.clear ();
1935 display_patch_changes ();
1939 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1941 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1942 c->remove (pc->patch ());
1943 _model->apply_command (*trackview.session(), c);
1945 _patch_changes.clear ();
1946 display_patch_changes ();
1950 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1952 if (patch.patch()->program() < 127) {
1953 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1954 key.program_number++;
1955 change_patch_change (patch, key);
1960 MidiRegionView::next_patch (CanvasPatchChange& patch)
1962 if (patch.patch()->program() > 0) {
1963 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1964 key.program_number--;
1965 change_patch_change (patch, key);
1970 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1972 if (patch.patch()->program() < 127) {
1973 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1974 if (key.bank_number > 0) {
1976 change_patch_change (patch, key);
1982 MidiRegionView::next_bank (CanvasPatchChange& patch)
1984 if (patch.patch()->program() > 0) {
1985 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1986 if (key.bank_number < 127) {
1988 change_patch_change (patch, key);
1994 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1996 if (_selection.empty()) {
2000 _selection.erase (cne);
2004 MidiRegionView::delete_selection()
2006 if (_selection.empty()) {
2010 start_note_diff_command (_("delete selection"));
2012 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2013 if ((*i)->selected()) {
2014 _note_diff_command->remove((*i)->note());
2024 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2026 start_note_diff_command (_("delete note"));
2027 _note_diff_command->remove (n);
2030 trackview.editor().verbose_cursor()->hide ();
2034 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2036 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2038 Selection::iterator tmp = i;
2041 (*i)->set_selected (false);
2042 (*i)->hide_velocity ();
2043 _selection.erase (i);
2051 /* this does not change the status of this regionview w.r.t the editor
2056 SelectionCleared (this); /* EMIT SIGNAL */
2061 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2063 clear_selection_except (ev);
2065 /* don't bother with checking to see if we should remove this
2066 regionview from the editor selection, since we're about to add
2067 another note, and thus put/keep this regionview in the editor
2071 if (!ev->selected()) {
2072 add_to_selection (ev);
2077 MidiRegionView::select_all_notes ()
2081 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2082 add_to_selection (*i);
2087 MidiRegionView::select_range (framepos_t start, framepos_t end)
2091 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2092 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2093 if (t >= start && t <= end) {
2094 add_to_selection (*i);
2100 MidiRegionView::invert_selection ()
2102 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2103 if ((*i)->selected()) {
2104 remove_from_selection(*i);
2106 add_to_selection (*i);
2112 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2114 uint8_t low_note = 127;
2115 uint8_t high_note = 0;
2116 MidiModel::Notes& notes (_model->notes());
2117 _optimization_iterator = _events.begin();
2123 if (extend && _selection.empty()) {
2129 /* scan existing selection to get note range */
2131 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2132 if ((*i)->note()->note() < low_note) {
2133 low_note = (*i)->note()->note();
2135 if ((*i)->note()->note() > high_note) {
2136 high_note = (*i)->note()->note();
2140 low_note = min (low_note, notenum);
2141 high_note = max (high_note, notenum);
2144 _no_sound_notes = true;
2146 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2148 boost::shared_ptr<NoteType> note (*n);
2149 CanvasNoteEvent* cne;
2150 bool select = false;
2152 if (((1 << note->channel()) & channel_mask) != 0) {
2154 if ((note->note() >= low_note && note->note() <= high_note)) {
2157 } else if (note->note() == notenum) {
2163 if ((cne = find_canvas_note (note)) != 0) {
2164 // extend is false because we've taken care of it,
2165 // since it extends by time range, not pitch.
2166 note_selected (cne, add, false);
2170 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2174 _no_sound_notes = false;
2178 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2180 MidiModel::Notes& notes (_model->notes());
2181 _optimization_iterator = _events.begin();
2183 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2185 boost::shared_ptr<NoteType> note (*n);
2186 CanvasNoteEvent* cne;
2188 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2189 if ((cne = find_canvas_note (note)) != 0) {
2190 if (cne->selected()) {
2191 note_deselected (cne);
2193 note_selected (cne, true, false);
2201 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2204 clear_selection_except (ev);
2205 if (!_selection.empty()) {
2206 PublicEditor& editor (trackview.editor());
2207 editor.get_selection().add (this);
2213 if (!ev->selected()) {
2214 add_to_selection (ev);
2218 /* find end of latest note selected, select all between that and the start of "ev" */
2220 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2221 Evoral::MusicalTime latest = 0;
2223 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2224 if ((*i)->note()->end_time() > latest) {
2225 latest = (*i)->note()->end_time();
2227 if ((*i)->note()->time() < earliest) {
2228 earliest = (*i)->note()->time();
2232 if (ev->note()->end_time() > latest) {
2233 latest = ev->note()->end_time();
2236 if (ev->note()->time() < earliest) {
2237 earliest = ev->note()->time();
2240 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2242 /* find notes entirely within OR spanning the earliest..latest range */
2244 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2245 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2246 add_to_selection (*i);
2254 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2256 remove_from_selection (ev);
2260 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2270 // TODO: Make this faster by storing the last updated selection rect, and only
2271 // adjusting things that are in the area that appears/disappeared.
2272 // We probably need a tree to be able to find events in O(log(n)) time.
2274 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2276 /* check if any corner of the note is inside the rect
2279 1) this is computing "touched by", not "contained by" the rect.
2280 2) this does not require that events be sorted in time.
2283 const double ix1 = (*i)->x1();
2284 const double ix2 = (*i)->x2();
2285 const double iy1 = (*i)->y1();
2286 const double iy2 = (*i)->y2();
2288 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2289 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2290 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2291 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2294 if (!(*i)->selected()) {
2295 add_to_selection (*i);
2297 } else if ((*i)->selected() && !extend) {
2298 // Not inside rectangle
2299 remove_from_selection (*i);
2305 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2311 // TODO: Make this faster by storing the last updated selection rect, and only
2312 // adjusting things that are in the area that appears/disappeared.
2313 // We probably need a tree to be able to find events in O(log(n)) time.
2315 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2317 /* check if any corner of the note is inside the rect
2320 1) this is computing "touched by", not "contained by" the rect.
2321 2) this does not require that events be sorted in time.
2324 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2325 // within y- (note-) range
2326 if (!(*i)->selected()) {
2327 add_to_selection (*i);
2329 } else if ((*i)->selected() && !extend) {
2330 // Not inside rectangle
2331 remove_from_selection (*i);
2337 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2339 Selection::iterator i = _selection.find (ev);
2341 if (i != _selection.end()) {
2342 _selection.erase (i);
2345 ev->set_selected (false);
2346 ev->hide_velocity ();
2348 if (_selection.empty()) {
2349 PublicEditor& editor (trackview.editor());
2350 editor.get_selection().remove (this);
2355 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2357 bool add_mrv_selection = false;
2359 if (_selection.empty()) {
2360 add_mrv_selection = true;
2363 if (_selection.insert (ev).second) {
2364 ev->set_selected (true);
2365 start_playing_midi_note ((ev)->note());
2368 if (add_mrv_selection) {
2369 PublicEditor& editor (trackview.editor());
2370 editor.get_selection().add (this);
2375 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2377 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2378 PossibleChord to_play;
2379 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2381 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2382 if ((*i)->note()->time() < earliest) {
2383 earliest = (*i)->note()->time();
2387 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2388 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2389 to_play.push_back ((*i)->note());
2391 (*i)->move_event(dx, dy);
2394 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2396 if (to_play.size() > 1) {
2398 PossibleChord shifted;
2400 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2401 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2402 moved_note->set_note (moved_note->note() + cumulative_dy);
2403 shifted.push_back (moved_note);
2406 start_playing_midi_chord (shifted);
2408 } else if (!to_play.empty()) {
2410 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2411 moved_note->set_note (moved_note->note() + cumulative_dy);
2412 start_playing_midi_note (moved_note);
2418 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2420 assert (!_selection.empty());
2422 uint8_t lowest_note_in_selection = 127;
2423 uint8_t highest_note_in_selection = 0;
2424 uint8_t highest_note_difference = 0;
2426 // find highest and lowest notes first
2428 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2429 uint8_t pitch = (*i)->note()->note();
2430 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2431 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2435 cerr << "dnote: " << (int) dnote << endl;
2436 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2437 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2438 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2439 << int(highest_note_in_selection) << endl;
2440 cerr << "selection size: " << _selection.size() << endl;
2441 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2444 // Make sure the note pitch does not exceed the MIDI standard range
2445 if (highest_note_in_selection + dnote > 127) {
2446 highest_note_difference = highest_note_in_selection - 127;
2449 start_note_diff_command (_("move notes"));
2451 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2453 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2454 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2460 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2462 uint8_t original_pitch = (*i)->note()->note();
2463 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2465 // keep notes in standard midi range
2466 clamp_to_0_127(new_pitch);
2468 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2469 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2471 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2476 // care about notes being moved beyond the upper/lower bounds on the canvas
2477 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2478 highest_note_in_selection > midi_stream_view()->highest_note()) {
2479 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2483 /** @param x Pixel relative to the region position.
2484 * @return Snapped frame relative to the region position.
2487 MidiRegionView::snap_pixel_to_frame(double x)
2489 PublicEditor& editor (trackview.editor());
2490 return snap_frame_to_frame (editor.pixel_to_frame (x));
2493 /** @param x Pixel relative to the region position.
2494 * @return Snapped pixel relative to the region position.
2497 MidiRegionView::snap_to_pixel(double x)
2499 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2503 MidiRegionView::get_position_pixels()
2505 framepos_t region_frame = get_position();
2506 return trackview.editor().frame_to_pixel(region_frame);
2510 MidiRegionView::get_end_position_pixels()
2512 framepos_t frame = get_position() + get_duration ();
2513 return trackview.editor().frame_to_pixel(frame);
2517 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2519 /* the time converter will return the frame corresponding to `beats'
2520 relative to the start of the source. The start of the source
2521 is an implied position given by region->position - region->start
2523 const framepos_t source_start = _region->position() - _region->start();
2524 return source_start + _source_relative_time_converter.to (beats);
2528 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2530 /* the `frames' argument needs to be converted into a frame count
2531 relative to the start of the source before being passed in to the
2534 const framepos_t source_start = _region->position() - _region->start();
2535 return _source_relative_time_converter.from (frames - source_start);
2539 MidiRegionView::region_beats_to_region_frames(double beats) const
2541 return _region_relative_time_converter.to(beats);
2545 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2547 return _region_relative_time_converter.from(frames);
2551 MidiRegionView::begin_resizing (bool /*at_front*/)
2553 _resize_data.clear();
2555 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2556 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2558 // only insert CanvasNotes into the map
2560 NoteResizeData *resize_data = new NoteResizeData();
2561 resize_data->canvas_note = note;
2563 // create a new SimpleRect from the note which will be the resize preview
2564 SimpleRect *resize_rect = new SimpleRect(
2565 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2567 // calculate the colors: get the color settings
2568 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2569 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2572 // make the resize preview notes more transparent and bright
2573 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2575 // calculate color based on note velocity
2576 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2577 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2581 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2582 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2584 resize_data->resize_rect = resize_rect;
2585 _resize_data.push_back(resize_data);
2590 /** Update resizing notes while user drags.
2591 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2592 * @param at_front which end of the note (true == note on, false == note off)
2593 * @param delta_x change in mouse position since the start of the drag
2594 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2595 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2596 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2597 * as the \a primary note.
2600 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2602 bool cursor_set = false;
2604 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2605 SimpleRect* resize_rect = (*i)->resize_rect;
2606 CanvasNote* canvas_note = (*i)->canvas_note;
2611 current_x = canvas_note->x1() + delta_x;
2613 current_x = primary->x1() + delta_x;
2617 current_x = canvas_note->x2() + delta_x;
2619 current_x = primary->x2() + delta_x;
2624 resize_rect->property_x1() = snap_to_pixel(current_x);
2625 resize_rect->property_x2() = canvas_note->x2();
2627 resize_rect->property_x2() = snap_to_pixel(current_x);
2628 resize_rect->property_x1() = canvas_note->x1();
2634 beats = snap_pixel_to_frame (current_x);
2635 beats = region_frames_to_region_beats (beats);
2640 if (beats < canvas_note->note()->end_time()) {
2641 len = canvas_note->note()->time() - beats;
2642 len += canvas_note->note()->length();
2647 if (beats >= canvas_note->note()->time()) {
2648 len = beats - canvas_note->note()->time();
2655 snprintf (buf, sizeof (buf), "%.3g beats", len);
2656 show_verbose_cursor (buf, 0, 0);
2665 /** Finish resizing notes when the user releases the mouse button.
2666 * Parameters the same as for \a update_resizing().
2669 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2671 start_note_diff_command (_("resize notes"));
2673 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2674 CanvasNote* canvas_note = (*i)->canvas_note;
2675 SimpleRect* resize_rect = (*i)->resize_rect;
2677 /* Get the new x position for this resize, which is in pixels relative
2678 * to the region position.
2685 current_x = canvas_note->x1() + delta_x;
2687 current_x = primary->x1() + delta_x;
2691 current_x = canvas_note->x2() + delta_x;
2693 current_x = primary->x2() + delta_x;
2697 /* Convert that to a frame within the source */
2698 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2700 /* and then to beats */
2701 current_x = region_frames_to_region_beats (current_x);
2703 if (at_front && current_x < canvas_note->note()->end_time()) {
2704 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2706 double len = canvas_note->note()->time() - current_x;
2707 len += canvas_note->note()->length();
2710 /* XXX convert to beats */
2711 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2716 double len = current_x - canvas_note->note()->time();
2719 /* XXX convert to beats */
2720 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2728 _resize_data.clear();
2733 MidiRegionView::abort_resizing ()
2735 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2736 delete (*i)->resize_rect;
2740 _resize_data.clear ();
2744 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2746 uint8_t new_velocity;
2749 new_velocity = event->note()->velocity() + velocity;
2750 clamp_to_0_127(new_velocity);
2752 new_velocity = velocity;
2755 event->set_selected (event->selected()); // change color
2757 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2761 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2766 new_note = event->note()->note() + note;
2771 clamp_to_0_127 (new_note);
2772 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2776 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2778 bool change_start = false;
2779 bool change_length = false;
2780 Evoral::MusicalTime new_start = 0;
2781 Evoral::MusicalTime new_length = 0;
2783 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2785 front_delta: if positive - move the start of the note later in time (shortening it)
2786 if negative - move the start of the note earlier in time (lengthening it)
2788 end_delta: if positive - move the end of the note later in time (lengthening it)
2789 if negative - move the end of the note earlier in time (shortening it)
2793 if (front_delta < 0) {
2795 if (event->note()->time() < -front_delta) {
2798 new_start = event->note()->time() + front_delta; // moves earlier
2801 /* start moved toward zero, so move the end point out to where it used to be.
2802 Note that front_delta is negative, so this increases the length.
2805 new_length = event->note()->length() - front_delta;
2806 change_start = true;
2807 change_length = true;
2811 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2813 if (new_pos < event->note()->end_time()) {
2814 new_start = event->note()->time() + front_delta;
2815 /* start moved toward the end, so move the end point back to where it used to be */
2816 new_length = event->note()->length() - front_delta;
2817 change_start = true;
2818 change_length = true;
2825 bool can_change = true;
2826 if (end_delta < 0) {
2827 if (event->note()->length() < -end_delta) {
2833 new_length = event->note()->length() + end_delta;
2834 change_length = true;
2839 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2842 if (change_length) {
2843 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2848 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2850 uint8_t new_channel;
2854 if (event->note()->channel() < -chn) {
2857 new_channel = event->note()->channel() + chn;
2860 new_channel = event->note()->channel() + chn;
2863 new_channel = (uint8_t) chn;
2866 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2870 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2872 Evoral::MusicalTime new_time;
2876 if (event->note()->time() < -delta) {
2879 new_time = event->note()->time() + delta;
2882 new_time = event->note()->time() + delta;
2888 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2892 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2894 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2898 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2903 if (_selection.empty()) {
2918 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2919 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2925 start_note_diff_command (_("change velocities"));
2927 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2928 Selection::iterator next = i;
2932 if (i == _selection.begin()) {
2933 change_note_velocity (*i, delta, true);
2934 value = (*i)->note()->velocity() + delta;
2936 change_note_velocity (*i, value, false);
2940 change_note_velocity (*i, delta, true);
2948 if (!_selection.empty()) {
2950 snprintf (buf, sizeof (buf), "Vel %d",
2951 (int) (*_selection.begin())->note()->velocity());
2952 show_verbose_cursor (buf, 10, 10);
2958 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2960 if (_selection.empty()) {
2977 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2979 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2983 if ((int8_t) (*i)->note()->note() + delta > 127) {
2990 start_note_diff_command (_("transpose"));
2992 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2993 Selection::iterator next = i;
2995 change_note_note (*i, delta, true);
3003 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
3009 /* grab the current grid distance */
3011 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
3013 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
3014 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3024 start_note_diff_command (_("change note lengths"));
3026 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3027 Selection::iterator next = i;
3030 /* note the negation of the delta for start */
3032 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3041 MidiRegionView::nudge_notes (bool forward)
3043 if (_selection.empty()) {
3047 /* pick a note as the point along the timeline to get the nudge distance.
3048 its not necessarily the earliest note, so we may want to pull the notes out
3049 into a vector and sort before using the first one.
3052 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3054 framecnt_t distance;
3056 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3058 /* grid is off - use nudge distance */
3060 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3066 framepos_t next_pos = ref_point;
3069 if (max_framepos - 1 < next_pos) {
3073 if (next_pos == 0) {
3079 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3080 distance = ref_point - next_pos;
3083 if (distance == 0) {
3087 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3093 start_note_diff_command (_("nudge"));
3095 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3096 Selection::iterator next = i;
3098 change_note_time (*i, delta, true);
3106 MidiRegionView::change_channel(uint8_t channel)
3108 start_note_diff_command(_("change channel"));
3109 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3110 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3118 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3120 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3122 pre_enter_cursor = editor->get_canvas_cursor ();
3124 if (_mouse_state == SelectTouchDragging) {
3125 note_selected (ev, true);
3128 show_verbose_cursor (ev->note ());
3132 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3134 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3136 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3137 (*i)->hide_velocity ();
3140 editor->verbose_cursor()->hide ();
3142 if (pre_enter_cursor) {
3143 editor->set_canvas_cursor (pre_enter_cursor);
3144 pre_enter_cursor = 0;
3149 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* p)
3152 /* XXX should get patch name if we can */
3153 s << _("Bank:") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3154 << _("Program:") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3155 << _("Channel:") << ((int) p->patch()->channel() + 1);
3156 show_verbose_cursor (s.str(), 10, 20);
3161 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3163 trackview.editor().verbose_cursor()->hide ();
3164 /* focus will transfer back via the enter-notify event sent to this
3170 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3172 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3173 Editing::MouseMode mm = editor->current_mouse_mode();
3174 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3176 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3177 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3178 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3179 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3181 if (pre_enter_cursor && can_set_cursor) {
3182 editor->set_canvas_cursor (pre_enter_cursor);
3188 MidiRegionView::set_frame_color()
3192 TimeAxisViewItem::set_frame_color ();
3199 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3200 } else if (high_enough_for_name) {
3201 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3206 if (!rect_visible) {
3207 f = UINT_RGBA_CHANGE_A (f, 0);
3210 frame->property_fill_color_rgba() = f;
3214 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3216 if (mode == ForceChannel) {
3217 mask = 0xFFFF; // Show all notes as active (below)
3220 // Update notes for selection
3221 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3222 (*i)->on_channel_selection_change(mask);
3225 _last_channel_selection = mask;
3227 _patch_changes.clear ();
3228 display_patch_changes ();
3232 MidiRegionView::instrument_settings_changed ()
3238 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3240 if (_selection.empty()) {
3244 PublicEditor& editor (trackview.editor());
3248 /* XXX what to do ? */
3252 editor.get_cut_buffer().add (selection_as_cut_buffer());
3260 start_note_diff_command();
3262 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3269 note_diff_remove_note (*i);
3279 MidiRegionView::selection_as_cut_buffer () const
3283 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3284 NoteType* n = (*i)->note().get();
3285 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3288 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3294 /** This method handles undo */
3296 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3302 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3304 trackview.session()->begin_reversible_command (_("paste"));
3306 start_note_diff_command (_("paste"));
3308 Evoral::MusicalTime beat_delta;
3309 Evoral::MusicalTime paste_pos_beats;
3310 Evoral::MusicalTime duration;
3311 Evoral::MusicalTime end_point = 0;
3313 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3314 paste_pos_beats = absolute_frames_to_source_beats (pos);
3315 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3316 paste_pos_beats = 0;
3318 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",
3319 (*mcb.notes().begin())->time(),
3320 (*mcb.notes().rbegin())->end_time(),
3321 duration, pos, _region->position(),
3322 paste_pos_beats, beat_delta));
3326 for (int n = 0; n < (int) times; ++n) {
3328 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3330 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3331 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3333 /* make all newly added notes selected */
3335 note_diff_add_note (copied_note, true);
3336 end_point = copied_note->end_time();
3339 paste_pos_beats += duration;
3342 /* if we pasted past the current end of the region, extend the region */
3344 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3345 framepos_t region_end = _region->position() + _region->length() - 1;
3347 if (end_frame > region_end) {
3349 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3351 _region->clear_changes ();
3352 _region->set_length (end_frame - _region->position());
3353 trackview.session()->add_command (new StatefulDiffCommand (_region));
3358 trackview.session()->commit_reversible_command ();
3361 struct EventNoteTimeEarlyFirstComparator {
3362 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3363 return a->note()->time() < b->note()->time();
3368 MidiRegionView::time_sort_events ()
3370 if (!_sort_needed) {
3374 EventNoteTimeEarlyFirstComparator cmp;
3377 _sort_needed = false;
3381 MidiRegionView::goto_next_note (bool add_to_selection)
3383 bool use_next = false;
3385 if (_events.back()->selected()) {
3389 time_sort_events ();
3391 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3392 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3394 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3395 if ((*i)->selected()) {
3398 } else if (use_next) {
3399 if (channel_mask & (1 << (*i)->note()->channel())) {
3400 if (!add_to_selection) {
3403 note_selected (*i, true, false);
3410 /* use the first one */
3412 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3413 unique_select (_events.front());
3418 MidiRegionView::goto_previous_note (bool add_to_selection)
3420 bool use_next = false;
3422 if (_events.front()->selected()) {
3426 time_sort_events ();
3428 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3429 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3431 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3432 if ((*i)->selected()) {
3435 } else if (use_next) {
3436 if (channel_mask & (1 << (*i)->note()->channel())) {
3437 if (!add_to_selection) {
3440 note_selected (*i, true, false);
3447 /* use the last one */
3449 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3450 unique_select (*(_events.rbegin()));
3455 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3457 bool had_selected = false;
3459 time_sort_events ();
3461 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3462 if ((*i)->selected()) {
3463 selected.insert ((*i)->note());
3464 had_selected = true;
3468 if (allow_all_if_none_selected && !had_selected) {
3469 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3470 selected.insert ((*i)->note());
3476 MidiRegionView::update_ghost_note (double x, double y)
3478 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3483 _note_group->w2i (x, y);
3485 PublicEditor& editor = trackview.editor ();
3487 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3488 framecnt_t grid_frames;
3489 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3491 /* use region_frames... because we are converting a delta within the region
3495 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3501 /* note that this sets the time of the ghost note in beats relative to
3502 the start of the source; that is how all note times are stored.
3504 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3505 _ghost_note->note()->set_length (length);
3506 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3507 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3509 /* the ghost note does not appear in ghost regions, so pass false in here */
3510 update_note (_ghost_note, false);
3512 show_verbose_cursor (_ghost_note->note ());
3516 MidiRegionView::create_ghost_note (double x, double y)
3518 remove_ghost_note ();
3520 boost::shared_ptr<NoteType> g (new NoteType);
3521 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3522 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3523 update_ghost_note (x, y);
3524 _ghost_note->show ();
3529 show_verbose_cursor (_ghost_note->note ());
3533 MidiRegionView::snap_changed ()
3539 create_ghost_note (_last_ghost_x, _last_ghost_y);
3543 MidiRegionView::drop_down_keys ()
3545 _mouse_state = None;
3549 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3551 double note = midi_stream_view()->y_to_note(y);
3553 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3555 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3557 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3558 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3559 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3560 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3565 bool add_mrv_selection = false;
3567 if (_selection.empty()) {
3568 add_mrv_selection = true;
3571 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3572 if (_selection.insert (*i).second) {
3573 (*i)->set_selected (true);
3577 if (add_mrv_selection) {
3578 PublicEditor& editor (trackview.editor());
3579 editor.get_selection().add (this);
3584 MidiRegionView::color_handler ()
3586 RegionView::color_handler ();
3588 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3589 (*i)->set_selected ((*i)->selected()); // will change color
3592 /* XXX probably more to do here */
3596 MidiRegionView::enable_display (bool yn)
3598 RegionView::enable_display (yn);
3605 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3607 if (_step_edit_cursor == 0) {
3608 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3610 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3611 _step_edit_cursor->property_y1() = 0;
3612 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3613 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3614 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3617 move_step_edit_cursor (pos);
3618 _step_edit_cursor->show ();
3622 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3624 _step_edit_cursor_position = pos;
3626 if (_step_edit_cursor) {
3627 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3628 _step_edit_cursor->property_x1() = pixel;
3629 set_step_edit_cursor_width (_step_edit_cursor_width);
3634 MidiRegionView::hide_step_edit_cursor ()
3636 if (_step_edit_cursor) {
3637 _step_edit_cursor->hide ();
3642 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3644 _step_edit_cursor_width = beats;
3646 if (_step_edit_cursor) {
3647 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3651 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3652 * @param w Source that the data will end up in.
3655 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3657 if (!_active_notes) {
3658 /* we aren't actively being recorded to */
3662 boost::shared_ptr<MidiSource> src = w.lock ();
3663 if (!src || src != midi_region()->midi_source()) {
3664 /* recorded data was not destined for our source */
3668 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3670 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3672 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3674 framepos_t back = max_framepos;
3676 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3677 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3678 assert (ev.buffer ());
3680 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3681 frames from the start of the source, and so time_beats is in terms of the
3685 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3687 if (ev.type() == MIDI_CMD_NOTE_ON) {
3689 boost::shared_ptr<NoteType> note (
3690 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3693 add_note (note, true);
3695 /* fix up our note range */
3696 if (ev.note() < _current_range_min) {
3697 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3698 } else if (ev.note() > _current_range_max) {
3699 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3702 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3703 resolve_note (ev.note (), time_beats);
3709 midi_stream_view()->check_record_layers (region(), back);
3713 MidiRegionView::trim_front_starting ()
3715 /* Reparent the note group to the region view's parent, so that it doesn't change
3716 when the region view is trimmed.
3718 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3719 _temporary_note_group->move (group->property_x(), group->property_y());
3720 _note_group->reparent (*_temporary_note_group);
3724 MidiRegionView::trim_front_ending ()
3726 _note_group->reparent (*group);
3727 delete _temporary_note_group;
3728 _temporary_note_group = 0;
3730 if (_region->start() < 0) {
3731 /* Trim drag made start time -ve; fix this */
3732 midi_region()->fix_negative_start ();
3737 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3739 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY);
3741 d.set_position (Gtk::WIN_POS_MOUSE);
3743 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3747 change_patch_change (pc->patch(), d.patch ());
3752 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3755 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3756 Evoral::midi_note_name (n->note()).c_str(),
3758 (int) n->channel() + 1,
3759 (int) n->velocity());
3761 show_verbose_cursor (buf, 10, 20);
3765 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3769 trackview.editor().get_pointer_position (wx, wy);
3774 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3776 double x1, y1, x2, y2;
3777 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3779 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3780 wy -= (y2 - y1) + 2 * yoffset;
3783 trackview.editor().verbose_cursor()->set (text, wx, wy);
3784 trackview.editor().verbose_cursor()->show ();
3787 /** @param p A session framepos.
3788 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3789 * @return p snapped to the grid subdivision underneath it.
3792 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3794 PublicEditor& editor = trackview.editor ();
3797 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3803 grid_frames = region_beats_to_region_frames (grid_beats);
3805 /* Hack so that we always snap to the note that we are over, instead of snapping
3806 to the next one if we're more than halfway through the one we're over.
3808 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3809 p -= grid_frames / 2;
3812 return snap_frame_to_frame (p);
3815 /** Called when the selection has been cleared in any MidiRegionView.
3816 * @param rv MidiRegionView that the selection was cleared in.
3819 MidiRegionView::selection_cleared (MidiRegionView* rv)
3825 /* Clear our selection in sympathy; but don't signal the fact */
3826 clear_selection (false);
3830 MidiRegionView::note_button_release ()
3832 delete _note_player;