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 /* Look up MIDNAM details from our MidiTimeAxisView */
119 MidiTimeAxisView& mtv = dynamic_cast<MidiTimeAxisView&> (tv);
120 midi_patch_settings_changed (mtv.midi_patch_model (), mtv.midi_patch_custom_device_node ());
122 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
123 connect_to_diskstream ();
125 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
128 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
129 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
130 TimeAxisViewItem::Visibility visibility)
131 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
132 , _last_channel_selection(0xFFFF)
133 , _current_range_min(0)
134 , _current_range_max(0)
136 , _note_group(new ArdourCanvas::Group(*parent))
137 , _note_diff_command (0)
139 , _step_edit_cursor (0)
140 , _step_edit_cursor_width (1.0)
141 , _step_edit_cursor_position (0.0)
142 , _channel_selection_scoped_note (0)
143 , _temporary_note_group (0)
146 , _sort_needed (true)
147 , _optimization_iterator (_events.end())
149 , _no_sound_notes (false)
152 , pre_enter_cursor (0)
153 , pre_press_cursor (0)
156 _note_group->raise_to_top();
157 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
159 /* Look up MIDNAM details from our MidiTimeAxisView */
160 MidiTimeAxisView& mtv = dynamic_cast<MidiTimeAxisView&> (tv);
161 midi_patch_settings_changed (mtv.midi_patch_model (), mtv.midi_patch_custom_device_node ());
163 connect_to_diskstream ();
165 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
169 MidiRegionView::parameter_changed (std::string const & p)
171 if (p == "diplay-first-midi-bank-as-zero") {
172 if (_enable_display) {
178 MidiRegionView::MidiRegionView (const MidiRegionView& other)
179 : sigc::trackable(other)
181 , _last_channel_selection(0xFFFF)
182 , _current_range_min(0)
183 , _current_range_max(0)
185 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
186 , _note_diff_command (0)
188 , _step_edit_cursor (0)
189 , _step_edit_cursor_width (1.0)
190 , _step_edit_cursor_position (0.0)
191 , _channel_selection_scoped_note (0)
192 , _temporary_note_group (0)
195 , _sort_needed (true)
196 , _optimization_iterator (_events.end())
198 , _no_sound_notes (false)
201 , pre_enter_cursor (0)
202 , pre_press_cursor (0)
208 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
209 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
214 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
215 : RegionView (other, boost::shared_ptr<Region> (region))
216 , _last_channel_selection(0xFFFF)
217 , _current_range_min(0)
218 , _current_range_max(0)
220 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
221 , _note_diff_command (0)
223 , _step_edit_cursor (0)
224 , _step_edit_cursor_width (1.0)
225 , _step_edit_cursor_position (0.0)
226 , _channel_selection_scoped_note (0)
227 , _temporary_note_group (0)
230 , _sort_needed (true)
231 , _optimization_iterator (_events.end())
233 , _no_sound_notes (false)
236 , pre_enter_cursor (0)
237 , pre_press_cursor (0)
243 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
244 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
250 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
252 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
254 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
255 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
259 midi_region()->midi_source(0)->load_model();
262 _model = midi_region()->midi_source(0)->model();
263 _enable_display = false;
265 RegionView::init (basic_color, false);
267 compute_colors (basic_color);
269 set_height (trackview.current_height());
272 region_sync_changed ();
273 region_resized (ARDOUR::bounds_change);
276 reset_width_dependent_items (_pixel_width);
280 _enable_display = true;
283 display_model (_model);
287 group->raise_to_top();
288 group->signal_event().connect(
289 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
291 midi_view()->signal_channel_mode_changed().connect(
292 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
294 midi_view()->signal_midi_patch_settings_changed().connect(
295 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
297 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
298 boost::bind (&MidiRegionView::snap_changed, this),
301 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
302 connect_to_diskstream ();
304 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
307 const boost::shared_ptr<ARDOUR::MidiRegion>
308 MidiRegionView::midi_region() const
310 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
314 MidiRegionView::connect_to_diskstream ()
316 midi_view()->midi_track()->DataRecorded.connect(
317 *this, invalidator(*this),
318 boost::bind (&MidiRegionView::data_recorded, this, _1),
323 MidiRegionView::canvas_event(GdkEvent* ev)
328 case GDK_ENTER_NOTIFY:
329 case GDK_LEAVE_NOTIFY:
330 _last_event_x = ev->crossing.x;
331 _last_event_y = ev->crossing.y;
333 case GDK_MOTION_NOTIFY:
334 _last_event_x = ev->motion.x;
335 _last_event_y = ev->motion.y;
341 if (ev->type == GDK_2BUTTON_PRESS) {
342 return trackview.editor().toggle_internal_editing_from_double_click (ev);
345 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
346 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
347 (trackview.editor().current_mouse_mode() == MouseZoom)) {
348 // handle non-draw modes elsewhere
354 return scroll (&ev->scroll);
357 return key_press (&ev->key);
359 case GDK_KEY_RELEASE:
360 return key_release (&ev->key);
362 case GDK_BUTTON_PRESS:
363 return button_press (&ev->button);
365 case GDK_BUTTON_RELEASE:
366 r = button_release (&ev->button);
371 case GDK_ENTER_NOTIFY:
372 return enter_notify (&ev->crossing);
374 case GDK_LEAVE_NOTIFY:
375 return leave_notify (&ev->crossing);
377 case GDK_MOTION_NOTIFY:
378 return motion (&ev->motion);
388 MidiRegionView::remove_ghost_note ()
395 MidiRegionView::enter_notify (GdkEventCrossing* ev)
397 trackview.editor().MouseModeChanged.connect (
398 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
401 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
402 create_ghost_note (ev->x, ev->y);
405 if (!trackview.editor().internal_editing()) {
406 Keyboard::magic_widget_drop_focus();
408 Keyboard::magic_widget_grab_focus();
412 // if current operation is non-operational in a midi region, change the cursor to so indicate
413 if (trackview.editor().current_mouse_mode() == MouseGain) {
414 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
415 pre_enter_cursor = editor->get_canvas_cursor();
416 editor->set_canvas_cursor(editor->cursors()->timebar);
423 MidiRegionView::leave_notify (GdkEventCrossing*)
425 _mouse_mode_connection.disconnect ();
427 trackview.editor().verbose_cursor()->hide ();
428 remove_ghost_note ();
430 if (pre_enter_cursor) {
431 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
432 editor->set_canvas_cursor(pre_enter_cursor);
439 MidiRegionView::mouse_mode_changed ()
441 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
442 create_ghost_note (_last_event_x, _last_event_y);
444 remove_ghost_note ();
445 trackview.editor().verbose_cursor()->hide ();
448 if (!trackview.editor().internal_editing()) {
449 Keyboard::magic_widget_drop_focus();
451 Keyboard::magic_widget_grab_focus();
457 MidiRegionView::button_press (GdkEventButton* ev)
459 if (ev->button != 1) {
463 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
464 MouseMode m = editor->current_mouse_mode();
466 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
467 pre_press_cursor = editor->get_canvas_cursor ();
468 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
471 if (_mouse_state != SelectTouchDragging) {
473 _pressed_button = ev->button;
474 _mouse_state = Pressed;
479 _pressed_button = ev->button;
485 MidiRegionView::button_release (GdkEventButton* ev)
487 double event_x, event_y;
489 if (ev->button != 1) {
496 group->w2i(event_x, event_y);
497 group->ungrab(ev->time);
499 PublicEditor& editor = trackview.editor ();
501 if (pre_press_cursor) {
502 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
503 pre_press_cursor = 0;
506 switch (_mouse_state) {
507 case Pressed: // Clicked
509 switch (editor.current_mouse_mode()) {
511 /* no motion occured - simple click */
520 if (Keyboard::is_insert_note_event(ev)) {
522 double event_x, event_y;
526 group->w2i(event_x, event_y);
529 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
535 /* Shorten the length by 1 tick so that we can add a new note at the next
536 grid snap without it overlapping this one.
538 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
540 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
548 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
554 /* Shorten the length by 1 tick so that we can add a new note at the next
555 grid snap without it overlapping this one.
557 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
559 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
570 case SelectRectDragging:
572 editor.drags()->end_grab ((GdkEvent *) ev);
574 create_ghost_note (ev->x, ev->y);
586 MidiRegionView::motion (GdkEventMotion* ev)
588 PublicEditor& editor = trackview.editor ();
590 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
591 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
592 _mouse_state != AddDragging) {
594 create_ghost_note (ev->x, ev->y);
596 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
597 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
599 update_ghost_note (ev->x, ev->y);
601 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
603 remove_ghost_note ();
604 editor.verbose_cursor()->hide ();
606 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
608 update_ghost_note (ev->x, ev->y);
611 /* any motion immediately hides velocity text that may have been visible */
613 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
614 (*i)->hide_velocity ();
617 switch (_mouse_state) {
620 if (_pressed_button == 1) {
622 MouseMode m = editor.current_mouse_mode();
624 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
626 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
627 _mouse_state = AddDragging;
628 remove_ghost_note ();
629 editor.verbose_cursor()->hide ();
631 } else if (m == MouseObject) {
632 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
634 _mouse_state = SelectRectDragging;
636 } else if (m == MouseRange) {
637 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
638 _mouse_state = SelectVerticalDragging;
645 case SelectRectDragging:
646 case SelectVerticalDragging:
648 editor.drags()->motion_handler ((GdkEvent *) ev, false);
651 case SelectTouchDragging:
663 MidiRegionView::scroll (GdkEventScroll* ev)
665 if (_selection.empty()) {
669 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
670 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
671 it still works for zoom.
676 trackview.editor().verbose_cursor()->hide ();
678 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
679 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
681 if (ev->direction == GDK_SCROLL_UP) {
682 change_velocities (true, fine, false, together);
683 } else if (ev->direction == GDK_SCROLL_DOWN) {
684 change_velocities (false, fine, false, together);
690 MidiRegionView::key_press (GdkEventKey* ev)
692 /* since GTK bindings are generally activated on press, and since
693 detectable auto-repeat is the name of the game and only sends
694 repeated presses, carry out key actions at key press, not release.
697 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
699 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
700 _mouse_state = SelectTouchDragging;
703 } else if (ev->keyval == GDK_Escape && unmodified) {
707 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
709 bool start = (ev->keyval == GDK_comma);
710 bool end = (ev->keyval == GDK_period);
711 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
712 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
714 change_note_lengths (fine, shorter, 0.0, start, end);
718 } else if (ev->keyval == GDK_Delete && unmodified) {
723 } else if (ev->keyval == GDK_Tab) {
725 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
726 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
728 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
732 } else if (ev->keyval == GDK_ISO_Left_Tab) {
734 /* Shift-TAB generates ISO Left Tab, for some reason */
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));
745 } else if (ev->keyval == GDK_Up) {
747 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
748 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
749 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
751 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
752 change_velocities (true, fine, allow_smush, together);
754 transpose (true, fine, allow_smush);
758 } else if (ev->keyval == GDK_Down) {
760 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
761 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
762 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
764 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
765 change_velocities (false, fine, allow_smush, together);
767 transpose (false, fine, allow_smush);
771 } else if (ev->keyval == GDK_Left && unmodified) {
776 } else if (ev->keyval == GDK_Right && unmodified) {
781 } else if (ev->keyval == GDK_c && unmodified) {
785 } else if (ev->keyval == GDK_v && unmodified) {
794 MidiRegionView::key_release (GdkEventKey* ev)
796 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
804 MidiRegionView::channel_edit ()
806 if (_selection.empty()) {
810 /* pick a note somewhat at random (since Selection is a set<>) to
811 * provide the "current" channel for the dialog.
814 uint8_t current_channel = (*_selection.begin())->note()->channel ();
815 MidiChannelDialog channel_dialog (current_channel);
816 int ret = channel_dialog.run ();
819 case Gtk::RESPONSE_OK:
825 uint8_t new_channel = channel_dialog.active_channel ();
827 start_note_diff_command (_("channel edit"));
829 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
830 Selection::iterator next = i;
832 change_note_channel (*i, new_channel);
840 MidiRegionView::velocity_edit ()
842 if (_selection.empty()) {
846 /* pick a note somewhat at random (since Selection is a set<>) to
847 * provide the "current" velocity for the dialog.
850 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
851 MidiVelocityDialog velocity_dialog (current_velocity);
852 int ret = velocity_dialog.run ();
855 case Gtk::RESPONSE_OK:
861 uint8_t new_velocity = velocity_dialog.velocity ();
863 start_note_diff_command (_("velocity edit"));
865 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
866 Selection::iterator next = i;
868 change_note_velocity (*i, new_velocity, false);
876 MidiRegionView::show_list_editor ()
879 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
881 _list_editor->present ();
884 /** Add a note to the model, and the view, at a canvas (click) coordinate.
885 * \param t time in frames relative to the position of the region
886 * \param y vertical position in pixels
887 * \param length duration of the note in beats
888 * \param snap_t true to snap t to the grid, otherwise false.
891 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
893 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
894 MidiStreamView* const view = mtv->midi_view();
896 double note = view->y_to_note(y);
899 assert(note <= 127.0);
901 // Start of note in frames relative to region start
903 framecnt_t grid_frames;
904 t = snap_frame_to_grid_underneath (t, grid_frames);
908 assert (length != 0);
910 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
911 region_frames_to_region_beats(t + _region->start()),
913 (uint8_t)note, 0x40));
915 if (_model->contains (new_note)) {
919 view->update_note_range(new_note->note());
921 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
923 _model->apply_command(*trackview.session(), cmd);
925 play_midi_note (new_note);
929 MidiRegionView::clear_events()
934 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
935 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
940 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
945 _patch_changes.clear();
947 _optimization_iterator = _events.end();
951 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
955 content_connection.disconnect ();
956 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
960 if (_enable_display) {
966 MidiRegionView::start_note_diff_command (string name)
968 if (!_note_diff_command) {
969 _note_diff_command = _model->new_note_diff_command (name);
974 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
976 if (_note_diff_command) {
977 _note_diff_command->add (note);
980 _marked_for_selection.insert(note);
983 _marked_for_velocity.insert(note);
988 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
990 if (_note_diff_command && ev->note()) {
991 _note_diff_command->remove(ev->note());
996 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
997 MidiModel::NoteDiffCommand::Property property,
1000 if (_note_diff_command) {
1001 _note_diff_command->change (ev->note(), property, val);
1006 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1007 MidiModel::NoteDiffCommand::Property property,
1008 Evoral::MusicalTime val)
1010 if (_note_diff_command) {
1011 _note_diff_command->change (ev->note(), property, val);
1016 MidiRegionView::apply_diff (bool as_subcommand)
1020 if (!_note_diff_command) {
1024 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1025 // Mark all selected notes for selection when model reloads
1026 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1027 _marked_for_selection.insert((*i)->note());
1031 if (as_subcommand) {
1032 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1034 _model->apply_command (*trackview.session(), _note_diff_command);
1037 _note_diff_command = 0;
1038 midi_view()->midi_track()->playlist_modified();
1040 if (add_or_remove) {
1041 _marked_for_selection.clear();
1044 _marked_for_velocity.clear();
1048 MidiRegionView::abort_command()
1050 delete _note_diff_command;
1051 _note_diff_command = 0;
1056 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1058 if (_optimization_iterator != _events.end()) {
1059 ++_optimization_iterator;
1062 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1063 return *_optimization_iterator;
1066 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1067 if ((*_optimization_iterator)->note() == note) {
1068 return *_optimization_iterator;
1076 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1078 MidiModel::Notes notes;
1079 _model->get_notes (notes, op, val, chan_mask);
1081 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1082 CanvasNoteEvent* cne = find_canvas_note (*n);
1090 MidiRegionView::redisplay_model()
1092 // Don't redisplay the model if we're currently recording and displaying that
1093 if (_active_notes) {
1101 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1102 (*i)->invalidate ();
1105 MidiModel::ReadLock lock(_model->read_lock());
1107 MidiModel::Notes& notes (_model->notes());
1108 _optimization_iterator = _events.begin();
1110 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1112 boost::shared_ptr<NoteType> note (*n);
1113 CanvasNoteEvent* cne;
1116 if (note_in_region_range (note, visible)) {
1118 if ((cne = find_canvas_note (note)) != 0) {
1125 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1127 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1139 add_note (note, visible);
1144 if ((cne = find_canvas_note (note)) != 0) {
1152 /* remove note items that are no longer valid */
1154 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1155 if (!(*i)->valid ()) {
1157 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1158 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1160 gr->remove_note (*i);
1165 i = _events.erase (i);
1172 _patch_changes.clear();
1176 display_patch_changes ();
1178 _marked_for_selection.clear ();
1179 _marked_for_velocity.clear ();
1181 /* we may have caused _events to contain things out of order (e.g. if a note
1182 moved earlier or later). we don't generally need them in time order, but
1183 make a note that a sort is required for those cases that require it.
1186 _sort_needed = true;
1190 MidiRegionView::display_patch_changes ()
1192 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1193 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1195 for (uint8_t i = 0; i < 16; ++i) {
1196 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1200 /** @param active_channel true to display patch changes fully, false to display
1201 * them `greyed-out' (as on an inactive channel)
1204 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1206 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1208 if ((*i)->channel() != channel) {
1212 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1214 boost::shared_ptr<MIDI::Name::Patch> patch =
1215 MIDI::Name::MidiPatchManager::instance().find_patch(
1216 _model_name, _custom_device_mode, channel, patch_key);
1219 add_canvas_patch_change (*i, patch->name(), active_channel);
1222 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1223 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1224 add_canvas_patch_change (*i, buf, active_channel);
1230 MidiRegionView::display_sysexes()
1232 bool have_periodic_system_messages = false;
1233 bool display_periodic_messages = true;
1235 if (!Config->get_never_display_periodic_midi()) {
1237 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1238 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1239 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1242 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1243 have_periodic_system_messages = true;
1249 if (have_periodic_system_messages) {
1250 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1252 /* get an approximate value for the number of samples per video frame */
1254 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1256 /* if we are zoomed out beyond than the cutoff (i.e. more
1257 * frames per pixel than frames per 4 video frames), don't
1258 * show periodic sysex messages.
1261 if (zoom > (video_frame*4)) {
1262 display_periodic_messages = false;
1266 display_periodic_messages = false;
1269 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1271 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1272 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1274 Evoral::MusicalTime time = (*i)->time();
1278 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1279 if (!display_periodic_messages) {
1287 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1288 str << int((*i)->buffer()[b]);
1289 if (b != (*i)->size() -1) {
1293 string text = str.str();
1295 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1297 double height = midi_stream_view()->contents_height();
1299 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1300 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1302 // Show unless message is beyond the region bounds
1303 if (time - _region->start() >= _region->length() || time < _region->start()) {
1309 _sys_exes.push_back(sysex);
1313 MidiRegionView::~MidiRegionView ()
1315 in_destructor = true;
1317 trackview.editor().verbose_cursor()->hide ();
1319 note_delete_connection.disconnect ();
1321 delete _list_editor;
1323 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1325 if (_active_notes) {
1329 _selection_cleared_connection.disconnect ();
1335 delete _note_diff_command;
1336 delete _step_edit_cursor;
1337 delete _temporary_note_group;
1341 MidiRegionView::region_resized (const PropertyChange& what_changed)
1343 RegionView::region_resized(what_changed);
1345 if (what_changed.contains (ARDOUR::Properties::position)) {
1346 set_duration(_region->length(), 0);
1347 if (_enable_display) {
1354 MidiRegionView::reset_width_dependent_items (double pixel_width)
1356 RegionView::reset_width_dependent_items(pixel_width);
1357 assert(_pixel_width == pixel_width);
1359 if (_enable_display) {
1363 move_step_edit_cursor (_step_edit_cursor_position);
1364 set_step_edit_cursor_width (_step_edit_cursor_width);
1368 MidiRegionView::set_height (double height)
1370 static const double FUDGE = 2.0;
1371 const double old_height = _height;
1372 RegionView::set_height(height);
1373 _height = height - FUDGE;
1375 apply_note_range(midi_stream_view()->lowest_note(),
1376 midi_stream_view()->highest_note(),
1377 height != old_height + FUDGE);
1380 name_pixbuf->raise_to_top();
1383 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1384 (*x)->set_height (midi_stream_view()->contents_height());
1387 if (_step_edit_cursor) {
1388 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1393 /** Apply the current note range from the stream view
1394 * by repositioning/hiding notes as necessary
1397 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1399 if (!_enable_display) {
1403 if (!force && _current_range_min == min && _current_range_max == max) {
1407 _current_range_min = min;
1408 _current_range_max = max;
1410 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1411 CanvasNoteEvent* event = *i;
1412 boost::shared_ptr<NoteType> note (event->note());
1414 if (note->note() < _current_range_min ||
1415 note->note() > _current_range_max) {
1421 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1423 const double y1 = midi_stream_view()->note_to_y(note->note());
1424 const double y2 = y1 + floor(midi_stream_view()->note_height());
1426 cnote->property_y1() = y1;
1427 cnote->property_y2() = y2;
1429 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1431 const double diamond_size = update_hit (chit);
1433 chit->set_height (diamond_size);
1439 MidiRegionView::add_ghost (TimeAxisView& tv)
1443 double unit_position = _region->position () / samples_per_unit;
1444 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1445 MidiGhostRegion* ghost;
1447 if (mtv && mtv->midi_view()) {
1448 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1449 to allow having midi notes on top of note lines and waveforms.
1451 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1453 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1456 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1457 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1458 ghost->add_note(note);
1462 ghost->set_height ();
1463 ghost->set_duration (_region->length() / samples_per_unit);
1464 ghosts.push_back (ghost);
1466 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1472 /** Begin tracking note state for successive calls to add_event
1475 MidiRegionView::begin_write()
1477 assert(!_active_notes);
1478 _active_notes = new CanvasNote*[128];
1479 for (unsigned i=0; i < 128; ++i) {
1480 _active_notes[i] = 0;
1485 /** Destroy note state for add_event
1488 MidiRegionView::end_write()
1490 delete[] _active_notes;
1492 _marked_for_selection.clear();
1493 _marked_for_velocity.clear();
1497 /** Resolve an active MIDI note (while recording).
1500 MidiRegionView::resolve_note(uint8_t note, double end_time)
1502 if (midi_view()->note_mode() != Sustained) {
1506 if (_active_notes && _active_notes[note]) {
1508 /* XXX is end_time really region-centric? I think so, because
1509 this is a new region that we're recording, so source zero is
1510 the same as region zero
1512 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1514 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1515 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1516 _active_notes[note] = 0;
1521 /** Extend active notes to rightmost edge of region (if length is changed)
1524 MidiRegionView::extend_active_notes()
1526 if (!_active_notes) {
1530 for (unsigned i=0; i < 128; ++i) {
1531 if (_active_notes[i]) {
1532 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1539 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1541 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1545 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1547 if (!route_ui || !route_ui->midi_track()) {
1551 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1555 /* NotePlayer deletes itself */
1559 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1561 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1565 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1567 if (!route_ui || !route_ui->midi_track()) {
1571 delete _note_player;
1572 _note_player = new NotePlayer (route_ui->midi_track ());
1573 _note_player->add (note);
1574 _note_player->on ();
1578 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1580 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1584 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1586 if (!route_ui || !route_ui->midi_track()) {
1590 delete _note_player;
1591 _note_player = new NotePlayer (route_ui->midi_track());
1593 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1594 _note_player->add (*n);
1597 _note_player->on ();
1602 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1604 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1605 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1607 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1608 (note->note() <= midi_stream_view()->highest_note());
1613 /** Update a canvas note's size from its model note.
1614 * @param ev Canvas note to update.
1615 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1618 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1620 boost::shared_ptr<NoteType> note = ev->note();
1621 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1622 const double y1 = midi_stream_view()->note_to_y(note->note());
1624 ev->property_x1() = x;
1625 ev->property_y1() = y1;
1627 /* trim note display to not overlap the end of its region */
1629 if (note->length() > 0) {
1630 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1631 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1633 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1636 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1638 if (note->length() == 0) {
1639 if (_active_notes) {
1640 assert(note->note() < 128);
1641 // If this note is already active there's a stuck note,
1642 // finish the old note rectangle
1643 if (_active_notes[note->note()]) {
1644 CanvasNote* const old_rect = _active_notes[note->note()];
1645 boost::shared_ptr<NoteType> old_note = old_rect->note();
1646 old_rect->property_x2() = x;
1647 old_rect->property_outline_what() = (guint32) 0xF;
1649 _active_notes[note->note()] = ev;
1651 /* outline all but right edge */
1652 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1654 /* outline all edges */
1655 ev->property_outline_what() = (guint32) 0xF;
1658 if (update_ghost_regions) {
1659 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1660 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1662 gr->update_note (ev);
1669 MidiRegionView::update_hit (CanvasHit* ev)
1671 boost::shared_ptr<NoteType> note = ev->note();
1673 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1674 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1675 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1676 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1680 return diamond_size;
1683 /** Add a MIDI note to the view (with length).
1685 * If in sustained mode, notes with length 0 will be considered active
1686 * notes, and resolve_note should be called when the corresponding note off
1687 * event arrives, to properly display the note.
1690 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1692 CanvasNoteEvent* event = 0;
1694 assert(note->time() >= 0);
1695 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1697 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1699 if (midi_view()->note_mode() == Sustained) {
1701 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1703 update_note (ev_rect);
1707 MidiGhostRegion* gr;
1709 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1710 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1711 gr->add_note(ev_rect);
1715 } else if (midi_view()->note_mode() == Percussive) {
1717 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1719 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1721 update_hit (ev_diamond);
1730 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1731 note_selected(event, true);
1734 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1735 event->show_velocity();
1738 event->on_channel_selection_change(_last_channel_selection);
1739 _events.push_back(event);
1748 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1749 MidiStreamView* const view = mtv->midi_view();
1751 view->update_note_range (note->note());
1755 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1756 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1758 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1760 /* potentially extend region to hold new note */
1762 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1763 framepos_t region_end = _region->last_frame();
1765 if (end_frame > region_end) {
1766 _region->set_length (end_frame - _region->position());
1769 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1770 MidiStreamView* const view = mtv->midi_view();
1772 view->update_note_range(new_note->note());
1774 _marked_for_selection.clear ();
1777 start_note_diff_command (_("step add"));
1778 note_diff_add_note (new_note, true, false);
1781 // last_step_edit_note = new_note;
1785 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1787 change_note_lengths (false, false, beats, false, true);
1790 /** Add a new patch change flag to the canvas.
1791 * @param patch the patch change to add
1792 * @param the text to display in the flag
1793 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1796 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1798 assert (patch->time() >= 0);
1800 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1801 const double x = trackview.editor().frame_to_pixel (region_frames);
1803 double const height = midi_stream_view()->contents_height();
1805 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1806 new CanvasPatchChange(*this, *_note_group,
1811 _custom_device_mode,
1816 // Show unless patch change is beyond the region bounds
1817 if (region_frames < 0 || region_frames >= _region->length()) {
1818 patch_change->hide();
1820 patch_change->show();
1823 _patch_changes.push_back (patch_change);
1827 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1829 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1830 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1834 if (i != _model->patch_changes().end()) {
1835 key.msb = (*i)->bank_msb ();
1836 key.lsb = (*i)->bank_lsb ();
1837 key.program_number = (*i)->program ();
1839 key.msb = key.lsb = key.program_number = 0;
1842 assert (key.is_sane());
1847 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1849 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1851 if (pc.patch()->program() != new_patch.program_number) {
1852 c->change_program (pc.patch (), new_patch.program_number);
1855 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1856 if (pc.patch()->bank() != new_bank) {
1857 c->change_bank (pc.patch (), new_bank);
1860 _model->apply_command (*trackview.session(), c);
1862 _patch_changes.clear ();
1863 display_patch_changes ();
1867 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1869 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1871 if (old_change->time() != new_change.time()) {
1872 c->change_time (old_change, new_change.time());
1875 if (old_change->channel() != new_change.channel()) {
1876 c->change_channel (old_change, new_change.channel());
1879 if (old_change->program() != new_change.program()) {
1880 c->change_program (old_change, new_change.program());
1883 if (old_change->bank() != new_change.bank()) {
1884 c->change_bank (old_change, new_change.bank());
1887 _model->apply_command (*trackview.session(), c);
1889 _patch_changes.clear ();
1890 display_patch_changes ();
1893 /** Add a patch change to the region.
1894 * @param t Time in frames relative to region position
1895 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1896 * MidiTimeAxisView::get_channel_for_add())
1899 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1901 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1903 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1904 c->add (MidiModel::PatchChangePtr (
1905 new Evoral::PatchChange<Evoral::MusicalTime> (
1906 absolute_frames_to_source_beats (_region->position() + t),
1907 mtv->get_channel_for_add(), patch.program(), patch.bank()
1912 _model->apply_command (*trackview.session(), c);
1914 _patch_changes.clear ();
1915 display_patch_changes ();
1919 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1921 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1922 c->change_time (pc.patch (), t);
1923 _model->apply_command (*trackview.session(), c);
1925 _patch_changes.clear ();
1926 display_patch_changes ();
1930 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1932 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1933 c->remove (pc->patch ());
1934 _model->apply_command (*trackview.session(), c);
1936 _patch_changes.clear ();
1937 display_patch_changes ();
1941 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1943 if (patch.patch()->program() < 127) {
1944 MIDI::Name::PatchPrimaryKey key;
1945 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1946 key.program_number++;
1947 change_patch_change (patch, key);
1952 MidiRegionView::next_patch (CanvasPatchChange& patch)
1954 if (patch.patch()->program() > 0) {
1955 MIDI::Name::PatchPrimaryKey key;
1956 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1957 key.program_number--;
1958 change_patch_change (patch, key);
1963 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1965 if (patch.patch()->program() < 127) {
1966 MIDI::Name::PatchPrimaryKey key;
1967 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1970 change_patch_change (patch, key);
1975 change_patch_change (patch, key);
1982 MidiRegionView::next_bank (CanvasPatchChange& patch)
1984 if (patch.patch()->program() > 0) {
1985 MIDI::Name::PatchPrimaryKey key;
1986 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1987 if (key.lsb < 127) {
1989 change_patch_change (patch, key);
1991 if (key.msb < 127) {
1994 change_patch_change (patch, key);
2001 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
2003 if (_selection.empty()) {
2007 _selection.erase (cne);
2011 MidiRegionView::delete_selection()
2013 if (_selection.empty()) {
2017 start_note_diff_command (_("delete selection"));
2019 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2020 if ((*i)->selected()) {
2021 _note_diff_command->remove((*i)->note());
2031 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2033 start_note_diff_command (_("delete note"));
2034 _note_diff_command->remove (n);
2037 trackview.editor().verbose_cursor()->hide ();
2041 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2043 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2045 Selection::iterator tmp = i;
2048 (*i)->set_selected (false);
2049 (*i)->hide_velocity ();
2050 _selection.erase (i);
2058 /* this does not change the status of this regionview w.r.t the editor
2063 SelectionCleared (this); /* EMIT SIGNAL */
2068 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2070 clear_selection_except (ev);
2072 /* don't bother with checking to see if we should remove this
2073 regionview from the editor selection, since we're about to add
2074 another note, and thus put/keep this regionview in the editor
2078 if (!ev->selected()) {
2079 add_to_selection (ev);
2084 MidiRegionView::select_all_notes ()
2088 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2089 add_to_selection (*i);
2094 MidiRegionView::select_range (framepos_t start, framepos_t end)
2098 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2099 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2100 if (t >= start && t <= end) {
2101 add_to_selection (*i);
2107 MidiRegionView::invert_selection ()
2109 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2110 if ((*i)->selected()) {
2111 remove_from_selection(*i);
2113 add_to_selection (*i);
2119 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2121 uint8_t low_note = 127;
2122 uint8_t high_note = 0;
2123 MidiModel::Notes& notes (_model->notes());
2124 _optimization_iterator = _events.begin();
2130 if (extend && _selection.empty()) {
2136 /* scan existing selection to get note range */
2138 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2139 if ((*i)->note()->note() < low_note) {
2140 low_note = (*i)->note()->note();
2142 if ((*i)->note()->note() > high_note) {
2143 high_note = (*i)->note()->note();
2147 low_note = min (low_note, notenum);
2148 high_note = max (high_note, notenum);
2151 _no_sound_notes = true;
2153 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2155 boost::shared_ptr<NoteType> note (*n);
2156 CanvasNoteEvent* cne;
2157 bool select = false;
2159 if (((1 << note->channel()) & channel_mask) != 0) {
2161 if ((note->note() >= low_note && note->note() <= high_note)) {
2164 } else if (note->note() == notenum) {
2170 if ((cne = find_canvas_note (note)) != 0) {
2171 // extend is false because we've taken care of it,
2172 // since it extends by time range, not pitch.
2173 note_selected (cne, add, false);
2177 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2181 _no_sound_notes = false;
2185 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2187 MidiModel::Notes& notes (_model->notes());
2188 _optimization_iterator = _events.begin();
2190 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2192 boost::shared_ptr<NoteType> note (*n);
2193 CanvasNoteEvent* cne;
2195 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2196 if ((cne = find_canvas_note (note)) != 0) {
2197 if (cne->selected()) {
2198 note_deselected (cne);
2200 note_selected (cne, true, false);
2208 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2211 clear_selection_except (ev);
2212 if (!_selection.empty()) {
2213 PublicEditor& editor (trackview.editor());
2214 editor.get_selection().add (this);
2220 if (!ev->selected()) {
2221 add_to_selection (ev);
2225 /* find end of latest note selected, select all between that and the start of "ev" */
2227 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2228 Evoral::MusicalTime latest = 0;
2230 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2231 if ((*i)->note()->end_time() > latest) {
2232 latest = (*i)->note()->end_time();
2234 if ((*i)->note()->time() < earliest) {
2235 earliest = (*i)->note()->time();
2239 if (ev->note()->end_time() > latest) {
2240 latest = ev->note()->end_time();
2243 if (ev->note()->time() < earliest) {
2244 earliest = ev->note()->time();
2247 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2249 /* find notes entirely within OR spanning the earliest..latest range */
2251 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2252 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2253 add_to_selection (*i);
2261 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2263 remove_from_selection (ev);
2267 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2277 // TODO: Make this faster by storing the last updated selection rect, and only
2278 // adjusting things that are in the area that appears/disappeared.
2279 // We probably need a tree to be able to find events in O(log(n)) time.
2281 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2283 /* check if any corner of the note is inside the rect
2286 1) this is computing "touched by", not "contained by" the rect.
2287 2) this does not require that events be sorted in time.
2290 const double ix1 = (*i)->x1();
2291 const double ix2 = (*i)->x2();
2292 const double iy1 = (*i)->y1();
2293 const double iy2 = (*i)->y2();
2295 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2296 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2297 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2298 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2301 if (!(*i)->selected()) {
2302 add_to_selection (*i);
2304 } else if ((*i)->selected() && !extend) {
2305 // Not inside rectangle
2306 remove_from_selection (*i);
2312 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2318 // TODO: Make this faster by storing the last updated selection rect, and only
2319 // adjusting things that are in the area that appears/disappeared.
2320 // We probably need a tree to be able to find events in O(log(n)) time.
2322 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2324 /* check if any corner of the note is inside the rect
2327 1) this is computing "touched by", not "contained by" the rect.
2328 2) this does not require that events be sorted in time.
2331 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2332 // within y- (note-) range
2333 if (!(*i)->selected()) {
2334 add_to_selection (*i);
2336 } else if ((*i)->selected() && !extend) {
2337 // Not inside rectangle
2338 remove_from_selection (*i);
2344 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2346 Selection::iterator i = _selection.find (ev);
2348 if (i != _selection.end()) {
2349 _selection.erase (i);
2352 ev->set_selected (false);
2353 ev->hide_velocity ();
2355 if (_selection.empty()) {
2356 PublicEditor& editor (trackview.editor());
2357 editor.get_selection().remove (this);
2362 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2364 bool add_mrv_selection = false;
2366 if (_selection.empty()) {
2367 add_mrv_selection = true;
2370 if (_selection.insert (ev).second) {
2371 ev->set_selected (true);
2372 start_playing_midi_note ((ev)->note());
2375 if (add_mrv_selection) {
2376 PublicEditor& editor (trackview.editor());
2377 editor.get_selection().add (this);
2382 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2384 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2385 PossibleChord to_play;
2386 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2388 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2389 if ((*i)->note()->time() < earliest) {
2390 earliest = (*i)->note()->time();
2394 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2395 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2396 to_play.push_back ((*i)->note());
2398 (*i)->move_event(dx, dy);
2401 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2403 if (to_play.size() > 1) {
2405 PossibleChord shifted;
2407 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2408 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2409 moved_note->set_note (moved_note->note() + cumulative_dy);
2410 shifted.push_back (moved_note);
2413 start_playing_midi_chord (shifted);
2415 } else if (!to_play.empty()) {
2417 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2418 moved_note->set_note (moved_note->note() + cumulative_dy);
2419 start_playing_midi_note (moved_note);
2425 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2427 assert (!_selection.empty());
2429 uint8_t lowest_note_in_selection = 127;
2430 uint8_t highest_note_in_selection = 0;
2431 uint8_t highest_note_difference = 0;
2433 // find highest and lowest notes first
2435 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2436 uint8_t pitch = (*i)->note()->note();
2437 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2438 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2442 cerr << "dnote: " << (int) dnote << endl;
2443 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2444 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2445 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2446 << int(highest_note_in_selection) << endl;
2447 cerr << "selection size: " << _selection.size() << endl;
2448 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2451 // Make sure the note pitch does not exceed the MIDI standard range
2452 if (highest_note_in_selection + dnote > 127) {
2453 highest_note_difference = highest_note_in_selection - 127;
2456 start_note_diff_command (_("move notes"));
2458 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2460 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2461 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2467 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2469 uint8_t original_pitch = (*i)->note()->note();
2470 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2472 // keep notes in standard midi range
2473 clamp_to_0_127(new_pitch);
2475 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2476 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2478 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2483 // care about notes being moved beyond the upper/lower bounds on the canvas
2484 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2485 highest_note_in_selection > midi_stream_view()->highest_note()) {
2486 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2490 /** @param x Pixel relative to the region position.
2491 * @return Snapped frame relative to the region position.
2494 MidiRegionView::snap_pixel_to_frame(double x)
2496 PublicEditor& editor (trackview.editor());
2497 return snap_frame_to_frame (editor.pixel_to_frame (x));
2500 /** @param x Pixel relative to the region position.
2501 * @return Snapped pixel relative to the region position.
2504 MidiRegionView::snap_to_pixel(double x)
2506 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2510 MidiRegionView::get_position_pixels()
2512 framepos_t region_frame = get_position();
2513 return trackview.editor().frame_to_pixel(region_frame);
2517 MidiRegionView::get_end_position_pixels()
2519 framepos_t frame = get_position() + get_duration ();
2520 return trackview.editor().frame_to_pixel(frame);
2524 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2526 /* the time converter will return the frame corresponding to `beats'
2527 relative to the start of the source. The start of the source
2528 is an implied position given by region->position - region->start
2530 const framepos_t source_start = _region->position() - _region->start();
2531 return source_start + _source_relative_time_converter.to (beats);
2535 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2537 /* the `frames' argument needs to be converted into a frame count
2538 relative to the start of the source before being passed in to the
2541 const framepos_t source_start = _region->position() - _region->start();
2542 return _source_relative_time_converter.from (frames - source_start);
2546 MidiRegionView::region_beats_to_region_frames(double beats) const
2548 return _region_relative_time_converter.to(beats);
2552 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2554 return _region_relative_time_converter.from(frames);
2558 MidiRegionView::begin_resizing (bool /*at_front*/)
2560 _resize_data.clear();
2562 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2563 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2565 // only insert CanvasNotes into the map
2567 NoteResizeData *resize_data = new NoteResizeData();
2568 resize_data->canvas_note = note;
2570 // create a new SimpleRect from the note which will be the resize preview
2571 SimpleRect *resize_rect = new SimpleRect(
2572 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2574 // calculate the colors: get the color settings
2575 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2576 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2579 // make the resize preview notes more transparent and bright
2580 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2582 // calculate color based on note velocity
2583 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2584 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2588 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2589 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2591 resize_data->resize_rect = resize_rect;
2592 _resize_data.push_back(resize_data);
2597 /** Update resizing notes while user drags.
2598 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2599 * @param at_front which end of the note (true == note on, false == note off)
2600 * @param delta_x change in mouse position since the start of the drag
2601 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2602 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2603 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2604 * as the \a primary note.
2607 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2609 bool cursor_set = false;
2611 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2612 SimpleRect* resize_rect = (*i)->resize_rect;
2613 CanvasNote* canvas_note = (*i)->canvas_note;
2618 current_x = canvas_note->x1() + delta_x;
2620 current_x = primary->x1() + delta_x;
2624 current_x = canvas_note->x2() + delta_x;
2626 current_x = primary->x2() + delta_x;
2631 resize_rect->property_x1() = snap_to_pixel(current_x);
2632 resize_rect->property_x2() = canvas_note->x2();
2634 resize_rect->property_x2() = snap_to_pixel(current_x);
2635 resize_rect->property_x1() = canvas_note->x1();
2641 beats = snap_pixel_to_frame (current_x);
2642 beats = region_frames_to_region_beats (beats);
2647 if (beats < canvas_note->note()->end_time()) {
2648 len = canvas_note->note()->time() - beats;
2649 len += canvas_note->note()->length();
2654 if (beats >= canvas_note->note()->time()) {
2655 len = beats - canvas_note->note()->time();
2662 snprintf (buf, sizeof (buf), "%.3g beats", len);
2663 show_verbose_cursor (buf, 0, 0);
2672 /** Finish resizing notes when the user releases the mouse button.
2673 * Parameters the same as for \a update_resizing().
2676 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2678 start_note_diff_command (_("resize notes"));
2680 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2681 CanvasNote* canvas_note = (*i)->canvas_note;
2682 SimpleRect* resize_rect = (*i)->resize_rect;
2684 /* Get the new x position for this resize, which is in pixels relative
2685 * to the region position.
2692 current_x = canvas_note->x1() + delta_x;
2694 current_x = primary->x1() + delta_x;
2698 current_x = canvas_note->x2() + delta_x;
2700 current_x = primary->x2() + delta_x;
2704 /* Convert that to a frame within the source */
2705 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2707 /* and then to beats */
2708 current_x = region_frames_to_region_beats (current_x);
2710 if (at_front && current_x < canvas_note->note()->end_time()) {
2711 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2713 double len = canvas_note->note()->time() - current_x;
2714 len += canvas_note->note()->length();
2717 /* XXX convert to beats */
2718 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2723 double len = current_x - canvas_note->note()->time();
2726 /* XXX convert to beats */
2727 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2735 _resize_data.clear();
2740 MidiRegionView::abort_resizing ()
2742 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2743 delete (*i)->resize_rect;
2747 _resize_data.clear ();
2751 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2753 uint8_t new_velocity;
2756 new_velocity = event->note()->velocity() + velocity;
2757 clamp_to_0_127(new_velocity);
2759 new_velocity = velocity;
2762 event->set_selected (event->selected()); // change color
2764 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2768 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2773 new_note = event->note()->note() + note;
2778 clamp_to_0_127 (new_note);
2779 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2783 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2785 bool change_start = false;
2786 bool change_length = false;
2787 Evoral::MusicalTime new_start = 0;
2788 Evoral::MusicalTime new_length = 0;
2790 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2792 front_delta: if positive - move the start of the note later in time (shortening it)
2793 if negative - move the start of the note earlier in time (lengthening it)
2795 end_delta: if positive - move the end of the note later in time (lengthening it)
2796 if negative - move the end of the note earlier in time (shortening it)
2800 if (front_delta < 0) {
2802 if (event->note()->time() < -front_delta) {
2805 new_start = event->note()->time() + front_delta; // moves earlier
2808 /* start moved toward zero, so move the end point out to where it used to be.
2809 Note that front_delta is negative, so this increases the length.
2812 new_length = event->note()->length() - front_delta;
2813 change_start = true;
2814 change_length = true;
2818 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2820 if (new_pos < event->note()->end_time()) {
2821 new_start = event->note()->time() + front_delta;
2822 /* start moved toward the end, so move the end point back to where it used to be */
2823 new_length = event->note()->length() - front_delta;
2824 change_start = true;
2825 change_length = true;
2832 bool can_change = true;
2833 if (end_delta < 0) {
2834 if (event->note()->length() < -end_delta) {
2840 new_length = event->note()->length() + end_delta;
2841 change_length = true;
2846 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2849 if (change_length) {
2850 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2855 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2857 uint8_t new_channel;
2861 if (event->note()->channel() < -chn) {
2864 new_channel = event->note()->channel() + chn;
2867 new_channel = event->note()->channel() + chn;
2870 new_channel = (uint8_t) chn;
2873 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2877 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2879 Evoral::MusicalTime new_time;
2883 if (event->note()->time() < -delta) {
2886 new_time = event->note()->time() + delta;
2889 new_time = event->note()->time() + delta;
2895 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2899 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2901 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2905 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2910 if (_selection.empty()) {
2925 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2926 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2932 start_note_diff_command (_("change velocities"));
2934 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2935 Selection::iterator next = i;
2939 if (i == _selection.begin()) {
2940 change_note_velocity (*i, delta, true);
2941 value = (*i)->note()->velocity() + delta;
2943 change_note_velocity (*i, value, false);
2947 change_note_velocity (*i, delta, true);
2955 if (!_selection.empty()) {
2957 snprintf (buf, sizeof (buf), "Vel %d",
2958 (int) (*_selection.begin())->note()->velocity());
2959 show_verbose_cursor (buf, 10, 10);
2965 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2967 if (_selection.empty()) {
2984 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2986 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2990 if ((int8_t) (*i)->note()->note() + delta > 127) {
2997 start_note_diff_command (_("transpose"));
2999 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3000 Selection::iterator next = i;
3002 change_note_note (*i, delta, true);
3010 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
3016 /* grab the current grid distance */
3018 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
3020 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
3021 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3031 start_note_diff_command (_("change note lengths"));
3033 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3034 Selection::iterator next = i;
3037 /* note the negation of the delta for start */
3039 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3048 MidiRegionView::nudge_notes (bool forward)
3050 if (_selection.empty()) {
3054 /* pick a note as the point along the timeline to get the nudge distance.
3055 its not necessarily the earliest note, so we may want to pull the notes out
3056 into a vector and sort before using the first one.
3059 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3061 framecnt_t distance;
3063 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3065 /* grid is off - use nudge distance */
3067 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3073 framepos_t next_pos = ref_point;
3076 if (max_framepos - 1 < next_pos) {
3080 if (next_pos == 0) {
3086 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3087 distance = ref_point - next_pos;
3090 if (distance == 0) {
3094 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3100 start_note_diff_command (_("nudge"));
3102 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3103 Selection::iterator next = i;
3105 change_note_time (*i, delta, true);
3113 MidiRegionView::change_channel(uint8_t channel)
3115 start_note_diff_command(_("change channel"));
3116 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3117 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3125 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3127 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3129 pre_enter_cursor = editor->get_canvas_cursor ();
3131 if (_mouse_state == SelectTouchDragging) {
3132 note_selected (ev, true);
3135 show_verbose_cursor (ev->note ());
3139 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3141 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3143 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3144 (*i)->hide_velocity ();
3147 editor->verbose_cursor()->hide ();
3149 if (pre_enter_cursor) {
3150 editor->set_canvas_cursor (pre_enter_cursor);
3151 pre_enter_cursor = 0;
3156 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3159 /* XXX should get patch name if we can */
3160 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3161 show_verbose_cursor (s.str(), 10, 20);
3165 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3167 trackview.editor().verbose_cursor()->hide ();
3171 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3173 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3174 Editing::MouseMode mm = editor->current_mouse_mode();
3175 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3177 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3178 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3179 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3180 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3182 if (pre_enter_cursor && can_set_cursor) {
3183 editor->set_canvas_cursor (pre_enter_cursor);
3189 MidiRegionView::set_frame_color()
3193 TimeAxisViewItem::set_frame_color ();
3200 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3201 } else if (high_enough_for_name) {
3202 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3207 if (!rect_visible) {
3208 f = UINT_RGBA_CHANGE_A (f, 0);
3211 frame->property_fill_color_rgba() = f;
3215 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3217 if (mode == ForceChannel) {
3218 mask = 0xFFFF; // Show all notes as active (below)
3221 // Update notes for selection
3222 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3223 (*i)->on_channel_selection_change(mask);
3226 _last_channel_selection = mask;
3228 _patch_changes.clear ();
3229 display_patch_changes ();
3233 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3235 _model_name = model;
3236 _custom_device_mode = custom_device_mode;
3241 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3243 if (_selection.empty()) {
3247 PublicEditor& editor (trackview.editor());
3251 /* XXX what to do ? */
3255 editor.get_cut_buffer().add (selection_as_cut_buffer());
3263 start_note_diff_command();
3265 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3272 note_diff_remove_note (*i);
3282 MidiRegionView::selection_as_cut_buffer () const
3286 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3287 NoteType* n = (*i)->note().get();
3288 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3291 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3297 /** This method handles undo */
3299 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3305 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3307 trackview.session()->begin_reversible_command (_("paste"));
3309 start_note_diff_command (_("paste"));
3311 Evoral::MusicalTime beat_delta;
3312 Evoral::MusicalTime paste_pos_beats;
3313 Evoral::MusicalTime duration;
3314 Evoral::MusicalTime end_point = 0;
3316 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3317 paste_pos_beats = absolute_frames_to_source_beats (pos);
3318 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3319 paste_pos_beats = 0;
3321 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",
3322 (*mcb.notes().begin())->time(),
3323 (*mcb.notes().rbegin())->end_time(),
3324 duration, pos, _region->position(),
3325 paste_pos_beats, beat_delta));
3329 for (int n = 0; n < (int) times; ++n) {
3331 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3333 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3334 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3336 /* make all newly added notes selected */
3338 note_diff_add_note (copied_note, true);
3339 end_point = copied_note->end_time();
3342 paste_pos_beats += duration;
3345 /* if we pasted past the current end of the region, extend the region */
3347 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3348 framepos_t region_end = _region->position() + _region->length() - 1;
3350 if (end_frame > region_end) {
3352 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3354 _region->clear_changes ();
3355 _region->set_length (end_frame - _region->position());
3356 trackview.session()->add_command (new StatefulDiffCommand (_region));
3361 trackview.session()->commit_reversible_command ();
3364 struct EventNoteTimeEarlyFirstComparator {
3365 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3366 return a->note()->time() < b->note()->time();
3371 MidiRegionView::time_sort_events ()
3373 if (!_sort_needed) {
3377 EventNoteTimeEarlyFirstComparator cmp;
3380 _sort_needed = false;
3384 MidiRegionView::goto_next_note (bool add_to_selection)
3386 bool use_next = false;
3388 if (_events.back()->selected()) {
3392 time_sort_events ();
3394 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3395 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3397 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3398 if ((*i)->selected()) {
3401 } else if (use_next) {
3402 if (channel_mask & (1 << (*i)->note()->channel())) {
3403 if (!add_to_selection) {
3406 note_selected (*i, true, false);
3413 /* use the first one */
3415 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3416 unique_select (_events.front());
3421 MidiRegionView::goto_previous_note (bool add_to_selection)
3423 bool use_next = false;
3425 if (_events.front()->selected()) {
3429 time_sort_events ();
3431 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3432 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3434 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3435 if ((*i)->selected()) {
3438 } else if (use_next) {
3439 if (channel_mask & (1 << (*i)->note()->channel())) {
3440 if (!add_to_selection) {
3443 note_selected (*i, true, false);
3450 /* use the last one */
3452 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3453 unique_select (*(_events.rbegin()));
3458 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3460 bool had_selected = false;
3462 time_sort_events ();
3464 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3465 if ((*i)->selected()) {
3466 selected.insert ((*i)->note());
3467 had_selected = true;
3471 if (allow_all_if_none_selected && !had_selected) {
3472 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3473 selected.insert ((*i)->note());
3479 MidiRegionView::update_ghost_note (double x, double y)
3481 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3486 _note_group->w2i (x, y);
3488 PublicEditor& editor = trackview.editor ();
3490 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3491 framecnt_t grid_frames;
3492 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3494 /* use region_frames... because we are converting a delta within the region
3498 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3504 /* note that this sets the time of the ghost note in beats relative to
3505 the start of the source; that is how all note times are stored.
3507 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3508 _ghost_note->note()->set_length (length);
3509 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3510 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3512 /* the ghost note does not appear in ghost regions, so pass false in here */
3513 update_note (_ghost_note, false);
3515 show_verbose_cursor (_ghost_note->note ());
3519 MidiRegionView::create_ghost_note (double x, double y)
3521 remove_ghost_note ();
3523 boost::shared_ptr<NoteType> g (new NoteType);
3524 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3525 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3526 update_ghost_note (x, y);
3527 _ghost_note->show ();
3532 show_verbose_cursor (_ghost_note->note ());
3536 MidiRegionView::snap_changed ()
3542 create_ghost_note (_last_ghost_x, _last_ghost_y);
3546 MidiRegionView::drop_down_keys ()
3548 _mouse_state = None;
3552 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3554 double note = midi_stream_view()->y_to_note(y);
3556 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3558 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3560 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3561 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3562 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3563 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3568 bool add_mrv_selection = false;
3570 if (_selection.empty()) {
3571 add_mrv_selection = true;
3574 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3575 if (_selection.insert (*i).second) {
3576 (*i)->set_selected (true);
3580 if (add_mrv_selection) {
3581 PublicEditor& editor (trackview.editor());
3582 editor.get_selection().add (this);
3587 MidiRegionView::color_handler ()
3589 RegionView::color_handler ();
3591 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3592 (*i)->set_selected ((*i)->selected()); // will change color
3595 /* XXX probably more to do here */
3599 MidiRegionView::enable_display (bool yn)
3601 RegionView::enable_display (yn);
3608 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3610 if (_step_edit_cursor == 0) {
3611 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3613 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3614 _step_edit_cursor->property_y1() = 0;
3615 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3616 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3617 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3620 move_step_edit_cursor (pos);
3621 _step_edit_cursor->show ();
3625 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3627 _step_edit_cursor_position = pos;
3629 if (_step_edit_cursor) {
3630 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3631 _step_edit_cursor->property_x1() = pixel;
3632 set_step_edit_cursor_width (_step_edit_cursor_width);
3637 MidiRegionView::hide_step_edit_cursor ()
3639 if (_step_edit_cursor) {
3640 _step_edit_cursor->hide ();
3645 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3647 _step_edit_cursor_width = beats;
3649 if (_step_edit_cursor) {
3650 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3654 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3655 * @param w Source that the data will end up in.
3658 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3660 if (!_active_notes) {
3661 /* we aren't actively being recorded to */
3665 boost::shared_ptr<MidiSource> src = w.lock ();
3666 if (!src || src != midi_region()->midi_source()) {
3667 /* recorded data was not destined for our source */
3671 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3673 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3675 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3677 framepos_t back = max_framepos;
3679 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3680 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3681 assert (ev.buffer ());
3683 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3684 frames from the start of the source, and so time_beats is in terms of the
3688 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3690 if (ev.type() == MIDI_CMD_NOTE_ON) {
3692 boost::shared_ptr<NoteType> note (
3693 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3696 add_note (note, true);
3698 /* fix up our note range */
3699 if (ev.note() < _current_range_min) {
3700 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3701 } else if (ev.note() > _current_range_max) {
3702 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3705 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3706 resolve_note (ev.note (), time_beats);
3712 midi_stream_view()->check_record_layers (region(), back);
3716 MidiRegionView::trim_front_starting ()
3718 /* Reparent the note group to the region view's parent, so that it doesn't change
3719 when the region view is trimmed.
3721 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3722 _temporary_note_group->move (group->property_x(), group->property_y());
3723 _note_group->reparent (*_temporary_note_group);
3727 MidiRegionView::trim_front_ending ()
3729 _note_group->reparent (*group);
3730 delete _temporary_note_group;
3731 _temporary_note_group = 0;
3733 if (_region->start() < 0) {
3734 /* Trim drag made start time -ve; fix this */
3735 midi_region()->fix_negative_start ();
3740 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3742 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), _model_name, _custom_device_mode, Gtk::Stock::APPLY);
3743 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3747 change_patch_change (pc->patch(), d.patch ());
3752 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3755 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3756 Evoral::midi_note_name (n->note()).c_str(),
3758 (int) n->channel() + 1,
3759 (int) n->velocity());
3761 show_verbose_cursor (buf, 10, 20);
3765 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3769 trackview.editor().get_pointer_position (wx, wy);
3774 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3776 double x1, y1, x2, y2;
3777 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3779 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3780 wy -= (y2 - y1) + 2 * yoffset;
3783 trackview.editor().verbose_cursor()->set (text, wx, wy);
3784 trackview.editor().verbose_cursor()->show ();
3787 /** @param p A session framepos.
3788 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3789 * @return p snapped to the grid subdivision underneath it.
3792 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3794 PublicEditor& editor = trackview.editor ();
3797 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3803 grid_frames = region_beats_to_region_frames (grid_beats);
3805 /* Hack so that we always snap to the note that we are over, instead of snapping
3806 to the next one if we're more than halfway through the one we're over.
3808 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3809 p -= grid_frames / 2;
3812 return snap_frame_to_frame (p);
3815 /** Called when the selection has been cleared in any MidiRegionView.
3816 * @param rv MidiRegionView that the selection was cleared in.
3819 MidiRegionView::selection_cleared (MidiRegionView* rv)
3825 /* Clear our selection in sympathy; but don't signal the fact */
3826 clear_selection (false);
3830 MidiRegionView::note_button_release ()
3832 delete _note_player;