2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include "gtkmm2ext/gtk_ui.h"
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/midi_region.h"
35 #include "ardour/midi_source.h"
36 #include "ardour/midi_model.h"
37 #include "ardour/midi_patch_manager.h"
38 #include "ardour/session.h"
40 #include "evoral/Parameter.hpp"
41 #include "evoral/MIDIParameters.hpp"
42 #include "evoral/MIDIEvent.hpp"
43 #include "evoral/Control.hpp"
44 #include "evoral/midi_util.h"
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas_patch_change.h"
53 #include "editor_drag.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
57 #include "midi_channel_dialog.h"
58 #include "midi_cut_buffer.h"
59 #include "midi_list_editor.h"
60 #include "midi_region_view.h"
61 #include "midi_streamview.h"
62 #include "midi_time_axis.h"
63 #include "midi_util.h"
64 #include "midi_velocity_dialog.h"
65 #include "mouse_cursors.h"
66 #include "note_player.h"
67 #include "public_editor.h"
68 #include "rgb_macros.h"
69 #include "selection.h"
70 #include "simpleline.h"
71 #include "streamview.h"
73 #include "patch_change_dialog.h"
74 #include "verbose_cursor.h"
78 using namespace ARDOUR;
80 using namespace Editing;
81 using namespace ArdourCanvas;
82 using Gtkmm2ext::Keyboard;
84 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
86 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
88 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
89 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
90 : RegionView (parent, tv, r, spu, basic_color)
91 , _last_channel_selection(0xFFFF)
92 , _current_range_min(0)
93 , _current_range_max(0)
95 , _note_group(new ArdourCanvas::Group(*group))
96 , _note_diff_command (0)
98 , _step_edit_cursor (0)
99 , _step_edit_cursor_width (1.0)
100 , _step_edit_cursor_position (0.0)
101 , _channel_selection_scoped_note (0)
102 , _temporary_note_group (0)
105 , _sort_needed (true)
106 , _optimization_iterator (_events.end())
108 , _no_sound_notes (false)
111 , pre_enter_cursor (0)
112 , pre_press_cursor (0)
115 _note_group->raise_to_top();
116 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
118 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
119 connect_to_diskstream ();
121 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
124 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
125 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
126 TimeAxisViewItem::Visibility visibility)
127 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
128 , _last_channel_selection(0xFFFF)
129 , _current_range_min(0)
130 , _current_range_max(0)
132 , _note_group(new ArdourCanvas::Group(*parent))
133 , _note_diff_command (0)
135 , _step_edit_cursor (0)
136 , _step_edit_cursor_width (1.0)
137 , _step_edit_cursor_position (0.0)
138 , _channel_selection_scoped_note (0)
139 , _temporary_note_group (0)
142 , _sort_needed (true)
143 , _optimization_iterator (_events.end())
145 , _no_sound_notes (false)
148 , pre_enter_cursor (0)
149 , pre_press_cursor (0)
152 _note_group->raise_to_top();
153 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
155 connect_to_diskstream ();
157 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
161 MidiRegionView::parameter_changed (std::string const & p)
163 if (p == "diplay-first-midi-bank-as-zero") {
164 if (_enable_display) {
170 MidiRegionView::MidiRegionView (const MidiRegionView& other)
171 : sigc::trackable(other)
173 , _last_channel_selection(0xFFFF)
174 , _current_range_min(0)
175 , _current_range_max(0)
177 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
178 , _note_diff_command (0)
180 , _step_edit_cursor (0)
181 , _step_edit_cursor_width (1.0)
182 , _step_edit_cursor_position (0.0)
183 , _channel_selection_scoped_note (0)
184 , _temporary_note_group (0)
187 , _sort_needed (true)
188 , _optimization_iterator (_events.end())
190 , _no_sound_notes (false)
193 , pre_enter_cursor (0)
194 , pre_press_cursor (0)
200 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
201 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
206 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
207 : RegionView (other, boost::shared_ptr<Region> (region))
208 , _last_channel_selection(0xFFFF)
209 , _current_range_min(0)
210 , _current_range_max(0)
212 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
213 , _note_diff_command (0)
215 , _step_edit_cursor (0)
216 , _step_edit_cursor_width (1.0)
217 , _step_edit_cursor_position (0.0)
218 , _channel_selection_scoped_note (0)
219 , _temporary_note_group (0)
222 , _sort_needed (true)
223 , _optimization_iterator (_events.end())
225 , _no_sound_notes (false)
228 , pre_enter_cursor (0)
229 , pre_press_cursor (0)
235 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
236 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
242 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
244 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
246 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
247 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
251 midi_region()->midi_source(0)->load_model();
254 _model = midi_region()->midi_source(0)->model();
255 _enable_display = false;
257 RegionView::init (basic_color, false);
259 compute_colors (basic_color);
261 set_height (trackview.current_height());
264 region_sync_changed ();
265 region_resized (ARDOUR::bounds_change);
270 _enable_display = true;
273 display_model (_model);
277 reset_width_dependent_items (_pixel_width);
279 group->raise_to_top();
280 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
282 midi_view()->signal_channel_mode_changed().connect(
283 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
285 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
286 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
288 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
289 boost::bind (&MidiRegionView::snap_changed, this),
292 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
293 connect_to_diskstream ();
295 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
299 MidiRegionView::instrument_info () const
301 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
302 return route_ui->route()->instrument_info();
305 const boost::shared_ptr<ARDOUR::MidiRegion>
306 MidiRegionView::midi_region() const
308 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
312 MidiRegionView::connect_to_diskstream ()
314 midi_view()->midi_track()->DataRecorded.connect(
315 *this, invalidator(*this),
316 boost::bind (&MidiRegionView::data_recorded, this, _1),
321 MidiRegionView::canvas_event(GdkEvent* ev)
326 case GDK_ENTER_NOTIFY:
327 case GDK_LEAVE_NOTIFY:
328 _last_event_x = ev->crossing.x;
329 _last_event_y = ev->crossing.y;
331 case GDK_MOTION_NOTIFY:
332 _last_event_x = ev->motion.x;
333 _last_event_y = ev->motion.y;
339 if (ev->type == GDK_2BUTTON_PRESS) {
340 // cannot use double-click to exit internal mode if single-click is being used
341 MouseMode m = trackview.editor().current_mouse_mode();
343 if ((m != MouseObject || !Keyboard::modifier_state_contains (ev->button.state, Keyboard::insert_note_modifier())) && (m != MouseDraw)) {
344 return trackview.editor().toggle_internal_editing_from_double_click (ev);
348 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
349 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
350 (trackview.editor().current_mouse_mode() == MouseZoom)) {
351 // handle non-draw modes elsewhere
357 return scroll (&ev->scroll);
360 return key_press (&ev->key);
362 case GDK_KEY_RELEASE:
363 return key_release (&ev->key);
365 case GDK_BUTTON_PRESS:
366 return button_press (&ev->button);
368 case GDK_BUTTON_RELEASE:
369 r = button_release (&ev->button);
374 case GDK_ENTER_NOTIFY:
375 return enter_notify (&ev->crossing);
377 case GDK_LEAVE_NOTIFY:
378 return leave_notify (&ev->crossing);
380 case GDK_MOTION_NOTIFY:
381 return motion (&ev->motion);
391 MidiRegionView::remove_ghost_note ()
398 MidiRegionView::enter_notify (GdkEventCrossing* ev)
400 trackview.editor().MouseModeChanged.connect (
401 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
404 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
405 create_ghost_note (ev->x, ev->y);
408 if (!trackview.editor().internal_editing()) {
409 Keyboard::magic_widget_drop_focus();
411 Keyboard::magic_widget_grab_focus();
415 // if current operation is non-operational in a midi region, change the cursor to so indicate
416 if (trackview.editor().current_mouse_mode() == MouseGain) {
417 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
418 pre_enter_cursor = editor->get_canvas_cursor();
419 editor->set_canvas_cursor(editor->cursors()->timebar);
426 MidiRegionView::leave_notify (GdkEventCrossing*)
428 _mouse_mode_connection.disconnect ();
430 trackview.editor().verbose_cursor()->hide ();
431 remove_ghost_note ();
433 if (pre_enter_cursor) {
434 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
435 editor->set_canvas_cursor(pre_enter_cursor);
442 MidiRegionView::mouse_mode_changed ()
444 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
445 create_ghost_note (_last_event_x, _last_event_y);
447 remove_ghost_note ();
448 trackview.editor().verbose_cursor()->hide ();
451 if (!trackview.editor().internal_editing()) {
452 Keyboard::magic_widget_drop_focus();
454 Keyboard::magic_widget_grab_focus();
460 MidiRegionView::button_press (GdkEventButton* ev)
462 if (ev->button != 1) {
466 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
467 MouseMode m = editor->current_mouse_mode();
469 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
470 pre_press_cursor = editor->get_canvas_cursor ();
471 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
474 if (_mouse_state != SelectTouchDragging) {
476 _pressed_button = ev->button;
477 _mouse_state = Pressed;
482 _pressed_button = ev->button;
488 MidiRegionView::button_release (GdkEventButton* ev)
490 double event_x, event_y;
492 if (ev->button != 1) {
499 group->w2i(event_x, event_y);
500 group->ungrab(ev->time);
502 PublicEditor& editor = trackview.editor ();
504 if (pre_press_cursor) {
505 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
506 pre_press_cursor = 0;
509 switch (_mouse_state) {
510 case Pressed: // Clicked
512 switch (editor.current_mouse_mode()) {
514 /* no motion occured - simple click */
523 if (Keyboard::is_insert_note_event(ev)) {
525 double event_x, event_y;
529 group->w2i(event_x, event_y);
532 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
538 /* Shorten the length by 1 tick so that we can add a new note at the next
539 grid snap without it overlapping this one.
541 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
543 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
551 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
557 /* Shorten the length by 1 tick so that we can add a new note at the next
558 grid snap without it overlapping this one.
560 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
562 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
573 case SelectRectDragging:
575 editor.drags()->end_grab ((GdkEvent *) ev);
577 create_ghost_note (ev->x, ev->y);
589 MidiRegionView::motion (GdkEventMotion* ev)
591 PublicEditor& editor = trackview.editor ();
593 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
594 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
595 _mouse_state != AddDragging) {
597 create_ghost_note (ev->x, ev->y);
599 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
600 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
602 update_ghost_note (ev->x, ev->y);
604 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
606 remove_ghost_note ();
607 editor.verbose_cursor()->hide ();
609 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
611 update_ghost_note (ev->x, ev->y);
614 /* any motion immediately hides velocity text that may have been visible */
616 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
617 (*i)->hide_velocity ();
620 switch (_mouse_state) {
623 if (_pressed_button == 1) {
625 MouseMode m = editor.current_mouse_mode();
627 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
629 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
630 _mouse_state = AddDragging;
631 remove_ghost_note ();
632 editor.verbose_cursor()->hide ();
634 } else if (m == MouseObject) {
635 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
637 _mouse_state = SelectRectDragging;
639 } else if (m == MouseRange) {
640 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
641 _mouse_state = SelectVerticalDragging;
648 case SelectRectDragging:
649 case SelectVerticalDragging:
651 editor.drags()->motion_handler ((GdkEvent *) ev, false);
654 case SelectTouchDragging:
666 MidiRegionView::scroll (GdkEventScroll* ev)
668 if (_selection.empty()) {
672 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
673 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
674 it still works for zoom.
679 trackview.editor().verbose_cursor()->hide ();
681 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
682 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
684 if (ev->direction == GDK_SCROLL_UP) {
685 change_velocities (true, fine, false, together);
686 } else if (ev->direction == GDK_SCROLL_DOWN) {
687 change_velocities (false, fine, false, together);
689 /* left, right: we don't use them */
697 MidiRegionView::key_press (GdkEventKey* ev)
699 /* since GTK bindings are generally activated on press, and since
700 detectable auto-repeat is the name of the game and only sends
701 repeated presses, carry out key actions at key press, not release.
704 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
706 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
707 _mouse_state = SelectTouchDragging;
710 } else if (ev->keyval == GDK_Escape && unmodified) {
714 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
716 bool start = (ev->keyval == GDK_comma);
717 bool end = (ev->keyval == GDK_period);
718 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
719 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
721 change_note_lengths (fine, shorter, 0.0, start, end);
725 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
727 if (_selection.empty()) {
734 } else if (ev->keyval == GDK_Tab) {
736 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
737 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
739 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
743 } else if (ev->keyval == GDK_ISO_Left_Tab) {
745 /* Shift-TAB generates ISO Left Tab, for some reason */
747 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
748 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
750 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
756 } else if (ev->keyval == GDK_Up) {
758 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
759 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
760 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
762 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
763 change_velocities (true, fine, allow_smush, together);
765 transpose (true, fine, allow_smush);
769 } else if (ev->keyval == GDK_Down) {
771 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
772 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
773 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
775 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
776 change_velocities (false, fine, allow_smush, together);
778 transpose (false, fine, allow_smush);
782 } else if (ev->keyval == GDK_Left && unmodified) {
787 } else if (ev->keyval == GDK_Right && unmodified) {
792 } else if (ev->keyval == GDK_c && unmodified) {
796 } else if (ev->keyval == GDK_v && unmodified) {
805 MidiRegionView::key_release (GdkEventKey* ev)
807 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
815 MidiRegionView::channel_edit ()
817 if (_selection.empty()) {
821 /* pick a note somewhat at random (since Selection is a set<>) to
822 * provide the "current" channel for the dialog.
825 uint8_t current_channel = (*_selection.begin())->note()->channel ();
826 MidiChannelDialog channel_dialog (current_channel);
827 int ret = channel_dialog.run ();
830 case Gtk::RESPONSE_OK:
836 uint8_t new_channel = channel_dialog.active_channel ();
838 start_note_diff_command (_("channel edit"));
840 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
841 Selection::iterator next = i;
843 change_note_channel (*i, new_channel);
851 MidiRegionView::velocity_edit ()
853 if (_selection.empty()) {
857 /* pick a note somewhat at random (since Selection is a set<>) to
858 * provide the "current" velocity for the dialog.
861 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
862 MidiVelocityDialog velocity_dialog (current_velocity);
863 int ret = velocity_dialog.run ();
866 case Gtk::RESPONSE_OK:
872 uint8_t new_velocity = velocity_dialog.velocity ();
874 start_note_diff_command (_("velocity edit"));
876 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
877 Selection::iterator next = i;
879 change_note_velocity (*i, new_velocity, false);
887 MidiRegionView::show_list_editor ()
890 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
892 _list_editor->present ();
895 /** Add a note to the model, and the view, at a canvas (click) coordinate.
896 * \param t time in frames relative to the position of the region
897 * \param y vertical position in pixels
898 * \param length duration of the note in beats
899 * \param snap_t true to snap t to the grid, otherwise false.
902 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
904 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
905 MidiStreamView* const view = mtv->midi_view();
907 double note = view->y_to_note(y);
910 assert(note <= 127.0);
912 // Start of note in frames relative to region start
914 framecnt_t grid_frames;
915 t = snap_frame_to_grid_underneath (t, grid_frames);
919 assert (length != 0);
921 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
922 region_frames_to_region_beats(t + _region->start()),
924 (uint8_t)note, 0x40));
926 if (_model->contains (new_note)) {
930 view->update_note_range(new_note->note());
932 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
934 _model->apply_command(*trackview.session(), cmd);
936 play_midi_note (new_note);
940 MidiRegionView::clear_events (bool with_selection_signal)
942 clear_selection (with_selection_signal);
945 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
946 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
951 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
956 _patch_changes.clear();
958 _optimization_iterator = _events.end();
962 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
966 content_connection.disconnect ();
967 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
971 if (_enable_display) {
977 MidiRegionView::start_note_diff_command (string name)
979 if (!_note_diff_command) {
980 _note_diff_command = _model->new_note_diff_command (name);
985 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
987 if (_note_diff_command) {
988 _note_diff_command->add (note);
991 _marked_for_selection.insert(note);
994 _marked_for_velocity.insert(note);
999 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
1001 if (_note_diff_command && ev->note()) {
1002 _note_diff_command->remove(ev->note());
1007 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1008 MidiModel::NoteDiffCommand::Property property,
1011 if (_note_diff_command) {
1012 _note_diff_command->change (ev->note(), property, val);
1017 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1018 MidiModel::NoteDiffCommand::Property property,
1019 Evoral::MusicalTime val)
1021 if (_note_diff_command) {
1022 _note_diff_command->change (ev->note(), property, val);
1027 MidiRegionView::apply_diff (bool as_subcommand)
1031 if (!_note_diff_command) {
1035 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1036 // Mark all selected notes for selection when model reloads
1037 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1038 _marked_for_selection.insert((*i)->note());
1042 if (as_subcommand) {
1043 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1045 _model->apply_command (*trackview.session(), _note_diff_command);
1048 _note_diff_command = 0;
1049 midi_view()->midi_track()->playlist_modified();
1051 if (add_or_remove) {
1052 _marked_for_selection.clear();
1055 _marked_for_velocity.clear();
1059 MidiRegionView::abort_command()
1061 delete _note_diff_command;
1062 _note_diff_command = 0;
1067 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1069 if (_optimization_iterator != _events.end()) {
1070 ++_optimization_iterator;
1073 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1074 return *_optimization_iterator;
1077 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1078 if ((*_optimization_iterator)->note() == note) {
1079 return *_optimization_iterator;
1087 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1089 MidiModel::Notes notes;
1090 _model->get_notes (notes, op, val, chan_mask);
1092 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1093 CanvasNoteEvent* cne = find_canvas_note (*n);
1101 MidiRegionView::redisplay_model()
1103 // Don't redisplay the model if we're currently recording and displaying that
1104 if (_active_notes) {
1112 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1113 (*i)->invalidate ();
1116 MidiModel::ReadLock lock(_model->read_lock());
1118 MidiModel::Notes& notes (_model->notes());
1119 _optimization_iterator = _events.begin();
1121 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1123 boost::shared_ptr<NoteType> note (*n);
1124 CanvasNoteEvent* cne;
1127 if (note_in_region_range (note, visible)) {
1129 if ((cne = find_canvas_note (note)) != 0) {
1136 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1138 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1150 add_note (note, visible);
1155 if ((cne = find_canvas_note (note)) != 0) {
1163 /* remove note items that are no longer valid */
1165 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1166 if (!(*i)->valid ()) {
1168 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1169 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1171 gr->remove_note (*i);
1176 i = _events.erase (i);
1183 _patch_changes.clear();
1187 display_patch_changes ();
1189 _marked_for_selection.clear ();
1190 _marked_for_velocity.clear ();
1192 /* we may have caused _events to contain things out of order (e.g. if a note
1193 moved earlier or later). we don't generally need them in time order, but
1194 make a note that a sort is required for those cases that require it.
1197 _sort_needed = true;
1201 MidiRegionView::display_patch_changes ()
1203 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1204 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1206 for (uint8_t i = 0; i < 16; ++i) {
1207 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1211 /** @param active_channel true to display patch changes fully, false to display
1212 * them `greyed-out' (as on an inactive channel)
1215 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1217 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1219 if ((*i)->channel() != channel) {
1223 string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1224 add_canvas_patch_change (*i, patch_name, active_channel);
1229 MidiRegionView::display_sysexes()
1231 bool have_periodic_system_messages = false;
1232 bool display_periodic_messages = true;
1234 if (!Config->get_never_display_periodic_midi()) {
1236 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1237 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1238 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1241 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1242 have_periodic_system_messages = true;
1248 if (have_periodic_system_messages) {
1249 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1251 /* get an approximate value for the number of samples per video frame */
1253 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1255 /* if we are zoomed out beyond than the cutoff (i.e. more
1256 * frames per pixel than frames per 4 video frames), don't
1257 * show periodic sysex messages.
1260 if (zoom > (video_frame*4)) {
1261 display_periodic_messages = false;
1265 display_periodic_messages = false;
1268 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1270 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1271 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1273 Evoral::MusicalTime time = (*i)->time();
1277 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1278 if (!display_periodic_messages) {
1286 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1287 str << int((*i)->buffer()[b]);
1288 if (b != (*i)->size() -1) {
1292 string text = str.str();
1294 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1296 double height = midi_stream_view()->contents_height();
1298 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1299 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1301 // Show unless message is beyond the region bounds
1302 if (time - _region->start() >= _region->length() || time < _region->start()) {
1308 _sys_exes.push_back(sysex);
1312 MidiRegionView::~MidiRegionView ()
1314 in_destructor = true;
1316 trackview.editor().verbose_cursor()->hide ();
1318 note_delete_connection.disconnect ();
1320 delete _list_editor;
1322 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1324 if (_active_notes) {
1328 _selection_cleared_connection.disconnect ();
1331 clear_events (false);
1334 delete _note_diff_command;
1335 delete _step_edit_cursor;
1336 delete _temporary_note_group;
1340 MidiRegionView::region_resized (const PropertyChange& what_changed)
1342 RegionView::region_resized(what_changed);
1344 if (what_changed.contains (ARDOUR::Properties::position)) {
1345 set_duration(_region->length(), 0);
1346 if (_enable_display) {
1353 MidiRegionView::reset_width_dependent_items (double pixel_width)
1355 RegionView::reset_width_dependent_items(pixel_width);
1356 assert(_pixel_width == pixel_width);
1358 if (_enable_display) {
1362 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1363 if ((*x)->width() >= _pixel_width) {
1370 move_step_edit_cursor (_step_edit_cursor_position);
1371 set_step_edit_cursor_width (_step_edit_cursor_width);
1375 MidiRegionView::set_height (double height)
1377 static const double FUDGE = 2.0;
1378 const double old_height = _height;
1379 RegionView::set_height(height);
1380 _height = height - FUDGE;
1382 apply_note_range(midi_stream_view()->lowest_note(),
1383 midi_stream_view()->highest_note(),
1384 height != old_height + FUDGE);
1387 name_pixbuf->raise_to_top();
1390 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1391 (*x)->set_height (midi_stream_view()->contents_height());
1394 if (_step_edit_cursor) {
1395 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1400 /** Apply the current note range from the stream view
1401 * by repositioning/hiding notes as necessary
1404 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1406 if (!_enable_display) {
1410 if (!force && _current_range_min == min && _current_range_max == max) {
1414 _current_range_min = min;
1415 _current_range_max = max;
1417 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1418 CanvasNoteEvent* event = *i;
1419 boost::shared_ptr<NoteType> note (event->note());
1421 if (note->note() < _current_range_min ||
1422 note->note() > _current_range_max) {
1428 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1430 const double y1 = midi_stream_view()->note_to_y(note->note());
1431 const double y2 = y1 + floor(midi_stream_view()->note_height());
1433 cnote->property_y1() = y1;
1434 cnote->property_y2() = y2;
1436 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1438 const double diamond_size = update_hit (chit);
1440 chit->set_height (diamond_size);
1446 MidiRegionView::add_ghost (TimeAxisView& tv)
1450 double unit_position = _region->position () / samples_per_unit;
1451 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1452 MidiGhostRegion* ghost;
1454 if (mtv && mtv->midi_view()) {
1455 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1456 to allow having midi notes on top of note lines and waveforms.
1458 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1460 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1463 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1464 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1465 ghost->add_note(note);
1469 ghost->set_height ();
1470 ghost->set_duration (_region->length() / samples_per_unit);
1471 ghosts.push_back (ghost);
1473 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1479 /** Begin tracking note state for successive calls to add_event
1482 MidiRegionView::begin_write()
1484 assert(!_active_notes);
1485 _active_notes = new CanvasNote*[128];
1486 for (unsigned i=0; i < 128; ++i) {
1487 _active_notes[i] = 0;
1492 /** Destroy note state for add_event
1495 MidiRegionView::end_write()
1497 delete[] _active_notes;
1499 _marked_for_selection.clear();
1500 _marked_for_velocity.clear();
1504 /** Resolve an active MIDI note (while recording).
1507 MidiRegionView::resolve_note(uint8_t note, double end_time)
1509 if (midi_view()->note_mode() != Sustained) {
1513 if (_active_notes && _active_notes[note]) {
1515 /* XXX is end_time really region-centric? I think so, because
1516 this is a new region that we're recording, so source zero is
1517 the same as region zero
1519 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1521 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1522 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1523 _active_notes[note] = 0;
1528 /** Extend active notes to rightmost edge of region (if length is changed)
1531 MidiRegionView::extend_active_notes()
1533 if (!_active_notes) {
1537 for (unsigned i=0; i < 128; ++i) {
1538 if (_active_notes[i]) {
1539 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1546 MidiRegionView::play_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 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1562 /* NotePlayer deletes itself */
1566 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1568 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1572 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1574 if (!route_ui || !route_ui->midi_track()) {
1578 delete _note_player;
1579 _note_player = new NotePlayer (route_ui->midi_track ());
1580 _note_player->add (note);
1581 _note_player->on ();
1585 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1587 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1591 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1593 if (!route_ui || !route_ui->midi_track()) {
1597 delete _note_player;
1598 _note_player = new NotePlayer (route_ui->midi_track());
1600 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1601 _note_player->add (*n);
1604 _note_player->on ();
1609 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1611 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1612 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1614 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1615 (note->note() <= midi_stream_view()->highest_note());
1620 /** Update a canvas note's size from its model note.
1621 * @param ev Canvas note to update.
1622 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1625 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1627 boost::shared_ptr<NoteType> note = ev->note();
1628 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1629 const double y1 = midi_stream_view()->note_to_y(note->note());
1631 ev->property_x1() = x;
1632 ev->property_y1() = y1;
1634 /* trim note display to not overlap the end of its region */
1636 if (note->length() > 0) {
1637 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1638 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1640 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1643 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1645 if (note->length() == 0) {
1646 if (_active_notes) {
1647 assert(note->note() < 128);
1648 // If this note is already active there's a stuck note,
1649 // finish the old note rectangle
1650 if (_active_notes[note->note()]) {
1651 CanvasNote* const old_rect = _active_notes[note->note()];
1652 boost::shared_ptr<NoteType> old_note = old_rect->note();
1653 old_rect->property_x2() = x;
1654 old_rect->property_outline_what() = (guint32) 0xF;
1656 _active_notes[note->note()] = ev;
1658 /* outline all but right edge */
1659 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1661 /* outline all edges */
1662 ev->property_outline_what() = (guint32) 0xF;
1665 if (update_ghost_regions) {
1666 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1667 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1669 gr->update_note (ev);
1676 MidiRegionView::update_hit (CanvasHit* ev)
1678 boost::shared_ptr<NoteType> note = ev->note();
1680 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1681 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1682 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1683 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1687 return diamond_size;
1690 /** Add a MIDI note to the view (with length).
1692 * If in sustained mode, notes with length 0 will be considered active
1693 * notes, and resolve_note should be called when the corresponding note off
1694 * event arrives, to properly display the note.
1697 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1699 CanvasNoteEvent* event = 0;
1701 assert(note->time() >= 0);
1702 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1704 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1706 if (midi_view()->note_mode() == Sustained) {
1708 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1710 update_note (ev_rect);
1714 MidiGhostRegion* gr;
1716 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1717 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1718 gr->add_note(ev_rect);
1722 } else if (midi_view()->note_mode() == Percussive) {
1724 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1726 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1728 update_hit (ev_diamond);
1737 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1738 note_selected(event, true);
1741 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1742 event->show_velocity();
1745 event->on_channel_selection_change(_last_channel_selection);
1746 _events.push_back(event);
1755 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1756 MidiStreamView* const view = mtv->midi_view();
1758 view->update_note_range (note->note());
1762 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1763 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1765 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1767 /* potentially extend region to hold new note */
1769 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1770 framepos_t region_end = _region->last_frame();
1772 if (end_frame > region_end) {
1773 _region->set_length (end_frame - _region->position());
1776 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1777 MidiStreamView* const view = mtv->midi_view();
1779 view->update_note_range(new_note->note());
1781 _marked_for_selection.clear ();
1784 start_note_diff_command (_("step add"));
1785 note_diff_add_note (new_note, true, false);
1788 // last_step_edit_note = new_note;
1792 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1794 change_note_lengths (false, false, beats, false, true);
1797 /** Add a new patch change flag to the canvas.
1798 * @param patch the patch change to add
1799 * @param the text to display in the flag
1800 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1803 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1805 assert (patch->time() >= 0);
1807 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1808 const double x = trackview.editor().frame_to_pixel (region_frames);
1810 double const height = midi_stream_view()->contents_height();
1812 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1813 new CanvasPatchChange(*this, *group,
1822 if (patch_change->width() < _pixel_width) {
1823 // Show unless patch change is beyond the region bounds
1824 if (region_frames < 0 || region_frames >= _region->length()) {
1825 patch_change->hide();
1827 patch_change->show();
1830 patch_change->hide ();
1833 _patch_changes.push_back (patch_change);
1836 MIDI::Name::PatchPrimaryKey
1837 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1839 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1843 MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1845 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1846 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1850 if (i != _model->patch_changes().end()) {
1851 key.bank_number = (*i)->bank();
1852 key.program_number = (*i)->program ();
1854 key.bank_number = key.program_number = 0;
1857 assert (key.is_sane());
1861 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1863 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1865 if (pc.patch()->program() != new_patch.program_number) {
1866 c->change_program (pc.patch (), new_patch.program_number);
1869 int const new_bank = new_patch.bank_number;
1870 if (pc.patch()->bank() != new_bank) {
1871 c->change_bank (pc.patch (), new_bank);
1874 _model->apply_command (*trackview.session(), c);
1876 _patch_changes.clear ();
1877 display_patch_changes ();
1881 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1883 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1885 if (old_change->time() != new_change.time()) {
1886 c->change_time (old_change, new_change.time());
1889 if (old_change->channel() != new_change.channel()) {
1890 c->change_channel (old_change, new_change.channel());
1893 if (old_change->program() != new_change.program()) {
1894 c->change_program (old_change, new_change.program());
1897 if (old_change->bank() != new_change.bank()) {
1898 c->change_bank (old_change, new_change.bank());
1901 _model->apply_command (*trackview.session(), c);
1903 _patch_changes.clear ();
1904 display_patch_changes ();
1907 /** Add a patch change to the region.
1908 * @param t Time in frames relative to region position
1909 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1910 * MidiTimeAxisView::get_channel_for_add())
1913 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1915 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1917 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1918 c->add (MidiModel::PatchChangePtr (
1919 new Evoral::PatchChange<Evoral::MusicalTime> (
1920 absolute_frames_to_source_beats (_region->position() + t),
1921 mtv->get_channel_for_add(), patch.program(), patch.bank()
1926 _model->apply_command (*trackview.session(), c);
1928 _patch_changes.clear ();
1929 display_patch_changes ();
1933 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1935 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1936 c->change_time (pc.patch (), t);
1937 _model->apply_command (*trackview.session(), c);
1939 _patch_changes.clear ();
1940 display_patch_changes ();
1944 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1946 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1947 c->remove (pc->patch ());
1948 _model->apply_command (*trackview.session(), c);
1950 _patch_changes.clear ();
1951 display_patch_changes ();
1955 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1957 if (patch.patch()->program() < 127) {
1958 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1959 key.program_number++;
1960 change_patch_change (patch, key);
1965 MidiRegionView::next_patch (CanvasPatchChange& patch)
1967 if (patch.patch()->program() > 0) {
1968 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1969 key.program_number--;
1970 change_patch_change (patch, key);
1975 MidiRegionView::next_bank (CanvasPatchChange& patch)
1977 if (patch.patch()->program() < 127) {
1978 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1979 if (key.bank_number > 0) {
1981 change_patch_change (patch, key);
1987 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1989 if (patch.patch()->program() > 0) {
1990 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1991 if (key.bank_number < 127) {
1993 change_patch_change (patch, key);
1999 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
2001 if (_selection.empty()) {
2005 _selection.erase (cne);
2009 MidiRegionView::delete_selection()
2011 if (_selection.empty()) {
2015 start_note_diff_command (_("delete selection"));
2017 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2018 if ((*i)->selected()) {
2019 _note_diff_command->remove((*i)->note());
2029 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2031 start_note_diff_command (_("delete note"));
2032 _note_diff_command->remove (n);
2035 trackview.editor().verbose_cursor()->hide ();
2039 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2041 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2043 Selection::iterator tmp = i;
2046 (*i)->set_selected (false);
2047 (*i)->hide_velocity ();
2048 _selection.erase (i);
2056 /* this does not change the status of this regionview w.r.t the editor
2061 SelectionCleared (this); /* EMIT SIGNAL */
2066 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2068 clear_selection_except (ev);
2070 /* don't bother with checking to see if we should remove this
2071 regionview from the editor selection, since we're about to add
2072 another note, and thus put/keep this regionview in the editor
2076 if (!ev->selected()) {
2077 add_to_selection (ev);
2082 MidiRegionView::select_all_notes ()
2086 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2087 add_to_selection (*i);
2092 MidiRegionView::select_range (framepos_t start, framepos_t end)
2096 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2097 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2098 if (t >= start && t <= end) {
2099 add_to_selection (*i);
2105 MidiRegionView::invert_selection ()
2107 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2108 if ((*i)->selected()) {
2109 remove_from_selection(*i);
2111 add_to_selection (*i);
2117 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2119 uint8_t low_note = 127;
2120 uint8_t high_note = 0;
2121 MidiModel::Notes& notes (_model->notes());
2122 _optimization_iterator = _events.begin();
2128 if (extend && _selection.empty()) {
2134 /* scan existing selection to get note range */
2136 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2137 if ((*i)->note()->note() < low_note) {
2138 low_note = (*i)->note()->note();
2140 if ((*i)->note()->note() > high_note) {
2141 high_note = (*i)->note()->note();
2145 low_note = min (low_note, notenum);
2146 high_note = max (high_note, notenum);
2149 _no_sound_notes = true;
2151 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2153 boost::shared_ptr<NoteType> note (*n);
2154 CanvasNoteEvent* cne;
2155 bool select = false;
2157 if (((1 << note->channel()) & channel_mask) != 0) {
2159 if ((note->note() >= low_note && note->note() <= high_note)) {
2162 } else if (note->note() == notenum) {
2168 if ((cne = find_canvas_note (note)) != 0) {
2169 // extend is false because we've taken care of it,
2170 // since it extends by time range, not pitch.
2171 note_selected (cne, add, false);
2175 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2179 _no_sound_notes = false;
2183 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2185 MidiModel::Notes& notes (_model->notes());
2186 _optimization_iterator = _events.begin();
2188 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2190 boost::shared_ptr<NoteType> note (*n);
2191 CanvasNoteEvent* cne;
2193 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2194 if ((cne = find_canvas_note (note)) != 0) {
2195 if (cne->selected()) {
2196 note_deselected (cne);
2198 note_selected (cne, true, false);
2206 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2209 clear_selection_except (ev);
2210 if (!_selection.empty()) {
2211 PublicEditor& editor (trackview.editor());
2212 editor.get_selection().add (this);
2218 if (!ev->selected()) {
2219 add_to_selection (ev);
2223 /* find end of latest note selected, select all between that and the start of "ev" */
2225 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2226 Evoral::MusicalTime latest = 0;
2228 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2229 if ((*i)->note()->end_time() > latest) {
2230 latest = (*i)->note()->end_time();
2232 if ((*i)->note()->time() < earliest) {
2233 earliest = (*i)->note()->time();
2237 if (ev->note()->end_time() > latest) {
2238 latest = ev->note()->end_time();
2241 if (ev->note()->time() < earliest) {
2242 earliest = ev->note()->time();
2245 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2247 /* find notes entirely within OR spanning the earliest..latest range */
2249 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2250 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2251 add_to_selection (*i);
2259 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2261 remove_from_selection (ev);
2265 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2275 // TODO: Make this faster by storing the last updated selection rect, and only
2276 // adjusting things that are in the area that appears/disappeared.
2277 // We probably need a tree to be able to find events in O(log(n)) time.
2279 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2281 /* check if any corner of the note is inside the rect
2284 1) this is computing "touched by", not "contained by" the rect.
2285 2) this does not require that events be sorted in time.
2288 const double ix1 = (*i)->x1();
2289 const double ix2 = (*i)->x2();
2290 const double iy1 = (*i)->y1();
2291 const double iy2 = (*i)->y2();
2293 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2294 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2295 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2296 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2299 if (!(*i)->selected()) {
2300 add_to_selection (*i);
2302 } else if ((*i)->selected() && !extend) {
2303 // Not inside rectangle
2304 remove_from_selection (*i);
2310 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2316 // TODO: Make this faster by storing the last updated selection rect, and only
2317 // adjusting things that are in the area that appears/disappeared.
2318 // We probably need a tree to be able to find events in O(log(n)) time.
2320 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2322 /* check if any corner of the note is inside the rect
2325 1) this is computing "touched by", not "contained by" the rect.
2326 2) this does not require that events be sorted in time.
2329 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2330 // within y- (note-) range
2331 if (!(*i)->selected()) {
2332 add_to_selection (*i);
2334 } else if ((*i)->selected() && !extend) {
2335 // Not inside rectangle
2336 remove_from_selection (*i);
2342 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2344 Selection::iterator i = _selection.find (ev);
2346 if (i != _selection.end()) {
2347 _selection.erase (i);
2350 ev->set_selected (false);
2351 ev->hide_velocity ();
2353 if (_selection.empty()) {
2354 PublicEditor& editor (trackview.editor());
2355 editor.get_selection().remove (this);
2360 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2362 bool add_mrv_selection = false;
2364 if (_selection.empty()) {
2365 add_mrv_selection = true;
2368 if (_selection.insert (ev).second) {
2369 ev->set_selected (true);
2370 start_playing_midi_note ((ev)->note());
2373 if (add_mrv_selection) {
2374 PublicEditor& editor (trackview.editor());
2375 editor.get_selection().add (this);
2380 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2382 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2383 PossibleChord to_play;
2384 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2386 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2387 if ((*i)->note()->time() < earliest) {
2388 earliest = (*i)->note()->time();
2392 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2393 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2394 to_play.push_back ((*i)->note());
2396 (*i)->move_event(dx, dy);
2399 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2401 if (to_play.size() > 1) {
2403 PossibleChord shifted;
2405 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2406 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2407 moved_note->set_note (moved_note->note() + cumulative_dy);
2408 shifted.push_back (moved_note);
2411 start_playing_midi_chord (shifted);
2413 } else if (!to_play.empty()) {
2415 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2416 moved_note->set_note (moved_note->note() + cumulative_dy);
2417 start_playing_midi_note (moved_note);
2423 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2425 assert (!_selection.empty());
2427 uint8_t lowest_note_in_selection = 127;
2428 uint8_t highest_note_in_selection = 0;
2429 uint8_t highest_note_difference = 0;
2431 // find highest and lowest notes first
2433 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2434 uint8_t pitch = (*i)->note()->note();
2435 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2436 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2440 cerr << "dnote: " << (int) dnote << endl;
2441 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2442 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2443 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2444 << int(highest_note_in_selection) << endl;
2445 cerr << "selection size: " << _selection.size() << endl;
2446 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2449 // Make sure the note pitch does not exceed the MIDI standard range
2450 if (highest_note_in_selection + dnote > 127) {
2451 highest_note_difference = highest_note_in_selection - 127;
2454 start_note_diff_command (_("move notes"));
2456 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2458 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2459 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2465 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2467 uint8_t original_pitch = (*i)->note()->note();
2468 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2470 // keep notes in standard midi range
2471 clamp_to_0_127(new_pitch);
2473 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2474 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2476 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2481 // care about notes being moved beyond the upper/lower bounds on the canvas
2482 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2483 highest_note_in_selection > midi_stream_view()->highest_note()) {
2484 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2488 /** @param x Pixel relative to the region position.
2489 * @return Snapped frame relative to the region position.
2492 MidiRegionView::snap_pixel_to_frame(double x)
2494 PublicEditor& editor (trackview.editor());
2495 return snap_frame_to_frame (editor.pixel_to_frame (x));
2498 /** @param x Pixel relative to the region position.
2499 * @return Snapped pixel relative to the region position.
2502 MidiRegionView::snap_to_pixel(double x)
2504 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2508 MidiRegionView::get_position_pixels()
2510 framepos_t region_frame = get_position();
2511 return trackview.editor().frame_to_pixel(region_frame);
2515 MidiRegionView::get_end_position_pixels()
2517 framepos_t frame = get_position() + get_duration ();
2518 return trackview.editor().frame_to_pixel(frame);
2522 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2524 /* the time converter will return the frame corresponding to `beats'
2525 relative to the start of the source. The start of the source
2526 is an implied position given by region->position - region->start
2528 const framepos_t source_start = _region->position() - _region->start();
2529 return source_start + _source_relative_time_converter.to (beats);
2533 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2535 /* the `frames' argument needs to be converted into a frame count
2536 relative to the start of the source before being passed in to the
2539 const framepos_t source_start = _region->position() - _region->start();
2540 return _source_relative_time_converter.from (frames - source_start);
2544 MidiRegionView::region_beats_to_region_frames(double beats) const
2546 return _region_relative_time_converter.to(beats);
2550 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2552 return _region_relative_time_converter.from(frames);
2556 MidiRegionView::begin_resizing (bool /*at_front*/)
2558 _resize_data.clear();
2560 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2561 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2563 // only insert CanvasNotes into the map
2565 NoteResizeData *resize_data = new NoteResizeData();
2566 resize_data->canvas_note = note;
2568 // create a new SimpleRect from the note which will be the resize preview
2569 SimpleRect *resize_rect = new SimpleRect(
2570 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2572 // calculate the colors: get the color settings
2573 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2574 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2577 // make the resize preview notes more transparent and bright
2578 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2580 // calculate color based on note velocity
2581 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2582 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2586 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2587 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2589 resize_data->resize_rect = resize_rect;
2590 _resize_data.push_back(resize_data);
2595 /** Update resizing notes while user drags.
2596 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2597 * @param at_front which end of the note (true == note on, false == note off)
2598 * @param delta_x change in mouse position since the start of the drag
2599 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2600 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2601 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2602 * as the \a primary note.
2605 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2607 bool cursor_set = false;
2609 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2610 SimpleRect* resize_rect = (*i)->resize_rect;
2611 CanvasNote* canvas_note = (*i)->canvas_note;
2616 current_x = canvas_note->x1() + delta_x;
2618 current_x = primary->x1() + delta_x;
2622 current_x = canvas_note->x2() + delta_x;
2624 current_x = primary->x2() + delta_x;
2629 resize_rect->property_x1() = snap_to_pixel(current_x);
2630 resize_rect->property_x2() = canvas_note->x2();
2632 resize_rect->property_x2() = snap_to_pixel(current_x);
2633 resize_rect->property_x1() = canvas_note->x1();
2639 beats = snap_pixel_to_frame (current_x);
2640 beats = region_frames_to_region_beats (beats);
2645 if (beats < canvas_note->note()->end_time()) {
2646 len = canvas_note->note()->time() - beats;
2647 len += canvas_note->note()->length();
2652 if (beats >= canvas_note->note()->time()) {
2653 len = beats - canvas_note->note()->time();
2660 snprintf (buf, sizeof (buf), "%.3g beats", len);
2661 show_verbose_cursor (buf, 0, 0);
2670 /** Finish resizing notes when the user releases the mouse button.
2671 * Parameters the same as for \a update_resizing().
2674 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2676 start_note_diff_command (_("resize notes"));
2678 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2679 CanvasNote* canvas_note = (*i)->canvas_note;
2680 SimpleRect* resize_rect = (*i)->resize_rect;
2682 /* Get the new x position for this resize, which is in pixels relative
2683 * to the region position.
2690 current_x = canvas_note->x1() + delta_x;
2692 current_x = primary->x1() + delta_x;
2696 current_x = canvas_note->x2() + delta_x;
2698 current_x = primary->x2() + delta_x;
2702 /* Convert that to a frame within the source */
2703 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2705 /* and then to beats */
2706 current_x = region_frames_to_region_beats (current_x);
2708 if (at_front && current_x < canvas_note->note()->end_time()) {
2709 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2711 double len = canvas_note->note()->time() - current_x;
2712 len += canvas_note->note()->length();
2715 /* XXX convert to beats */
2716 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2721 double len = current_x - canvas_note->note()->time();
2724 /* XXX convert to beats */
2725 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2733 _resize_data.clear();
2738 MidiRegionView::abort_resizing ()
2740 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2741 delete (*i)->resize_rect;
2745 _resize_data.clear ();
2749 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2751 uint8_t new_velocity;
2754 new_velocity = event->note()->velocity() + velocity;
2755 clamp_to_0_127(new_velocity);
2757 new_velocity = velocity;
2760 event->set_selected (event->selected()); // change color
2762 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2766 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2771 new_note = event->note()->note() + note;
2776 clamp_to_0_127 (new_note);
2777 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2781 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2783 bool change_start = false;
2784 bool change_length = false;
2785 Evoral::MusicalTime new_start = 0;
2786 Evoral::MusicalTime new_length = 0;
2788 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2790 front_delta: if positive - move the start of the note later in time (shortening it)
2791 if negative - move the start of the note earlier in time (lengthening it)
2793 end_delta: if positive - move the end of the note later in time (lengthening it)
2794 if negative - move the end of the note earlier in time (shortening it)
2798 if (front_delta < 0) {
2800 if (event->note()->time() < -front_delta) {
2803 new_start = event->note()->time() + front_delta; // moves earlier
2806 /* start moved toward zero, so move the end point out to where it used to be.
2807 Note that front_delta is negative, so this increases the length.
2810 new_length = event->note()->length() - front_delta;
2811 change_start = true;
2812 change_length = true;
2816 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2818 if (new_pos < event->note()->end_time()) {
2819 new_start = event->note()->time() + front_delta;
2820 /* start moved toward the end, so move the end point back to where it used to be */
2821 new_length = event->note()->length() - front_delta;
2822 change_start = true;
2823 change_length = true;
2830 bool can_change = true;
2831 if (end_delta < 0) {
2832 if (event->note()->length() < -end_delta) {
2838 new_length = event->note()->length() + end_delta;
2839 change_length = true;
2844 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2847 if (change_length) {
2848 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2853 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2855 uint8_t new_channel;
2859 if (event->note()->channel() < -chn) {
2862 new_channel = event->note()->channel() + chn;
2865 new_channel = event->note()->channel() + chn;
2868 new_channel = (uint8_t) chn;
2871 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2875 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2877 Evoral::MusicalTime new_time;
2881 if (event->note()->time() < -delta) {
2884 new_time = event->note()->time() + delta;
2887 new_time = event->note()->time() + delta;
2893 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2897 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2899 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2903 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2908 if (_selection.empty()) {
2923 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2924 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2930 start_note_diff_command (_("change velocities"));
2932 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2933 Selection::iterator next = i;
2937 if (i == _selection.begin()) {
2938 change_note_velocity (*i, delta, true);
2939 value = (*i)->note()->velocity() + delta;
2941 change_note_velocity (*i, value, false);
2945 change_note_velocity (*i, delta, true);
2953 if (!_selection.empty()) {
2955 snprintf (buf, sizeof (buf), "Vel %d",
2956 (int) (*_selection.begin())->note()->velocity());
2957 show_verbose_cursor (buf, 10, 10);
2963 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2965 if (_selection.empty()) {
2982 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2984 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2988 if ((int8_t) (*i)->note()->note() + delta > 127) {
2995 start_note_diff_command (_("transpose"));
2997 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2998 Selection::iterator next = i;
3000 change_note_note (*i, delta, true);
3008 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
3014 /* grab the current grid distance */
3016 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
3018 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
3019 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3029 start_note_diff_command (_("change note lengths"));
3031 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3032 Selection::iterator next = i;
3035 /* note the negation of the delta for start */
3037 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3046 MidiRegionView::nudge_notes (bool forward)
3048 if (_selection.empty()) {
3052 /* pick a note as the point along the timeline to get the nudge distance.
3053 its not necessarily the earliest note, so we may want to pull the notes out
3054 into a vector and sort before using the first one.
3057 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3059 framecnt_t distance;
3061 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3063 /* grid is off - use nudge distance */
3065 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3071 framepos_t next_pos = ref_point;
3074 if (max_framepos - 1 < next_pos) {
3078 if (next_pos == 0) {
3084 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3085 distance = ref_point - next_pos;
3088 if (distance == 0) {
3092 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3098 start_note_diff_command (_("nudge"));
3100 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3101 Selection::iterator next = i;
3103 change_note_time (*i, delta, true);
3111 MidiRegionView::change_channel(uint8_t channel)
3113 start_note_diff_command(_("change channel"));
3114 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3115 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3123 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3125 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3127 pre_enter_cursor = editor->get_canvas_cursor ();
3129 if (_mouse_state == SelectTouchDragging) {
3130 note_selected (ev, true);
3133 show_verbose_cursor (ev->note ());
3137 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3139 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3141 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3142 (*i)->hide_velocity ();
3145 editor->verbose_cursor()->hide ();
3147 if (pre_enter_cursor) {
3148 editor->set_canvas_cursor (pre_enter_cursor);
3149 pre_enter_cursor = 0;
3154 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* p)
3157 /* XXX should get patch name if we can */
3158 s << _("Bank:") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3159 << _("Program:") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3160 << _("Channel:") << ((int) p->patch()->channel() + 1);
3161 show_verbose_cursor (s.str(), 10, 20);
3166 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3168 trackview.editor().verbose_cursor()->hide ();
3169 /* focus will transfer back via the enter-notify event sent to this
3175 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3177 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3178 Editing::MouseMode mm = editor->current_mouse_mode();
3179 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3181 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3182 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3183 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3184 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3186 if (pre_enter_cursor && can_set_cursor) {
3187 editor->set_canvas_cursor (pre_enter_cursor);
3193 MidiRegionView::set_frame_color()
3197 TimeAxisViewItem::set_frame_color ();
3204 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3205 } else if (high_enough_for_name) {
3206 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3211 if (!rect_visible) {
3212 f = UINT_RGBA_CHANGE_A (f, 0);
3215 frame->property_fill_color_rgba() = f;
3219 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3221 if (mode == ForceChannel) {
3222 mask = 0xFFFF; // Show all notes as active (below)
3225 // Update notes for selection
3226 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3227 (*i)->on_channel_selection_change(mask);
3230 _last_channel_selection = mask;
3232 _patch_changes.clear ();
3233 display_patch_changes ();
3237 MidiRegionView::instrument_settings_changed ()
3243 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3245 if (_selection.empty()) {
3249 PublicEditor& editor (trackview.editor());
3253 /* XXX what to do ? */
3257 editor.get_cut_buffer().add (selection_as_cut_buffer());
3265 start_note_diff_command();
3267 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3274 note_diff_remove_note (*i);
3284 MidiRegionView::selection_as_cut_buffer () const
3288 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3289 NoteType* n = (*i)->note().get();
3290 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3293 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3299 /** This method handles undo */
3301 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3307 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3309 trackview.session()->begin_reversible_command (_("paste"));
3311 start_note_diff_command (_("paste"));
3313 Evoral::MusicalTime beat_delta;
3314 Evoral::MusicalTime paste_pos_beats;
3315 Evoral::MusicalTime duration;
3316 Evoral::MusicalTime end_point = 0;
3318 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3319 paste_pos_beats = absolute_frames_to_source_beats (pos);
3320 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3321 paste_pos_beats = 0;
3323 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",
3324 (*mcb.notes().begin())->time(),
3325 (*mcb.notes().rbegin())->end_time(),
3326 duration, pos, _region->position(),
3327 paste_pos_beats, beat_delta));
3331 for (int n = 0; n < (int) times; ++n) {
3333 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3335 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3336 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3338 /* make all newly added notes selected */
3340 note_diff_add_note (copied_note, true);
3341 end_point = copied_note->end_time();
3344 paste_pos_beats += duration;
3347 /* if we pasted past the current end of the region, extend the region */
3349 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3350 framepos_t region_end = _region->position() + _region->length() - 1;
3352 if (end_frame > region_end) {
3354 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3356 _region->clear_changes ();
3357 _region->set_length (end_frame - _region->position());
3358 trackview.session()->add_command (new StatefulDiffCommand (_region));
3363 trackview.session()->commit_reversible_command ();
3366 struct EventNoteTimeEarlyFirstComparator {
3367 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3368 return a->note()->time() < b->note()->time();
3373 MidiRegionView::time_sort_events ()
3375 if (!_sort_needed) {
3379 EventNoteTimeEarlyFirstComparator cmp;
3382 _sort_needed = false;
3386 MidiRegionView::goto_next_note (bool add_to_selection)
3388 bool use_next = false;
3390 if (_events.back()->selected()) {
3394 time_sort_events ();
3396 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3397 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3399 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3400 if ((*i)->selected()) {
3403 } else if (use_next) {
3404 if (channel_mask & (1 << (*i)->note()->channel())) {
3405 if (!add_to_selection) {
3408 note_selected (*i, true, false);
3415 /* use the first one */
3417 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3418 unique_select (_events.front());
3423 MidiRegionView::goto_previous_note (bool add_to_selection)
3425 bool use_next = false;
3427 if (_events.front()->selected()) {
3431 time_sort_events ();
3433 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3434 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3436 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3437 if ((*i)->selected()) {
3440 } else if (use_next) {
3441 if (channel_mask & (1 << (*i)->note()->channel())) {
3442 if (!add_to_selection) {
3445 note_selected (*i, true, false);
3452 /* use the last one */
3454 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3455 unique_select (*(_events.rbegin()));
3460 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3462 bool had_selected = false;
3464 time_sort_events ();
3466 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3467 if ((*i)->selected()) {
3468 selected.insert ((*i)->note());
3469 had_selected = true;
3473 if (allow_all_if_none_selected && !had_selected) {
3474 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3475 selected.insert ((*i)->note());
3481 MidiRegionView::update_ghost_note (double x, double y)
3483 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3488 _note_group->w2i (x, y);
3490 PublicEditor& editor = trackview.editor ();
3492 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3493 framecnt_t grid_frames;
3494 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3496 /* use region_frames... because we are converting a delta within the region
3500 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3506 /* note that this sets the time of the ghost note in beats relative to
3507 the start of the source; that is how all note times are stored.
3509 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3510 _ghost_note->note()->set_length (length);
3511 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3512 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3514 /* the ghost note does not appear in ghost regions, so pass false in here */
3515 update_note (_ghost_note, false);
3517 show_verbose_cursor (_ghost_note->note ());
3521 MidiRegionView::create_ghost_note (double x, double y)
3523 remove_ghost_note ();
3525 boost::shared_ptr<NoteType> g (new NoteType);
3526 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3527 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3528 update_ghost_note (x, y);
3529 _ghost_note->show ();
3534 show_verbose_cursor (_ghost_note->note ());
3538 MidiRegionView::snap_changed ()
3544 create_ghost_note (_last_ghost_x, _last_ghost_y);
3548 MidiRegionView::drop_down_keys ()
3550 _mouse_state = None;
3554 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3556 double note = midi_stream_view()->y_to_note(y);
3558 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3560 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3562 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3563 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3564 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3565 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3570 bool add_mrv_selection = false;
3572 if (_selection.empty()) {
3573 add_mrv_selection = true;
3576 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3577 if (_selection.insert (*i).second) {
3578 (*i)->set_selected (true);
3582 if (add_mrv_selection) {
3583 PublicEditor& editor (trackview.editor());
3584 editor.get_selection().add (this);
3589 MidiRegionView::color_handler ()
3591 RegionView::color_handler ();
3593 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3594 (*i)->set_selected ((*i)->selected()); // will change color
3597 /* XXX probably more to do here */
3601 MidiRegionView::enable_display (bool yn)
3603 RegionView::enable_display (yn);
3610 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3612 if (_step_edit_cursor == 0) {
3613 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3615 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3616 _step_edit_cursor->property_y1() = 0;
3617 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3618 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3619 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3622 move_step_edit_cursor (pos);
3623 _step_edit_cursor->show ();
3627 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3629 _step_edit_cursor_position = pos;
3631 if (_step_edit_cursor) {
3632 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3633 _step_edit_cursor->property_x1() = pixel;
3634 set_step_edit_cursor_width (_step_edit_cursor_width);
3639 MidiRegionView::hide_step_edit_cursor ()
3641 if (_step_edit_cursor) {
3642 _step_edit_cursor->hide ();
3647 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3649 _step_edit_cursor_width = beats;
3651 if (_step_edit_cursor) {
3652 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3656 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3657 * @param w Source that the data will end up in.
3660 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3662 if (!_active_notes) {
3663 /* we aren't actively being recorded to */
3667 boost::shared_ptr<MidiSource> src = w.lock ();
3668 if (!src || src != midi_region()->midi_source()) {
3669 /* recorded data was not destined for our source */
3673 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3675 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3677 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3679 framepos_t back = max_framepos;
3681 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3682 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3683 assert (ev.buffer ());
3685 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3686 frames from the start of the source, and so time_beats is in terms of the
3690 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3692 if (ev.type() == MIDI_CMD_NOTE_ON) {
3694 boost::shared_ptr<NoteType> note (
3695 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3698 add_note (note, true);
3700 /* fix up our note range */
3701 if (ev.note() < _current_range_min) {
3702 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3703 } else if (ev.note() > _current_range_max) {
3704 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3707 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3708 resolve_note (ev.note (), time_beats);
3714 midi_stream_view()->check_record_layers (region(), back);
3718 MidiRegionView::trim_front_starting ()
3720 /* Reparent the note group to the region view's parent, so that it doesn't change
3721 when the region view is trimmed.
3723 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3724 _temporary_note_group->move (group->property_x(), group->property_y());
3725 _note_group->reparent (*_temporary_note_group);
3729 MidiRegionView::trim_front_ending ()
3731 _note_group->reparent (*group);
3732 delete _temporary_note_group;
3733 _temporary_note_group = 0;
3735 if (_region->start() < 0) {
3736 /* Trim drag made start time -ve; fix this */
3737 midi_region()->fix_negative_start ();
3742 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3744 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY);
3746 d.set_position (Gtk::WIN_POS_MOUSE);
3748 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3752 change_patch_change (pc->patch(), d.patch ());
3757 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3760 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3761 Evoral::midi_note_name (n->note()).c_str(),
3763 (int) n->channel() + 1,
3764 (int) n->velocity());
3766 show_verbose_cursor (buf, 10, 20);
3770 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3774 trackview.editor().get_pointer_position (wx, wy);
3779 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3781 double x1, y1, x2, y2;
3782 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3784 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3785 wy -= (y2 - y1) + 2 * yoffset;
3788 trackview.editor().verbose_cursor()->set (text, wx, wy);
3789 trackview.editor().verbose_cursor()->show ();
3792 /** @param p A session framepos.
3793 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3794 * @return p snapped to the grid subdivision underneath it.
3797 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3799 PublicEditor& editor = trackview.editor ();
3802 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3808 grid_frames = region_beats_to_region_frames (grid_beats);
3810 /* Hack so that we always snap to the note that we are over, instead of snapping
3811 to the next one if we're more than halfway through the one we're over.
3813 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3814 p -= grid_frames / 2;
3817 return snap_frame_to_frame (p);
3820 /** Called when the selection has been cleared in any MidiRegionView.
3821 * @param rv MidiRegionView that the selection was cleared in.
3824 MidiRegionView::selection_cleared (MidiRegionView* rv)
3830 /* Clear our selection in sympathy; but don't signal the fact */
3831 clear_selection (false);
3835 MidiRegionView::note_button_release ()
3837 delete _note_player;