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.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "pbd/memento_command.h"
31 #include "pbd/stateful_diff_command.h"
33 #include "ardour/midi_model.h"
34 #include "ardour/midi_patch_manager.h"
35 #include "ardour/midi_region.h"
36 #include "ardour/midi_source.h"
37 #include "ardour/midi_track.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 "canvas/debug.h"
48 #include "automation_region_view.h"
49 #include "automation_time_axis.h"
52 #include "editor_drag.h"
53 #include "ghostregion.h"
54 #include "gui_thread.h"
56 #include "midi_channel_dialog.h"
57 #include "midi_cut_buffer.h"
58 #include "midi_list_editor.h"
59 #include "midi_region_view.h"
60 #include "midi_streamview.h"
61 #include "midi_time_axis.h"
62 #include "midi_util.h"
63 #include "midi_velocity_dialog.h"
64 #include "mouse_cursors.h"
65 #include "note_player.h"
66 #include "public_editor.h"
67 #include "route_time_axis.h"
68 #include "rgb_macros.h"
69 #include "selection.h"
70 #include "streamview.h"
71 #include "patch_change_dialog.h"
72 #include "verbose_cursor.h"
73 #include "ardour_ui.h"
76 #include "patch_change.h"
81 using namespace ARDOUR;
83 using namespace Editing;
84 using Gtkmm2ext::Keyboard;
86 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
88 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
90 MidiRegionView::MidiRegionView (ArdourCanvas::Container *parent, RouteTimeAxisView &tv,
91 boost::shared_ptr<MidiRegion> r, double spu, uint32_t basic_color)
92 : RegionView (parent, tv, r, spu, basic_color)
93 , _current_range_min(0)
94 , _current_range_max(0)
96 , _note_group (new ArdourCanvas::Container (group))
97 , _note_diff_command (0)
99 , _step_edit_cursor (0)
100 , _step_edit_cursor_width (1.0)
101 , _step_edit_cursor_position (0.0)
102 , _channel_selection_scoped_note (0)
103 , _temporary_note_group (0)
106 , _sort_needed (true)
107 , _optimization_iterator (_events.end())
109 , _no_sound_notes (false)
112 , pre_enter_cursor (0)
113 , pre_press_cursor (0)
116 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
117 _note_group->raise_to_top();
118 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
120 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
121 connect_to_diskstream ();
123 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
126 MidiRegionView::MidiRegionView (ArdourCanvas::Container *parent, RouteTimeAxisView &tv,
127 boost::shared_ptr<MidiRegion> r, double spu, uint32_t basic_color,
128 TimeAxisViewItem::Visibility visibility)
129 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
130 , _current_range_min(0)
131 , _current_range_max(0)
133 , _note_group (new ArdourCanvas::Container (parent))
134 , _note_diff_command (0)
136 , _step_edit_cursor (0)
137 , _step_edit_cursor_width (1.0)
138 , _step_edit_cursor_position (0.0)
139 , _channel_selection_scoped_note (0)
140 , _temporary_note_group (0)
143 , _sort_needed (true)
144 , _optimization_iterator (_events.end())
146 , _no_sound_notes (false)
149 , pre_enter_cursor (0)
150 , pre_press_cursor (0)
153 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
154 _note_group->raise_to_top();
156 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
158 connect_to_diskstream ();
160 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
164 MidiRegionView::parameter_changed (std::string const & p)
166 if (p == "display-first-midi-bank-as-zero") {
167 if (_enable_display) {
173 MidiRegionView::MidiRegionView (const MidiRegionView& other)
174 : sigc::trackable(other)
176 , _current_range_min(0)
177 , _current_range_max(0)
179 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
180 , _note_diff_command (0)
182 , _step_edit_cursor (0)
183 , _step_edit_cursor_width (1.0)
184 , _step_edit_cursor_position (0.0)
185 , _channel_selection_scoped_note (0)
186 , _temporary_note_group (0)
189 , _sort_needed (true)
190 , _optimization_iterator (_events.end())
192 , _no_sound_notes (false)
195 , pre_enter_cursor (0)
196 , pre_press_cursor (0)
202 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
203 : RegionView (other, boost::shared_ptr<Region> (region))
204 , _current_range_min(0)
205 , _current_range_max(0)
207 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
208 , _note_diff_command (0)
210 , _step_edit_cursor (0)
211 , _step_edit_cursor_width (1.0)
212 , _step_edit_cursor_position (0.0)
213 , _channel_selection_scoped_note (0)
214 , _temporary_note_group (0)
217 , _sort_needed (true)
218 , _optimization_iterator (_events.end())
220 , _no_sound_notes (false)
223 , pre_enter_cursor (0)
224 , pre_press_cursor (0)
231 MidiRegionView::init (bool wfd)
233 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
235 NoteBase::NoteBaseDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
236 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
240 midi_region()->midi_source(0)->load_model();
243 _model = midi_region()->midi_source(0)->model();
244 _enable_display = false;
246 RegionView::init (false);
248 set_height (trackview.current_height());
251 region_sync_changed ();
252 region_resized (ARDOUR::bounds_change);
257 _enable_display = true;
260 display_model (_model);
264 reset_width_dependent_items (_pixel_width);
266 group->raise_to_top();
268 midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
269 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
272 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
273 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
275 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
276 boost::bind (&MidiRegionView::snap_changed, this),
279 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
280 connect_to_diskstream ();
282 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
286 MidiRegionView::instrument_info () const
288 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
289 return route_ui->route()->instrument_info();
292 const boost::shared_ptr<ARDOUR::MidiRegion>
293 MidiRegionView::midi_region() const
295 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
299 MidiRegionView::connect_to_diskstream ()
301 midi_view()->midi_track()->DataRecorded.connect(
302 *this, invalidator(*this),
303 boost::bind (&MidiRegionView::data_recorded, this, _1),
308 MidiRegionView::canvas_group_event(GdkEvent* ev)
313 case GDK_ENTER_NOTIFY:
314 case GDK_LEAVE_NOTIFY:
315 _last_event_x = ev->crossing.x;
316 _last_event_y = ev->crossing.y;
318 case GDK_MOTION_NOTIFY:
319 _last_event_x = ev->motion.x;
320 _last_event_y = ev->motion.y;
326 if (ev->type == GDK_2BUTTON_PRESS) {
327 // cannot use double-click to exit internal mode if single-click is being used
328 MouseMode m = trackview.editor().current_mouse_mode();
330 if ((m != MouseObject || !Keyboard::modifier_state_contains (ev->button.state, Keyboard::insert_note_modifier())) && (m != MouseDraw)) {
331 return trackview.editor().toggle_internal_editing_from_double_click (ev);
335 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
336 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
337 (trackview.editor().current_mouse_mode() == MouseZoom)) {
338 // handle non-internal-edit/non-draw modes elsewhere
339 return RegionView::canvas_group_event (ev);
344 return scroll (&ev->scroll);
347 return key_press (&ev->key);
349 case GDK_KEY_RELEASE:
350 return key_release (&ev->key);
352 case GDK_BUTTON_PRESS:
353 return button_press (&ev->button);
355 case GDK_BUTTON_RELEASE:
356 r = button_release (&ev->button);
361 case GDK_ENTER_NOTIFY:
362 return enter_notify (&ev->crossing);
364 case GDK_LEAVE_NOTIFY:
365 return leave_notify (&ev->crossing);
367 case GDK_MOTION_NOTIFY:
368 return motion (&ev->motion);
374 return trackview.editor().canvas_region_view_event (ev, group, this);
378 MidiRegionView::enter_notify (GdkEventCrossing* ev)
380 trackview.editor().MouseModeChanged.connect (
381 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
384 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
385 create_ghost_note (ev->x, ev->y);
388 if (!trackview.editor().internal_editing()) {
389 Keyboard::magic_widget_drop_focus();
391 Keyboard::magic_widget_grab_focus();
395 // if current operation is non-operational in a midi region, change the cursor to so indicate
396 if (trackview.editor().current_mouse_mode() == MouseGain) {
397 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
398 pre_enter_cursor = editor->get_canvas_cursor();
399 editor->set_canvas_cursor(editor->cursors()->timebar);
406 MidiRegionView::leave_notify (GdkEventCrossing*)
408 _mouse_mode_connection.disconnect ();
410 trackview.editor().verbose_cursor()->hide ();
411 remove_ghost_note ();
413 if (pre_enter_cursor) {
414 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
415 editor->set_canvas_cursor(pre_enter_cursor);
422 MidiRegionView::mouse_mode_changed ()
424 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
425 create_ghost_note (_last_event_x, _last_event_y);
427 remove_ghost_note ();
428 trackview.editor().verbose_cursor()->hide ();
431 if (!trackview.editor().internal_editing()) {
432 Keyboard::magic_widget_drop_focus();
434 Keyboard::magic_widget_grab_focus();
440 MidiRegionView::button_press (GdkEventButton* ev)
442 if (ev->button != 1) {
446 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
447 MouseMode m = editor->current_mouse_mode();
449 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
450 pre_press_cursor = editor->get_canvas_cursor ();
451 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
454 if (_mouse_state != SelectTouchDragging) {
456 _pressed_button = ev->button;
457 _mouse_state = Pressed;
462 _pressed_button = ev->button;
468 MidiRegionView::button_release (GdkEventButton* ev)
470 double event_x, event_y;
472 if (ev->button != 1) {
479 group->canvas_to_item (event_x, event_y);
482 PublicEditor& editor = trackview.editor ();
484 if (pre_press_cursor) {
485 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
486 pre_press_cursor = 0;
489 switch (_mouse_state) {
490 case Pressed: // Clicked
492 switch (editor.current_mouse_mode()) {
494 /* no motion occured - simple click */
503 if (Keyboard::is_insert_note_event(ev)) {
505 double event_x, event_y;
509 group->canvas_to_item (event_x, event_y);
512 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_sample (event_x));
518 /* Shorten the length by 1 tick so that we can add a new note at the next
519 grid snap without it overlapping this one.
521 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
523 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
531 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_sample (event_x));
537 /* Shorten the length by 1 tick so that we can add a new note at the next
538 grid snap without it overlapping this one.
540 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
542 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
553 case SelectRectDragging:
555 editor.drags()->end_grab ((GdkEvent *) ev);
557 create_ghost_note (ev->x, ev->y);
569 MidiRegionView::motion (GdkEventMotion* ev)
571 PublicEditor& editor = trackview.editor ();
573 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
574 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
575 _mouse_state != AddDragging) {
577 create_ghost_note (ev->x, ev->y);
579 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
580 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
582 update_ghost_note (ev->x, ev->y);
584 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
586 remove_ghost_note ();
587 editor.verbose_cursor()->hide ();
589 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
591 update_ghost_note (ev->x, ev->y);
594 /* any motion immediately hides velocity text that may have been visible */
596 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
597 (*i)->hide_velocity ();
600 switch (_mouse_state) {
603 if (_pressed_button == 1) {
605 MouseMode m = editor.current_mouse_mode();
607 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
608 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
609 _mouse_state = AddDragging;
610 remove_ghost_note ();
611 editor.verbose_cursor()->hide ();
613 } else if (m == MouseObject) {
614 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
616 _mouse_state = SelectRectDragging;
618 } else if (m == MouseRange) {
619 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
620 _mouse_state = SelectVerticalDragging;
627 case SelectRectDragging:
628 case SelectVerticalDragging:
630 editor.drags()->motion_handler ((GdkEvent *) ev, false);
633 case SelectTouchDragging:
641 /* we may be dragging some non-note object (eg. patch-change, sysex)
644 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
649 MidiRegionView::scroll (GdkEventScroll* ev)
651 if (_selection.empty()) {
655 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
656 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
657 it still works for zoom.
662 trackview.editor().verbose_cursor()->hide ();
664 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
665 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
667 if (ev->direction == GDK_SCROLL_UP) {
668 change_velocities (true, fine, false, together);
669 } else if (ev->direction == GDK_SCROLL_DOWN) {
670 change_velocities (false, fine, false, together);
672 /* left, right: we don't use them */
680 MidiRegionView::key_press (GdkEventKey* ev)
682 /* since GTK bindings are generally activated on press, and since
683 detectable auto-repeat is the name of the game and only sends
684 repeated presses, carry out key actions at key press, not release.
687 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
689 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
690 _mouse_state = SelectTouchDragging;
693 } else if (ev->keyval == GDK_Escape && unmodified) {
697 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
699 bool start = (ev->keyval == GDK_comma);
700 bool end = (ev->keyval == GDK_period);
701 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
702 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
704 change_note_lengths (fine, shorter, 0.0, start, end);
708 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
710 if (_selection.empty()) {
717 } else if (ev->keyval == GDK_Tab) {
719 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
720 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
722 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
726 } else if (ev->keyval == GDK_ISO_Left_Tab) {
728 /* Shift-TAB generates ISO Left Tab, for some reason */
730 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
731 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
733 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
739 } else if (ev->keyval == GDK_Up) {
741 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
742 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
743 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
745 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
746 change_velocities (true, fine, allow_smush, together);
748 transpose (true, fine, allow_smush);
752 } else if (ev->keyval == GDK_Down) {
754 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
755 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
756 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
758 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
759 change_velocities (false, fine, allow_smush, together);
761 transpose (false, fine, allow_smush);
765 } else if (ev->keyval == GDK_Left && unmodified) {
770 } else if (ev->keyval == GDK_Right && unmodified) {
775 } else if (ev->keyval == GDK_c && unmodified) {
779 } else if (ev->keyval == GDK_v && unmodified) {
788 MidiRegionView::key_release (GdkEventKey* ev)
790 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
798 MidiRegionView::channel_edit ()
800 if (_selection.empty()) {
804 /* pick a note somewhat at random (since Selection is a set<>) to
805 * provide the "current" channel for the dialog.
808 uint8_t current_channel = (*_selection.begin())->note()->channel ();
809 MidiChannelDialog channel_dialog (current_channel);
810 int ret = channel_dialog.run ();
813 case Gtk::RESPONSE_OK:
819 uint8_t new_channel = channel_dialog.active_channel ();
821 start_note_diff_command (_("channel edit"));
823 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
824 Selection::iterator next = i;
826 change_note_channel (*i, new_channel);
834 MidiRegionView::velocity_edit ()
836 if (_selection.empty()) {
840 /* pick a note somewhat at random (since Selection is a set<>) to
841 * provide the "current" velocity for the dialog.
844 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
845 MidiVelocityDialog velocity_dialog (current_velocity);
846 int ret = velocity_dialog.run ();
849 case Gtk::RESPONSE_OK:
855 uint8_t new_velocity = velocity_dialog.velocity ();
857 start_note_diff_command (_("velocity edit"));
859 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
860 Selection::iterator next = i;
862 change_note_velocity (*i, new_velocity, false);
870 MidiRegionView::show_list_editor ()
873 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
875 _list_editor->present ();
878 /** Add a note to the model, and the view, at a canvas (click) coordinate.
879 * \param t time in frames relative to the position of the region
880 * \param y vertical position in pixels
881 * \param length duration of the note in beats
882 * \param snap_t true to snap t to the grid, otherwise false.
885 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
887 if (length < 2 * DBL_EPSILON) {
891 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
892 MidiStreamView* const view = mtv->midi_view();
894 const double note = view->y_to_note(y);
896 // Start of note in frames relative to region start
898 framecnt_t grid_frames;
899 t = snap_frame_to_grid_underneath (t, grid_frames);
902 const boost::shared_ptr<NoteType> new_note (
903 new NoteType (mtv->get_channel_for_add (),
904 region_frames_to_region_beats(t + _region->start()),
906 (uint8_t)note, 0x40));
908 if (_model->contains (new_note)) {
912 view->update_note_range(new_note->note());
914 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
916 _model->apply_command(*trackview.session(), cmd);
918 play_midi_note (new_note);
922 MidiRegionView::clear_events (bool with_selection_signal)
924 clear_selection (with_selection_signal);
927 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
928 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
933 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
938 _patch_changes.clear();
940 _optimization_iterator = _events.end();
944 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
948 content_connection.disconnect ();
949 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
953 if (_enable_display) {
959 MidiRegionView::start_note_diff_command (string name)
961 if (!_note_diff_command) {
962 _note_diff_command = _model->new_note_diff_command (name);
967 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
969 if (_note_diff_command) {
970 _note_diff_command->add (note);
973 _marked_for_selection.insert(note);
976 _marked_for_velocity.insert(note);
981 MidiRegionView::note_diff_remove_note (NoteBase* ev)
983 if (_note_diff_command && ev->note()) {
984 _note_diff_command->remove(ev->note());
989 MidiRegionView::note_diff_add_change (NoteBase* ev,
990 MidiModel::NoteDiffCommand::Property property,
993 if (_note_diff_command) {
994 _note_diff_command->change (ev->note(), property, val);
999 MidiRegionView::note_diff_add_change (NoteBase* ev,
1000 MidiModel::NoteDiffCommand::Property property,
1001 Evoral::MusicalTime val)
1003 if (_note_diff_command) {
1004 _note_diff_command->change (ev->note(), property, val);
1009 MidiRegionView::apply_diff (bool as_subcommand)
1013 if (!_note_diff_command) {
1017 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1018 // Mark all selected notes for selection when model reloads
1019 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1020 _marked_for_selection.insert((*i)->note());
1024 if (as_subcommand) {
1025 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1027 _model->apply_command (*trackview.session(), _note_diff_command);
1030 _note_diff_command = 0;
1031 midi_view()->midi_track()->playlist_modified();
1033 if (add_or_remove) {
1034 _marked_for_selection.clear();
1037 _marked_for_velocity.clear();
1041 MidiRegionView::abort_command()
1043 delete _note_diff_command;
1044 _note_diff_command = 0;
1049 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1051 if (_optimization_iterator != _events.end()) {
1052 ++_optimization_iterator;
1055 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1056 return *_optimization_iterator;
1059 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1060 if ((*_optimization_iterator)->note() == note) {
1061 return *_optimization_iterator;
1069 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1071 MidiModel::Notes notes;
1072 _model->get_notes (notes, op, val, chan_mask);
1074 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1075 NoteBase* cne = find_canvas_note (*n);
1083 MidiRegionView::redisplay_model()
1085 // Don't redisplay the model if we're currently recording and displaying that
1086 if (_active_notes) {
1094 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1095 (*i)->invalidate ();
1098 MidiModel::ReadLock lock(_model->read_lock());
1100 MidiModel::Notes& notes (_model->notes());
1101 _optimization_iterator = _events.begin();
1103 bool empty_when_starting = _events.empty();
1105 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1107 boost::shared_ptr<NoteType> note (*n);
1111 if (note_in_region_range (note, visible)) {
1113 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1120 if ((cn = dynamic_cast<Note*>(cne)) != 0) {
1122 } else if ((ch = dynamic_cast<Hit*>(cne)) != 0) {
1134 add_note (note, visible);
1139 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1147 /* remove note items that are no longer valid */
1149 if (!empty_when_starting) {
1150 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1151 if (!(*i)->valid ()) {
1153 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1154 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1156 gr->remove_note (*i);
1161 i = _events.erase (i);
1169 _patch_changes.clear();
1173 display_patch_changes ();
1175 _marked_for_selection.clear ();
1176 _marked_for_velocity.clear ();
1178 /* we may have caused _events to contain things out of order (e.g. if a note
1179 moved earlier or later). we don't generally need them in time order, but
1180 make a note that a sort is required for those cases that require it.
1183 _sort_needed = true;
1187 MidiRegionView::display_patch_changes ()
1189 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1190 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1192 for (uint8_t i = 0; i < 16; ++i) {
1193 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1197 /** @param active_channel true to display patch changes fully, false to display
1198 * them `greyed-out' (as on an inactive channel)
1201 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1203 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1205 if ((*i)->channel() != channel) {
1209 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1210 add_canvas_patch_change (*i, patch_name, active_channel);
1215 MidiRegionView::display_sysexes()
1217 bool have_periodic_system_messages = false;
1218 bool display_periodic_messages = true;
1220 if (!Config->get_never_display_periodic_midi()) {
1222 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1223 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1224 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1227 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1228 have_periodic_system_messages = true;
1234 if (have_periodic_system_messages) {
1235 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1237 /* get an approximate value for the number of samples per video frame */
1239 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1241 /* if we are zoomed out beyond than the cutoff (i.e. more
1242 * frames per pixel than frames per 4 video frames), don't
1243 * show periodic sysex messages.
1246 if (zoom > (video_frame*4)) {
1247 display_periodic_messages = false;
1251 display_periodic_messages = false;
1254 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1256 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1257 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1259 Evoral::MusicalTime time = (*i)->time();
1262 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1263 if (!display_periodic_messages) {
1271 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1272 str << int((*i)->buffer()[b]);
1273 if (b != (*i)->size() -1) {
1277 string text = str.str();
1279 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1281 double height = midi_stream_view()->contents_height();
1283 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1284 // SysEx canvas object!!!
1286 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1287 new SysEx (*this, _note_group, text, height, x, 1.0));
1289 // Show unless message is beyond the region bounds
1290 if (time - _region->start() >= _region->length() || time < _region->start()) {
1296 _sys_exes.push_back(sysex);
1300 MidiRegionView::~MidiRegionView ()
1302 in_destructor = true;
1304 trackview.editor().verbose_cursor()->hide ();
1306 note_delete_connection.disconnect ();
1308 delete _list_editor;
1310 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1312 if (_active_notes) {
1316 _selection_cleared_connection.disconnect ();
1319 clear_events (false);
1322 delete _note_diff_command;
1323 delete _step_edit_cursor;
1324 delete _temporary_note_group;
1328 MidiRegionView::region_resized (const PropertyChange& what_changed)
1330 RegionView::region_resized(what_changed);
1332 if (what_changed.contains (ARDOUR::Properties::position)) {
1333 set_duration(_region->length(), 0);
1334 if (_enable_display) {
1341 MidiRegionView::reset_width_dependent_items (double pixel_width)
1343 RegionView::reset_width_dependent_items(pixel_width);
1345 if (_enable_display) {
1349 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1350 if ((*x)->canvas_item()->width() >= _pixel_width) {
1357 move_step_edit_cursor (_step_edit_cursor_position);
1358 set_step_edit_cursor_width (_step_edit_cursor_width);
1362 MidiRegionView::set_height (double height)
1364 double old_height = _height;
1365 RegionView::set_height(height);
1367 apply_note_range (midi_stream_view()->lowest_note(),
1368 midi_stream_view()->highest_note(),
1369 height != old_height);
1372 name_text->raise_to_top();
1375 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1376 (*x)->set_height (midi_stream_view()->contents_height());
1379 if (_step_edit_cursor) {
1380 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1385 /** Apply the current note range from the stream view
1386 * by repositioning/hiding notes as necessary
1389 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1391 if (!_enable_display) {
1395 if (!force && _current_range_min == min && _current_range_max == max) {
1399 _current_range_min = min;
1400 _current_range_max = max;
1402 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1403 NoteBase* event = *i;
1404 boost::shared_ptr<NoteType> note (event->note());
1406 if (note->note() < _current_range_min ||
1407 note->note() > _current_range_max) {
1413 if (Note* cnote = dynamic_cast<Note*>(event)) {
1415 const double y0 = midi_stream_view()->note_to_y(note->note());
1416 const double y1 = y0 + floor(midi_stream_view()->note_height());
1421 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1428 MidiRegionView::add_ghost (TimeAxisView& tv)
1432 double unit_position = _region->position () / samples_per_pixel;
1433 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1434 MidiGhostRegion* ghost;
1436 if (mtv && mtv->midi_view()) {
1437 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1438 to allow having midi notes on top of note lines and waveforms.
1440 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1442 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1445 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1446 if ((note = dynamic_cast<Note*>(*i)) != 0) {
1447 ghost->add_note(note);
1451 ghost->set_height ();
1452 ghost->set_duration (_region->length() / samples_per_pixel);
1453 ghosts.push_back (ghost);
1455 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1461 /** Begin tracking note state for successive calls to add_event
1464 MidiRegionView::begin_write()
1466 if (_active_notes) {
1467 delete[] _active_notes;
1469 _active_notes = new Note*[128];
1470 for (unsigned i = 0; i < 128; ++i) {
1471 _active_notes[i] = 0;
1476 /** Destroy note state for add_event
1479 MidiRegionView::end_write()
1481 delete[] _active_notes;
1483 _marked_for_selection.clear();
1484 _marked_for_velocity.clear();
1488 /** Resolve an active MIDI note (while recording).
1491 MidiRegionView::resolve_note(uint8_t note, double end_time)
1493 if (midi_view()->note_mode() != Sustained) {
1497 if (_active_notes && _active_notes[note]) {
1499 /* XXX is end_time really region-centric? I think so, because
1500 this is a new region that we're recording, so source zero is
1501 the same as region zero
1503 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1505 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1506 _active_notes[note]->set_outline_all ();
1507 _active_notes[note] = 0;
1513 /** Extend active notes to rightmost edge of region (if length is changed)
1516 MidiRegionView::extend_active_notes()
1518 if (!_active_notes) {
1522 for (unsigned i=0; i < 128; ++i) {
1523 if (_active_notes[i]) {
1524 _active_notes[i]->set_x1 (trackview.editor().sample_to_pixel(_region->length()));
1531 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1533 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1537 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1539 if (!route_ui || !route_ui->midi_track()) {
1543 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1547 /* NotePlayer deletes itself */
1551 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1553 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1557 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1559 if (!route_ui || !route_ui->midi_track()) {
1563 delete _note_player;
1564 _note_player = new NotePlayer (route_ui->midi_track ());
1565 _note_player->add (note);
1566 _note_player->on ();
1570 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1572 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1576 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1578 if (!route_ui || !route_ui->midi_track()) {
1582 delete _note_player;
1583 _note_player = new NotePlayer (route_ui->midi_track());
1585 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1586 _note_player->add (*n);
1589 _note_player->on ();
1594 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1596 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1597 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1599 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1600 (note->note() <= midi_stream_view()->highest_note());
1605 /** Update a canvas note's size from its model note.
1606 * @param ev Canvas note to update.
1607 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1610 MidiRegionView::update_note (Note* ev, bool update_ghost_regions)
1612 boost::shared_ptr<NoteType> note = ev->note();
1613 const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1614 const double y0 = midi_stream_view()->note_to_y(note->note());
1619 /* trim note display to not overlap the end of its region */
1621 if (note->length() > 0) {
1622 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1623 ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames));
1625 ev->set_x1 (trackview.editor().sample_to_pixel (_region->length()));
1628 ev->set_y1 (y0 + floor(midi_stream_view()->note_height()));
1630 if (note->length() == 0) {
1631 if (_active_notes && note->note() < 128) {
1632 // If this note is already active there's a stuck note,
1633 // finish the old note rectangle
1634 if (_active_notes[note->note()]) {
1635 Note* const old_rect = _active_notes[note->note()];
1636 boost::shared_ptr<NoteType> old_note = old_rect->note();
1637 old_rect->set_x1 (x);
1638 old_rect->set_outline_all ();
1640 _active_notes[note->note()] = ev;
1642 /* outline all but right edge */
1643 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1644 ArdourCanvas::Rectangle::TOP|
1645 ArdourCanvas::Rectangle::LEFT|
1646 ArdourCanvas::Rectangle::BOTTOM));
1648 /* outline all edges */
1649 ev->set_outline_all ();
1652 if (update_ghost_regions) {
1653 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1654 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1656 gr->update_note (ev);
1663 MidiRegionView::update_hit (Hit* ev)
1665 boost::shared_ptr<NoteType> note = ev->note();
1667 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1668 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1669 const double diamond_size = midi_stream_view()->note_height();
1670 const double y = midi_stream_view()->note_to_y(note->note()) + (diamond_size/2.0);
1672 ev->set_position (ArdourCanvas::Duple (x, y));
1673 ev->set_height (diamond_size);
1676 /** Add a MIDI note to the view (with length).
1678 * If in sustained mode, notes with length 0 will be considered active
1679 * notes, and resolve_note should be called when the corresponding note off
1680 * event arrives, to properly display the note.
1683 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1685 NoteBase* event = 0;
1687 if (midi_view()->note_mode() == Sustained) {
1689 Note* ev_rect = new Note (*this, _note_group, note);
1691 update_note (ev_rect);
1695 MidiGhostRegion* gr;
1697 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1698 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1699 gr->add_note(ev_rect);
1703 } else if (midi_view()->note_mode() == Percussive) {
1705 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1707 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1709 update_hit (ev_diamond);
1718 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1719 note_selected(event, true);
1722 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1723 event->show_velocity();
1726 event->on_channel_selection_change (get_selected_channels());
1727 _events.push_back(event);
1736 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1737 MidiStreamView* const view = mtv->midi_view();
1739 view->update_note_range (note->note());
1743 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1744 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1746 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1748 /* potentially extend region to hold new note */
1750 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1751 framepos_t region_end = _region->last_frame();
1753 if (end_frame > region_end) {
1754 _region->set_length (end_frame - _region->position());
1757 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1758 MidiStreamView* const view = mtv->midi_view();
1760 view->update_note_range(new_note->note());
1762 _marked_for_selection.clear ();
1765 start_note_diff_command (_("step add"));
1766 note_diff_add_note (new_note, true, false);
1769 // last_step_edit_note = new_note;
1773 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1775 change_note_lengths (false, false, beats, false, true);
1778 /** Add a new patch change flag to the canvas.
1779 * @param patch the patch change to add
1780 * @param the text to display in the flag
1781 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1784 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1786 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1787 const double x = trackview.editor().sample_to_pixel (region_frames);
1789 double const height = midi_stream_view()->contents_height();
1791 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1792 // so we need to do something more sophisticated to keep its color
1793 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1796 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1797 new PatchChange(*this, group,
1804 if (patch_change->item().width() < _pixel_width) {
1805 // Show unless patch change is beyond the region bounds
1806 if (region_frames < 0 || region_frames >= _region->length()) {
1807 patch_change->hide();
1809 patch_change->show();
1812 patch_change->hide ();
1815 _patch_changes.push_back (patch_change);
1818 MIDI::Name::PatchPrimaryKey
1819 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1821 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1824 /// Return true iff @p pc applies to the given time on the given channel.
1826 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, double time, uint8_t channel)
1828 return pc->time() <= time && pc->channel() == channel;
1832 MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1834 // The earliest event not before time
1835 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1837 // Go backwards until we find the latest PC for this channel, or the start
1838 while (i != _model->patch_changes().begin() &&
1839 (i == _model->patch_changes().end() ||
1840 !patch_applies(*i, time, channel))) {
1844 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1845 key.bank_number = (*i)->bank();
1846 key.program_number = (*i)->program ();
1848 key.bank_number = key.program_number = 0;
1851 if (!key.is_sane()) {
1852 error << string_compose(_("insane MIDI patch key %1:%2"),
1853 key.bank_number, key.program_number) << endmsg;
1858 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1860 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1862 if (pc.patch()->program() != new_patch.program_number) {
1863 c->change_program (pc.patch (), new_patch.program_number);
1866 int const new_bank = new_patch.bank_number;
1867 if (pc.patch()->bank() != new_bank) {
1868 c->change_bank (pc.patch (), new_bank);
1871 _model->apply_command (*trackview.session(), c);
1873 _patch_changes.clear ();
1874 display_patch_changes ();
1878 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1880 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1882 if (old_change->time() != new_change.time()) {
1883 c->change_time (old_change, new_change.time());
1886 if (old_change->channel() != new_change.channel()) {
1887 c->change_channel (old_change, new_change.channel());
1890 if (old_change->program() != new_change.program()) {
1891 c->change_program (old_change, new_change.program());
1894 if (old_change->bank() != new_change.bank()) {
1895 c->change_bank (old_change, new_change.bank());
1898 _model->apply_command (*trackview.session(), c);
1900 _patch_changes.clear ();
1901 display_patch_changes ();
1904 /** Add a patch change to the region.
1905 * @param t Time in frames relative to region position
1906 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1907 * MidiTimeAxisView::get_channel_for_add())
1910 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1912 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1914 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1915 c->add (MidiModel::PatchChangePtr (
1916 new Evoral::PatchChange<Evoral::MusicalTime> (
1917 absolute_frames_to_source_beats (_region->position() + t),
1918 mtv->get_channel_for_add(), patch.program(), patch.bank()
1923 _model->apply_command (*trackview.session(), c);
1925 _patch_changes.clear ();
1926 display_patch_changes ();
1930 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::MusicalTime t)
1932 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1933 c->change_time (pc.patch (), t);
1934 _model->apply_command (*trackview.session(), c);
1936 _patch_changes.clear ();
1937 display_patch_changes ();
1941 MidiRegionView::delete_patch_change (PatchChange* pc)
1943 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1944 c->remove (pc->patch ());
1945 _model->apply_command (*trackview.session(), c);
1947 _patch_changes.clear ();
1948 display_patch_changes ();
1952 MidiRegionView::previous_patch (PatchChange& patch)
1954 if (patch.patch()->program() < 127) {
1955 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1956 key.program_number++;
1957 change_patch_change (patch, key);
1962 MidiRegionView::next_patch (PatchChange& patch)
1964 if (patch.patch()->program() > 0) {
1965 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1966 key.program_number--;
1967 change_patch_change (patch, key);
1972 MidiRegionView::next_bank (PatchChange& patch)
1974 if (patch.patch()->program() < 127) {
1975 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1976 if (key.bank_number > 0) {
1978 change_patch_change (patch, key);
1984 MidiRegionView::previous_bank (PatchChange& patch)
1986 if (patch.patch()->program() > 0) {
1987 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1988 if (key.bank_number < 127) {
1990 change_patch_change (patch, key);
1996 MidiRegionView::maybe_remove_deleted_note_from_selection (NoteBase* cne)
1998 if (_selection.empty()) {
2002 _selection.erase (cne);
2006 MidiRegionView::delete_selection()
2008 if (_selection.empty()) {
2012 start_note_diff_command (_("delete selection"));
2014 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2015 if ((*i)->selected()) {
2016 _note_diff_command->remove((*i)->note());
2026 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2028 start_note_diff_command (_("delete note"));
2029 _note_diff_command->remove (n);
2032 trackview.editor().verbose_cursor()->hide ();
2036 MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
2038 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2040 Selection::iterator tmp = i;
2043 (*i)->set_selected (false);
2044 (*i)->hide_velocity ();
2045 _selection.erase (i);
2053 /* this does not change the status of this regionview w.r.t the editor
2058 SelectionCleared (this); /* EMIT SIGNAL */
2063 MidiRegionView::unique_select(NoteBase* ev)
2065 clear_selection_except (ev);
2067 /* don't bother with checking to see if we should remove this
2068 regionview from the editor selection, since we're about to add
2069 another note, and thus put/keep this regionview in the editor
2073 if (!ev->selected()) {
2074 add_to_selection (ev);
2079 MidiRegionView::select_all_notes ()
2083 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2084 add_to_selection (*i);
2089 MidiRegionView::select_range (framepos_t start, framepos_t end)
2093 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2094 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2095 if (t >= start && t <= end) {
2096 add_to_selection (*i);
2102 MidiRegionView::invert_selection ()
2104 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2105 if ((*i)->selected()) {
2106 remove_from_selection(*i);
2108 add_to_selection (*i);
2114 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2116 bool have_selection = !_selection.empty();
2117 uint8_t low_note = 127;
2118 uint8_t high_note = 0;
2119 MidiModel::Notes& notes (_model->notes());
2120 _optimization_iterator = _events.begin();
2122 if (extend && !have_selection) {
2126 /* scan existing selection to get note range */
2128 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2129 if ((*i)->note()->note() < low_note) {
2130 low_note = (*i)->note()->note();
2132 if ((*i)->note()->note() > high_note) {
2133 high_note = (*i)->note()->note();
2140 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2141 /* only note previously selected is the one we are
2142 * reselecting. treat this as cancelling the selection.
2149 low_note = min (low_note, notenum);
2150 high_note = max (high_note, notenum);
2153 _no_sound_notes = true;
2155 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2157 boost::shared_ptr<NoteType> note (*n);
2159 bool select = false;
2161 if (((1 << note->channel()) & channel_mask) != 0) {
2163 if ((note->note() >= low_note && note->note() <= high_note)) {
2166 } else if (note->note() == notenum) {
2172 if ((cne = find_canvas_note (note)) != 0) {
2173 // extend is false because we've taken care of it,
2174 // since it extends by time range, not pitch.
2175 note_selected (cne, add, false);
2179 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2183 _no_sound_notes = false;
2187 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2189 MidiModel::Notes& notes (_model->notes());
2190 _optimization_iterator = _events.begin();
2192 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2194 boost::shared_ptr<NoteType> note (*n);
2197 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2198 if ((cne = find_canvas_note (note)) != 0) {
2199 if (cne->selected()) {
2200 note_deselected (cne);
2202 note_selected (cne, true, false);
2210 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2213 clear_selection_except (ev);
2214 if (!_selection.empty()) {
2215 PublicEditor& editor (trackview.editor());
2216 editor.get_selection().add (this);
2222 if (!ev->selected()) {
2223 add_to_selection (ev);
2227 /* find end of latest note selected, select all between that and the start of "ev" */
2229 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2230 Evoral::MusicalTime latest = 0;
2232 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2233 if ((*i)->note()->end_time() > latest) {
2234 latest = (*i)->note()->end_time();
2236 if ((*i)->note()->time() < earliest) {
2237 earliest = (*i)->note()->time();
2241 if (ev->note()->end_time() > latest) {
2242 latest = ev->note()->end_time();
2245 if (ev->note()->time() < earliest) {
2246 earliest = ev->note()->time();
2249 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2251 /* find notes entirely within OR spanning the earliest..latest range */
2253 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2254 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2255 add_to_selection (*i);
2263 MidiRegionView::note_deselected(NoteBase* ev)
2265 remove_from_selection (ev);
2269 MidiRegionView::update_drag_selection(double x0, double x1, double y0, double y1, bool extend)
2271 // TODO: Make this faster by storing the last updated selection rect, and only
2272 // adjusting things that are in the area that appears/disappeared.
2273 // We probably need a tree to be able to find events in O(log(n)) time.
2275 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2276 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2277 // Rectangles intersect
2278 if (!(*i)->selected()) {
2279 add_to_selection (*i);
2281 } else if ((*i)->selected() && !extend) {
2282 // Rectangles do not intersect
2283 remove_from_selection (*i);
2289 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2295 // TODO: Make this faster by storing the last updated selection rect, and only
2296 // adjusting things that are in the area that appears/disappeared.
2297 // We probably need a tree to be able to find events in O(log(n)) time.
2299 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2300 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2301 // within y- (note-) range
2302 if (!(*i)->selected()) {
2303 add_to_selection (*i);
2305 } else if ((*i)->selected() && !extend) {
2306 remove_from_selection (*i);
2312 MidiRegionView::remove_from_selection (NoteBase* ev)
2314 Selection::iterator i = _selection.find (ev);
2316 if (i != _selection.end()) {
2317 _selection.erase (i);
2320 ev->set_selected (false);
2321 ev->hide_velocity ();
2323 if (_selection.empty()) {
2324 PublicEditor& editor (trackview.editor());
2325 editor.get_selection().remove (this);
2330 MidiRegionView::add_to_selection (NoteBase* ev)
2332 bool add_mrv_selection = false;
2334 if (_selection.empty()) {
2335 add_mrv_selection = true;
2338 if (_selection.insert (ev).second) {
2339 ev->set_selected (true);
2340 start_playing_midi_note ((ev)->note());
2343 if (add_mrv_selection) {
2344 PublicEditor& editor (trackview.editor());
2345 editor.get_selection().add (this);
2350 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2352 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2353 PossibleChord to_play;
2354 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2356 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2357 if ((*i)->note()->time() < earliest) {
2358 earliest = (*i)->note()->time();
2362 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2363 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2364 to_play.push_back ((*i)->note());
2366 (*i)->move_event(dx, dy);
2369 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2371 if (to_play.size() > 1) {
2373 PossibleChord shifted;
2375 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2376 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2377 moved_note->set_note (moved_note->note() + cumulative_dy);
2378 shifted.push_back (moved_note);
2381 start_playing_midi_chord (shifted);
2383 } else if (!to_play.empty()) {
2385 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2386 moved_note->set_note (moved_note->note() + cumulative_dy);
2387 start_playing_midi_note (moved_note);
2393 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2395 uint8_t lowest_note_in_selection = 127;
2396 uint8_t highest_note_in_selection = 0;
2397 uint8_t highest_note_difference = 0;
2399 // find highest and lowest notes first
2401 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2402 uint8_t pitch = (*i)->note()->note();
2403 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2404 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2408 cerr << "dnote: " << (int) dnote << endl;
2409 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2410 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2411 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2412 << int(highest_note_in_selection) << endl;
2413 cerr << "selection size: " << _selection.size() << endl;
2414 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2417 // Make sure the note pitch does not exceed the MIDI standard range
2418 if (highest_note_in_selection + dnote > 127) {
2419 highest_note_difference = highest_note_in_selection - 127;
2422 start_note_diff_command (_("move notes"));
2424 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2426 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2427 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2433 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2435 uint8_t original_pitch = (*i)->note()->note();
2436 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2438 // keep notes in standard midi range
2439 clamp_to_0_127(new_pitch);
2441 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2442 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2444 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2449 // care about notes being moved beyond the upper/lower bounds on the canvas
2450 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2451 highest_note_in_selection > midi_stream_view()->highest_note()) {
2452 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2456 /** @param x Pixel relative to the region position.
2457 * @return Snapped frame relative to the region position.
2460 MidiRegionView::snap_pixel_to_sample(double x)
2462 PublicEditor& editor (trackview.editor());
2463 return snap_frame_to_frame (editor.pixel_to_sample (x));
2466 /** @param x Pixel relative to the region position.
2467 * @return Snapped pixel relative to the region position.
2470 MidiRegionView::snap_to_pixel(double x)
2472 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x));
2476 MidiRegionView::get_position_pixels()
2478 framepos_t region_frame = get_position();
2479 return trackview.editor().sample_to_pixel(region_frame);
2483 MidiRegionView::get_end_position_pixels()
2485 framepos_t frame = get_position() + get_duration ();
2486 return trackview.editor().sample_to_pixel(frame);
2490 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2492 /* the time converter will return the frame corresponding to `beats'
2493 relative to the start of the source. The start of the source
2494 is an implied position given by region->position - region->start
2496 const framepos_t source_start = _region->position() - _region->start();
2497 return source_start + _source_relative_time_converter.to (beats);
2501 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2503 /* the `frames' argument needs to be converted into a frame count
2504 relative to the start of the source before being passed in to the
2507 const framepos_t source_start = _region->position() - _region->start();
2508 return _source_relative_time_converter.from (frames - source_start);
2512 MidiRegionView::region_beats_to_region_frames(double beats) const
2514 return _region_relative_time_converter.to(beats);
2518 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2520 return _region_relative_time_converter.from(frames);
2524 MidiRegionView::begin_resizing (bool /*at_front*/)
2526 _resize_data.clear();
2528 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2529 Note *note = dynamic_cast<Note*> (*i);
2531 // only insert CanvasNotes into the map
2533 NoteResizeData *resize_data = new NoteResizeData();
2534 resize_data->note = note;
2536 // create a new SimpleRect from the note which will be the resize preview
2537 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2538 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2540 // calculate the colors: get the color settings
2541 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2542 ARDOUR_UI::config()->get_canvasvar_MidiNoteSelected(),
2545 // make the resize preview notes more transparent and bright
2546 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2548 // calculate color based on note velocity
2549 resize_rect->set_fill_color (UINT_INTERPOLATE(
2550 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2554 resize_rect->set_outline_color (NoteBase::calculate_outline (
2555 ARDOUR_UI::config()->get_canvasvar_MidiNoteSelected()));
2557 resize_data->resize_rect = resize_rect;
2558 _resize_data.push_back(resize_data);
2563 /** Update resizing notes while user drags.
2564 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2565 * @param at_front which end of the note (true == note on, false == note off)
2566 * @param delta_x change in mouse position since the start of the drag
2567 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2568 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2569 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2570 * as the \a primary note.
2573 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2575 bool cursor_set = false;
2577 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2578 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2579 Note* canvas_note = (*i)->note;
2584 current_x = canvas_note->x0() + delta_x;
2586 current_x = primary->x0() + delta_x;
2590 current_x = canvas_note->x1() + delta_x;
2592 current_x = primary->x1() + delta_x;
2597 resize_rect->set_x0 (snap_to_pixel(current_x));
2598 resize_rect->set_x1 (canvas_note->x1());
2600 resize_rect->set_x1 (snap_to_pixel(current_x));
2601 resize_rect->set_x0 (canvas_note->x0());
2607 beats = snap_pixel_to_sample (current_x);
2608 beats = region_frames_to_region_beats (beats);
2613 if (beats < canvas_note->note()->end_time()) {
2614 len = canvas_note->note()->time() - beats;
2615 len += canvas_note->note()->length();
2620 if (beats >= canvas_note->note()->time()) {
2621 len = beats - canvas_note->note()->time();
2628 snprintf (buf, sizeof (buf), "%.3g beats", len);
2629 show_verbose_cursor (buf, 0, 0);
2638 /** Finish resizing notes when the user releases the mouse button.
2639 * Parameters the same as for \a update_resizing().
2642 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2644 start_note_diff_command (_("resize notes"));
2646 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2647 Note* canvas_note = (*i)->note;
2648 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2650 /* Get the new x position for this resize, which is in pixels relative
2651 * to the region position.
2658 current_x = canvas_note->x0() + delta_x;
2660 current_x = primary->x0() + delta_x;
2664 current_x = canvas_note->x1() + delta_x;
2666 current_x = primary->x1() + delta_x;
2670 /* Convert that to a frame within the source */
2671 current_x = snap_pixel_to_sample (current_x) + _region->start ();
2673 /* and then to beats */
2674 current_x = region_frames_to_region_beats (current_x);
2676 if (at_front && current_x < canvas_note->note()->end_time()) {
2677 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2679 double len = canvas_note->note()->time() - current_x;
2680 len += canvas_note->note()->length();
2683 /* XXX convert to beats */
2684 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2689 double len = current_x - canvas_note->note()->time();
2692 /* XXX convert to beats */
2693 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2701 _resize_data.clear();
2706 MidiRegionView::abort_resizing ()
2708 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2709 delete (*i)->resize_rect;
2713 _resize_data.clear ();
2717 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2719 uint8_t new_velocity;
2722 new_velocity = event->note()->velocity() + velocity;
2723 clamp_to_0_127(new_velocity);
2725 new_velocity = velocity;
2728 event->set_selected (event->selected()); // change color
2730 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2734 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2739 new_note = event->note()->note() + note;
2744 clamp_to_0_127 (new_note);
2745 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2749 MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2751 bool change_start = false;
2752 bool change_length = false;
2753 Evoral::MusicalTime new_start = 0;
2754 Evoral::MusicalTime new_length = 0;
2756 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2758 front_delta: if positive - move the start of the note later in time (shortening it)
2759 if negative - move the start of the note earlier in time (lengthening it)
2761 end_delta: if positive - move the end of the note later in time (lengthening it)
2762 if negative - move the end of the note earlier in time (shortening it)
2766 if (front_delta < 0) {
2768 if (event->note()->time() < -front_delta) {
2771 new_start = event->note()->time() + front_delta; // moves earlier
2774 /* start moved toward zero, so move the end point out to where it used to be.
2775 Note that front_delta is negative, so this increases the length.
2778 new_length = event->note()->length() - front_delta;
2779 change_start = true;
2780 change_length = true;
2784 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2786 if (new_pos < event->note()->end_time()) {
2787 new_start = event->note()->time() + front_delta;
2788 /* start moved toward the end, so move the end point back to where it used to be */
2789 new_length = event->note()->length() - front_delta;
2790 change_start = true;
2791 change_length = true;
2798 bool can_change = true;
2799 if (end_delta < 0) {
2800 if (event->note()->length() < -end_delta) {
2806 new_length = event->note()->length() + end_delta;
2807 change_length = true;
2812 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2815 if (change_length) {
2816 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2821 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
2823 uint8_t new_channel;
2827 if (event->note()->channel() < -chn) {
2830 new_channel = event->note()->channel() + chn;
2833 new_channel = event->note()->channel() + chn;
2836 new_channel = (uint8_t) chn;
2839 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2843 MidiRegionView::change_note_time (NoteBase* event, Evoral::MusicalTime delta, bool relative)
2845 Evoral::MusicalTime new_time;
2849 if (event->note()->time() < -delta) {
2852 new_time = event->note()->time() + delta;
2855 new_time = event->note()->time() + delta;
2861 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2865 MidiRegionView::change_note_length (NoteBase* event, Evoral::MusicalTime t)
2867 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2871 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2876 if (_selection.empty()) {
2891 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2892 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
2898 start_note_diff_command (_("change velocities"));
2900 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2901 Selection::iterator next = i;
2905 if (i == _selection.begin()) {
2906 change_note_velocity (*i, delta, true);
2907 value = (*i)->note()->velocity() + delta;
2909 change_note_velocity (*i, value, false);
2913 change_note_velocity (*i, delta, true);
2922 if (!_selection.empty()) {
2924 snprintf (buf, sizeof (buf), "Vel %d",
2925 (int) (*_selection.begin())->note()->velocity());
2926 show_verbose_cursor (buf, 10, 10);
2932 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2934 if (_selection.empty()) {
2951 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2953 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2957 if ((int8_t) (*i)->note()->note() + delta > 127) {
2964 start_note_diff_command (_("transpose"));
2966 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2967 Selection::iterator next = i;
2969 change_note_note (*i, delta, true);
2977 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2983 /* grab the current grid distance */
2985 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2987 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2988 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
2998 start_note_diff_command (_("change note lengths"));
3000 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3001 Selection::iterator next = i;
3004 /* note the negation of the delta for start */
3006 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3015 MidiRegionView::nudge_notes (bool forward)
3017 if (_selection.empty()) {
3021 /* pick a note as the point along the timeline to get the nudge distance.
3022 its not necessarily the earliest note, so we may want to pull the notes out
3023 into a vector and sort before using the first one.
3026 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3028 framecnt_t distance;
3030 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3032 /* grid is off - use nudge distance */
3034 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3040 framepos_t next_pos = ref_point;
3043 if (max_framepos - 1 < next_pos) {
3047 if (next_pos == 0) {
3053 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3054 distance = ref_point - next_pos;
3057 if (distance == 0) {
3061 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs ((double)distance));
3067 start_note_diff_command (_("nudge"));
3069 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3070 Selection::iterator next = i;
3072 change_note_time (*i, delta, true);
3080 MidiRegionView::change_channel(uint8_t channel)
3082 start_note_diff_command(_("change channel"));
3083 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3084 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3092 MidiRegionView::note_entered(NoteBase* ev)
3094 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3096 pre_enter_cursor = editor->get_canvas_cursor ();
3098 if (_mouse_state == SelectTouchDragging) {
3099 note_selected (ev, true);
3102 show_verbose_cursor (ev->note ());
3106 MidiRegionView::note_left (NoteBase*)
3108 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3110 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3111 (*i)->hide_velocity ();
3114 editor->verbose_cursor()->hide ();
3116 if (pre_enter_cursor) {
3117 editor->set_canvas_cursor (pre_enter_cursor);
3118 pre_enter_cursor = 0;
3123 MidiRegionView::patch_entered (PatchChange* p)
3126 /* XXX should get patch name if we can */
3127 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3128 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3129 << _("Channel ") << ((int) p->patch()->channel() + 1);
3130 show_verbose_cursor (s.str(), 10, 20);
3131 p->item().grab_focus();
3135 MidiRegionView::patch_left (PatchChange *)
3137 trackview.editor().verbose_cursor()->hide ();
3138 /* focus will transfer back via the enter-notify event sent to this
3144 MidiRegionView::sysex_entered (SysEx* p)
3148 // need a way to extract text from p->_flag->_text
3150 // show_verbose_cursor (s.str(), 10, 20);
3151 p->item().grab_focus();
3155 MidiRegionView::sysex_left (SysEx *)
3157 trackview.editor().verbose_cursor()->hide ();
3158 /* focus will transfer back via the enter-notify event sent to this
3164 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3166 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3167 Editing::MouseMode mm = editor->current_mouse_mode();
3168 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3170 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3171 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3172 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3173 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3175 if (pre_enter_cursor && can_set_cursor) {
3176 editor->set_canvas_cursor (pre_enter_cursor);
3182 MidiRegionView::set_frame_color()
3186 TimeAxisViewItem::set_frame_color ();
3193 f = ARDOUR_UI::config()->get_canvasvar_SelectedFrameBase();
3194 } else if (high_enough_for_name) {
3195 f= ARDOUR_UI::config()->get_canvasvar_MidiFrameBase();
3200 if (!rect_visible) {
3201 f = UINT_RGBA_CHANGE_A (f, 0);
3204 frame->set_fill_color (f);
3208 MidiRegionView::midi_channel_mode_changed ()
3210 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3211 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3212 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3214 if (mode == ForceChannel) {
3215 mask = 0xFFFF; // Show all notes as active (below)
3218 // Update notes for selection
3219 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3220 (*i)->on_channel_selection_change (mask);
3223 _patch_changes.clear ();
3224 display_patch_changes ();
3228 MidiRegionView::instrument_settings_changed ()
3234 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3236 if (_selection.empty()) {
3240 PublicEditor& editor (trackview.editor());
3244 /* XXX what to do ? */
3248 editor.get_cut_buffer().add (selection_as_cut_buffer());
3256 start_note_diff_command();
3258 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3265 note_diff_remove_note (*i);
3275 MidiRegionView::selection_as_cut_buffer () const
3279 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3280 NoteType* n = (*i)->note().get();
3281 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3284 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3290 /** This method handles undo */
3292 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3298 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3300 trackview.session()->begin_reversible_command (_("paste"));
3302 start_note_diff_command (_("paste"));
3304 Evoral::MusicalTime beat_delta;
3305 Evoral::MusicalTime paste_pos_beats;
3306 Evoral::MusicalTime duration;
3307 Evoral::MusicalTime end_point = 0;
3309 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3310 paste_pos_beats = absolute_frames_to_source_beats (pos);
3311 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3312 paste_pos_beats = 0;
3314 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n",
3315 (*mcb.notes().begin())->time(),
3316 (*mcb.notes().rbegin())->end_time(),
3317 duration, pos, _region->position(),
3318 paste_pos_beats, beat_delta));
3322 for (int n = 0; n < (int) times; ++n) {
3324 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3326 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3327 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3329 /* make all newly added notes selected */
3331 note_diff_add_note (copied_note, true);
3332 end_point = copied_note->end_time();
3335 paste_pos_beats += duration;
3338 /* if we pasted past the current end of the region, extend the region */
3340 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3341 framepos_t region_end = _region->position() + _region->length() - 1;
3343 if (end_frame > region_end) {
3345 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3347 _region->clear_changes ();
3348 _region->set_length (end_frame - _region->position());
3349 trackview.session()->add_command (new StatefulDiffCommand (_region));
3354 trackview.session()->commit_reversible_command ();
3357 struct EventNoteTimeEarlyFirstComparator {
3358 bool operator() (NoteBase* a, NoteBase* b) {
3359 return a->note()->time() < b->note()->time();
3364 MidiRegionView::time_sort_events ()
3366 if (!_sort_needed) {
3370 EventNoteTimeEarlyFirstComparator cmp;
3373 _sort_needed = false;
3377 MidiRegionView::goto_next_note (bool add_to_selection)
3379 bool use_next = false;
3381 if (_events.back()->selected()) {
3385 time_sort_events ();
3387 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3388 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3390 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3391 if ((*i)->selected()) {
3394 } else if (use_next) {
3395 if (channel_mask & (1 << (*i)->note()->channel())) {
3396 if (!add_to_selection) {
3399 note_selected (*i, true, false);
3406 /* use the first one */
3408 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3409 unique_select (_events.front());
3414 MidiRegionView::goto_previous_note (bool add_to_selection)
3416 bool use_next = false;
3418 if (_events.front()->selected()) {
3422 time_sort_events ();
3424 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3425 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3427 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3428 if ((*i)->selected()) {
3431 } else if (use_next) {
3432 if (channel_mask & (1 << (*i)->note()->channel())) {
3433 if (!add_to_selection) {
3436 note_selected (*i, true, false);
3443 /* use the last one */
3445 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3446 unique_select (*(_events.rbegin()));
3451 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3453 bool had_selected = false;
3455 time_sort_events ();
3457 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3458 if ((*i)->selected()) {
3459 selected.insert ((*i)->note());
3460 had_selected = true;
3464 if (allow_all_if_none_selected && !had_selected) {
3465 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3466 selected.insert ((*i)->note());
3472 MidiRegionView::update_ghost_note (double x, double y)
3474 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3479 _note_group->canvas_to_item (x, y);
3481 PublicEditor& editor = trackview.editor ();
3483 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3484 framecnt_t grid_frames;
3485 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3487 /* use region_frames... because we are converting a delta within the region
3491 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3497 /* note that this sets the time of the ghost note in beats relative to
3498 the start of the source; that is how all note times are stored.
3500 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3501 _ghost_note->note()->set_length (length);
3502 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3503 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3505 /* the ghost note does not appear in ghost regions, so pass false in here */
3506 update_note (_ghost_note, false);
3508 show_verbose_cursor (_ghost_note->note ());
3512 MidiRegionView::create_ghost_note (double x, double y)
3514 remove_ghost_note ();
3516 boost::shared_ptr<NoteType> g (new NoteType);
3517 _ghost_note = new Note (*this, _note_group, g);
3518 _ghost_note->set_ignore_events (true);
3519 _ghost_note->set_outline_color (0x000000aa);
3520 update_ghost_note (x, y);
3521 _ghost_note->show ();
3526 show_verbose_cursor (_ghost_note->note ());
3530 MidiRegionView::remove_ghost_note ()
3537 MidiRegionView::snap_changed ()
3543 create_ghost_note (_last_ghost_x, _last_ghost_y);
3547 MidiRegionView::drop_down_keys ()
3549 _mouse_state = None;
3553 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3555 double note = midi_stream_view()->y_to_note(y);
3557 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3559 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3561 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3562 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3563 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3564 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3569 bool add_mrv_selection = false;
3571 if (_selection.empty()) {
3572 add_mrv_selection = true;
3575 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3576 if (_selection.insert (*i).second) {
3577 (*i)->set_selected (true);
3581 if (add_mrv_selection) {
3582 PublicEditor& editor (trackview.editor());
3583 editor.get_selection().add (this);
3588 MidiRegionView::color_handler ()
3590 RegionView::color_handler ();
3592 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3593 (*i)->set_selected ((*i)->selected()); // will change color
3596 /* XXX probably more to do here */
3600 MidiRegionView::enable_display (bool yn)
3602 RegionView::enable_display (yn);
3609 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3611 if (_step_edit_cursor == 0) {
3612 ArdourCanvas::Item* const group = get_canvas_group();
3614 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3615 _step_edit_cursor->set_y0 (0);
3616 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3617 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3618 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3621 move_step_edit_cursor (pos);
3622 _step_edit_cursor->show ();
3626 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3628 _step_edit_cursor_position = pos;
3630 if (_step_edit_cursor) {
3631 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3632 _step_edit_cursor->set_x0 (pixel);
3633 set_step_edit_cursor_width (_step_edit_cursor_width);
3638 MidiRegionView::hide_step_edit_cursor ()
3640 if (_step_edit_cursor) {
3641 _step_edit_cursor->hide ();
3646 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3648 _step_edit_cursor_width = beats;
3650 if (_step_edit_cursor) {
3651 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
3655 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3656 * @param w Source that the data will end up in.
3659 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3661 if (!_active_notes) {
3662 /* we aren't actively being recorded to */
3666 boost::shared_ptr<MidiSource> src = w.lock ();
3667 if (!src || src != midi_region()->midi_source()) {
3668 /* recorded data was not destined for our source */
3672 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3674 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3676 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3678 framepos_t back = max_framepos;
3680 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3681 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3683 if (ev.is_channel_event()) {
3684 if (get_channel_mode() == FilterChannels) {
3685 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3691 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3692 frames from the start of the source, and so time_beats is in terms of the
3696 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3698 if (ev.type() == MIDI_CMD_NOTE_ON) {
3699 boost::shared_ptr<NoteType> note (
3700 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity()));
3702 add_note (note, true);
3704 /* fix up our note range */
3705 if (ev.note() < _current_range_min) {
3706 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3707 } else if (ev.note() > _current_range_max) {
3708 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3711 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3712 resolve_note (ev.note (), time_beats);
3718 midi_stream_view()->check_record_layers (region(), back);
3722 MidiRegionView::trim_front_starting ()
3724 /* Reparent the note group to the region view's parent, so that it doesn't change
3725 when the region view is trimmed.
3727 _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3728 _temporary_note_group->move (group->position ());
3729 _note_group->reparent (_temporary_note_group);
3733 MidiRegionView::trim_front_ending ()
3735 _note_group->reparent (group);
3736 delete _temporary_note_group;
3737 _temporary_note_group = 0;
3739 if (_region->start() < 0) {
3740 /* Trim drag made start time -ve; fix this */
3741 midi_region()->fix_negative_start ();
3746 MidiRegionView::edit_patch_change (PatchChange* pc)
3748 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3750 int response = d.run();
3753 case Gtk::RESPONSE_ACCEPT:
3755 case Gtk::RESPONSE_REJECT:
3756 delete_patch_change (pc);
3762 change_patch_change (pc->patch(), d.patch ());
3766 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
3769 // sysyex object doesn't have a pointer to a sysex event
3770 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3771 // c->remove (sysex->sysex());
3772 // _model->apply_command (*trackview.session(), c);
3774 //_sys_exes.clear ();
3775 // display_sysexes();
3779 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3781 using namespace MIDI::Name;
3785 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3787 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
3789 MIDI::Name::PatchPrimaryKey patch_key;
3790 get_patch_key_at(n->time(), n->channel(), patch_key);
3791 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
3793 patch_key.bank_number,
3794 patch_key.program_number,
3800 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
3802 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
3803 (int) n->channel() + 1,
3804 (int) n->velocity());
3806 show_verbose_cursor(buf, 10, 20);
3810 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3814 trackview.editor().verbose_cursor()->set_text (text);
3815 trackview.editor().get_pointer_position (wx, wy);
3820 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3822 boost::optional<ArdourCanvas::Rect> bbo = trackview.editor().verbose_cursor()->item().bounding_box();
3826 ArdourCanvas::Rect bb = bbo.get();
3828 if ((wy + bb.y1 - bb.y0) > trackview.editor().visible_canvas_height()) {
3829 wy -= (bb.y1 - bb.y0) + 2 * yoffset;
3832 trackview.editor().verbose_cursor()->set_position (wx, wy);
3833 trackview.editor().verbose_cursor()->show ();
3836 /** @param p A session framepos.
3837 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3838 * @return p snapped to the grid subdivision underneath it.
3841 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3843 PublicEditor& editor = trackview.editor ();
3846 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3852 grid_frames = region_beats_to_region_frames (grid_beats);
3854 /* Hack so that we always snap to the note that we are over, instead of snapping
3855 to the next one if we're more than halfway through the one we're over.
3857 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3858 p -= grid_frames / 2;
3861 return snap_frame_to_frame (p);
3864 /** Called when the selection has been cleared in any MidiRegionView.
3865 * @param rv MidiRegionView that the selection was cleared in.
3868 MidiRegionView::selection_cleared (MidiRegionView* rv)
3874 /* Clear our selection in sympathy; but don't signal the fact */
3875 clear_selection (false);
3879 MidiRegionView::note_button_release ()
3881 delete _note_player;
3886 MidiRegionView::get_channel_mode () const
3888 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
3889 return rtav->midi_track()->get_playback_channel_mode();
3893 MidiRegionView::get_selected_channels () const
3895 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
3896 return rtav->midi_track()->get_playback_channel_mask();