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);
268 reset_width_dependent_items (_pixel_width);
272 _enable_display = true;
275 display_model (_model);
279 group->raise_to_top();
280 group->signal_event().connect(
281 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
283 midi_view()->signal_channel_mode_changed().connect(
284 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
286 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
288 route_ui->route()->instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
289 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
292 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
293 boost::bind (&MidiRegionView::snap_changed, this),
296 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
297 connect_to_diskstream ();
299 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
302 const boost::shared_ptr<ARDOUR::MidiRegion>
303 MidiRegionView::midi_region() const
305 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
309 MidiRegionView::connect_to_diskstream ()
311 midi_view()->midi_track()->DataRecorded.connect(
312 *this, invalidator(*this),
313 boost::bind (&MidiRegionView::data_recorded, this, _1),
318 MidiRegionView::canvas_event(GdkEvent* ev)
323 case GDK_ENTER_NOTIFY:
324 case GDK_LEAVE_NOTIFY:
325 _last_event_x = ev->crossing.x;
326 _last_event_y = ev->crossing.y;
328 case GDK_MOTION_NOTIFY:
329 _last_event_x = ev->motion.x;
330 _last_event_y = ev->motion.y;
336 if (ev->type == GDK_2BUTTON_PRESS) {
337 return trackview.editor().toggle_internal_editing_from_double_click (ev);
340 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
341 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
342 (trackview.editor().current_mouse_mode() == MouseZoom)) {
343 // handle non-draw modes elsewhere
349 return scroll (&ev->scroll);
352 return key_press (&ev->key);
354 case GDK_KEY_RELEASE:
355 return key_release (&ev->key);
357 case GDK_BUTTON_PRESS:
358 return button_press (&ev->button);
360 case GDK_BUTTON_RELEASE:
361 r = button_release (&ev->button);
366 case GDK_ENTER_NOTIFY:
367 return enter_notify (&ev->crossing);
369 case GDK_LEAVE_NOTIFY:
370 return leave_notify (&ev->crossing);
372 case GDK_MOTION_NOTIFY:
373 return motion (&ev->motion);
383 MidiRegionView::remove_ghost_note ()
390 MidiRegionView::enter_notify (GdkEventCrossing* ev)
392 trackview.editor().MouseModeChanged.connect (
393 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
396 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
397 create_ghost_note (ev->x, ev->y);
400 if (!trackview.editor().internal_editing()) {
401 Keyboard::magic_widget_drop_focus();
403 Keyboard::magic_widget_grab_focus();
407 // if current operation is non-operational in a midi region, change the cursor to so indicate
408 if (trackview.editor().current_mouse_mode() == MouseGain) {
409 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
410 pre_enter_cursor = editor->get_canvas_cursor();
411 editor->set_canvas_cursor(editor->cursors()->timebar);
418 MidiRegionView::leave_notify (GdkEventCrossing*)
420 _mouse_mode_connection.disconnect ();
422 trackview.editor().verbose_cursor()->hide ();
423 remove_ghost_note ();
425 if (pre_enter_cursor) {
426 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
427 editor->set_canvas_cursor(pre_enter_cursor);
434 MidiRegionView::mouse_mode_changed ()
436 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
437 create_ghost_note (_last_event_x, _last_event_y);
439 remove_ghost_note ();
440 trackview.editor().verbose_cursor()->hide ();
443 if (!trackview.editor().internal_editing()) {
444 Keyboard::magic_widget_drop_focus();
446 Keyboard::magic_widget_grab_focus();
452 MidiRegionView::button_press (GdkEventButton* ev)
454 if (ev->button != 1) {
458 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
459 MouseMode m = editor->current_mouse_mode();
461 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
462 pre_press_cursor = editor->get_canvas_cursor ();
463 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
466 if (_mouse_state != SelectTouchDragging) {
468 _pressed_button = ev->button;
469 _mouse_state = Pressed;
474 _pressed_button = ev->button;
480 MidiRegionView::button_release (GdkEventButton* ev)
482 double event_x, event_y;
484 if (ev->button != 1) {
491 group->w2i(event_x, event_y);
492 group->ungrab(ev->time);
494 PublicEditor& editor = trackview.editor ();
496 if (pre_press_cursor) {
497 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
498 pre_press_cursor = 0;
501 switch (_mouse_state) {
502 case Pressed: // Clicked
504 switch (editor.current_mouse_mode()) {
506 /* no motion occured - simple click */
515 if (Keyboard::is_insert_note_event(ev)) {
517 double event_x, event_y;
521 group->w2i(event_x, event_y);
524 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
530 /* Shorten the length by 1 tick so that we can add a new note at the next
531 grid snap without it overlapping this one.
533 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
535 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
543 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
549 /* Shorten the length by 1 tick so that we can add a new note at the next
550 grid snap without it overlapping this one.
552 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
554 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
565 case SelectRectDragging:
567 editor.drags()->end_grab ((GdkEvent *) ev);
569 create_ghost_note (ev->x, ev->y);
581 MidiRegionView::motion (GdkEventMotion* ev)
583 PublicEditor& editor = trackview.editor ();
585 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
586 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
587 _mouse_state != AddDragging) {
589 create_ghost_note (ev->x, ev->y);
591 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
592 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
594 update_ghost_note (ev->x, ev->y);
596 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
598 remove_ghost_note ();
599 editor.verbose_cursor()->hide ();
601 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
603 update_ghost_note (ev->x, ev->y);
606 /* any motion immediately hides velocity text that may have been visible */
608 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
609 (*i)->hide_velocity ();
612 switch (_mouse_state) {
615 if (_pressed_button == 1) {
617 MouseMode m = editor.current_mouse_mode();
619 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
621 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
622 _mouse_state = AddDragging;
623 remove_ghost_note ();
624 editor.verbose_cursor()->hide ();
626 } else if (m == MouseObject) {
627 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
629 _mouse_state = SelectRectDragging;
631 } else if (m == MouseRange) {
632 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
633 _mouse_state = SelectVerticalDragging;
640 case SelectRectDragging:
641 case SelectVerticalDragging:
643 editor.drags()->motion_handler ((GdkEvent *) ev, false);
646 case SelectTouchDragging:
658 MidiRegionView::scroll (GdkEventScroll* ev)
660 if (_selection.empty()) {
664 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
665 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
666 it still works for zoom.
671 trackview.editor().verbose_cursor()->hide ();
673 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
674 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
676 if (ev->direction == GDK_SCROLL_UP) {
677 change_velocities (true, fine, false, together);
678 } else if (ev->direction == GDK_SCROLL_DOWN) {
679 change_velocities (false, fine, false, together);
685 MidiRegionView::key_press (GdkEventKey* ev)
687 /* since GTK bindings are generally activated on press, and since
688 detectable auto-repeat is the name of the game and only sends
689 repeated presses, carry out key actions at key press, not release.
692 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
694 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
695 _mouse_state = SelectTouchDragging;
698 } else if (ev->keyval == GDK_Escape && unmodified) {
702 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
704 bool start = (ev->keyval == GDK_comma);
705 bool end = (ev->keyval == GDK_period);
706 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
707 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
709 change_note_lengths (fine, shorter, 0.0, start, end);
713 } else if (ev->keyval == GDK_Delete && unmodified) {
718 } else if (ev->keyval == GDK_Tab) {
720 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
721 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
723 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
727 } else if (ev->keyval == GDK_ISO_Left_Tab) {
729 /* Shift-TAB generates ISO Left Tab, for some reason */
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));
740 } else if (ev->keyval == GDK_Up) {
742 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
743 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
744 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
746 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
747 change_velocities (true, fine, allow_smush, together);
749 transpose (true, fine, allow_smush);
753 } else if (ev->keyval == GDK_Down) {
755 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
756 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
757 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
759 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
760 change_velocities (false, fine, allow_smush, together);
762 transpose (false, fine, allow_smush);
766 } else if (ev->keyval == GDK_Left && unmodified) {
771 } else if (ev->keyval == GDK_Right && unmodified) {
776 } else if (ev->keyval == GDK_c && unmodified) {
780 } else if (ev->keyval == GDK_v && unmodified) {
789 MidiRegionView::key_release (GdkEventKey* ev)
791 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
799 MidiRegionView::channel_edit ()
801 if (_selection.empty()) {
805 /* pick a note somewhat at random (since Selection is a set<>) to
806 * provide the "current" channel for the dialog.
809 uint8_t current_channel = (*_selection.begin())->note()->channel ();
810 MidiChannelDialog channel_dialog (current_channel);
811 int ret = channel_dialog.run ();
814 case Gtk::RESPONSE_OK:
820 uint8_t new_channel = channel_dialog.active_channel ();
822 start_note_diff_command (_("channel edit"));
824 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
825 Selection::iterator next = i;
827 change_note_channel (*i, new_channel);
835 MidiRegionView::velocity_edit ()
837 if (_selection.empty()) {
841 /* pick a note somewhat at random (since Selection is a set<>) to
842 * provide the "current" velocity for the dialog.
845 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
846 MidiVelocityDialog velocity_dialog (current_velocity);
847 int ret = velocity_dialog.run ();
850 case Gtk::RESPONSE_OK:
856 uint8_t new_velocity = velocity_dialog.velocity ();
858 start_note_diff_command (_("velocity edit"));
860 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
861 Selection::iterator next = i;
863 change_note_velocity (*i, new_velocity, false);
871 MidiRegionView::show_list_editor ()
874 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
876 _list_editor->present ();
879 /** Add a note to the model, and the view, at a canvas (click) coordinate.
880 * \param t time in frames relative to the position of the region
881 * \param y vertical position in pixels
882 * \param length duration of the note in beats
883 * \param snap_t true to snap t to the grid, otherwise false.
886 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
888 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
889 MidiStreamView* const view = mtv->midi_view();
891 double note = view->y_to_note(y);
894 assert(note <= 127.0);
896 // Start of note in frames relative to region start
898 framecnt_t grid_frames;
899 t = snap_frame_to_grid_underneath (t, grid_frames);
903 assert (length != 0);
905 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
906 region_frames_to_region_beats(t + _region->start()),
908 (uint8_t)note, 0x40));
910 if (_model->contains (new_note)) {
914 view->update_note_range(new_note->note());
916 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
918 _model->apply_command(*trackview.session(), cmd);
920 play_midi_note (new_note);
924 MidiRegionView::clear_events()
929 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
930 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
935 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
940 _patch_changes.clear();
942 _optimization_iterator = _events.end();
946 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
950 content_connection.disconnect ();
951 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
955 if (_enable_display) {
961 MidiRegionView::start_note_diff_command (string name)
963 if (!_note_diff_command) {
964 _note_diff_command = _model->new_note_diff_command (name);
969 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
971 if (_note_diff_command) {
972 _note_diff_command->add (note);
975 _marked_for_selection.insert(note);
978 _marked_for_velocity.insert(note);
983 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
985 if (_note_diff_command && ev->note()) {
986 _note_diff_command->remove(ev->note());
991 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
992 MidiModel::NoteDiffCommand::Property property,
995 if (_note_diff_command) {
996 _note_diff_command->change (ev->note(), property, val);
1001 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1002 MidiModel::NoteDiffCommand::Property property,
1003 Evoral::MusicalTime val)
1005 if (_note_diff_command) {
1006 _note_diff_command->change (ev->note(), property, val);
1011 MidiRegionView::apply_diff (bool as_subcommand)
1015 if (!_note_diff_command) {
1019 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1020 // Mark all selected notes for selection when model reloads
1021 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1022 _marked_for_selection.insert((*i)->note());
1026 if (as_subcommand) {
1027 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1029 _model->apply_command (*trackview.session(), _note_diff_command);
1032 _note_diff_command = 0;
1033 midi_view()->midi_track()->playlist_modified();
1035 if (add_or_remove) {
1036 _marked_for_selection.clear();
1039 _marked_for_velocity.clear();
1043 MidiRegionView::abort_command()
1045 delete _note_diff_command;
1046 _note_diff_command = 0;
1051 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1053 if (_optimization_iterator != _events.end()) {
1054 ++_optimization_iterator;
1057 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1058 return *_optimization_iterator;
1061 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1062 if ((*_optimization_iterator)->note() == note) {
1063 return *_optimization_iterator;
1071 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1073 MidiModel::Notes notes;
1074 _model->get_notes (notes, op, val, chan_mask);
1076 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1077 CanvasNoteEvent* cne = find_canvas_note (*n);
1085 MidiRegionView::redisplay_model()
1087 // Don't redisplay the model if we're currently recording and displaying that
1088 if (_active_notes) {
1096 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1097 (*i)->invalidate ();
1100 MidiModel::ReadLock lock(_model->read_lock());
1102 MidiModel::Notes& notes (_model->notes());
1103 _optimization_iterator = _events.begin();
1105 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1107 boost::shared_ptr<NoteType> note (*n);
1108 CanvasNoteEvent* cne;
1111 if (note_in_region_range (note, visible)) {
1113 if ((cne = find_canvas_note (note)) != 0) {
1120 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1122 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1134 add_note (note, visible);
1139 if ((cne = find_canvas_note (note)) != 0) {
1147 /* remove note items that are no longer valid */
1149 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1150 if (!(*i)->valid ()) {
1152 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1153 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1155 gr->remove_note (*i);
1160 i = _events.erase (i);
1167 _patch_changes.clear();
1171 display_patch_changes ();
1173 _marked_for_selection.clear ();
1174 _marked_for_velocity.clear ();
1176 /* we may have caused _events to contain things out of order (e.g. if a note
1177 moved earlier or later). we don't generally need them in time order, but
1178 make a note that a sort is required for those cases that require it.
1181 _sort_needed = true;
1185 MidiRegionView::display_patch_changes ()
1187 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1188 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1190 for (uint8_t i = 0; i < 16; ++i) {
1191 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1195 /** @param active_channel true to display patch changes fully, false to display
1196 * them `greyed-out' (as on an inactive channel)
1199 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1201 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1203 if ((*i)->channel() != channel) {
1207 // MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1208 //string patch_name = mtv->get_patch_name ((*i)->bank(), (*i)->program(), channel);
1210 // add_canvas_patch_change (*i, patch_name, active_channel);
1215 MidiRegionView::display_sysexes()
1217 bool have_periodic_system_messages = false;
1218 bool display_periodic_messages = true;
1220 if (!Config->get_never_display_periodic_midi()) {
1222 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1223 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1224 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1227 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1228 have_periodic_system_messages = true;
1234 if (have_periodic_system_messages) {
1235 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1237 /* get an approximate value for the number of samples per video frame */
1239 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1241 /* if we are zoomed out beyond than the cutoff (i.e. more
1242 * frames per pixel than frames per 4 video frames), don't
1243 * show periodic sysex messages.
1246 if (zoom > (video_frame*4)) {
1247 display_periodic_messages = false;
1251 display_periodic_messages = false;
1254 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1256 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1257 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1259 Evoral::MusicalTime time = (*i)->time();
1263 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1264 if (!display_periodic_messages) {
1272 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1273 str << int((*i)->buffer()[b]);
1274 if (b != (*i)->size() -1) {
1278 string text = str.str();
1280 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1282 double height = midi_stream_view()->contents_height();
1284 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1285 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1287 // Show unless message is beyond the region bounds
1288 if (time - _region->start() >= _region->length() || time < _region->start()) {
1294 _sys_exes.push_back(sysex);
1298 MidiRegionView::~MidiRegionView ()
1300 in_destructor = true;
1302 trackview.editor().verbose_cursor()->hide ();
1304 note_delete_connection.disconnect ();
1306 delete _list_editor;
1308 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1310 if (_active_notes) {
1314 _selection_cleared_connection.disconnect ();
1320 delete _note_diff_command;
1321 delete _step_edit_cursor;
1322 delete _temporary_note_group;
1326 MidiRegionView::region_resized (const PropertyChange& what_changed)
1328 RegionView::region_resized(what_changed);
1330 if (what_changed.contains (ARDOUR::Properties::position)) {
1331 set_duration(_region->length(), 0);
1332 if (_enable_display) {
1339 MidiRegionView::reset_width_dependent_items (double pixel_width)
1341 RegionView::reset_width_dependent_items(pixel_width);
1342 assert(_pixel_width == pixel_width);
1344 if (_enable_display) {
1348 move_step_edit_cursor (_step_edit_cursor_position);
1349 set_step_edit_cursor_width (_step_edit_cursor_width);
1353 MidiRegionView::set_height (double height)
1355 static const double FUDGE = 2.0;
1356 const double old_height = _height;
1357 RegionView::set_height(height);
1358 _height = height - FUDGE;
1360 apply_note_range(midi_stream_view()->lowest_note(),
1361 midi_stream_view()->highest_note(),
1362 height != old_height + FUDGE);
1365 name_pixbuf->raise_to_top();
1368 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1369 (*x)->set_height (midi_stream_view()->contents_height());
1372 if (_step_edit_cursor) {
1373 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1378 /** Apply the current note range from the stream view
1379 * by repositioning/hiding notes as necessary
1382 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1384 if (!_enable_display) {
1388 if (!force && _current_range_min == min && _current_range_max == max) {
1392 _current_range_min = min;
1393 _current_range_max = max;
1395 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1396 CanvasNoteEvent* event = *i;
1397 boost::shared_ptr<NoteType> note (event->note());
1399 if (note->note() < _current_range_min ||
1400 note->note() > _current_range_max) {
1406 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1408 const double y1 = midi_stream_view()->note_to_y(note->note());
1409 const double y2 = y1 + floor(midi_stream_view()->note_height());
1411 cnote->property_y1() = y1;
1412 cnote->property_y2() = y2;
1414 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1416 const double diamond_size = update_hit (chit);
1418 chit->set_height (diamond_size);
1424 MidiRegionView::add_ghost (TimeAxisView& tv)
1428 double unit_position = _region->position () / samples_per_unit;
1429 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1430 MidiGhostRegion* ghost;
1432 if (mtv && mtv->midi_view()) {
1433 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1434 to allow having midi notes on top of note lines and waveforms.
1436 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1438 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1441 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1442 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1443 ghost->add_note(note);
1447 ghost->set_height ();
1448 ghost->set_duration (_region->length() / samples_per_unit);
1449 ghosts.push_back (ghost);
1451 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1457 /** Begin tracking note state for successive calls to add_event
1460 MidiRegionView::begin_write()
1462 assert(!_active_notes);
1463 _active_notes = new CanvasNote*[128];
1464 for (unsigned i=0; i < 128; ++i) {
1465 _active_notes[i] = 0;
1470 /** Destroy note state for add_event
1473 MidiRegionView::end_write()
1475 delete[] _active_notes;
1477 _marked_for_selection.clear();
1478 _marked_for_velocity.clear();
1482 /** Resolve an active MIDI note (while recording).
1485 MidiRegionView::resolve_note(uint8_t note, double end_time)
1487 if (midi_view()->note_mode() != Sustained) {
1491 if (_active_notes && _active_notes[note]) {
1493 /* XXX is end_time really region-centric? I think so, because
1494 this is a new region that we're recording, so source zero is
1495 the same as region zero
1497 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1499 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1500 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1501 _active_notes[note] = 0;
1506 /** Extend active notes to rightmost edge of region (if length is changed)
1509 MidiRegionView::extend_active_notes()
1511 if (!_active_notes) {
1515 for (unsigned i=0; i < 128; ++i) {
1516 if (_active_notes[i]) {
1517 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1524 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1526 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1530 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1532 if (!route_ui || !route_ui->midi_track()) {
1536 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1540 /* NotePlayer deletes itself */
1544 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1546 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1550 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1552 if (!route_ui || !route_ui->midi_track()) {
1556 delete _note_player;
1557 _note_player = new NotePlayer (route_ui->midi_track ());
1558 _note_player->add (note);
1559 _note_player->on ();
1563 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1565 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1569 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1571 if (!route_ui || !route_ui->midi_track()) {
1575 delete _note_player;
1576 _note_player = new NotePlayer (route_ui->midi_track());
1578 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1579 _note_player->add (*n);
1582 _note_player->on ();
1587 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1589 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1590 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1592 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1593 (note->note() <= midi_stream_view()->highest_note());
1598 /** Update a canvas note's size from its model note.
1599 * @param ev Canvas note to update.
1600 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1603 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1605 boost::shared_ptr<NoteType> note = ev->note();
1606 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1607 const double y1 = midi_stream_view()->note_to_y(note->note());
1609 ev->property_x1() = x;
1610 ev->property_y1() = y1;
1612 /* trim note display to not overlap the end of its region */
1614 if (note->length() > 0) {
1615 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1616 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1618 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1621 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1623 if (note->length() == 0) {
1624 if (_active_notes) {
1625 assert(note->note() < 128);
1626 // If this note is already active there's a stuck note,
1627 // finish the old note rectangle
1628 if (_active_notes[note->note()]) {
1629 CanvasNote* const old_rect = _active_notes[note->note()];
1630 boost::shared_ptr<NoteType> old_note = old_rect->note();
1631 old_rect->property_x2() = x;
1632 old_rect->property_outline_what() = (guint32) 0xF;
1634 _active_notes[note->note()] = ev;
1636 /* outline all but right edge */
1637 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1639 /* outline all edges */
1640 ev->property_outline_what() = (guint32) 0xF;
1643 if (update_ghost_regions) {
1644 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1645 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1647 gr->update_note (ev);
1654 MidiRegionView::update_hit (CanvasHit* ev)
1656 boost::shared_ptr<NoteType> note = ev->note();
1658 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1659 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1660 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1661 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1665 return diamond_size;
1668 /** Add a MIDI note to the view (with length).
1670 * If in sustained mode, notes with length 0 will be considered active
1671 * notes, and resolve_note should be called when the corresponding note off
1672 * event arrives, to properly display the note.
1675 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1677 CanvasNoteEvent* event = 0;
1679 assert(note->time() >= 0);
1680 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1682 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1684 if (midi_view()->note_mode() == Sustained) {
1686 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1688 update_note (ev_rect);
1692 MidiGhostRegion* gr;
1694 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1695 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1696 gr->add_note(ev_rect);
1700 } else if (midi_view()->note_mode() == Percussive) {
1702 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1704 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1706 update_hit (ev_diamond);
1715 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1716 note_selected(event, true);
1719 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1720 event->show_velocity();
1723 event->on_channel_selection_change(_last_channel_selection);
1724 _events.push_back(event);
1733 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1734 MidiStreamView* const view = mtv->midi_view();
1736 view->update_note_range (note->note());
1740 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1741 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1743 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1745 /* potentially extend region to hold new note */
1747 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1748 framepos_t region_end = _region->last_frame();
1750 if (end_frame > region_end) {
1751 _region->set_length (end_frame - _region->position());
1754 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1755 MidiStreamView* const view = mtv->midi_view();
1757 view->update_note_range(new_note->note());
1759 _marked_for_selection.clear ();
1762 start_note_diff_command (_("step add"));
1763 note_diff_add_note (new_note, true, false);
1766 // last_step_edit_note = new_note;
1770 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1772 change_note_lengths (false, false, beats, false, true);
1775 /** Add a new patch change flag to the canvas.
1776 * @param patch the patch change to add
1777 * @param the text to display in the flag
1778 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1781 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1783 assert (patch->time() >= 0);
1785 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1786 const double x = trackview.editor().frame_to_pixel (region_frames);
1788 double const height = midi_stream_view()->contents_height();
1790 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1791 new CanvasPatchChange(*this, *_note_group,
1796 _custom_device_mode,
1801 // Show unless patch change is beyond the region bounds
1802 if (region_frames < 0 || region_frames >= _region->length()) {
1803 patch_change->hide();
1805 patch_change->show();
1808 _patch_changes.push_back (patch_change);
1812 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1814 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1815 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1819 if (i != _model->patch_changes().end()) {
1820 key.bank_number = (*i)->bank();
1821 key.program_number = (*i)->program ();
1823 key.bank_number = key.program_number = 0;
1826 assert (key.is_sane());
1831 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1833 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1835 if (pc.patch()->program() != new_patch.program_number) {
1836 c->change_program (pc.patch (), new_patch.program_number);
1839 int const new_bank = new_patch.bank_number;
1840 if (pc.patch()->bank() != new_bank) {
1841 c->change_bank (pc.patch (), new_bank);
1844 _model->apply_command (*trackview.session(), c);
1846 _patch_changes.clear ();
1847 display_patch_changes ();
1851 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1853 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1855 if (old_change->time() != new_change.time()) {
1856 c->change_time (old_change, new_change.time());
1859 if (old_change->channel() != new_change.channel()) {
1860 c->change_channel (old_change, new_change.channel());
1863 if (old_change->program() != new_change.program()) {
1864 c->change_program (old_change, new_change.program());
1867 if (old_change->bank() != new_change.bank()) {
1868 c->change_bank (old_change, new_change.bank());
1871 _model->apply_command (*trackview.session(), c);
1873 _patch_changes.clear ();
1874 display_patch_changes ();
1877 /** Add a patch change to the region.
1878 * @param t Time in frames relative to region position
1879 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1880 * MidiTimeAxisView::get_channel_for_add())
1883 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1885 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1887 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1888 c->add (MidiModel::PatchChangePtr (
1889 new Evoral::PatchChange<Evoral::MusicalTime> (
1890 absolute_frames_to_source_beats (_region->position() + t),
1891 mtv->get_channel_for_add(), patch.program(), patch.bank()
1896 _model->apply_command (*trackview.session(), c);
1898 _patch_changes.clear ();
1899 display_patch_changes ();
1903 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1905 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1906 c->change_time (pc.patch (), t);
1907 _model->apply_command (*trackview.session(), c);
1909 _patch_changes.clear ();
1910 display_patch_changes ();
1914 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1916 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1917 c->remove (pc->patch ());
1918 _model->apply_command (*trackview.session(), c);
1920 _patch_changes.clear ();
1921 display_patch_changes ();
1925 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1927 if (patch.patch()->program() < 127) {
1928 MIDI::Name::PatchPrimaryKey key;
1929 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1930 key.program_number++;
1931 change_patch_change (patch, key);
1936 MidiRegionView::next_patch (CanvasPatchChange& patch)
1938 if (patch.patch()->program() > 0) {
1939 MIDI::Name::PatchPrimaryKey key;
1940 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1941 key.program_number--;
1942 change_patch_change (patch, key);
1947 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1949 if (patch.patch()->program() < 127) {
1950 MIDI::Name::PatchPrimaryKey key;
1951 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1952 if (key.bank_number > 0) {
1954 change_patch_change (patch, key);
1960 MidiRegionView::next_bank (CanvasPatchChange& patch)
1962 if (patch.patch()->program() > 0) {
1963 MIDI::Name::PatchPrimaryKey key;
1964 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1965 if (key.bank_number < 127) {
1967 change_patch_change (patch, key);
1973 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1975 if (_selection.empty()) {
1979 _selection.erase (cne);
1983 MidiRegionView::delete_selection()
1985 if (_selection.empty()) {
1989 start_note_diff_command (_("delete selection"));
1991 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1992 if ((*i)->selected()) {
1993 _note_diff_command->remove((*i)->note());
2003 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2005 start_note_diff_command (_("delete note"));
2006 _note_diff_command->remove (n);
2009 trackview.editor().verbose_cursor()->hide ();
2013 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2015 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2017 Selection::iterator tmp = i;
2020 (*i)->set_selected (false);
2021 (*i)->hide_velocity ();
2022 _selection.erase (i);
2030 /* this does not change the status of this regionview w.r.t the editor
2035 SelectionCleared (this); /* EMIT SIGNAL */
2040 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2042 clear_selection_except (ev);
2044 /* don't bother with checking to see if we should remove this
2045 regionview from the editor selection, since we're about to add
2046 another note, and thus put/keep this regionview in the editor
2050 if (!ev->selected()) {
2051 add_to_selection (ev);
2056 MidiRegionView::select_all_notes ()
2060 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2061 add_to_selection (*i);
2066 MidiRegionView::select_range (framepos_t start, framepos_t end)
2070 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2071 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2072 if (t >= start && t <= end) {
2073 add_to_selection (*i);
2079 MidiRegionView::invert_selection ()
2081 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2082 if ((*i)->selected()) {
2083 remove_from_selection(*i);
2085 add_to_selection (*i);
2091 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2093 uint8_t low_note = 127;
2094 uint8_t high_note = 0;
2095 MidiModel::Notes& notes (_model->notes());
2096 _optimization_iterator = _events.begin();
2102 if (extend && _selection.empty()) {
2108 /* scan existing selection to get note range */
2110 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2111 if ((*i)->note()->note() < low_note) {
2112 low_note = (*i)->note()->note();
2114 if ((*i)->note()->note() > high_note) {
2115 high_note = (*i)->note()->note();
2119 low_note = min (low_note, notenum);
2120 high_note = max (high_note, notenum);
2123 _no_sound_notes = true;
2125 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2127 boost::shared_ptr<NoteType> note (*n);
2128 CanvasNoteEvent* cne;
2129 bool select = false;
2131 if (((1 << note->channel()) & channel_mask) != 0) {
2133 if ((note->note() >= low_note && note->note() <= high_note)) {
2136 } else if (note->note() == notenum) {
2142 if ((cne = find_canvas_note (note)) != 0) {
2143 // extend is false because we've taken care of it,
2144 // since it extends by time range, not pitch.
2145 note_selected (cne, add, false);
2149 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2153 _no_sound_notes = false;
2157 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2159 MidiModel::Notes& notes (_model->notes());
2160 _optimization_iterator = _events.begin();
2162 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2164 boost::shared_ptr<NoteType> note (*n);
2165 CanvasNoteEvent* cne;
2167 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2168 if ((cne = find_canvas_note (note)) != 0) {
2169 if (cne->selected()) {
2170 note_deselected (cne);
2172 note_selected (cne, true, false);
2180 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2183 clear_selection_except (ev);
2184 if (!_selection.empty()) {
2185 PublicEditor& editor (trackview.editor());
2186 editor.get_selection().add (this);
2192 if (!ev->selected()) {
2193 add_to_selection (ev);
2197 /* find end of latest note selected, select all between that and the start of "ev" */
2199 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2200 Evoral::MusicalTime latest = 0;
2202 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2203 if ((*i)->note()->end_time() > latest) {
2204 latest = (*i)->note()->end_time();
2206 if ((*i)->note()->time() < earliest) {
2207 earliest = (*i)->note()->time();
2211 if (ev->note()->end_time() > latest) {
2212 latest = ev->note()->end_time();
2215 if (ev->note()->time() < earliest) {
2216 earliest = ev->note()->time();
2219 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2221 /* find notes entirely within OR spanning the earliest..latest range */
2223 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2224 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2225 add_to_selection (*i);
2233 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2235 remove_from_selection (ev);
2239 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2249 // TODO: Make this faster by storing the last updated selection rect, and only
2250 // adjusting things that are in the area that appears/disappeared.
2251 // We probably need a tree to be able to find events in O(log(n)) time.
2253 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2255 /* check if any corner of the note is inside the rect
2258 1) this is computing "touched by", not "contained by" the rect.
2259 2) this does not require that events be sorted in time.
2262 const double ix1 = (*i)->x1();
2263 const double ix2 = (*i)->x2();
2264 const double iy1 = (*i)->y1();
2265 const double iy2 = (*i)->y2();
2267 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2268 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2269 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2270 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2273 if (!(*i)->selected()) {
2274 add_to_selection (*i);
2276 } else if ((*i)->selected() && !extend) {
2277 // Not inside rectangle
2278 remove_from_selection (*i);
2284 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2290 // TODO: Make this faster by storing the last updated selection rect, and only
2291 // adjusting things that are in the area that appears/disappeared.
2292 // We probably need a tree to be able to find events in O(log(n)) time.
2294 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2296 /* check if any corner of the note is inside the rect
2299 1) this is computing "touched by", not "contained by" the rect.
2300 2) this does not require that events be sorted in time.
2303 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2304 // within y- (note-) range
2305 if (!(*i)->selected()) {
2306 add_to_selection (*i);
2308 } else if ((*i)->selected() && !extend) {
2309 // Not inside rectangle
2310 remove_from_selection (*i);
2316 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2318 Selection::iterator i = _selection.find (ev);
2320 if (i != _selection.end()) {
2321 _selection.erase (i);
2324 ev->set_selected (false);
2325 ev->hide_velocity ();
2327 if (_selection.empty()) {
2328 PublicEditor& editor (trackview.editor());
2329 editor.get_selection().remove (this);
2334 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2336 bool add_mrv_selection = false;
2338 if (_selection.empty()) {
2339 add_mrv_selection = true;
2342 if (_selection.insert (ev).second) {
2343 ev->set_selected (true);
2344 start_playing_midi_note ((ev)->note());
2347 if (add_mrv_selection) {
2348 PublicEditor& editor (trackview.editor());
2349 editor.get_selection().add (this);
2354 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2356 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2357 PossibleChord to_play;
2358 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2360 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2361 if ((*i)->note()->time() < earliest) {
2362 earliest = (*i)->note()->time();
2366 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2367 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2368 to_play.push_back ((*i)->note());
2370 (*i)->move_event(dx, dy);
2373 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2375 if (to_play.size() > 1) {
2377 PossibleChord shifted;
2379 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2380 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2381 moved_note->set_note (moved_note->note() + cumulative_dy);
2382 shifted.push_back (moved_note);
2385 start_playing_midi_chord (shifted);
2387 } else if (!to_play.empty()) {
2389 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2390 moved_note->set_note (moved_note->note() + cumulative_dy);
2391 start_playing_midi_note (moved_note);
2397 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2399 assert (!_selection.empty());
2401 uint8_t lowest_note_in_selection = 127;
2402 uint8_t highest_note_in_selection = 0;
2403 uint8_t highest_note_difference = 0;
2405 // find highest and lowest notes first
2407 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2408 uint8_t pitch = (*i)->note()->note();
2409 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2410 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2414 cerr << "dnote: " << (int) dnote << endl;
2415 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2416 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2417 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2418 << int(highest_note_in_selection) << endl;
2419 cerr << "selection size: " << _selection.size() << endl;
2420 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2423 // Make sure the note pitch does not exceed the MIDI standard range
2424 if (highest_note_in_selection + dnote > 127) {
2425 highest_note_difference = highest_note_in_selection - 127;
2428 start_note_diff_command (_("move notes"));
2430 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2432 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2433 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2439 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2441 uint8_t original_pitch = (*i)->note()->note();
2442 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2444 // keep notes in standard midi range
2445 clamp_to_0_127(new_pitch);
2447 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2448 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2450 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2455 // care about notes being moved beyond the upper/lower bounds on the canvas
2456 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2457 highest_note_in_selection > midi_stream_view()->highest_note()) {
2458 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2462 /** @param x Pixel relative to the region position.
2463 * @return Snapped frame relative to the region position.
2466 MidiRegionView::snap_pixel_to_frame(double x)
2468 PublicEditor& editor (trackview.editor());
2469 return snap_frame_to_frame (editor.pixel_to_frame (x));
2472 /** @param x Pixel relative to the region position.
2473 * @return Snapped pixel relative to the region position.
2476 MidiRegionView::snap_to_pixel(double x)
2478 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2482 MidiRegionView::get_position_pixels()
2484 framepos_t region_frame = get_position();
2485 return trackview.editor().frame_to_pixel(region_frame);
2489 MidiRegionView::get_end_position_pixels()
2491 framepos_t frame = get_position() + get_duration ();
2492 return trackview.editor().frame_to_pixel(frame);
2496 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2498 /* the time converter will return the frame corresponding to `beats'
2499 relative to the start of the source. The start of the source
2500 is an implied position given by region->position - region->start
2502 const framepos_t source_start = _region->position() - _region->start();
2503 return source_start + _source_relative_time_converter.to (beats);
2507 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2509 /* the `frames' argument needs to be converted into a frame count
2510 relative to the start of the source before being passed in to the
2513 const framepos_t source_start = _region->position() - _region->start();
2514 return _source_relative_time_converter.from (frames - source_start);
2518 MidiRegionView::region_beats_to_region_frames(double beats) const
2520 return _region_relative_time_converter.to(beats);
2524 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2526 return _region_relative_time_converter.from(frames);
2530 MidiRegionView::begin_resizing (bool /*at_front*/)
2532 _resize_data.clear();
2534 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2535 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2537 // only insert CanvasNotes into the map
2539 NoteResizeData *resize_data = new NoteResizeData();
2540 resize_data->canvas_note = note;
2542 // create a new SimpleRect from the note which will be the resize preview
2543 SimpleRect *resize_rect = new SimpleRect(
2544 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2546 // calculate the colors: get the color settings
2547 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2548 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2551 // make the resize preview notes more transparent and bright
2552 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2554 // calculate color based on note velocity
2555 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2556 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2560 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2561 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2563 resize_data->resize_rect = resize_rect;
2564 _resize_data.push_back(resize_data);
2569 /** Update resizing notes while user drags.
2570 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2571 * @param at_front which end of the note (true == note on, false == note off)
2572 * @param delta_x change in mouse position since the start of the drag
2573 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2574 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2575 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2576 * as the \a primary note.
2579 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2581 bool cursor_set = false;
2583 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2584 SimpleRect* resize_rect = (*i)->resize_rect;
2585 CanvasNote* canvas_note = (*i)->canvas_note;
2590 current_x = canvas_note->x1() + delta_x;
2592 current_x = primary->x1() + delta_x;
2596 current_x = canvas_note->x2() + delta_x;
2598 current_x = primary->x2() + delta_x;
2603 resize_rect->property_x1() = snap_to_pixel(current_x);
2604 resize_rect->property_x2() = canvas_note->x2();
2606 resize_rect->property_x2() = snap_to_pixel(current_x);
2607 resize_rect->property_x1() = canvas_note->x1();
2613 beats = snap_pixel_to_frame (current_x);
2614 beats = region_frames_to_region_beats (beats);
2619 if (beats < canvas_note->note()->end_time()) {
2620 len = canvas_note->note()->time() - beats;
2621 len += canvas_note->note()->length();
2626 if (beats >= canvas_note->note()->time()) {
2627 len = beats - canvas_note->note()->time();
2634 snprintf (buf, sizeof (buf), "%.3g beats", len);
2635 show_verbose_cursor (buf, 0, 0);
2644 /** Finish resizing notes when the user releases the mouse button.
2645 * Parameters the same as for \a update_resizing().
2648 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2650 start_note_diff_command (_("resize notes"));
2652 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2653 CanvasNote* canvas_note = (*i)->canvas_note;
2654 SimpleRect* resize_rect = (*i)->resize_rect;
2656 /* Get the new x position for this resize, which is in pixels relative
2657 * to the region position.
2664 current_x = canvas_note->x1() + delta_x;
2666 current_x = primary->x1() + delta_x;
2670 current_x = canvas_note->x2() + delta_x;
2672 current_x = primary->x2() + delta_x;
2676 /* Convert that to a frame within the source */
2677 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2679 /* and then to beats */
2680 current_x = region_frames_to_region_beats (current_x);
2682 if (at_front && current_x < canvas_note->note()->end_time()) {
2683 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2685 double len = canvas_note->note()->time() - current_x;
2686 len += canvas_note->note()->length();
2689 /* XXX convert to beats */
2690 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2695 double len = current_x - canvas_note->note()->time();
2698 /* XXX convert to beats */
2699 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2707 _resize_data.clear();
2712 MidiRegionView::abort_resizing ()
2714 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2715 delete (*i)->resize_rect;
2719 _resize_data.clear ();
2723 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2725 uint8_t new_velocity;
2728 new_velocity = event->note()->velocity() + velocity;
2729 clamp_to_0_127(new_velocity);
2731 new_velocity = velocity;
2734 event->set_selected (event->selected()); // change color
2736 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2740 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2745 new_note = event->note()->note() + note;
2750 clamp_to_0_127 (new_note);
2751 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2755 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2757 bool change_start = false;
2758 bool change_length = false;
2759 Evoral::MusicalTime new_start = 0;
2760 Evoral::MusicalTime new_length = 0;
2762 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2764 front_delta: if positive - move the start of the note later in time (shortening it)
2765 if negative - move the start of the note earlier in time (lengthening it)
2767 end_delta: if positive - move the end of the note later in time (lengthening it)
2768 if negative - move the end of the note earlier in time (shortening it)
2772 if (front_delta < 0) {
2774 if (event->note()->time() < -front_delta) {
2777 new_start = event->note()->time() + front_delta; // moves earlier
2780 /* start moved toward zero, so move the end point out to where it used to be.
2781 Note that front_delta is negative, so this increases the length.
2784 new_length = event->note()->length() - front_delta;
2785 change_start = true;
2786 change_length = true;
2790 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2792 if (new_pos < event->note()->end_time()) {
2793 new_start = event->note()->time() + front_delta;
2794 /* start moved toward the end, so move the end point back to where it used to be */
2795 new_length = event->note()->length() - front_delta;
2796 change_start = true;
2797 change_length = true;
2804 bool can_change = true;
2805 if (end_delta < 0) {
2806 if (event->note()->length() < -end_delta) {
2812 new_length = event->note()->length() + end_delta;
2813 change_length = true;
2818 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2821 if (change_length) {
2822 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2827 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2829 uint8_t new_channel;
2833 if (event->note()->channel() < -chn) {
2836 new_channel = event->note()->channel() + chn;
2839 new_channel = event->note()->channel() + chn;
2842 new_channel = (uint8_t) chn;
2845 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2849 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2851 Evoral::MusicalTime new_time;
2855 if (event->note()->time() < -delta) {
2858 new_time = event->note()->time() + delta;
2861 new_time = event->note()->time() + delta;
2867 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2871 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2873 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2877 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2882 if (_selection.empty()) {
2897 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2898 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2904 start_note_diff_command (_("change velocities"));
2906 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2907 Selection::iterator next = i;
2911 if (i == _selection.begin()) {
2912 change_note_velocity (*i, delta, true);
2913 value = (*i)->note()->velocity() + delta;
2915 change_note_velocity (*i, value, false);
2919 change_note_velocity (*i, delta, true);
2927 if (!_selection.empty()) {
2929 snprintf (buf, sizeof (buf), "Vel %d",
2930 (int) (*_selection.begin())->note()->velocity());
2931 show_verbose_cursor (buf, 10, 10);
2937 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2939 if (_selection.empty()) {
2956 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2958 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2962 if ((int8_t) (*i)->note()->note() + delta > 127) {
2969 start_note_diff_command (_("transpose"));
2971 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2972 Selection::iterator next = i;
2974 change_note_note (*i, delta, true);
2982 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2988 /* grab the current grid distance */
2990 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2992 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2993 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3003 start_note_diff_command (_("change note lengths"));
3005 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3006 Selection::iterator next = i;
3009 /* note the negation of the delta for start */
3011 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3020 MidiRegionView::nudge_notes (bool forward)
3022 if (_selection.empty()) {
3026 /* pick a note as the point along the timeline to get the nudge distance.
3027 its not necessarily the earliest note, so we may want to pull the notes out
3028 into a vector and sort before using the first one.
3031 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3033 framecnt_t distance;
3035 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3037 /* grid is off - use nudge distance */
3039 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3045 framepos_t next_pos = ref_point;
3048 if (max_framepos - 1 < next_pos) {
3052 if (next_pos == 0) {
3058 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3059 distance = ref_point - next_pos;
3062 if (distance == 0) {
3066 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3072 start_note_diff_command (_("nudge"));
3074 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3075 Selection::iterator next = i;
3077 change_note_time (*i, delta, true);
3085 MidiRegionView::change_channel(uint8_t channel)
3087 start_note_diff_command(_("change channel"));
3088 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3089 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3097 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3099 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3101 pre_enter_cursor = editor->get_canvas_cursor ();
3103 if (_mouse_state == SelectTouchDragging) {
3104 note_selected (ev, true);
3107 show_verbose_cursor (ev->note ());
3111 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3113 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3115 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3116 (*i)->hide_velocity ();
3119 editor->verbose_cursor()->hide ();
3121 if (pre_enter_cursor) {
3122 editor->set_canvas_cursor (pre_enter_cursor);
3123 pre_enter_cursor = 0;
3128 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3131 /* XXX should get patch name if we can */
3132 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3133 show_verbose_cursor (s.str(), 10, 20);
3137 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3139 trackview.editor().verbose_cursor()->hide ();
3143 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3145 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3146 Editing::MouseMode mm = editor->current_mouse_mode();
3147 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3149 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3150 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3151 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3152 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3154 if (pre_enter_cursor && can_set_cursor) {
3155 editor->set_canvas_cursor (pre_enter_cursor);
3161 MidiRegionView::set_frame_color()
3165 TimeAxisViewItem::set_frame_color ();
3172 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3173 } else if (high_enough_for_name) {
3174 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3179 if (!rect_visible) {
3180 f = UINT_RGBA_CHANGE_A (f, 0);
3183 frame->property_fill_color_rgba() = f;
3187 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3189 if (mode == ForceChannel) {
3190 mask = 0xFFFF; // Show all notes as active (below)
3193 // Update notes for selection
3194 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3195 (*i)->on_channel_selection_change(mask);
3198 _last_channel_selection = mask;
3200 _patch_changes.clear ();
3201 display_patch_changes ();
3205 MidiRegionView::instrument_settings_changed ()
3211 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3213 if (_selection.empty()) {
3217 PublicEditor& editor (trackview.editor());
3221 /* XXX what to do ? */
3225 editor.get_cut_buffer().add (selection_as_cut_buffer());
3233 start_note_diff_command();
3235 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3242 note_diff_remove_note (*i);
3252 MidiRegionView::selection_as_cut_buffer () const
3256 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3257 NoteType* n = (*i)->note().get();
3258 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3261 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3267 /** This method handles undo */
3269 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3275 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3277 trackview.session()->begin_reversible_command (_("paste"));
3279 start_note_diff_command (_("paste"));
3281 Evoral::MusicalTime beat_delta;
3282 Evoral::MusicalTime paste_pos_beats;
3283 Evoral::MusicalTime duration;
3284 Evoral::MusicalTime end_point = 0;
3286 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3287 paste_pos_beats = absolute_frames_to_source_beats (pos);
3288 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3289 paste_pos_beats = 0;
3291 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",
3292 (*mcb.notes().begin())->time(),
3293 (*mcb.notes().rbegin())->end_time(),
3294 duration, pos, _region->position(),
3295 paste_pos_beats, beat_delta));
3299 for (int n = 0; n < (int) times; ++n) {
3301 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3303 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3304 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3306 /* make all newly added notes selected */
3308 note_diff_add_note (copied_note, true);
3309 end_point = copied_note->end_time();
3312 paste_pos_beats += duration;
3315 /* if we pasted past the current end of the region, extend the region */
3317 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3318 framepos_t region_end = _region->position() + _region->length() - 1;
3320 if (end_frame > region_end) {
3322 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3324 _region->clear_changes ();
3325 _region->set_length (end_frame - _region->position());
3326 trackview.session()->add_command (new StatefulDiffCommand (_region));
3331 trackview.session()->commit_reversible_command ();
3334 struct EventNoteTimeEarlyFirstComparator {
3335 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3336 return a->note()->time() < b->note()->time();
3341 MidiRegionView::time_sort_events ()
3343 if (!_sort_needed) {
3347 EventNoteTimeEarlyFirstComparator cmp;
3350 _sort_needed = false;
3354 MidiRegionView::goto_next_note (bool add_to_selection)
3356 bool use_next = false;
3358 if (_events.back()->selected()) {
3362 time_sort_events ();
3364 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3365 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3367 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3368 if ((*i)->selected()) {
3371 } else if (use_next) {
3372 if (channel_mask & (1 << (*i)->note()->channel())) {
3373 if (!add_to_selection) {
3376 note_selected (*i, true, false);
3383 /* use the first one */
3385 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3386 unique_select (_events.front());
3391 MidiRegionView::goto_previous_note (bool add_to_selection)
3393 bool use_next = false;
3395 if (_events.front()->selected()) {
3399 time_sort_events ();
3401 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3402 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3404 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3405 if ((*i)->selected()) {
3408 } else if (use_next) {
3409 if (channel_mask & (1 << (*i)->note()->channel())) {
3410 if (!add_to_selection) {
3413 note_selected (*i, true, false);
3420 /* use the last one */
3422 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3423 unique_select (*(_events.rbegin()));
3428 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3430 bool had_selected = false;
3432 time_sort_events ();
3434 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3435 if ((*i)->selected()) {
3436 selected.insert ((*i)->note());
3437 had_selected = true;
3441 if (allow_all_if_none_selected && !had_selected) {
3442 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3443 selected.insert ((*i)->note());
3449 MidiRegionView::update_ghost_note (double x, double y)
3451 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3456 _note_group->w2i (x, y);
3458 PublicEditor& editor = trackview.editor ();
3460 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3461 framecnt_t grid_frames;
3462 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3464 /* use region_frames... because we are converting a delta within the region
3468 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3474 /* note that this sets the time of the ghost note in beats relative to
3475 the start of the source; that is how all note times are stored.
3477 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3478 _ghost_note->note()->set_length (length);
3479 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3480 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3482 /* the ghost note does not appear in ghost regions, so pass false in here */
3483 update_note (_ghost_note, false);
3485 show_verbose_cursor (_ghost_note->note ());
3489 MidiRegionView::create_ghost_note (double x, double y)
3491 remove_ghost_note ();
3493 boost::shared_ptr<NoteType> g (new NoteType);
3494 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3495 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3496 update_ghost_note (x, y);
3497 _ghost_note->show ();
3502 show_verbose_cursor (_ghost_note->note ());
3506 MidiRegionView::snap_changed ()
3512 create_ghost_note (_last_ghost_x, _last_ghost_y);
3516 MidiRegionView::drop_down_keys ()
3518 _mouse_state = None;
3522 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3524 double note = midi_stream_view()->y_to_note(y);
3526 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3528 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3530 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3531 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3532 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3533 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3538 bool add_mrv_selection = false;
3540 if (_selection.empty()) {
3541 add_mrv_selection = true;
3544 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3545 if (_selection.insert (*i).second) {
3546 (*i)->set_selected (true);
3550 if (add_mrv_selection) {
3551 PublicEditor& editor (trackview.editor());
3552 editor.get_selection().add (this);
3557 MidiRegionView::color_handler ()
3559 RegionView::color_handler ();
3561 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3562 (*i)->set_selected ((*i)->selected()); // will change color
3565 /* XXX probably more to do here */
3569 MidiRegionView::enable_display (bool yn)
3571 RegionView::enable_display (yn);
3578 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3580 if (_step_edit_cursor == 0) {
3581 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3583 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3584 _step_edit_cursor->property_y1() = 0;
3585 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3586 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3587 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3590 move_step_edit_cursor (pos);
3591 _step_edit_cursor->show ();
3595 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3597 _step_edit_cursor_position = pos;
3599 if (_step_edit_cursor) {
3600 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3601 _step_edit_cursor->property_x1() = pixel;
3602 set_step_edit_cursor_width (_step_edit_cursor_width);
3607 MidiRegionView::hide_step_edit_cursor ()
3609 if (_step_edit_cursor) {
3610 _step_edit_cursor->hide ();
3615 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3617 _step_edit_cursor_width = beats;
3619 if (_step_edit_cursor) {
3620 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3624 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3625 * @param w Source that the data will end up in.
3628 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3630 if (!_active_notes) {
3631 /* we aren't actively being recorded to */
3635 boost::shared_ptr<MidiSource> src = w.lock ();
3636 if (!src || src != midi_region()->midi_source()) {
3637 /* recorded data was not destined for our source */
3641 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3643 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3645 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3647 framepos_t back = max_framepos;
3649 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3650 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3651 assert (ev.buffer ());
3653 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3654 frames from the start of the source, and so time_beats is in terms of the
3658 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3660 if (ev.type() == MIDI_CMD_NOTE_ON) {
3662 boost::shared_ptr<NoteType> note (
3663 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3666 add_note (note, true);
3668 /* fix up our note range */
3669 if (ev.note() < _current_range_min) {
3670 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3671 } else if (ev.note() > _current_range_max) {
3672 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3675 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3676 resolve_note (ev.note (), time_beats);
3682 midi_stream_view()->check_record_layers (region(), back);
3686 MidiRegionView::trim_front_starting ()
3688 /* Reparent the note group to the region view's parent, so that it doesn't change
3689 when the region view is trimmed.
3691 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3692 _temporary_note_group->move (group->property_x(), group->property_y());
3693 _note_group->reparent (*_temporary_note_group);
3697 MidiRegionView::trim_front_ending ()
3699 _note_group->reparent (*group);
3700 delete _temporary_note_group;
3701 _temporary_note_group = 0;
3703 if (_region->start() < 0) {
3704 /* Trim drag made start time -ve; fix this */
3705 midi_region()->fix_negative_start ();
3710 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3712 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), _model_name, _custom_device_mode, Gtk::Stock::APPLY);
3713 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3717 change_patch_change (pc->patch(), d.patch ());
3722 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3725 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3726 Evoral::midi_note_name (n->note()).c_str(),
3728 (int) n->channel() + 1,
3729 (int) n->velocity());
3731 show_verbose_cursor (buf, 10, 20);
3735 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3739 trackview.editor().get_pointer_position (wx, wy);
3744 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3746 double x1, y1, x2, y2;
3747 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3749 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3750 wy -= (y2 - y1) + 2 * yoffset;
3753 trackview.editor().verbose_cursor()->set (text, wx, wy);
3754 trackview.editor().verbose_cursor()->show ();
3757 /** @param p A session framepos.
3758 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3759 * @return p snapped to the grid subdivision underneath it.
3762 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3764 PublicEditor& editor = trackview.editor ();
3767 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3773 grid_frames = region_beats_to_region_frames (grid_beats);
3775 /* Hack so that we always snap to the note that we are over, instead of snapping
3776 to the next one if we're more than halfway through the one we're over.
3778 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3779 p -= grid_frames / 2;
3782 return snap_frame_to_frame (p);
3785 /** Called when the selection has been cleared in any MidiRegionView.
3786 * @param rv MidiRegionView that the selection was cleared in.
3789 MidiRegionView::selection_cleared (MidiRegionView* rv)
3795 /* Clear our selection in sympathy; but don't signal the fact */
3796 clear_selection (false);
3800 MidiRegionView::note_button_release ()
3802 delete _note_player;