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);
688 MidiRegionView::key_press (GdkEventKey* ev)
690 /* since GTK bindings are generally activated on press, and since
691 detectable auto-repeat is the name of the game and only sends
692 repeated presses, carry out key actions at key press, not release.
695 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
697 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
698 _mouse_state = SelectTouchDragging;
701 } else if (ev->keyval == GDK_Escape && unmodified) {
705 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
707 bool start = (ev->keyval == GDK_comma);
708 bool end = (ev->keyval == GDK_period);
709 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
710 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
712 change_note_lengths (fine, shorter, 0.0, start, end);
716 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
718 if (_selection.empty()) {
725 } else if (ev->keyval == GDK_Tab) {
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));
734 } else if (ev->keyval == GDK_ISO_Left_Tab) {
736 /* Shift-TAB generates ISO Left Tab, for some reason */
738 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
739 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
741 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
747 } else if (ev->keyval == GDK_Up) {
749 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
750 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
751 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
753 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
754 change_velocities (true, fine, allow_smush, together);
756 transpose (true, fine, allow_smush);
760 } else if (ev->keyval == GDK_Down) {
762 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
763 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
764 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
766 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
767 change_velocities (false, fine, allow_smush, together);
769 transpose (false, fine, allow_smush);
773 } else if (ev->keyval == GDK_Left && unmodified) {
778 } else if (ev->keyval == GDK_Right && unmodified) {
783 } else if (ev->keyval == GDK_c && unmodified) {
787 } else if (ev->keyval == GDK_v && unmodified) {
796 MidiRegionView::key_release (GdkEventKey* ev)
798 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
806 MidiRegionView::channel_edit ()
808 if (_selection.empty()) {
812 /* pick a note somewhat at random (since Selection is a set<>) to
813 * provide the "current" channel for the dialog.
816 uint8_t current_channel = (*_selection.begin())->note()->channel ();
817 MidiChannelDialog channel_dialog (current_channel);
818 int ret = channel_dialog.run ();
821 case Gtk::RESPONSE_OK:
827 uint8_t new_channel = channel_dialog.active_channel ();
829 start_note_diff_command (_("channel edit"));
831 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
832 Selection::iterator next = i;
834 change_note_channel (*i, new_channel);
842 MidiRegionView::velocity_edit ()
844 if (_selection.empty()) {
848 /* pick a note somewhat at random (since Selection is a set<>) to
849 * provide the "current" velocity for the dialog.
852 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
853 MidiVelocityDialog velocity_dialog (current_velocity);
854 int ret = velocity_dialog.run ();
857 case Gtk::RESPONSE_OK:
863 uint8_t new_velocity = velocity_dialog.velocity ();
865 start_note_diff_command (_("velocity edit"));
867 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
868 Selection::iterator next = i;
870 change_note_velocity (*i, new_velocity, false);
878 MidiRegionView::show_list_editor ()
881 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
883 _list_editor->present ();
886 /** Add a note to the model, and the view, at a canvas (click) coordinate.
887 * \param t time in frames relative to the position of the region
888 * \param y vertical position in pixels
889 * \param length duration of the note in beats
890 * \param snap_t true to snap t to the grid, otherwise false.
893 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
895 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
896 MidiStreamView* const view = mtv->midi_view();
898 double note = view->y_to_note(y);
901 assert(note <= 127.0);
903 // Start of note in frames relative to region start
905 framecnt_t grid_frames;
906 t = snap_frame_to_grid_underneath (t, grid_frames);
910 assert (length != 0);
912 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
913 region_frames_to_region_beats(t + _region->start()),
915 (uint8_t)note, 0x40));
917 if (_model->contains (new_note)) {
921 view->update_note_range(new_note->note());
923 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
925 _model->apply_command(*trackview.session(), cmd);
927 play_midi_note (new_note);
931 MidiRegionView::clear_events()
936 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
937 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
942 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
947 _patch_changes.clear();
949 _optimization_iterator = _events.end();
953 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
957 content_connection.disconnect ();
958 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
962 if (_enable_display) {
968 MidiRegionView::start_note_diff_command (string name)
970 if (!_note_diff_command) {
971 _note_diff_command = _model->new_note_diff_command (name);
976 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
978 if (_note_diff_command) {
979 _note_diff_command->add (note);
982 _marked_for_selection.insert(note);
985 _marked_for_velocity.insert(note);
990 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
992 if (_note_diff_command && ev->note()) {
993 _note_diff_command->remove(ev->note());
998 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
999 MidiModel::NoteDiffCommand::Property property,
1002 if (_note_diff_command) {
1003 _note_diff_command->change (ev->note(), property, val);
1008 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1009 MidiModel::NoteDiffCommand::Property property,
1010 Evoral::MusicalTime val)
1012 if (_note_diff_command) {
1013 _note_diff_command->change (ev->note(), property, val);
1018 MidiRegionView::apply_diff (bool as_subcommand)
1022 if (!_note_diff_command) {
1026 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1027 // Mark all selected notes for selection when model reloads
1028 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1029 _marked_for_selection.insert((*i)->note());
1033 if (as_subcommand) {
1034 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1036 _model->apply_command (*trackview.session(), _note_diff_command);
1039 _note_diff_command = 0;
1040 midi_view()->midi_track()->playlist_modified();
1042 if (add_or_remove) {
1043 _marked_for_selection.clear();
1046 _marked_for_velocity.clear();
1050 MidiRegionView::abort_command()
1052 delete _note_diff_command;
1053 _note_diff_command = 0;
1058 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1060 if (_optimization_iterator != _events.end()) {
1061 ++_optimization_iterator;
1064 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1065 return *_optimization_iterator;
1068 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1069 if ((*_optimization_iterator)->note() == note) {
1070 return *_optimization_iterator;
1078 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1080 MidiModel::Notes notes;
1081 _model->get_notes (notes, op, val, chan_mask);
1083 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1084 CanvasNoteEvent* cne = find_canvas_note (*n);
1092 MidiRegionView::redisplay_model()
1094 // Don't redisplay the model if we're currently recording and displaying that
1095 if (_active_notes) {
1103 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1104 (*i)->invalidate ();
1107 MidiModel::ReadLock lock(_model->read_lock());
1109 MidiModel::Notes& notes (_model->notes());
1110 _optimization_iterator = _events.begin();
1112 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1114 boost::shared_ptr<NoteType> note (*n);
1115 CanvasNoteEvent* cne;
1118 if (note_in_region_range (note, visible)) {
1120 if ((cne = find_canvas_note (note)) != 0) {
1127 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1129 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1141 add_note (note, visible);
1146 if ((cne = find_canvas_note (note)) != 0) {
1154 /* remove note items that are no longer valid */
1156 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1157 if (!(*i)->valid ()) {
1159 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1160 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1162 gr->remove_note (*i);
1167 i = _events.erase (i);
1174 _patch_changes.clear();
1178 display_patch_changes ();
1180 _marked_for_selection.clear ();
1181 _marked_for_velocity.clear ();
1183 /* we may have caused _events to contain things out of order (e.g. if a note
1184 moved earlier or later). we don't generally need them in time order, but
1185 make a note that a sort is required for those cases that require it.
1188 _sort_needed = true;
1192 MidiRegionView::display_patch_changes ()
1194 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1195 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1197 for (uint8_t i = 0; i < 16; ++i) {
1198 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1202 /** @param active_channel true to display patch changes fully, false to display
1203 * them `greyed-out' (as on an inactive channel)
1206 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1208 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1210 if ((*i)->channel() != channel) {
1214 string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1215 add_canvas_patch_change (*i, patch_name, active_channel);
1220 MidiRegionView::display_sysexes()
1222 bool have_periodic_system_messages = false;
1223 bool display_periodic_messages = true;
1225 if (!Config->get_never_display_periodic_midi()) {
1227 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1228 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1229 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1232 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1233 have_periodic_system_messages = true;
1239 if (have_periodic_system_messages) {
1240 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1242 /* get an approximate value for the number of samples per video frame */
1244 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1246 /* if we are zoomed out beyond than the cutoff (i.e. more
1247 * frames per pixel than frames per 4 video frames), don't
1248 * show periodic sysex messages.
1251 if (zoom > (video_frame*4)) {
1252 display_periodic_messages = false;
1256 display_periodic_messages = false;
1259 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1261 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1262 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1264 Evoral::MusicalTime time = (*i)->time();
1268 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1269 if (!display_periodic_messages) {
1277 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1278 str << int((*i)->buffer()[b]);
1279 if (b != (*i)->size() -1) {
1283 string text = str.str();
1285 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1287 double height = midi_stream_view()->contents_height();
1289 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1290 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1292 // Show unless message is beyond the region bounds
1293 if (time - _region->start() >= _region->length() || time < _region->start()) {
1299 _sys_exes.push_back(sysex);
1303 MidiRegionView::~MidiRegionView ()
1305 in_destructor = true;
1307 trackview.editor().verbose_cursor()->hide ();
1309 note_delete_connection.disconnect ();
1311 delete _list_editor;
1313 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1315 if (_active_notes) {
1319 _selection_cleared_connection.disconnect ();
1325 delete _note_diff_command;
1326 delete _step_edit_cursor;
1327 delete _temporary_note_group;
1331 MidiRegionView::region_resized (const PropertyChange& what_changed)
1333 RegionView::region_resized(what_changed);
1335 if (what_changed.contains (ARDOUR::Properties::position)) {
1336 set_duration(_region->length(), 0);
1337 if (_enable_display) {
1344 MidiRegionView::reset_width_dependent_items (double pixel_width)
1346 RegionView::reset_width_dependent_items(pixel_width);
1347 assert(_pixel_width == pixel_width);
1349 if (_enable_display) {
1353 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1354 if ((*x)->width() >= _pixel_width) {
1361 move_step_edit_cursor (_step_edit_cursor_position);
1362 set_step_edit_cursor_width (_step_edit_cursor_width);
1366 MidiRegionView::set_height (double height)
1368 static const double FUDGE = 2.0;
1369 const double old_height = _height;
1370 RegionView::set_height(height);
1371 _height = height - FUDGE;
1373 apply_note_range(midi_stream_view()->lowest_note(),
1374 midi_stream_view()->highest_note(),
1375 height != old_height + FUDGE);
1378 name_pixbuf->raise_to_top();
1381 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1382 (*x)->set_height (midi_stream_view()->contents_height());
1385 if (_step_edit_cursor) {
1386 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1391 /** Apply the current note range from the stream view
1392 * by repositioning/hiding notes as necessary
1395 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1397 if (!_enable_display) {
1401 if (!force && _current_range_min == min && _current_range_max == max) {
1405 _current_range_min = min;
1406 _current_range_max = max;
1408 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1409 CanvasNoteEvent* event = *i;
1410 boost::shared_ptr<NoteType> note (event->note());
1412 if (note->note() < _current_range_min ||
1413 note->note() > _current_range_max) {
1419 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1421 const double y1 = midi_stream_view()->note_to_y(note->note());
1422 const double y2 = y1 + floor(midi_stream_view()->note_height());
1424 cnote->property_y1() = y1;
1425 cnote->property_y2() = y2;
1427 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1429 const double diamond_size = update_hit (chit);
1431 chit->set_height (diamond_size);
1437 MidiRegionView::add_ghost (TimeAxisView& tv)
1441 double unit_position = _region->position () / samples_per_unit;
1442 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1443 MidiGhostRegion* ghost;
1445 if (mtv && mtv->midi_view()) {
1446 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1447 to allow having midi notes on top of note lines and waveforms.
1449 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1451 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1454 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1455 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1456 ghost->add_note(note);
1460 ghost->set_height ();
1461 ghost->set_duration (_region->length() / samples_per_unit);
1462 ghosts.push_back (ghost);
1464 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1470 /** Begin tracking note state for successive calls to add_event
1473 MidiRegionView::begin_write()
1475 assert(!_active_notes);
1476 _active_notes = new CanvasNote*[128];
1477 for (unsigned i=0; i < 128; ++i) {
1478 _active_notes[i] = 0;
1483 /** Destroy note state for add_event
1486 MidiRegionView::end_write()
1488 delete[] _active_notes;
1490 _marked_for_selection.clear();
1491 _marked_for_velocity.clear();
1495 /** Resolve an active MIDI note (while recording).
1498 MidiRegionView::resolve_note(uint8_t note, double end_time)
1500 if (midi_view()->note_mode() != Sustained) {
1504 if (_active_notes && _active_notes[note]) {
1506 /* XXX is end_time really region-centric? I think so, because
1507 this is a new region that we're recording, so source zero is
1508 the same as region zero
1510 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1512 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1513 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1514 _active_notes[note] = 0;
1519 /** Extend active notes to rightmost edge of region (if length is changed)
1522 MidiRegionView::extend_active_notes()
1524 if (!_active_notes) {
1528 for (unsigned i=0; i < 128; ++i) {
1529 if (_active_notes[i]) {
1530 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1537 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1539 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1543 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1545 if (!route_ui || !route_ui->midi_track()) {
1549 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1553 /* NotePlayer deletes itself */
1557 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1559 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1563 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1565 if (!route_ui || !route_ui->midi_track()) {
1569 delete _note_player;
1570 _note_player = new NotePlayer (route_ui->midi_track ());
1571 _note_player->add (note);
1572 _note_player->on ();
1576 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1578 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1582 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1584 if (!route_ui || !route_ui->midi_track()) {
1588 delete _note_player;
1589 _note_player = new NotePlayer (route_ui->midi_track());
1591 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1592 _note_player->add (*n);
1595 _note_player->on ();
1600 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1602 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1603 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1605 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1606 (note->note() <= midi_stream_view()->highest_note());
1611 /** Update a canvas note's size from its model note.
1612 * @param ev Canvas note to update.
1613 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1616 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1618 boost::shared_ptr<NoteType> note = ev->note();
1619 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1620 const double y1 = midi_stream_view()->note_to_y(note->note());
1622 ev->property_x1() = x;
1623 ev->property_y1() = y1;
1625 /* trim note display to not overlap the end of its region */
1627 if (note->length() > 0) {
1628 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1629 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1631 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1634 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1636 if (note->length() == 0) {
1637 if (_active_notes) {
1638 assert(note->note() < 128);
1639 // If this note is already active there's a stuck note,
1640 // finish the old note rectangle
1641 if (_active_notes[note->note()]) {
1642 CanvasNote* const old_rect = _active_notes[note->note()];
1643 boost::shared_ptr<NoteType> old_note = old_rect->note();
1644 old_rect->property_x2() = x;
1645 old_rect->property_outline_what() = (guint32) 0xF;
1647 _active_notes[note->note()] = ev;
1649 /* outline all but right edge */
1650 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1652 /* outline all edges */
1653 ev->property_outline_what() = (guint32) 0xF;
1656 if (update_ghost_regions) {
1657 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1658 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1660 gr->update_note (ev);
1667 MidiRegionView::update_hit (CanvasHit* ev)
1669 boost::shared_ptr<NoteType> note = ev->note();
1671 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1672 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1673 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1674 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1678 return diamond_size;
1681 /** Add a MIDI note to the view (with length).
1683 * If in sustained mode, notes with length 0 will be considered active
1684 * notes, and resolve_note should be called when the corresponding note off
1685 * event arrives, to properly display the note.
1688 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1690 CanvasNoteEvent* event = 0;
1692 assert(note->time() >= 0);
1693 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1695 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1697 if (midi_view()->note_mode() == Sustained) {
1699 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1701 update_note (ev_rect);
1705 MidiGhostRegion* gr;
1707 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1708 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1709 gr->add_note(ev_rect);
1713 } else if (midi_view()->note_mode() == Percussive) {
1715 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1717 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1719 update_hit (ev_diamond);
1728 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1729 note_selected(event, true);
1732 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1733 event->show_velocity();
1736 event->on_channel_selection_change(_last_channel_selection);
1737 _events.push_back(event);
1746 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1747 MidiStreamView* const view = mtv->midi_view();
1749 view->update_note_range (note->note());
1753 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1754 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1756 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1758 /* potentially extend region to hold new note */
1760 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1761 framepos_t region_end = _region->last_frame();
1763 if (end_frame > region_end) {
1764 _region->set_length (end_frame - _region->position());
1767 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1768 MidiStreamView* const view = mtv->midi_view();
1770 view->update_note_range(new_note->note());
1772 _marked_for_selection.clear ();
1775 start_note_diff_command (_("step add"));
1776 note_diff_add_note (new_note, true, false);
1779 // last_step_edit_note = new_note;
1783 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1785 change_note_lengths (false, false, beats, false, true);
1788 /** Add a new patch change flag to the canvas.
1789 * @param patch the patch change to add
1790 * @param the text to display in the flag
1791 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1794 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1796 assert (patch->time() >= 0);
1798 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1799 const double x = trackview.editor().frame_to_pixel (region_frames);
1801 double const height = midi_stream_view()->contents_height();
1803 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1804 new CanvasPatchChange(*this, *group,
1813 if (patch_change->width() < _pixel_width) {
1814 // Show unless patch change is beyond the region bounds
1815 if (region_frames < 0 || region_frames >= _region->length()) {
1816 patch_change->hide();
1818 patch_change->show();
1821 patch_change->hide ();
1824 _patch_changes.push_back (patch_change);
1827 MIDI::Name::PatchPrimaryKey
1828 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1830 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1834 MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1836 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1837 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1841 if (i != _model->patch_changes().end()) {
1842 key.bank_number = (*i)->bank();
1843 key.program_number = (*i)->program ();
1845 key.bank_number = key.program_number = 0;
1848 assert (key.is_sane());
1852 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1854 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1856 if (pc.patch()->program() != new_patch.program_number) {
1857 c->change_program (pc.patch (), new_patch.program_number);
1860 int const new_bank = new_patch.bank_number;
1861 if (pc.patch()->bank() != new_bank) {
1862 c->change_bank (pc.patch (), new_bank);
1865 _model->apply_command (*trackview.session(), c);
1867 _patch_changes.clear ();
1868 display_patch_changes ();
1872 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1874 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1876 if (old_change->time() != new_change.time()) {
1877 c->change_time (old_change, new_change.time());
1880 if (old_change->channel() != new_change.channel()) {
1881 c->change_channel (old_change, new_change.channel());
1884 if (old_change->program() != new_change.program()) {
1885 c->change_program (old_change, new_change.program());
1888 if (old_change->bank() != new_change.bank()) {
1889 c->change_bank (old_change, new_change.bank());
1892 _model->apply_command (*trackview.session(), c);
1894 _patch_changes.clear ();
1895 display_patch_changes ();
1898 /** Add a patch change to the region.
1899 * @param t Time in frames relative to region position
1900 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1901 * MidiTimeAxisView::get_channel_for_add())
1904 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1906 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1908 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1909 c->add (MidiModel::PatchChangePtr (
1910 new Evoral::PatchChange<Evoral::MusicalTime> (
1911 absolute_frames_to_source_beats (_region->position() + t),
1912 mtv->get_channel_for_add(), patch.program(), patch.bank()
1917 _model->apply_command (*trackview.session(), c);
1919 _patch_changes.clear ();
1920 display_patch_changes ();
1924 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1926 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1927 c->change_time (pc.patch (), t);
1928 _model->apply_command (*trackview.session(), c);
1930 _patch_changes.clear ();
1931 display_patch_changes ();
1935 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1937 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1938 c->remove (pc->patch ());
1939 _model->apply_command (*trackview.session(), c);
1941 _patch_changes.clear ();
1942 display_patch_changes ();
1946 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1948 if (patch.patch()->program() < 127) {
1949 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1950 key.program_number++;
1951 change_patch_change (patch, key);
1956 MidiRegionView::next_patch (CanvasPatchChange& patch)
1958 if (patch.patch()->program() > 0) {
1959 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1960 key.program_number--;
1961 change_patch_change (patch, key);
1966 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1968 if (patch.patch()->program() < 127) {
1969 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1970 if (key.bank_number > 0) {
1972 change_patch_change (patch, key);
1978 MidiRegionView::next_bank (CanvasPatchChange& patch)
1980 if (patch.patch()->program() > 0) {
1981 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1982 if (key.bank_number < 127) {
1984 change_patch_change (patch, key);
1990 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1992 if (_selection.empty()) {
1996 _selection.erase (cne);
2000 MidiRegionView::delete_selection()
2002 if (_selection.empty()) {
2006 start_note_diff_command (_("delete selection"));
2008 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2009 if ((*i)->selected()) {
2010 _note_diff_command->remove((*i)->note());
2020 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2022 start_note_diff_command (_("delete note"));
2023 _note_diff_command->remove (n);
2026 trackview.editor().verbose_cursor()->hide ();
2030 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2032 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2034 Selection::iterator tmp = i;
2037 (*i)->set_selected (false);
2038 (*i)->hide_velocity ();
2039 _selection.erase (i);
2047 /* this does not change the status of this regionview w.r.t the editor
2052 SelectionCleared (this); /* EMIT SIGNAL */
2057 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2059 clear_selection_except (ev);
2061 /* don't bother with checking to see if we should remove this
2062 regionview from the editor selection, since we're about to add
2063 another note, and thus put/keep this regionview in the editor
2067 if (!ev->selected()) {
2068 add_to_selection (ev);
2073 MidiRegionView::select_all_notes ()
2077 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2078 add_to_selection (*i);
2083 MidiRegionView::select_range (framepos_t start, framepos_t end)
2087 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2088 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2089 if (t >= start && t <= end) {
2090 add_to_selection (*i);
2096 MidiRegionView::invert_selection ()
2098 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2099 if ((*i)->selected()) {
2100 remove_from_selection(*i);
2102 add_to_selection (*i);
2108 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2110 uint8_t low_note = 127;
2111 uint8_t high_note = 0;
2112 MidiModel::Notes& notes (_model->notes());
2113 _optimization_iterator = _events.begin();
2119 if (extend && _selection.empty()) {
2125 /* scan existing selection to get note range */
2127 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2128 if ((*i)->note()->note() < low_note) {
2129 low_note = (*i)->note()->note();
2131 if ((*i)->note()->note() > high_note) {
2132 high_note = (*i)->note()->note();
2136 low_note = min (low_note, notenum);
2137 high_note = max (high_note, notenum);
2140 _no_sound_notes = true;
2142 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2144 boost::shared_ptr<NoteType> note (*n);
2145 CanvasNoteEvent* cne;
2146 bool select = false;
2148 if (((1 << note->channel()) & channel_mask) != 0) {
2150 if ((note->note() >= low_note && note->note() <= high_note)) {
2153 } else if (note->note() == notenum) {
2159 if ((cne = find_canvas_note (note)) != 0) {
2160 // extend is false because we've taken care of it,
2161 // since it extends by time range, not pitch.
2162 note_selected (cne, add, false);
2166 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2170 _no_sound_notes = false;
2174 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2176 MidiModel::Notes& notes (_model->notes());
2177 _optimization_iterator = _events.begin();
2179 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2181 boost::shared_ptr<NoteType> note (*n);
2182 CanvasNoteEvent* cne;
2184 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2185 if ((cne = find_canvas_note (note)) != 0) {
2186 if (cne->selected()) {
2187 note_deselected (cne);
2189 note_selected (cne, true, false);
2197 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2200 clear_selection_except (ev);
2201 if (!_selection.empty()) {
2202 PublicEditor& editor (trackview.editor());
2203 editor.get_selection().add (this);
2209 if (!ev->selected()) {
2210 add_to_selection (ev);
2214 /* find end of latest note selected, select all between that and the start of "ev" */
2216 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2217 Evoral::MusicalTime latest = 0;
2219 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2220 if ((*i)->note()->end_time() > latest) {
2221 latest = (*i)->note()->end_time();
2223 if ((*i)->note()->time() < earliest) {
2224 earliest = (*i)->note()->time();
2228 if (ev->note()->end_time() > latest) {
2229 latest = ev->note()->end_time();
2232 if (ev->note()->time() < earliest) {
2233 earliest = ev->note()->time();
2236 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2238 /* find notes entirely within OR spanning the earliest..latest range */
2240 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2241 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2242 add_to_selection (*i);
2250 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2252 remove_from_selection (ev);
2256 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2266 // TODO: Make this faster by storing the last updated selection rect, and only
2267 // adjusting things that are in the area that appears/disappeared.
2268 // We probably need a tree to be able to find events in O(log(n)) time.
2270 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2272 /* check if any corner of the note is inside the rect
2275 1) this is computing "touched by", not "contained by" the rect.
2276 2) this does not require that events be sorted in time.
2279 const double ix1 = (*i)->x1();
2280 const double ix2 = (*i)->x2();
2281 const double iy1 = (*i)->y1();
2282 const double iy2 = (*i)->y2();
2284 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2285 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2286 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2287 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2290 if (!(*i)->selected()) {
2291 add_to_selection (*i);
2293 } else if ((*i)->selected() && !extend) {
2294 // Not inside rectangle
2295 remove_from_selection (*i);
2301 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2307 // TODO: Make this faster by storing the last updated selection rect, and only
2308 // adjusting things that are in the area that appears/disappeared.
2309 // We probably need a tree to be able to find events in O(log(n)) time.
2311 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2313 /* check if any corner of the note is inside the rect
2316 1) this is computing "touched by", not "contained by" the rect.
2317 2) this does not require that events be sorted in time.
2320 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2321 // within y- (note-) range
2322 if (!(*i)->selected()) {
2323 add_to_selection (*i);
2325 } else if ((*i)->selected() && !extend) {
2326 // Not inside rectangle
2327 remove_from_selection (*i);
2333 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2335 Selection::iterator i = _selection.find (ev);
2337 if (i != _selection.end()) {
2338 _selection.erase (i);
2341 ev->set_selected (false);
2342 ev->hide_velocity ();
2344 if (_selection.empty()) {
2345 PublicEditor& editor (trackview.editor());
2346 editor.get_selection().remove (this);
2351 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2353 bool add_mrv_selection = false;
2355 if (_selection.empty()) {
2356 add_mrv_selection = true;
2359 if (_selection.insert (ev).second) {
2360 ev->set_selected (true);
2361 start_playing_midi_note ((ev)->note());
2364 if (add_mrv_selection) {
2365 PublicEditor& editor (trackview.editor());
2366 editor.get_selection().add (this);
2371 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2373 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2374 PossibleChord to_play;
2375 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2377 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2378 if ((*i)->note()->time() < earliest) {
2379 earliest = (*i)->note()->time();
2383 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2384 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2385 to_play.push_back ((*i)->note());
2387 (*i)->move_event(dx, dy);
2390 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2392 if (to_play.size() > 1) {
2394 PossibleChord shifted;
2396 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2397 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2398 moved_note->set_note (moved_note->note() + cumulative_dy);
2399 shifted.push_back (moved_note);
2402 start_playing_midi_chord (shifted);
2404 } else if (!to_play.empty()) {
2406 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2407 moved_note->set_note (moved_note->note() + cumulative_dy);
2408 start_playing_midi_note (moved_note);
2414 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2416 assert (!_selection.empty());
2418 uint8_t lowest_note_in_selection = 127;
2419 uint8_t highest_note_in_selection = 0;
2420 uint8_t highest_note_difference = 0;
2422 // find highest and lowest notes first
2424 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2425 uint8_t pitch = (*i)->note()->note();
2426 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2427 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2431 cerr << "dnote: " << (int) dnote << endl;
2432 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2433 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2434 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2435 << int(highest_note_in_selection) << endl;
2436 cerr << "selection size: " << _selection.size() << endl;
2437 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2440 // Make sure the note pitch does not exceed the MIDI standard range
2441 if (highest_note_in_selection + dnote > 127) {
2442 highest_note_difference = highest_note_in_selection - 127;
2445 start_note_diff_command (_("move notes"));
2447 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2449 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2450 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2456 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2458 uint8_t original_pitch = (*i)->note()->note();
2459 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2461 // keep notes in standard midi range
2462 clamp_to_0_127(new_pitch);
2464 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2465 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2467 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2472 // care about notes being moved beyond the upper/lower bounds on the canvas
2473 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2474 highest_note_in_selection > midi_stream_view()->highest_note()) {
2475 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2479 /** @param x Pixel relative to the region position.
2480 * @return Snapped frame relative to the region position.
2483 MidiRegionView::snap_pixel_to_frame(double x)
2485 PublicEditor& editor (trackview.editor());
2486 return snap_frame_to_frame (editor.pixel_to_frame (x));
2489 /** @param x Pixel relative to the region position.
2490 * @return Snapped pixel relative to the region position.
2493 MidiRegionView::snap_to_pixel(double x)
2495 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2499 MidiRegionView::get_position_pixels()
2501 framepos_t region_frame = get_position();
2502 return trackview.editor().frame_to_pixel(region_frame);
2506 MidiRegionView::get_end_position_pixels()
2508 framepos_t frame = get_position() + get_duration ();
2509 return trackview.editor().frame_to_pixel(frame);
2513 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2515 /* the time converter will return the frame corresponding to `beats'
2516 relative to the start of the source. The start of the source
2517 is an implied position given by region->position - region->start
2519 const framepos_t source_start = _region->position() - _region->start();
2520 return source_start + _source_relative_time_converter.to (beats);
2524 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2526 /* the `frames' argument needs to be converted into a frame count
2527 relative to the start of the source before being passed in to the
2530 const framepos_t source_start = _region->position() - _region->start();
2531 return _source_relative_time_converter.from (frames - source_start);
2535 MidiRegionView::region_beats_to_region_frames(double beats) const
2537 return _region_relative_time_converter.to(beats);
2541 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2543 return _region_relative_time_converter.from(frames);
2547 MidiRegionView::begin_resizing (bool /*at_front*/)
2549 _resize_data.clear();
2551 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2552 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2554 // only insert CanvasNotes into the map
2556 NoteResizeData *resize_data = new NoteResizeData();
2557 resize_data->canvas_note = note;
2559 // create a new SimpleRect from the note which will be the resize preview
2560 SimpleRect *resize_rect = new SimpleRect(
2561 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2563 // calculate the colors: get the color settings
2564 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2565 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2568 // make the resize preview notes more transparent and bright
2569 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2571 // calculate color based on note velocity
2572 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2573 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2577 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2578 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2580 resize_data->resize_rect = resize_rect;
2581 _resize_data.push_back(resize_data);
2586 /** Update resizing notes while user drags.
2587 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2588 * @param at_front which end of the note (true == note on, false == note off)
2589 * @param delta_x change in mouse position since the start of the drag
2590 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2591 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2592 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2593 * as the \a primary note.
2596 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2598 bool cursor_set = false;
2600 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2601 SimpleRect* resize_rect = (*i)->resize_rect;
2602 CanvasNote* canvas_note = (*i)->canvas_note;
2607 current_x = canvas_note->x1() + delta_x;
2609 current_x = primary->x1() + delta_x;
2613 current_x = canvas_note->x2() + delta_x;
2615 current_x = primary->x2() + delta_x;
2620 resize_rect->property_x1() = snap_to_pixel(current_x);
2621 resize_rect->property_x2() = canvas_note->x2();
2623 resize_rect->property_x2() = snap_to_pixel(current_x);
2624 resize_rect->property_x1() = canvas_note->x1();
2630 beats = snap_pixel_to_frame (current_x);
2631 beats = region_frames_to_region_beats (beats);
2636 if (beats < canvas_note->note()->end_time()) {
2637 len = canvas_note->note()->time() - beats;
2638 len += canvas_note->note()->length();
2643 if (beats >= canvas_note->note()->time()) {
2644 len = beats - canvas_note->note()->time();
2651 snprintf (buf, sizeof (buf), "%.3g beats", len);
2652 show_verbose_cursor (buf, 0, 0);
2661 /** Finish resizing notes when the user releases the mouse button.
2662 * Parameters the same as for \a update_resizing().
2665 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2667 start_note_diff_command (_("resize notes"));
2669 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2670 CanvasNote* canvas_note = (*i)->canvas_note;
2671 SimpleRect* resize_rect = (*i)->resize_rect;
2673 /* Get the new x position for this resize, which is in pixels relative
2674 * to the region position.
2681 current_x = canvas_note->x1() + delta_x;
2683 current_x = primary->x1() + delta_x;
2687 current_x = canvas_note->x2() + delta_x;
2689 current_x = primary->x2() + delta_x;
2693 /* Convert that to a frame within the source */
2694 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2696 /* and then to beats */
2697 current_x = region_frames_to_region_beats (current_x);
2699 if (at_front && current_x < canvas_note->note()->end_time()) {
2700 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2702 double len = canvas_note->note()->time() - current_x;
2703 len += canvas_note->note()->length();
2706 /* XXX convert to beats */
2707 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2712 double len = current_x - canvas_note->note()->time();
2715 /* XXX convert to beats */
2716 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2724 _resize_data.clear();
2729 MidiRegionView::abort_resizing ()
2731 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2732 delete (*i)->resize_rect;
2736 _resize_data.clear ();
2740 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2742 uint8_t new_velocity;
2745 new_velocity = event->note()->velocity() + velocity;
2746 clamp_to_0_127(new_velocity);
2748 new_velocity = velocity;
2751 event->set_selected (event->selected()); // change color
2753 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2757 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2762 new_note = event->note()->note() + note;
2767 clamp_to_0_127 (new_note);
2768 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2772 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2774 bool change_start = false;
2775 bool change_length = false;
2776 Evoral::MusicalTime new_start = 0;
2777 Evoral::MusicalTime new_length = 0;
2779 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2781 front_delta: if positive - move the start of the note later in time (shortening it)
2782 if negative - move the start of the note earlier in time (lengthening it)
2784 end_delta: if positive - move the end of the note later in time (lengthening it)
2785 if negative - move the end of the note earlier in time (shortening it)
2789 if (front_delta < 0) {
2791 if (event->note()->time() < -front_delta) {
2794 new_start = event->note()->time() + front_delta; // moves earlier
2797 /* start moved toward zero, so move the end point out to where it used to be.
2798 Note that front_delta is negative, so this increases the length.
2801 new_length = event->note()->length() - front_delta;
2802 change_start = true;
2803 change_length = true;
2807 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2809 if (new_pos < event->note()->end_time()) {
2810 new_start = event->note()->time() + front_delta;
2811 /* start moved toward the end, so move the end point back to where it used to be */
2812 new_length = event->note()->length() - front_delta;
2813 change_start = true;
2814 change_length = true;
2821 bool can_change = true;
2822 if (end_delta < 0) {
2823 if (event->note()->length() < -end_delta) {
2829 new_length = event->note()->length() + end_delta;
2830 change_length = true;
2835 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2838 if (change_length) {
2839 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2844 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2846 uint8_t new_channel;
2850 if (event->note()->channel() < -chn) {
2853 new_channel = event->note()->channel() + chn;
2856 new_channel = event->note()->channel() + chn;
2859 new_channel = (uint8_t) chn;
2862 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2866 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2868 Evoral::MusicalTime new_time;
2872 if (event->note()->time() < -delta) {
2875 new_time = event->note()->time() + delta;
2878 new_time = event->note()->time() + delta;
2884 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2888 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2890 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2894 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2899 if (_selection.empty()) {
2914 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2915 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2921 start_note_diff_command (_("change velocities"));
2923 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2924 Selection::iterator next = i;
2928 if (i == _selection.begin()) {
2929 change_note_velocity (*i, delta, true);
2930 value = (*i)->note()->velocity() + delta;
2932 change_note_velocity (*i, value, false);
2936 change_note_velocity (*i, delta, true);
2944 if (!_selection.empty()) {
2946 snprintf (buf, sizeof (buf), "Vel %d",
2947 (int) (*_selection.begin())->note()->velocity());
2948 show_verbose_cursor (buf, 10, 10);
2954 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2956 if (_selection.empty()) {
2973 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2975 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2979 if ((int8_t) (*i)->note()->note() + delta > 127) {
2986 start_note_diff_command (_("transpose"));
2988 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2989 Selection::iterator next = i;
2991 change_note_note (*i, delta, true);
2999 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
3005 /* grab the current grid distance */
3007 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
3009 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
3010 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3020 start_note_diff_command (_("change note lengths"));
3022 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3023 Selection::iterator next = i;
3026 /* note the negation of the delta for start */
3028 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3037 MidiRegionView::nudge_notes (bool forward)
3039 if (_selection.empty()) {
3043 /* pick a note as the point along the timeline to get the nudge distance.
3044 its not necessarily the earliest note, so we may want to pull the notes out
3045 into a vector and sort before using the first one.
3048 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3050 framecnt_t distance;
3052 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3054 /* grid is off - use nudge distance */
3056 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3062 framepos_t next_pos = ref_point;
3065 if (max_framepos - 1 < next_pos) {
3069 if (next_pos == 0) {
3075 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3076 distance = ref_point - next_pos;
3079 if (distance == 0) {
3083 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3089 start_note_diff_command (_("nudge"));
3091 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3092 Selection::iterator next = i;
3094 change_note_time (*i, delta, true);
3102 MidiRegionView::change_channel(uint8_t channel)
3104 start_note_diff_command(_("change channel"));
3105 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3106 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3114 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3116 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3118 pre_enter_cursor = editor->get_canvas_cursor ();
3120 if (_mouse_state == SelectTouchDragging) {
3121 note_selected (ev, true);
3124 show_verbose_cursor (ev->note ());
3128 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3130 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3132 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3133 (*i)->hide_velocity ();
3136 editor->verbose_cursor()->hide ();
3138 if (pre_enter_cursor) {
3139 editor->set_canvas_cursor (pre_enter_cursor);
3140 pre_enter_cursor = 0;
3145 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* p)
3148 /* XXX should get patch name if we can */
3149 s << _("Bank:") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3150 << _("Program:") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3151 << _("Channel:") << ((int) p->patch()->channel() + 1);
3152 show_verbose_cursor (s.str(), 10, 20);
3157 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3159 trackview.editor().verbose_cursor()->hide ();
3160 /* focus will transfer back via the enter-notify event sent to this
3166 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3168 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3169 Editing::MouseMode mm = editor->current_mouse_mode();
3170 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3172 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3173 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3174 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3175 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3177 if (pre_enter_cursor && can_set_cursor) {
3178 editor->set_canvas_cursor (pre_enter_cursor);
3184 MidiRegionView::set_frame_color()
3188 TimeAxisViewItem::set_frame_color ();
3195 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3196 } else if (high_enough_for_name) {
3197 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3202 if (!rect_visible) {
3203 f = UINT_RGBA_CHANGE_A (f, 0);
3206 frame->property_fill_color_rgba() = f;
3210 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3212 if (mode == ForceChannel) {
3213 mask = 0xFFFF; // Show all notes as active (below)
3216 // Update notes for selection
3217 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3218 (*i)->on_channel_selection_change(mask);
3221 _last_channel_selection = mask;
3223 _patch_changes.clear ();
3224 display_patch_changes ();
3228 MidiRegionView::instrument_settings_changed ()
3234 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3236 if (_selection.empty()) {
3240 PublicEditor& editor (trackview.editor());
3244 /* XXX what to do ? */
3248 editor.get_cut_buffer().add (selection_as_cut_buffer());
3256 start_note_diff_command();
3258 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3265 note_diff_remove_note (*i);
3275 MidiRegionView::selection_as_cut_buffer () const
3279 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3280 NoteType* n = (*i)->note().get();
3281 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3284 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3290 /** This method handles undo */
3292 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3298 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3300 trackview.session()->begin_reversible_command (_("paste"));
3302 start_note_diff_command (_("paste"));
3304 Evoral::MusicalTime beat_delta;
3305 Evoral::MusicalTime paste_pos_beats;
3306 Evoral::MusicalTime duration;
3307 Evoral::MusicalTime end_point = 0;
3309 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3310 paste_pos_beats = absolute_frames_to_source_beats (pos);
3311 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3312 paste_pos_beats = 0;
3314 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",
3315 (*mcb.notes().begin())->time(),
3316 (*mcb.notes().rbegin())->end_time(),
3317 duration, pos, _region->position(),
3318 paste_pos_beats, beat_delta));
3322 for (int n = 0; n < (int) times; ++n) {
3324 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3326 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3327 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3329 /* make all newly added notes selected */
3331 note_diff_add_note (copied_note, true);
3332 end_point = copied_note->end_time();
3335 paste_pos_beats += duration;
3338 /* if we pasted past the current end of the region, extend the region */
3340 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3341 framepos_t region_end = _region->position() + _region->length() - 1;
3343 if (end_frame > region_end) {
3345 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3347 _region->clear_changes ();
3348 _region->set_length (end_frame - _region->position());
3349 trackview.session()->add_command (new StatefulDiffCommand (_region));
3354 trackview.session()->commit_reversible_command ();
3357 struct EventNoteTimeEarlyFirstComparator {
3358 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3359 return a->note()->time() < b->note()->time();
3364 MidiRegionView::time_sort_events ()
3366 if (!_sort_needed) {
3370 EventNoteTimeEarlyFirstComparator cmp;
3373 _sort_needed = false;
3377 MidiRegionView::goto_next_note (bool add_to_selection)
3379 bool use_next = false;
3381 if (_events.back()->selected()) {
3385 time_sort_events ();
3387 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3388 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3390 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3391 if ((*i)->selected()) {
3394 } else if (use_next) {
3395 if (channel_mask & (1 << (*i)->note()->channel())) {
3396 if (!add_to_selection) {
3399 note_selected (*i, true, false);
3406 /* use the first one */
3408 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3409 unique_select (_events.front());
3414 MidiRegionView::goto_previous_note (bool add_to_selection)
3416 bool use_next = false;
3418 if (_events.front()->selected()) {
3422 time_sort_events ();
3424 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3425 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3427 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3428 if ((*i)->selected()) {
3431 } else if (use_next) {
3432 if (channel_mask & (1 << (*i)->note()->channel())) {
3433 if (!add_to_selection) {
3436 note_selected (*i, true, false);
3443 /* use the last one */
3445 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3446 unique_select (*(_events.rbegin()));
3451 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3453 bool had_selected = false;
3455 time_sort_events ();
3457 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3458 if ((*i)->selected()) {
3459 selected.insert ((*i)->note());
3460 had_selected = true;
3464 if (allow_all_if_none_selected && !had_selected) {
3465 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3466 selected.insert ((*i)->note());
3472 MidiRegionView::update_ghost_note (double x, double y)
3474 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3479 _note_group->w2i (x, y);
3481 PublicEditor& editor = trackview.editor ();
3483 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3484 framecnt_t grid_frames;
3485 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3487 /* use region_frames... because we are converting a delta within the region
3491 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3497 /* note that this sets the time of the ghost note in beats relative to
3498 the start of the source; that is how all note times are stored.
3500 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3501 _ghost_note->note()->set_length (length);
3502 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3503 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3505 /* the ghost note does not appear in ghost regions, so pass false in here */
3506 update_note (_ghost_note, false);
3508 show_verbose_cursor (_ghost_note->note ());
3512 MidiRegionView::create_ghost_note (double x, double y)
3514 remove_ghost_note ();
3516 boost::shared_ptr<NoteType> g (new NoteType);
3517 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3518 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3519 update_ghost_note (x, y);
3520 _ghost_note->show ();
3525 show_verbose_cursor (_ghost_note->note ());
3529 MidiRegionView::snap_changed ()
3535 create_ghost_note (_last_ghost_x, _last_ghost_y);
3539 MidiRegionView::drop_down_keys ()
3541 _mouse_state = None;
3545 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3547 double note = midi_stream_view()->y_to_note(y);
3549 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3551 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3553 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3554 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3555 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3556 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3561 bool add_mrv_selection = false;
3563 if (_selection.empty()) {
3564 add_mrv_selection = true;
3567 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3568 if (_selection.insert (*i).second) {
3569 (*i)->set_selected (true);
3573 if (add_mrv_selection) {
3574 PublicEditor& editor (trackview.editor());
3575 editor.get_selection().add (this);
3580 MidiRegionView::color_handler ()
3582 RegionView::color_handler ();
3584 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3585 (*i)->set_selected ((*i)->selected()); // will change color
3588 /* XXX probably more to do here */
3592 MidiRegionView::enable_display (bool yn)
3594 RegionView::enable_display (yn);
3601 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3603 if (_step_edit_cursor == 0) {
3604 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3606 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3607 _step_edit_cursor->property_y1() = 0;
3608 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3609 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3610 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3613 move_step_edit_cursor (pos);
3614 _step_edit_cursor->show ();
3618 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3620 _step_edit_cursor_position = pos;
3622 if (_step_edit_cursor) {
3623 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3624 _step_edit_cursor->property_x1() = pixel;
3625 set_step_edit_cursor_width (_step_edit_cursor_width);
3630 MidiRegionView::hide_step_edit_cursor ()
3632 if (_step_edit_cursor) {
3633 _step_edit_cursor->hide ();
3638 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3640 _step_edit_cursor_width = beats;
3642 if (_step_edit_cursor) {
3643 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3647 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3648 * @param w Source that the data will end up in.
3651 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3653 if (!_active_notes) {
3654 /* we aren't actively being recorded to */
3658 boost::shared_ptr<MidiSource> src = w.lock ();
3659 if (!src || src != midi_region()->midi_source()) {
3660 /* recorded data was not destined for our source */
3664 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3666 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3668 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3670 framepos_t back = max_framepos;
3672 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3673 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3674 assert (ev.buffer ());
3676 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3677 frames from the start of the source, and so time_beats is in terms of the
3681 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3683 if (ev.type() == MIDI_CMD_NOTE_ON) {
3685 boost::shared_ptr<NoteType> note (
3686 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3689 add_note (note, true);
3691 /* fix up our note range */
3692 if (ev.note() < _current_range_min) {
3693 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3694 } else if (ev.note() > _current_range_max) {
3695 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3698 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3699 resolve_note (ev.note (), time_beats);
3705 midi_stream_view()->check_record_layers (region(), back);
3709 MidiRegionView::trim_front_starting ()
3711 /* Reparent the note group to the region view's parent, so that it doesn't change
3712 when the region view is trimmed.
3714 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3715 _temporary_note_group->move (group->property_x(), group->property_y());
3716 _note_group->reparent (*_temporary_note_group);
3720 MidiRegionView::trim_front_ending ()
3722 _note_group->reparent (*group);
3723 delete _temporary_note_group;
3724 _temporary_note_group = 0;
3726 if (_region->start() < 0) {
3727 /* Trim drag made start time -ve; fix this */
3728 midi_region()->fix_negative_start ();
3733 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3735 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY);
3736 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3740 change_patch_change (pc->patch(), d.patch ());
3745 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3748 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3749 Evoral::midi_note_name (n->note()).c_str(),
3751 (int) n->channel() + 1,
3752 (int) n->velocity());
3754 show_verbose_cursor (buf, 10, 20);
3758 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3762 trackview.editor().get_pointer_position (wx, wy);
3767 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3769 double x1, y1, x2, y2;
3770 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3772 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3773 wy -= (y2 - y1) + 2 * yoffset;
3776 trackview.editor().verbose_cursor()->set (text, wx, wy);
3777 trackview.editor().verbose_cursor()->show ();
3780 /** @param p A session framepos.
3781 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3782 * @return p snapped to the grid subdivision underneath it.
3785 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3787 PublicEditor& editor = trackview.editor ();
3790 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3796 grid_frames = region_beats_to_region_frames (grid_beats);
3798 /* Hack so that we always snap to the note that we are over, instead of snapping
3799 to the next one if we're more than halfway through the one we're over.
3801 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3802 p -= grid_frames / 2;
3805 return snap_frame_to_frame (p);
3808 /** Called when the selection has been cleared in any MidiRegionView.
3809 * @param rv MidiRegionView that the selection was cleared in.
3812 MidiRegionView::selection_cleared (MidiRegionView* rv)
3818 /* Clear our selection in sympathy; but don't signal the fact */
3819 clear_selection (false);
3823 MidiRegionView::note_button_release ()
3825 delete _note_player;