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 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
287 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
289 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
290 boost::bind (&MidiRegionView::snap_changed, this),
293 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
294 connect_to_diskstream ();
296 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
300 MidiRegionView::instrument_info () const
302 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
303 return route_ui->route()->instrument_info();
306 const boost::shared_ptr<ARDOUR::MidiRegion>
307 MidiRegionView::midi_region() const
309 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
313 MidiRegionView::connect_to_diskstream ()
315 midi_view()->midi_track()->DataRecorded.connect(
316 *this, invalidator(*this),
317 boost::bind (&MidiRegionView::data_recorded, this, _1),
322 MidiRegionView::canvas_event(GdkEvent* ev)
327 case GDK_ENTER_NOTIFY:
328 case GDK_LEAVE_NOTIFY:
329 _last_event_x = ev->crossing.x;
330 _last_event_y = ev->crossing.y;
332 case GDK_MOTION_NOTIFY:
333 _last_event_x = ev->motion.x;
334 _last_event_y = ev->motion.y;
340 if (ev->type == GDK_2BUTTON_PRESS) {
341 return trackview.editor().toggle_internal_editing_from_double_click (ev);
344 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
345 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
346 (trackview.editor().current_mouse_mode() == MouseZoom)) {
347 // handle non-draw modes elsewhere
353 return scroll (&ev->scroll);
356 return key_press (&ev->key);
358 case GDK_KEY_RELEASE:
359 return key_release (&ev->key);
361 case GDK_BUTTON_PRESS:
362 return button_press (&ev->button);
364 case GDK_BUTTON_RELEASE:
365 r = button_release (&ev->button);
370 case GDK_ENTER_NOTIFY:
371 return enter_notify (&ev->crossing);
373 case GDK_LEAVE_NOTIFY:
374 return leave_notify (&ev->crossing);
376 case GDK_MOTION_NOTIFY:
377 return motion (&ev->motion);
387 MidiRegionView::remove_ghost_note ()
394 MidiRegionView::enter_notify (GdkEventCrossing* ev)
396 trackview.editor().MouseModeChanged.connect (
397 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
400 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
401 create_ghost_note (ev->x, ev->y);
404 if (!trackview.editor().internal_editing()) {
405 Keyboard::magic_widget_drop_focus();
407 Keyboard::magic_widget_grab_focus();
411 // if current operation is non-operational in a midi region, change the cursor to so indicate
412 if (trackview.editor().current_mouse_mode() == MouseGain) {
413 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
414 pre_enter_cursor = editor->get_canvas_cursor();
415 editor->set_canvas_cursor(editor->cursors()->timebar);
422 MidiRegionView::leave_notify (GdkEventCrossing*)
424 _mouse_mode_connection.disconnect ();
426 trackview.editor().verbose_cursor()->hide ();
427 remove_ghost_note ();
429 if (pre_enter_cursor) {
430 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
431 editor->set_canvas_cursor(pre_enter_cursor);
438 MidiRegionView::mouse_mode_changed ()
440 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
441 create_ghost_note (_last_event_x, _last_event_y);
443 remove_ghost_note ();
444 trackview.editor().verbose_cursor()->hide ();
447 if (!trackview.editor().internal_editing()) {
448 Keyboard::magic_widget_drop_focus();
450 Keyboard::magic_widget_grab_focus();
456 MidiRegionView::button_press (GdkEventButton* ev)
458 if (ev->button != 1) {
462 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
463 MouseMode m = editor->current_mouse_mode();
465 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
466 pre_press_cursor = editor->get_canvas_cursor ();
467 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
470 if (_mouse_state != SelectTouchDragging) {
472 _pressed_button = ev->button;
473 _mouse_state = Pressed;
478 _pressed_button = ev->button;
484 MidiRegionView::button_release (GdkEventButton* ev)
486 double event_x, event_y;
488 if (ev->button != 1) {
495 group->w2i(event_x, event_y);
496 group->ungrab(ev->time);
498 PublicEditor& editor = trackview.editor ();
500 if (pre_press_cursor) {
501 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
502 pre_press_cursor = 0;
505 switch (_mouse_state) {
506 case Pressed: // Clicked
508 switch (editor.current_mouse_mode()) {
510 /* no motion occured - simple click */
519 if (Keyboard::is_insert_note_event(ev)) {
521 double event_x, event_y;
525 group->w2i(event_x, event_y);
528 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
534 /* Shorten the length by 1 tick so that we can add a new note at the next
535 grid snap without it overlapping this one.
537 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
539 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
547 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
553 /* Shorten the length by 1 tick so that we can add a new note at the next
554 grid snap without it overlapping this one.
556 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
558 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
569 case SelectRectDragging:
571 editor.drags()->end_grab ((GdkEvent *) ev);
573 create_ghost_note (ev->x, ev->y);
585 MidiRegionView::motion (GdkEventMotion* ev)
587 PublicEditor& editor = trackview.editor ();
589 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
590 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
591 _mouse_state != AddDragging) {
593 create_ghost_note (ev->x, ev->y);
595 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
596 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
598 update_ghost_note (ev->x, ev->y);
600 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
602 remove_ghost_note ();
603 editor.verbose_cursor()->hide ();
605 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
607 update_ghost_note (ev->x, ev->y);
610 /* any motion immediately hides velocity text that may have been visible */
612 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
613 (*i)->hide_velocity ();
616 switch (_mouse_state) {
619 if (_pressed_button == 1) {
621 MouseMode m = editor.current_mouse_mode();
623 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
625 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
626 _mouse_state = AddDragging;
627 remove_ghost_note ();
628 editor.verbose_cursor()->hide ();
630 } else if (m == MouseObject) {
631 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
633 _mouse_state = SelectRectDragging;
635 } else if (m == MouseRange) {
636 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
637 _mouse_state = SelectVerticalDragging;
644 case SelectRectDragging:
645 case SelectVerticalDragging:
647 editor.drags()->motion_handler ((GdkEvent *) ev, false);
650 case SelectTouchDragging:
662 MidiRegionView::scroll (GdkEventScroll* ev)
664 if (_selection.empty()) {
668 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
669 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
670 it still works for zoom.
675 trackview.editor().verbose_cursor()->hide ();
677 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
678 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
680 if (ev->direction == GDK_SCROLL_UP) {
681 change_velocities (true, fine, false, together);
682 } else if (ev->direction == GDK_SCROLL_DOWN) {
683 change_velocities (false, fine, false, together);
689 MidiRegionView::key_press (GdkEventKey* ev)
691 /* since GTK bindings are generally activated on press, and since
692 detectable auto-repeat is the name of the game and only sends
693 repeated presses, carry out key actions at key press, not release.
696 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
698 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
699 _mouse_state = SelectTouchDragging;
702 } else if (ev->keyval == GDK_Escape && unmodified) {
706 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
708 bool start = (ev->keyval == GDK_comma);
709 bool end = (ev->keyval == GDK_period);
710 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
711 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
713 change_note_lengths (fine, shorter, 0.0, start, end);
717 } else if (ev->keyval == GDK_Delete && unmodified) {
722 } else if (ev->keyval == GDK_Tab) {
724 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
725 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
727 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
731 } else if (ev->keyval == GDK_ISO_Left_Tab) {
733 /* Shift-TAB generates ISO Left Tab, for some reason */
735 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
736 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
738 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
744 } else if (ev->keyval == GDK_Up) {
746 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
747 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
748 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
750 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
751 change_velocities (true, fine, allow_smush, together);
753 transpose (true, fine, allow_smush);
757 } else if (ev->keyval == GDK_Down) {
759 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
760 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
761 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
763 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
764 change_velocities (false, fine, allow_smush, together);
766 transpose (false, fine, allow_smush);
770 } else if (ev->keyval == GDK_Left && unmodified) {
775 } else if (ev->keyval == GDK_Right && unmodified) {
780 } else if (ev->keyval == GDK_c && unmodified) {
784 } else if (ev->keyval == GDK_v && unmodified) {
793 MidiRegionView::key_release (GdkEventKey* ev)
795 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
803 MidiRegionView::channel_edit ()
805 if (_selection.empty()) {
809 /* pick a note somewhat at random (since Selection is a set<>) to
810 * provide the "current" channel for the dialog.
813 uint8_t current_channel = (*_selection.begin())->note()->channel ();
814 MidiChannelDialog channel_dialog (current_channel);
815 int ret = channel_dialog.run ();
818 case Gtk::RESPONSE_OK:
824 uint8_t new_channel = channel_dialog.active_channel ();
826 start_note_diff_command (_("channel edit"));
828 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
829 Selection::iterator next = i;
831 change_note_channel (*i, new_channel);
839 MidiRegionView::velocity_edit ()
841 if (_selection.empty()) {
845 /* pick a note somewhat at random (since Selection is a set<>) to
846 * provide the "current" velocity for the dialog.
849 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
850 MidiVelocityDialog velocity_dialog (current_velocity);
851 int ret = velocity_dialog.run ();
854 case Gtk::RESPONSE_OK:
860 uint8_t new_velocity = velocity_dialog.velocity ();
862 start_note_diff_command (_("velocity edit"));
864 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
865 Selection::iterator next = i;
867 change_note_velocity (*i, new_velocity, false);
875 MidiRegionView::show_list_editor ()
878 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
880 _list_editor->present ();
883 /** Add a note to the model, and the view, at a canvas (click) coordinate.
884 * \param t time in frames relative to the position of the region
885 * \param y vertical position in pixels
886 * \param length duration of the note in beats
887 * \param snap_t true to snap t to the grid, otherwise false.
890 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
892 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
893 MidiStreamView* const view = mtv->midi_view();
895 double note = view->y_to_note(y);
898 assert(note <= 127.0);
900 // Start of note in frames relative to region start
902 framecnt_t grid_frames;
903 t = snap_frame_to_grid_underneath (t, grid_frames);
907 assert (length != 0);
909 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
910 region_frames_to_region_beats(t + _region->start()),
912 (uint8_t)note, 0x40));
914 if (_model->contains (new_note)) {
918 view->update_note_range(new_note->note());
920 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
922 _model->apply_command(*trackview.session(), cmd);
924 play_midi_note (new_note);
928 MidiRegionView::clear_events()
933 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
934 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
939 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
944 _patch_changes.clear();
946 _optimization_iterator = _events.end();
950 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
954 content_connection.disconnect ();
955 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
959 if (_enable_display) {
965 MidiRegionView::start_note_diff_command (string name)
967 if (!_note_diff_command) {
968 _note_diff_command = _model->new_note_diff_command (name);
973 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
975 if (_note_diff_command) {
976 _note_diff_command->add (note);
979 _marked_for_selection.insert(note);
982 _marked_for_velocity.insert(note);
987 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
989 if (_note_diff_command && ev->note()) {
990 _note_diff_command->remove(ev->note());
995 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
996 MidiModel::NoteDiffCommand::Property property,
999 if (_note_diff_command) {
1000 _note_diff_command->change (ev->note(), property, val);
1005 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1006 MidiModel::NoteDiffCommand::Property property,
1007 Evoral::MusicalTime val)
1009 if (_note_diff_command) {
1010 _note_diff_command->change (ev->note(), property, val);
1015 MidiRegionView::apply_diff (bool as_subcommand)
1019 if (!_note_diff_command) {
1023 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1024 // Mark all selected notes for selection when model reloads
1025 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1026 _marked_for_selection.insert((*i)->note());
1030 if (as_subcommand) {
1031 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1033 _model->apply_command (*trackview.session(), _note_diff_command);
1036 _note_diff_command = 0;
1037 midi_view()->midi_track()->playlist_modified();
1039 if (add_or_remove) {
1040 _marked_for_selection.clear();
1043 _marked_for_velocity.clear();
1047 MidiRegionView::abort_command()
1049 delete _note_diff_command;
1050 _note_diff_command = 0;
1055 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1057 if (_optimization_iterator != _events.end()) {
1058 ++_optimization_iterator;
1061 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1062 return *_optimization_iterator;
1065 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1066 if ((*_optimization_iterator)->note() == note) {
1067 return *_optimization_iterator;
1075 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1077 MidiModel::Notes notes;
1078 _model->get_notes (notes, op, val, chan_mask);
1080 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1081 CanvasNoteEvent* cne = find_canvas_note (*n);
1089 MidiRegionView::redisplay_model()
1091 // Don't redisplay the model if we're currently recording and displaying that
1092 if (_active_notes) {
1100 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1101 (*i)->invalidate ();
1104 MidiModel::ReadLock lock(_model->read_lock());
1106 MidiModel::Notes& notes (_model->notes());
1107 _optimization_iterator = _events.begin();
1109 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1111 boost::shared_ptr<NoteType> note (*n);
1112 CanvasNoteEvent* cne;
1115 if (note_in_region_range (note, visible)) {
1117 if ((cne = find_canvas_note (note)) != 0) {
1124 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1126 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1138 add_note (note, visible);
1143 if ((cne = find_canvas_note (note)) != 0) {
1151 /* remove note items that are no longer valid */
1153 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1154 if (!(*i)->valid ()) {
1156 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1157 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1159 gr->remove_note (*i);
1164 i = _events.erase (i);
1171 _patch_changes.clear();
1175 display_patch_changes ();
1177 _marked_for_selection.clear ();
1178 _marked_for_velocity.clear ();
1180 /* we may have caused _events to contain things out of order (e.g. if a note
1181 moved earlier or later). we don't generally need them in time order, but
1182 make a note that a sort is required for those cases that require it.
1185 _sort_needed = true;
1189 MidiRegionView::display_patch_changes ()
1191 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1192 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1194 for (uint8_t i = 0; i < 16; ++i) {
1195 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1199 /** @param active_channel true to display patch changes fully, false to display
1200 * them `greyed-out' (as on an inactive channel)
1203 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1205 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1207 if ((*i)->channel() != channel) {
1211 string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1212 add_canvas_patch_change (*i, patch_name, active_channel);
1217 MidiRegionView::display_sysexes()
1219 bool have_periodic_system_messages = false;
1220 bool display_periodic_messages = true;
1222 if (!Config->get_never_display_periodic_midi()) {
1224 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1225 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1226 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1229 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1230 have_periodic_system_messages = true;
1236 if (have_periodic_system_messages) {
1237 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1239 /* get an approximate value for the number of samples per video frame */
1241 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1243 /* if we are zoomed out beyond than the cutoff (i.e. more
1244 * frames per pixel than frames per 4 video frames), don't
1245 * show periodic sysex messages.
1248 if (zoom > (video_frame*4)) {
1249 display_periodic_messages = false;
1253 display_periodic_messages = false;
1256 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1258 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1259 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1261 Evoral::MusicalTime time = (*i)->time();
1265 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1266 if (!display_periodic_messages) {
1274 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1275 str << int((*i)->buffer()[b]);
1276 if (b != (*i)->size() -1) {
1280 string text = str.str();
1282 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1284 double height = midi_stream_view()->contents_height();
1286 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1287 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1289 // Show unless message is beyond the region bounds
1290 if (time - _region->start() >= _region->length() || time < _region->start()) {
1296 _sys_exes.push_back(sysex);
1300 MidiRegionView::~MidiRegionView ()
1302 in_destructor = true;
1304 trackview.editor().verbose_cursor()->hide ();
1306 note_delete_connection.disconnect ();
1308 delete _list_editor;
1310 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1312 if (_active_notes) {
1316 _selection_cleared_connection.disconnect ();
1322 delete _note_diff_command;
1323 delete _step_edit_cursor;
1324 delete _temporary_note_group;
1328 MidiRegionView::region_resized (const PropertyChange& what_changed)
1330 RegionView::region_resized(what_changed);
1332 if (what_changed.contains (ARDOUR::Properties::position)) {
1333 set_duration(_region->length(), 0);
1334 if (_enable_display) {
1341 MidiRegionView::reset_width_dependent_items (double pixel_width)
1343 RegionView::reset_width_dependent_items(pixel_width);
1344 assert(_pixel_width == pixel_width);
1346 if (_enable_display) {
1350 move_step_edit_cursor (_step_edit_cursor_position);
1351 set_step_edit_cursor_width (_step_edit_cursor_width);
1355 MidiRegionView::set_height (double height)
1357 static const double FUDGE = 2.0;
1358 const double old_height = _height;
1359 RegionView::set_height(height);
1360 _height = height - FUDGE;
1362 apply_note_range(midi_stream_view()->lowest_note(),
1363 midi_stream_view()->highest_note(),
1364 height != old_height + FUDGE);
1367 name_pixbuf->raise_to_top();
1370 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1371 (*x)->set_height (midi_stream_view()->contents_height());
1374 if (_step_edit_cursor) {
1375 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1380 /** Apply the current note range from the stream view
1381 * by repositioning/hiding notes as necessary
1384 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1386 if (!_enable_display) {
1390 if (!force && _current_range_min == min && _current_range_max == max) {
1394 _current_range_min = min;
1395 _current_range_max = max;
1397 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1398 CanvasNoteEvent* event = *i;
1399 boost::shared_ptr<NoteType> note (event->note());
1401 if (note->note() < _current_range_min ||
1402 note->note() > _current_range_max) {
1408 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1410 const double y1 = midi_stream_view()->note_to_y(note->note());
1411 const double y2 = y1 + floor(midi_stream_view()->note_height());
1413 cnote->property_y1() = y1;
1414 cnote->property_y2() = y2;
1416 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1418 const double diamond_size = update_hit (chit);
1420 chit->set_height (diamond_size);
1426 MidiRegionView::add_ghost (TimeAxisView& tv)
1430 double unit_position = _region->position () / samples_per_unit;
1431 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1432 MidiGhostRegion* ghost;
1434 if (mtv && mtv->midi_view()) {
1435 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1436 to allow having midi notes on top of note lines and waveforms.
1438 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1440 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1443 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1444 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1445 ghost->add_note(note);
1449 ghost->set_height ();
1450 ghost->set_duration (_region->length() / samples_per_unit);
1451 ghosts.push_back (ghost);
1453 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1459 /** Begin tracking note state for successive calls to add_event
1462 MidiRegionView::begin_write()
1464 assert(!_active_notes);
1465 _active_notes = new CanvasNote*[128];
1466 for (unsigned i=0; i < 128; ++i) {
1467 _active_notes[i] = 0;
1472 /** Destroy note state for add_event
1475 MidiRegionView::end_write()
1477 delete[] _active_notes;
1479 _marked_for_selection.clear();
1480 _marked_for_velocity.clear();
1484 /** Resolve an active MIDI note (while recording).
1487 MidiRegionView::resolve_note(uint8_t note, double end_time)
1489 if (midi_view()->note_mode() != Sustained) {
1493 if (_active_notes && _active_notes[note]) {
1495 /* XXX is end_time really region-centric? I think so, because
1496 this is a new region that we're recording, so source zero is
1497 the same as region zero
1499 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1501 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1502 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1503 _active_notes[note] = 0;
1508 /** Extend active notes to rightmost edge of region (if length is changed)
1511 MidiRegionView::extend_active_notes()
1513 if (!_active_notes) {
1517 for (unsigned i=0; i < 128; ++i) {
1518 if (_active_notes[i]) {
1519 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1526 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1528 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1532 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1534 if (!route_ui || !route_ui->midi_track()) {
1538 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1542 /* NotePlayer deletes itself */
1546 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1548 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1552 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1554 if (!route_ui || !route_ui->midi_track()) {
1558 delete _note_player;
1559 _note_player = new NotePlayer (route_ui->midi_track ());
1560 _note_player->add (note);
1561 _note_player->on ();
1565 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1567 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1571 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1573 if (!route_ui || !route_ui->midi_track()) {
1577 delete _note_player;
1578 _note_player = new NotePlayer (route_ui->midi_track());
1580 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1581 _note_player->add (*n);
1584 _note_player->on ();
1589 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1591 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1592 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1594 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1595 (note->note() <= midi_stream_view()->highest_note());
1600 /** Update a canvas note's size from its model note.
1601 * @param ev Canvas note to update.
1602 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1605 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1607 boost::shared_ptr<NoteType> note = ev->note();
1608 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1609 const double y1 = midi_stream_view()->note_to_y(note->note());
1611 ev->property_x1() = x;
1612 ev->property_y1() = y1;
1614 /* trim note display to not overlap the end of its region */
1616 if (note->length() > 0) {
1617 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1618 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1620 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1623 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1625 if (note->length() == 0) {
1626 if (_active_notes) {
1627 assert(note->note() < 128);
1628 // If this note is already active there's a stuck note,
1629 // finish the old note rectangle
1630 if (_active_notes[note->note()]) {
1631 CanvasNote* const old_rect = _active_notes[note->note()];
1632 boost::shared_ptr<NoteType> old_note = old_rect->note();
1633 old_rect->property_x2() = x;
1634 old_rect->property_outline_what() = (guint32) 0xF;
1636 _active_notes[note->note()] = ev;
1638 /* outline all but right edge */
1639 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1641 /* outline all edges */
1642 ev->property_outline_what() = (guint32) 0xF;
1645 if (update_ghost_regions) {
1646 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1647 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1649 gr->update_note (ev);
1656 MidiRegionView::update_hit (CanvasHit* ev)
1658 boost::shared_ptr<NoteType> note = ev->note();
1660 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1661 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1662 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1663 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1667 return diamond_size;
1670 /** Add a MIDI note to the view (with length).
1672 * If in sustained mode, notes with length 0 will be considered active
1673 * notes, and resolve_note should be called when the corresponding note off
1674 * event arrives, to properly display the note.
1677 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1679 CanvasNoteEvent* event = 0;
1681 assert(note->time() >= 0);
1682 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1684 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1686 if (midi_view()->note_mode() == Sustained) {
1688 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1690 update_note (ev_rect);
1694 MidiGhostRegion* gr;
1696 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1697 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1698 gr->add_note(ev_rect);
1702 } else if (midi_view()->note_mode() == Percussive) {
1704 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1706 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1708 update_hit (ev_diamond);
1717 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1718 note_selected(event, true);
1721 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1722 event->show_velocity();
1725 event->on_channel_selection_change(_last_channel_selection);
1726 _events.push_back(event);
1735 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1736 MidiStreamView* const view = mtv->midi_view();
1738 view->update_note_range (note->note());
1742 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1743 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1745 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1747 /* potentially extend region to hold new note */
1749 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1750 framepos_t region_end = _region->last_frame();
1752 if (end_frame > region_end) {
1753 _region->set_length (end_frame - _region->position());
1756 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1757 MidiStreamView* const view = mtv->midi_view();
1759 view->update_note_range(new_note->note());
1761 _marked_for_selection.clear ();
1764 start_note_diff_command (_("step add"));
1765 note_diff_add_note (new_note, true, false);
1768 // last_step_edit_note = new_note;
1772 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1774 change_note_lengths (false, false, beats, false, true);
1777 /** Add a new patch change flag to the canvas.
1778 * @param patch the patch change to add
1779 * @param the text to display in the flag
1780 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1783 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1785 assert (patch->time() >= 0);
1787 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1788 const double x = trackview.editor().frame_to_pixel (region_frames);
1790 double const height = midi_stream_view()->contents_height();
1792 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1793 new CanvasPatchChange(*this, *_note_group,
1802 // Show unless patch change is beyond the region bounds
1803 if (region_frames < 0 || region_frames >= _region->length()) {
1804 patch_change->hide();
1806 patch_change->show();
1809 _patch_changes.push_back (patch_change);
1813 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1815 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1816 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1820 if (i != _model->patch_changes().end()) {
1821 key.bank_number = (*i)->bank();
1822 key.program_number = (*i)->program ();
1824 key.bank_number = key.program_number = 0;
1827 assert (key.is_sane());
1832 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1834 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1836 if (pc.patch()->program() != new_patch.program_number) {
1837 c->change_program (pc.patch (), new_patch.program_number);
1840 int const new_bank = new_patch.bank_number;
1841 if (pc.patch()->bank() != new_bank) {
1842 c->change_bank (pc.patch (), new_bank);
1845 _model->apply_command (*trackview.session(), c);
1847 _patch_changes.clear ();
1848 display_patch_changes ();
1852 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1854 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1856 if (old_change->time() != new_change.time()) {
1857 c->change_time (old_change, new_change.time());
1860 if (old_change->channel() != new_change.channel()) {
1861 c->change_channel (old_change, new_change.channel());
1864 if (old_change->program() != new_change.program()) {
1865 c->change_program (old_change, new_change.program());
1868 if (old_change->bank() != new_change.bank()) {
1869 c->change_bank (old_change, new_change.bank());
1872 _model->apply_command (*trackview.session(), c);
1874 _patch_changes.clear ();
1875 display_patch_changes ();
1878 /** Add a patch change to the region.
1879 * @param t Time in frames relative to region position
1880 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1881 * MidiTimeAxisView::get_channel_for_add())
1884 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1886 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1888 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1889 c->add (MidiModel::PatchChangePtr (
1890 new Evoral::PatchChange<Evoral::MusicalTime> (
1891 absolute_frames_to_source_beats (_region->position() + t),
1892 mtv->get_channel_for_add(), patch.program(), patch.bank()
1897 _model->apply_command (*trackview.session(), c);
1899 _patch_changes.clear ();
1900 display_patch_changes ();
1904 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1906 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1907 c->change_time (pc.patch (), t);
1908 _model->apply_command (*trackview.session(), c);
1910 _patch_changes.clear ();
1911 display_patch_changes ();
1915 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1917 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1918 c->remove (pc->patch ());
1919 _model->apply_command (*trackview.session(), c);
1921 _patch_changes.clear ();
1922 display_patch_changes ();
1926 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1928 if (patch.patch()->program() < 127) {
1929 MIDI::Name::PatchPrimaryKey key;
1930 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1931 key.program_number++;
1932 change_patch_change (patch, key);
1937 MidiRegionView::next_patch (CanvasPatchChange& patch)
1939 if (patch.patch()->program() > 0) {
1940 MIDI::Name::PatchPrimaryKey key;
1941 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1942 key.program_number--;
1943 change_patch_change (patch, key);
1948 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1950 if (patch.patch()->program() < 127) {
1951 MIDI::Name::PatchPrimaryKey key;
1952 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1953 if (key.bank_number > 0) {
1955 change_patch_change (patch, key);
1961 MidiRegionView::next_bank (CanvasPatchChange& patch)
1963 if (patch.patch()->program() > 0) {
1964 MIDI::Name::PatchPrimaryKey key;
1965 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1966 if (key.bank_number < 127) {
1968 change_patch_change (patch, key);
1974 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1976 if (_selection.empty()) {
1980 _selection.erase (cne);
1984 MidiRegionView::delete_selection()
1986 if (_selection.empty()) {
1990 start_note_diff_command (_("delete selection"));
1992 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1993 if ((*i)->selected()) {
1994 _note_diff_command->remove((*i)->note());
2004 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2006 start_note_diff_command (_("delete note"));
2007 _note_diff_command->remove (n);
2010 trackview.editor().verbose_cursor()->hide ();
2014 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2016 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2018 Selection::iterator tmp = i;
2021 (*i)->set_selected (false);
2022 (*i)->hide_velocity ();
2023 _selection.erase (i);
2031 /* this does not change the status of this regionview w.r.t the editor
2036 SelectionCleared (this); /* EMIT SIGNAL */
2041 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2043 clear_selection_except (ev);
2045 /* don't bother with checking to see if we should remove this
2046 regionview from the editor selection, since we're about to add
2047 another note, and thus put/keep this regionview in the editor
2051 if (!ev->selected()) {
2052 add_to_selection (ev);
2057 MidiRegionView::select_all_notes ()
2061 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2062 add_to_selection (*i);
2067 MidiRegionView::select_range (framepos_t start, framepos_t end)
2071 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2072 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2073 if (t >= start && t <= end) {
2074 add_to_selection (*i);
2080 MidiRegionView::invert_selection ()
2082 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2083 if ((*i)->selected()) {
2084 remove_from_selection(*i);
2086 add_to_selection (*i);
2092 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2094 uint8_t low_note = 127;
2095 uint8_t high_note = 0;
2096 MidiModel::Notes& notes (_model->notes());
2097 _optimization_iterator = _events.begin();
2103 if (extend && _selection.empty()) {
2109 /* scan existing selection to get note range */
2111 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2112 if ((*i)->note()->note() < low_note) {
2113 low_note = (*i)->note()->note();
2115 if ((*i)->note()->note() > high_note) {
2116 high_note = (*i)->note()->note();
2120 low_note = min (low_note, notenum);
2121 high_note = max (high_note, notenum);
2124 _no_sound_notes = true;
2126 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2128 boost::shared_ptr<NoteType> note (*n);
2129 CanvasNoteEvent* cne;
2130 bool select = false;
2132 if (((1 << note->channel()) & channel_mask) != 0) {
2134 if ((note->note() >= low_note && note->note() <= high_note)) {
2137 } else if (note->note() == notenum) {
2143 if ((cne = find_canvas_note (note)) != 0) {
2144 // extend is false because we've taken care of it,
2145 // since it extends by time range, not pitch.
2146 note_selected (cne, add, false);
2150 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2154 _no_sound_notes = false;
2158 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2160 MidiModel::Notes& notes (_model->notes());
2161 _optimization_iterator = _events.begin();
2163 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2165 boost::shared_ptr<NoteType> note (*n);
2166 CanvasNoteEvent* cne;
2168 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2169 if ((cne = find_canvas_note (note)) != 0) {
2170 if (cne->selected()) {
2171 note_deselected (cne);
2173 note_selected (cne, true, false);
2181 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2184 clear_selection_except (ev);
2185 if (!_selection.empty()) {
2186 PublicEditor& editor (trackview.editor());
2187 editor.get_selection().add (this);
2193 if (!ev->selected()) {
2194 add_to_selection (ev);
2198 /* find end of latest note selected, select all between that and the start of "ev" */
2200 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2201 Evoral::MusicalTime latest = 0;
2203 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2204 if ((*i)->note()->end_time() > latest) {
2205 latest = (*i)->note()->end_time();
2207 if ((*i)->note()->time() < earliest) {
2208 earliest = (*i)->note()->time();
2212 if (ev->note()->end_time() > latest) {
2213 latest = ev->note()->end_time();
2216 if (ev->note()->time() < earliest) {
2217 earliest = ev->note()->time();
2220 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2222 /* find notes entirely within OR spanning the earliest..latest range */
2224 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2225 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2226 add_to_selection (*i);
2234 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2236 remove_from_selection (ev);
2240 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2250 // TODO: Make this faster by storing the last updated selection rect, and only
2251 // adjusting things that are in the area that appears/disappeared.
2252 // We probably need a tree to be able to find events in O(log(n)) time.
2254 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2256 /* check if any corner of the note is inside the rect
2259 1) this is computing "touched by", not "contained by" the rect.
2260 2) this does not require that events be sorted in time.
2263 const double ix1 = (*i)->x1();
2264 const double ix2 = (*i)->x2();
2265 const double iy1 = (*i)->y1();
2266 const double iy2 = (*i)->y2();
2268 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2269 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2270 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2271 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2274 if (!(*i)->selected()) {
2275 add_to_selection (*i);
2277 } else if ((*i)->selected() && !extend) {
2278 // Not inside rectangle
2279 remove_from_selection (*i);
2285 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2291 // TODO: Make this faster by storing the last updated selection rect, and only
2292 // adjusting things that are in the area that appears/disappeared.
2293 // We probably need a tree to be able to find events in O(log(n)) time.
2295 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2297 /* check if any corner of the note is inside the rect
2300 1) this is computing "touched by", not "contained by" the rect.
2301 2) this does not require that events be sorted in time.
2304 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2305 // within y- (note-) range
2306 if (!(*i)->selected()) {
2307 add_to_selection (*i);
2309 } else if ((*i)->selected() && !extend) {
2310 // Not inside rectangle
2311 remove_from_selection (*i);
2317 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2319 Selection::iterator i = _selection.find (ev);
2321 if (i != _selection.end()) {
2322 _selection.erase (i);
2325 ev->set_selected (false);
2326 ev->hide_velocity ();
2328 if (_selection.empty()) {
2329 PublicEditor& editor (trackview.editor());
2330 editor.get_selection().remove (this);
2335 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2337 bool add_mrv_selection = false;
2339 if (_selection.empty()) {
2340 add_mrv_selection = true;
2343 if (_selection.insert (ev).second) {
2344 ev->set_selected (true);
2345 start_playing_midi_note ((ev)->note());
2348 if (add_mrv_selection) {
2349 PublicEditor& editor (trackview.editor());
2350 editor.get_selection().add (this);
2355 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2357 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2358 PossibleChord to_play;
2359 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2361 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2362 if ((*i)->note()->time() < earliest) {
2363 earliest = (*i)->note()->time();
2367 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2368 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2369 to_play.push_back ((*i)->note());
2371 (*i)->move_event(dx, dy);
2374 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2376 if (to_play.size() > 1) {
2378 PossibleChord shifted;
2380 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2381 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2382 moved_note->set_note (moved_note->note() + cumulative_dy);
2383 shifted.push_back (moved_note);
2386 start_playing_midi_chord (shifted);
2388 } else if (!to_play.empty()) {
2390 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2391 moved_note->set_note (moved_note->note() + cumulative_dy);
2392 start_playing_midi_note (moved_note);
2398 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2400 assert (!_selection.empty());
2402 uint8_t lowest_note_in_selection = 127;
2403 uint8_t highest_note_in_selection = 0;
2404 uint8_t highest_note_difference = 0;
2406 // find highest and lowest notes first
2408 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2409 uint8_t pitch = (*i)->note()->note();
2410 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2411 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2415 cerr << "dnote: " << (int) dnote << endl;
2416 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2417 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2418 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2419 << int(highest_note_in_selection) << endl;
2420 cerr << "selection size: " << _selection.size() << endl;
2421 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2424 // Make sure the note pitch does not exceed the MIDI standard range
2425 if (highest_note_in_selection + dnote > 127) {
2426 highest_note_difference = highest_note_in_selection - 127;
2429 start_note_diff_command (_("move notes"));
2431 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2433 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2434 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2440 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2442 uint8_t original_pitch = (*i)->note()->note();
2443 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2445 // keep notes in standard midi range
2446 clamp_to_0_127(new_pitch);
2448 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2449 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2451 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2456 // care about notes being moved beyond the upper/lower bounds on the canvas
2457 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2458 highest_note_in_selection > midi_stream_view()->highest_note()) {
2459 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2463 /** @param x Pixel relative to the region position.
2464 * @return Snapped frame relative to the region position.
2467 MidiRegionView::snap_pixel_to_frame(double x)
2469 PublicEditor& editor (trackview.editor());
2470 return snap_frame_to_frame (editor.pixel_to_frame (x));
2473 /** @param x Pixel relative to the region position.
2474 * @return Snapped pixel relative to the region position.
2477 MidiRegionView::snap_to_pixel(double x)
2479 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2483 MidiRegionView::get_position_pixels()
2485 framepos_t region_frame = get_position();
2486 return trackview.editor().frame_to_pixel(region_frame);
2490 MidiRegionView::get_end_position_pixels()
2492 framepos_t frame = get_position() + get_duration ();
2493 return trackview.editor().frame_to_pixel(frame);
2497 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2499 /* the time converter will return the frame corresponding to `beats'
2500 relative to the start of the source. The start of the source
2501 is an implied position given by region->position - region->start
2503 const framepos_t source_start = _region->position() - _region->start();
2504 return source_start + _source_relative_time_converter.to (beats);
2508 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2510 /* the `frames' argument needs to be converted into a frame count
2511 relative to the start of the source before being passed in to the
2514 const framepos_t source_start = _region->position() - _region->start();
2515 return _source_relative_time_converter.from (frames - source_start);
2519 MidiRegionView::region_beats_to_region_frames(double beats) const
2521 return _region_relative_time_converter.to(beats);
2525 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2527 return _region_relative_time_converter.from(frames);
2531 MidiRegionView::begin_resizing (bool /*at_front*/)
2533 _resize_data.clear();
2535 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2536 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2538 // only insert CanvasNotes into the map
2540 NoteResizeData *resize_data = new NoteResizeData();
2541 resize_data->canvas_note = note;
2543 // create a new SimpleRect from the note which will be the resize preview
2544 SimpleRect *resize_rect = new SimpleRect(
2545 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2547 // calculate the colors: get the color settings
2548 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2549 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2552 // make the resize preview notes more transparent and bright
2553 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2555 // calculate color based on note velocity
2556 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2557 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2561 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2562 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2564 resize_data->resize_rect = resize_rect;
2565 _resize_data.push_back(resize_data);
2570 /** Update resizing notes while user drags.
2571 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2572 * @param at_front which end of the note (true == note on, false == note off)
2573 * @param delta_x change in mouse position since the start of the drag
2574 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2575 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2576 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2577 * as the \a primary note.
2580 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2582 bool cursor_set = false;
2584 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2585 SimpleRect* resize_rect = (*i)->resize_rect;
2586 CanvasNote* canvas_note = (*i)->canvas_note;
2591 current_x = canvas_note->x1() + delta_x;
2593 current_x = primary->x1() + delta_x;
2597 current_x = canvas_note->x2() + delta_x;
2599 current_x = primary->x2() + delta_x;
2604 resize_rect->property_x1() = snap_to_pixel(current_x);
2605 resize_rect->property_x2() = canvas_note->x2();
2607 resize_rect->property_x2() = snap_to_pixel(current_x);
2608 resize_rect->property_x1() = canvas_note->x1();
2614 beats = snap_pixel_to_frame (current_x);
2615 beats = region_frames_to_region_beats (beats);
2620 if (beats < canvas_note->note()->end_time()) {
2621 len = canvas_note->note()->time() - beats;
2622 len += canvas_note->note()->length();
2627 if (beats >= canvas_note->note()->time()) {
2628 len = beats - canvas_note->note()->time();
2635 snprintf (buf, sizeof (buf), "%.3g beats", len);
2636 show_verbose_cursor (buf, 0, 0);
2645 /** Finish resizing notes when the user releases the mouse button.
2646 * Parameters the same as for \a update_resizing().
2649 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2651 start_note_diff_command (_("resize notes"));
2653 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2654 CanvasNote* canvas_note = (*i)->canvas_note;
2655 SimpleRect* resize_rect = (*i)->resize_rect;
2657 /* Get the new x position for this resize, which is in pixels relative
2658 * to the region position.
2665 current_x = canvas_note->x1() + delta_x;
2667 current_x = primary->x1() + delta_x;
2671 current_x = canvas_note->x2() + delta_x;
2673 current_x = primary->x2() + delta_x;
2677 /* Convert that to a frame within the source */
2678 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2680 /* and then to beats */
2681 current_x = region_frames_to_region_beats (current_x);
2683 if (at_front && current_x < canvas_note->note()->end_time()) {
2684 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2686 double len = canvas_note->note()->time() - current_x;
2687 len += canvas_note->note()->length();
2690 /* XXX convert to beats */
2691 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2696 double len = current_x - canvas_note->note()->time();
2699 /* XXX convert to beats */
2700 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2708 _resize_data.clear();
2713 MidiRegionView::abort_resizing ()
2715 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2716 delete (*i)->resize_rect;
2720 _resize_data.clear ();
2724 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2726 uint8_t new_velocity;
2729 new_velocity = event->note()->velocity() + velocity;
2730 clamp_to_0_127(new_velocity);
2732 new_velocity = velocity;
2735 event->set_selected (event->selected()); // change color
2737 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2741 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2746 new_note = event->note()->note() + note;
2751 clamp_to_0_127 (new_note);
2752 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2756 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2758 bool change_start = false;
2759 bool change_length = false;
2760 Evoral::MusicalTime new_start = 0;
2761 Evoral::MusicalTime new_length = 0;
2763 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2765 front_delta: if positive - move the start of the note later in time (shortening it)
2766 if negative - move the start of the note earlier in time (lengthening it)
2768 end_delta: if positive - move the end of the note later in time (lengthening it)
2769 if negative - move the end of the note earlier in time (shortening it)
2773 if (front_delta < 0) {
2775 if (event->note()->time() < -front_delta) {
2778 new_start = event->note()->time() + front_delta; // moves earlier
2781 /* start moved toward zero, so move the end point out to where it used to be.
2782 Note that front_delta is negative, so this increases the length.
2785 new_length = event->note()->length() - front_delta;
2786 change_start = true;
2787 change_length = true;
2791 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2793 if (new_pos < event->note()->end_time()) {
2794 new_start = event->note()->time() + front_delta;
2795 /* start moved toward the end, so move the end point back to where it used to be */
2796 new_length = event->note()->length() - front_delta;
2797 change_start = true;
2798 change_length = true;
2805 bool can_change = true;
2806 if (end_delta < 0) {
2807 if (event->note()->length() < -end_delta) {
2813 new_length = event->note()->length() + end_delta;
2814 change_length = true;
2819 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2822 if (change_length) {
2823 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2828 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2830 uint8_t new_channel;
2834 if (event->note()->channel() < -chn) {
2837 new_channel = event->note()->channel() + chn;
2840 new_channel = event->note()->channel() + chn;
2843 new_channel = (uint8_t) chn;
2846 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2850 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2852 Evoral::MusicalTime new_time;
2856 if (event->note()->time() < -delta) {
2859 new_time = event->note()->time() + delta;
2862 new_time = event->note()->time() + delta;
2868 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2872 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2874 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2878 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2883 if (_selection.empty()) {
2898 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2899 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2905 start_note_diff_command (_("change velocities"));
2907 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2908 Selection::iterator next = i;
2912 if (i == _selection.begin()) {
2913 change_note_velocity (*i, delta, true);
2914 value = (*i)->note()->velocity() + delta;
2916 change_note_velocity (*i, value, false);
2920 change_note_velocity (*i, delta, true);
2928 if (!_selection.empty()) {
2930 snprintf (buf, sizeof (buf), "Vel %d",
2931 (int) (*_selection.begin())->note()->velocity());
2932 show_verbose_cursor (buf, 10, 10);
2938 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2940 if (_selection.empty()) {
2957 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2959 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2963 if ((int8_t) (*i)->note()->note() + delta > 127) {
2970 start_note_diff_command (_("transpose"));
2972 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2973 Selection::iterator next = i;
2975 change_note_note (*i, delta, true);
2983 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2989 /* grab the current grid distance */
2991 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2993 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2994 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3004 start_note_diff_command (_("change note lengths"));
3006 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3007 Selection::iterator next = i;
3010 /* note the negation of the delta for start */
3012 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3021 MidiRegionView::nudge_notes (bool forward)
3023 if (_selection.empty()) {
3027 /* pick a note as the point along the timeline to get the nudge distance.
3028 its not necessarily the earliest note, so we may want to pull the notes out
3029 into a vector and sort before using the first one.
3032 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3034 framecnt_t distance;
3036 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3038 /* grid is off - use nudge distance */
3040 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3046 framepos_t next_pos = ref_point;
3049 if (max_framepos - 1 < next_pos) {
3053 if (next_pos == 0) {
3059 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3060 distance = ref_point - next_pos;
3063 if (distance == 0) {
3067 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3073 start_note_diff_command (_("nudge"));
3075 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3076 Selection::iterator next = i;
3078 change_note_time (*i, delta, true);
3086 MidiRegionView::change_channel(uint8_t channel)
3088 start_note_diff_command(_("change channel"));
3089 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3090 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3098 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3100 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3102 pre_enter_cursor = editor->get_canvas_cursor ();
3104 if (_mouse_state == SelectTouchDragging) {
3105 note_selected (ev, true);
3108 show_verbose_cursor (ev->note ());
3112 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3114 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3116 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3117 (*i)->hide_velocity ();
3120 editor->verbose_cursor()->hide ();
3122 if (pre_enter_cursor) {
3123 editor->set_canvas_cursor (pre_enter_cursor);
3124 pre_enter_cursor = 0;
3129 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3132 /* XXX should get patch name if we can */
3133 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3134 show_verbose_cursor (s.str(), 10, 20);
3138 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3140 trackview.editor().verbose_cursor()->hide ();
3144 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3146 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3147 Editing::MouseMode mm = editor->current_mouse_mode();
3148 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3150 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3151 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3152 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3153 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3155 if (pre_enter_cursor && can_set_cursor) {
3156 editor->set_canvas_cursor (pre_enter_cursor);
3162 MidiRegionView::set_frame_color()
3166 TimeAxisViewItem::set_frame_color ();
3173 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3174 } else if (high_enough_for_name) {
3175 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3180 if (!rect_visible) {
3181 f = UINT_RGBA_CHANGE_A (f, 0);
3184 frame->property_fill_color_rgba() = f;
3188 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3190 if (mode == ForceChannel) {
3191 mask = 0xFFFF; // Show all notes as active (below)
3194 // Update notes for selection
3195 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3196 (*i)->on_channel_selection_change(mask);
3199 _last_channel_selection = mask;
3201 _patch_changes.clear ();
3202 display_patch_changes ();
3206 MidiRegionView::instrument_settings_changed ()
3212 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3214 if (_selection.empty()) {
3218 PublicEditor& editor (trackview.editor());
3222 /* XXX what to do ? */
3226 editor.get_cut_buffer().add (selection_as_cut_buffer());
3234 start_note_diff_command();
3236 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3243 note_diff_remove_note (*i);
3253 MidiRegionView::selection_as_cut_buffer () const
3257 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3258 NoteType* n = (*i)->note().get();
3259 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3262 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3268 /** This method handles undo */
3270 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3276 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3278 trackview.session()->begin_reversible_command (_("paste"));
3280 start_note_diff_command (_("paste"));
3282 Evoral::MusicalTime beat_delta;
3283 Evoral::MusicalTime paste_pos_beats;
3284 Evoral::MusicalTime duration;
3285 Evoral::MusicalTime end_point = 0;
3287 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3288 paste_pos_beats = absolute_frames_to_source_beats (pos);
3289 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3290 paste_pos_beats = 0;
3292 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",
3293 (*mcb.notes().begin())->time(),
3294 (*mcb.notes().rbegin())->end_time(),
3295 duration, pos, _region->position(),
3296 paste_pos_beats, beat_delta));
3300 for (int n = 0; n < (int) times; ++n) {
3302 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3304 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3305 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3307 /* make all newly added notes selected */
3309 note_diff_add_note (copied_note, true);
3310 end_point = copied_note->end_time();
3313 paste_pos_beats += duration;
3316 /* if we pasted past the current end of the region, extend the region */
3318 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3319 framepos_t region_end = _region->position() + _region->length() - 1;
3321 if (end_frame > region_end) {
3323 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3325 _region->clear_changes ();
3326 _region->set_length (end_frame - _region->position());
3327 trackview.session()->add_command (new StatefulDiffCommand (_region));
3332 trackview.session()->commit_reversible_command ();
3335 struct EventNoteTimeEarlyFirstComparator {
3336 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3337 return a->note()->time() < b->note()->time();
3342 MidiRegionView::time_sort_events ()
3344 if (!_sort_needed) {
3348 EventNoteTimeEarlyFirstComparator cmp;
3351 _sort_needed = false;
3355 MidiRegionView::goto_next_note (bool add_to_selection)
3357 bool use_next = false;
3359 if (_events.back()->selected()) {
3363 time_sort_events ();
3365 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3366 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3368 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3369 if ((*i)->selected()) {
3372 } else if (use_next) {
3373 if (channel_mask & (1 << (*i)->note()->channel())) {
3374 if (!add_to_selection) {
3377 note_selected (*i, true, false);
3384 /* use the first one */
3386 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3387 unique_select (_events.front());
3392 MidiRegionView::goto_previous_note (bool add_to_selection)
3394 bool use_next = false;
3396 if (_events.front()->selected()) {
3400 time_sort_events ();
3402 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3403 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3405 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3406 if ((*i)->selected()) {
3409 } else if (use_next) {
3410 if (channel_mask & (1 << (*i)->note()->channel())) {
3411 if (!add_to_selection) {
3414 note_selected (*i, true, false);
3421 /* use the last one */
3423 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3424 unique_select (*(_events.rbegin()));
3429 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3431 bool had_selected = false;
3433 time_sort_events ();
3435 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3436 if ((*i)->selected()) {
3437 selected.insert ((*i)->note());
3438 had_selected = true;
3442 if (allow_all_if_none_selected && !had_selected) {
3443 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3444 selected.insert ((*i)->note());
3450 MidiRegionView::update_ghost_note (double x, double y)
3452 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3457 _note_group->w2i (x, y);
3459 PublicEditor& editor = trackview.editor ();
3461 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3462 framecnt_t grid_frames;
3463 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3465 /* use region_frames... because we are converting a delta within the region
3469 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3475 /* note that this sets the time of the ghost note in beats relative to
3476 the start of the source; that is how all note times are stored.
3478 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3479 _ghost_note->note()->set_length (length);
3480 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3481 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3483 /* the ghost note does not appear in ghost regions, so pass false in here */
3484 update_note (_ghost_note, false);
3486 show_verbose_cursor (_ghost_note->note ());
3490 MidiRegionView::create_ghost_note (double x, double y)
3492 remove_ghost_note ();
3494 boost::shared_ptr<NoteType> g (new NoteType);
3495 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3496 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3497 update_ghost_note (x, y);
3498 _ghost_note->show ();
3503 show_verbose_cursor (_ghost_note->note ());
3507 MidiRegionView::snap_changed ()
3513 create_ghost_note (_last_ghost_x, _last_ghost_y);
3517 MidiRegionView::drop_down_keys ()
3519 _mouse_state = None;
3523 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3525 double note = midi_stream_view()->y_to_note(y);
3527 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3529 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3531 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3532 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3533 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3534 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3539 bool add_mrv_selection = false;
3541 if (_selection.empty()) {
3542 add_mrv_selection = true;
3545 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3546 if (_selection.insert (*i).second) {
3547 (*i)->set_selected (true);
3551 if (add_mrv_selection) {
3552 PublicEditor& editor (trackview.editor());
3553 editor.get_selection().add (this);
3558 MidiRegionView::color_handler ()
3560 RegionView::color_handler ();
3562 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3563 (*i)->set_selected ((*i)->selected()); // will change color
3566 /* XXX probably more to do here */
3570 MidiRegionView::enable_display (bool yn)
3572 RegionView::enable_display (yn);
3579 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3581 if (_step_edit_cursor == 0) {
3582 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3584 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3585 _step_edit_cursor->property_y1() = 0;
3586 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3587 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3588 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3591 move_step_edit_cursor (pos);
3592 _step_edit_cursor->show ();
3596 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3598 _step_edit_cursor_position = pos;
3600 if (_step_edit_cursor) {
3601 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3602 _step_edit_cursor->property_x1() = pixel;
3603 set_step_edit_cursor_width (_step_edit_cursor_width);
3608 MidiRegionView::hide_step_edit_cursor ()
3610 if (_step_edit_cursor) {
3611 _step_edit_cursor->hide ();
3616 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3618 _step_edit_cursor_width = beats;
3620 if (_step_edit_cursor) {
3621 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3625 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3626 * @param w Source that the data will end up in.
3629 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3631 if (!_active_notes) {
3632 /* we aren't actively being recorded to */
3636 boost::shared_ptr<MidiSource> src = w.lock ();
3637 if (!src || src != midi_region()->midi_source()) {
3638 /* recorded data was not destined for our source */
3642 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3644 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3646 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3648 framepos_t back = max_framepos;
3650 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3651 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3652 assert (ev.buffer ());
3654 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3655 frames from the start of the source, and so time_beats is in terms of the
3659 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3661 if (ev.type() == MIDI_CMD_NOTE_ON) {
3663 boost::shared_ptr<NoteType> note (
3664 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3667 add_note (note, true);
3669 /* fix up our note range */
3670 if (ev.note() < _current_range_min) {
3671 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3672 } else if (ev.note() > _current_range_max) {
3673 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3676 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3677 resolve_note (ev.note (), time_beats);
3683 midi_stream_view()->check_record_layers (region(), back);
3687 MidiRegionView::trim_front_starting ()
3689 /* Reparent the note group to the region view's parent, so that it doesn't change
3690 when the region view is trimmed.
3692 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3693 _temporary_note_group->move (group->property_x(), group->property_y());
3694 _note_group->reparent (*_temporary_note_group);
3698 MidiRegionView::trim_front_ending ()
3700 _note_group->reparent (*group);
3701 delete _temporary_note_group;
3702 _temporary_note_group = 0;
3704 if (_region->start() < 0) {
3705 /* Trim drag made start time -ve; fix this */
3706 midi_region()->fix_negative_start ();
3711 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3713 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY);
3714 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3718 change_patch_change (pc->patch(), d.patch ());
3723 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3726 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3727 Evoral::midi_note_name (n->note()).c_str(),
3729 (int) n->channel() + 1,
3730 (int) n->velocity());
3732 show_verbose_cursor (buf, 10, 20);
3736 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3740 trackview.editor().get_pointer_position (wx, wy);
3745 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3747 double x1, y1, x2, y2;
3748 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3750 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3751 wy -= (y2 - y1) + 2 * yoffset;
3754 trackview.editor().verbose_cursor()->set (text, wx, wy);
3755 trackview.editor().verbose_cursor()->show ();
3758 /** @param p A session framepos.
3759 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3760 * @return p snapped to the grid subdivision underneath it.
3763 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3765 PublicEditor& editor = trackview.editor ();
3768 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3774 grid_frames = region_beats_to_region_frames (grid_beats);
3776 /* Hack so that we always snap to the note that we are over, instead of snapping
3777 to the next one if we're more than halfway through the one we're over.
3779 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3780 p -= grid_frames / 2;
3783 return snap_frame_to_frame (p);
3786 /** Called when the selection has been cleared in any MidiRegionView.
3787 * @param rv MidiRegionView that the selection was cleared in.
3790 MidiRegionView::selection_cleared (MidiRegionView* rv)
3796 /* Clear our selection in sympathy; but don't signal the fact */
3797 clear_selection (false);
3801 MidiRegionView::note_button_release ()
3803 delete _note_player;