2 * Copyright (C) 2006-2016 David Robillard <d@drobilla.net>
3 * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
4 * Copyright (C) 2007-2018 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2008-2012 Hans Baier <hansfbaier@googlemail.com>
6 * Copyright (C) 2013-2017 John Emmas <john@creativepost.co.uk>
7 * Copyright (C) 2014-2017 Nick Mainsbridge <mainsbridge@gmail.com>
8 * Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
9 * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
10 * Copyright (C) 2015-2016 Tim Mayberry <mojofunk@gmail.com>
11 * Copyright (C) 2015-2017 André Nusser <andre.nusser@googlemail.com>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
34 #include "gtkmm2ext/gtk_ui.h"
36 #include <sigc++/signal.h>
38 #include "midi++/midnam_patch.h"
40 #include "pbd/memento_command.h"
41 #include "pbd/stateful_diff_command.h"
43 #include "ardour/midi_model.h"
44 #include "ardour/midi_playlist.h"
45 #include "ardour/midi_region.h"
46 #include "ardour/midi_source.h"
47 #include "ardour/midi_track.h"
48 #include "ardour/operations.h"
49 #include "ardour/session.h"
51 #include "evoral/Parameter.h"
52 #include "evoral/Event.h"
53 #include "evoral/Control.h"
54 #include "evoral/midi_util.h"
56 #include "canvas/debug.h"
57 #include "canvas/text.h"
59 #include "automation_region_view.h"
60 #include "automation_time_axis.h"
61 #include "control_point.h"
64 #include "editor_drag.h"
65 #include "ghostregion.h"
66 #include "gui_thread.h"
67 #include "item_counts.h"
69 #include "midi_channel_dialog.h"
70 #include "midi_cut_buffer.h"
71 #include "midi_list_editor.h"
72 #include "midi_region_view.h"
73 #include "midi_streamview.h"
74 #include "midi_time_axis.h"
75 #include "midi_util.h"
76 #include "midi_velocity_dialog.h"
77 #include "mouse_cursors.h"
78 #include "note_player.h"
79 #include "paste_context.h"
80 #include "public_editor.h"
81 #include "route_time_axis.h"
82 #include "rgb_macros.h"
83 #include "selection.h"
84 #include "streamview.h"
85 #include "patch_change_dialog.h"
86 #include "verbose_cursor.h"
89 #include "patch_change.h"
91 #include "ui_config.h"
95 using namespace ARDOUR;
97 using namespace Editing;
99 using Gtkmm2ext::Keyboard;
101 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
103 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
104 RouteTimeAxisView& tv,
105 boost::shared_ptr<MidiRegion> r,
107 uint32_t basic_color)
108 : RegionView (parent, tv, r, spu, basic_color)
109 , _current_range_min(0)
110 , _current_range_max(0)
111 , _region_relative_time_converter(r->session().tempo_map(), r->position())
112 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
113 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
115 , _note_group (new ArdourCanvas::Container (group))
116 , _note_diff_command (0)
118 , _step_edit_cursor (0)
119 , _step_edit_cursor_width (1.0)
120 , _step_edit_cursor_position (0.0)
121 , _channel_selection_scoped_note (0)
124 , _optimization_iterator (_events.end())
126 , _no_sound_notes (false)
127 , _last_display_zoom (0)
130 , _grabbed_keyboard (false)
133 , _mouse_changed_selection (false)
135 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
137 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
138 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
140 _note_group->raise_to_top();
141 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
143 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
144 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
146 connect_to_diskstream ();
149 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
150 RouteTimeAxisView& tv,
151 boost::shared_ptr<MidiRegion> r,
153 uint32_t basic_color,
155 TimeAxisViewItem::Visibility visibility)
156 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
157 , _current_range_min(0)
158 , _current_range_max(0)
159 , _region_relative_time_converter(r->session().tempo_map(), r->position())
160 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
161 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
163 , _note_group (new ArdourCanvas::Container (group))
164 , _note_diff_command (0)
166 , _step_edit_cursor (0)
167 , _step_edit_cursor_width (1.0)
168 , _step_edit_cursor_position (0.0)
169 , _channel_selection_scoped_note (0)
172 , _optimization_iterator (_events.end())
174 , _no_sound_notes (false)
175 , _last_display_zoom (0)
178 , _grabbed_keyboard (false)
181 , _mouse_changed_selection (false)
183 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
185 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
186 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
188 _note_group->raise_to_top();
190 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
192 connect_to_diskstream ();
196 MidiRegionView::parameter_changed (std::string const & p)
198 if (p == "display-first-midi-bank-as-zero") {
199 if (_enable_display) {
202 } else if (p == "color-regions-using-track-color") {
204 } else if (p == "use-note-color-for-velocity") {
209 MidiRegionView::MidiRegionView (const MidiRegionView& other)
210 : sigc::trackable(other)
212 , _current_range_min(0)
213 , _current_range_max(0)
214 , _region_relative_time_converter(other.region_relative_time_converter())
215 , _source_relative_time_converter(other.source_relative_time_converter())
216 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
218 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
219 , _note_diff_command (0)
221 , _step_edit_cursor (0)
222 , _step_edit_cursor_width (1.0)
223 , _step_edit_cursor_position (0.0)
224 , _channel_selection_scoped_note (0)
227 , _optimization_iterator (_events.end())
229 , _no_sound_notes (false)
230 , _last_display_zoom (0)
233 , _grabbed_keyboard (false)
236 , _mouse_changed_selection (false)
241 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
242 : RegionView (other, boost::shared_ptr<Region> (region))
243 , _current_range_min(0)
244 , _current_range_max(0)
245 , _region_relative_time_converter(other.region_relative_time_converter())
246 , _source_relative_time_converter(other.source_relative_time_converter())
247 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
249 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
250 , _note_diff_command (0)
252 , _step_edit_cursor (0)
253 , _step_edit_cursor_width (1.0)
254 , _step_edit_cursor_position (0.0)
255 , _channel_selection_scoped_note (0)
258 , _optimization_iterator (_events.end())
260 , _no_sound_notes (false)
261 , _last_display_zoom (0)
264 , _grabbed_keyboard (false)
267 , _mouse_changed_selection (false)
273 MidiRegionView::init (bool wfd)
275 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
278 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
279 midi_region()->midi_source(0)->load_model(lm);
282 _model = midi_region()->midi_source(0)->model();
283 _enable_display = false;
284 fill_color_name = "midi frame base";
286 RegionView::init (false);
288 //set_height (trackview.current_height());
291 region_sync_changed ();
292 region_resized (ARDOUR::bounds_change);
297 _enable_display = true;
300 display_model (_model);
304 reset_width_dependent_items (_pixel_width);
306 group->raise_to_top();
308 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
309 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
312 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
313 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
315 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
316 boost::bind (&MidiRegionView::snap_changed, this),
319 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
320 boost::bind (&MidiRegionView::mouse_mode_changed, this),
323 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
324 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
325 connect_to_diskstream ();
329 MidiRegionView::instrument_info () const
331 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
332 return route_ui->route()->instrument_info();
335 const boost::shared_ptr<ARDOUR::MidiRegion>
336 MidiRegionView::midi_region() const
338 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
342 MidiRegionView::connect_to_diskstream ()
344 midi_view()->midi_track()->DataRecorded.connect(
345 *this, invalidator(*this),
346 boost::bind (&MidiRegionView::data_recorded, this, _1),
351 MidiRegionView::canvas_group_event(GdkEvent* ev)
353 if (in_destructor || _recregion) {
357 if (!trackview.editor().internal_editing()) {
358 // not in internal edit mode, so just act like a normal region
359 return RegionView::canvas_group_event (ev);
362 //For now, move the snapped cursor aside so it doesn't bother you during internal editing
363 //trackview.editor().set_snapped_cursor_position(_region->position());
368 case GDK_ENTER_NOTIFY:
369 _last_event_x = ev->crossing.x;
370 _last_event_y = ev->crossing.y;
371 enter_notify(&ev->crossing);
372 // set entered_regionview (among other things)
373 return RegionView::canvas_group_event (ev);
375 case GDK_LEAVE_NOTIFY:
376 _last_event_x = ev->crossing.x;
377 _last_event_y = ev->crossing.y;
378 leave_notify(&ev->crossing);
379 // reset entered_regionview (among other things)
380 return RegionView::canvas_group_event (ev);
383 if (scroll (&ev->scroll)) {
389 return key_press (&ev->key);
391 case GDK_KEY_RELEASE:
392 return key_release (&ev->key);
394 case GDK_BUTTON_PRESS:
395 return button_press (&ev->button);
397 case GDK_BUTTON_RELEASE:
398 r = button_release (&ev->button);
401 case GDK_MOTION_NOTIFY:
402 _last_event_x = ev->motion.x;
403 _last_event_y = ev->motion.y;
404 return motion (&ev->motion);
410 return RegionView::canvas_group_event (ev);
414 MidiRegionView::enter_notify (GdkEventCrossing* ev)
416 enter_internal (ev->state);
423 MidiRegionView::leave_notify (GdkEventCrossing*)
432 MidiRegionView::mouse_mode_changed ()
434 // Adjust frame colour (become more transparent for internal tools)
438 if (!trackview.editor().internal_editing()) {
439 /* Switched out of internal editing mode while entered.
440 Only necessary for leave as a mouse_mode_change over a region
441 automatically triggers an enter event. */
444 else if (trackview.editor().current_mouse_mode() == MouseContent) {
445 // hide cursor and ghost note after changing to internal edit mode
446 remove_ghost_note ();
448 /* XXX This is problematic as the function is executed for every region
449 and only for one region _entered_note can be true. Still it's
450 necessary as to hide the verbose cursor when we're changing from
451 draw mode to internal edit mode. These lines are the reason why
452 in some situations no verbose cursor is shown when we enter internal
453 edit mode over a note. */
454 if (!_entered_note) {
455 hide_verbose_cursor ();
462 MidiRegionView::enter_internal (uint32_t state)
464 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
465 // Show ghost note under pencil
466 create_ghost_note(_last_event_x, _last_event_y, state);
469 if (!_selection.empty()) {
470 // Grab keyboard for moving selected notes with arrow keys
471 Keyboard::magic_widget_grab_focus();
472 _grabbed_keyboard = true;
475 // Lower frame handles below notes so they don't steal events
476 if (frame_handle_start) {
477 frame_handle_start->lower_to_bottom();
479 if (frame_handle_end) {
480 frame_handle_end->lower_to_bottom();
485 MidiRegionView::leave_internal()
487 hide_verbose_cursor ();
488 remove_ghost_note ();
491 if (_grabbed_keyboard) {
492 Keyboard::magic_widget_drop_focus();
493 _grabbed_keyboard = false;
496 // Raise frame handles above notes so they catch events
497 if (frame_handle_start) {
498 frame_handle_start->raise_to_top();
500 if (frame_handle_end) {
501 frame_handle_end->raise_to_top();
506 MidiRegionView::button_press (GdkEventButton* ev)
508 if (ev->button != 1) {
512 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
513 MouseMode m = editor->current_mouse_mode();
515 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
516 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
519 if (_mouse_state != SelectTouchDragging) {
521 _pressed_button = ev->button;
523 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
525 if (midi_view()->note_mode() == Percussive) {
526 editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
528 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
531 _mouse_state = AddDragging;
532 remove_ghost_note ();
533 hide_verbose_cursor ();
535 _mouse_state = Pressed;
541 _pressed_button = ev->button;
542 _mouse_changed_selection = false;
548 MidiRegionView::button_release (GdkEventButton* ev)
550 double event_x, event_y;
552 if (ev->button != 1) {
559 group->canvas_to_item (event_x, event_y);
562 PublicEditor& editor = trackview.editor ();
564 _press_cursor_ctx.reset();
566 switch (_mouse_state) {
567 case Pressed: // Clicked
569 switch (editor.current_mouse_mode()) {
571 /* no motion occurred - simple click */
572 clear_editor_note_selection ();
573 _mouse_changed_selection = true;
579 _mouse_changed_selection = true;
580 clear_editor_note_selection ();
595 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
596 we don't want one when we were drag-selecting either. */
597 case SelectRectDragging:
598 editor.drags()->end_grab ((GdkEvent *) ev);
607 if (_mouse_changed_selection) {
608 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
609 trackview.editor().commit_reversible_selection_op ();
616 MidiRegionView::motion (GdkEventMotion* ev)
618 PublicEditor& editor = trackview.editor ();
620 if (!_entered_note) {
622 if (_mouse_state == AddDragging) {
624 remove_ghost_note ();
627 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
628 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
629 _mouse_state != AddDragging) {
631 create_ghost_note (ev->x, ev->y, ev->state);
633 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
634 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
636 update_ghost_note (ev->x, ev->y, ev->state);
638 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
640 remove_ghost_note ();
641 hide_verbose_cursor ();
643 } else if (editor.current_mouse_mode() == MouseDraw) {
646 update_ghost_note (ev->x, ev->y, ev->state);
649 create_ghost_note (ev->x, ev->y, ev->state);
654 /* any motion immediately hides velocity text that may have been visible */
656 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
657 (*i)->hide_velocity ();
660 switch (_mouse_state) {
663 if (_pressed_button == 1) {
665 MouseMode m = editor.current_mouse_mode();
667 if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
668 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
669 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
670 clear_editor_note_selection ();
671 _mouse_changed_selection = true;
673 _mouse_state = SelectRectDragging;
675 } else if (m == MouseRange) {
676 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
677 _mouse_state = SelectVerticalDragging;
684 case SelectRectDragging:
685 case SelectVerticalDragging:
687 editor.drags()->motion_handler ((GdkEvent *) ev, false);
690 case SelectTouchDragging:
698 //let RegionView do it's thing. drags are handled in here
699 return RegionView::canvas_group_event ((GdkEvent *) ev);
704 MidiRegionView::scroll (GdkEventScroll* ev)
706 if (trackview.editor().drags()->active()) {
709 if (_selection.empty()) {
713 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
714 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
715 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
716 * through so that it still works for navigation.
721 hide_verbose_cursor ();
723 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
724 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
725 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
727 if (ev->direction == GDK_SCROLL_UP) {
728 change_velocities (true, fine, false, together);
729 } else if (ev->direction == GDK_SCROLL_DOWN) {
730 change_velocities (false, fine, false, together);
732 /* left, right: we don't use them */
740 MidiRegionView::key_press (GdkEventKey* ev)
742 /* since GTK bindings are generally activated on press, and since
743 detectable auto-repeat is the name of the game and only sends
744 repeated presses, carry out key actions at key press, not release.
746 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
748 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
750 if (_mouse_state != AddDragging) {
751 _mouse_state = SelectTouchDragging;
756 } else if (ev->keyval == GDK_Escape && unmodified) {
757 clear_editor_note_selection ();
760 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
762 bool start = (ev->keyval == GDK_comma);
763 bool end = (ev->keyval == GDK_period);
764 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
765 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
767 change_note_lengths (fine, shorter, Temporal::Beats(), start, end);
771 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
773 if (_selection.empty()) {
780 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
782 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
784 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
785 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
787 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
790 trackview.editor().commit_reversible_selection_op();
794 } else if (ev->keyval == GDK_Up) {
796 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
797 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
798 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
800 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
801 change_velocities (true, fine, allow_smush, together);
803 transpose (true, fine, allow_smush);
807 } else if (ev->keyval == GDK_Down) {
809 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
810 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
811 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
813 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
814 change_velocities (false, fine, allow_smush, together);
816 transpose (false, fine, allow_smush);
820 } else if (ev->keyval == GDK_Left) {
822 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
823 nudge_notes (false, fine);
826 } else if (ev->keyval == GDK_Right) {
828 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
829 nudge_notes (true, fine);
832 } else if (ev->keyval == GDK_c && unmodified) {
836 } else if (ev->keyval == GDK_v && unmodified) {
845 MidiRegionView::key_release (GdkEventKey* ev)
847 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
855 MidiRegionView::channel_edit ()
857 if (_selection.empty()) {
861 /* pick a note somewhat at random (since Selection is a set<>) to
862 * provide the "current" channel for the dialog.
865 uint8_t current_channel = (*_selection.begin())->note()->channel ();
866 MidiChannelDialog channel_dialog (current_channel);
867 int ret = channel_dialog.run ();
870 case Gtk::RESPONSE_OK:
876 uint8_t new_channel = channel_dialog.active_channel ();
878 start_note_diff_command (_("channel edit"));
880 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
881 Selection::iterator next = i;
883 change_note_channel (*i, new_channel);
891 MidiRegionView::velocity_edit ()
893 if (_selection.empty()) {
897 /* pick a note somewhat at random (since Selection is a set<>) to
898 * provide the "current" velocity for the dialog.
901 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
902 MidiVelocityDialog velocity_dialog (current_velocity);
903 int ret = velocity_dialog.run ();
906 case Gtk::RESPONSE_OK:
912 uint8_t new_velocity = velocity_dialog.velocity ();
914 start_note_diff_command (_("velocity edit"));
916 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
917 Selection::iterator next = i;
919 change_note_velocity (*i, new_velocity, false);
927 MidiRegionView::show_list_editor ()
930 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
932 _list_editor->present ();
935 /** Add a note to the model, and the view, at a canvas (click) coordinate.
936 * \param t time in samples relative to the position of the region
937 * \param y vertical position in pixels
938 * \param length duration of the note in beats
939 * \param snap_t true to snap t to the grid, otherwise false.
942 MidiRegionView::create_note_at (samplepos_t t, double y, Temporal::Beats length, uint32_t state, bool shift_snap)
944 if (length < 2 * DBL_EPSILON) {
948 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
949 MidiStreamView* const view = mtv->midi_view();
950 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
956 // Start of note in samples relative to region start
957 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
958 Temporal::Beats beat_time = snap_sample_to_grid_underneath (t, divisions, shift_snap);
960 const double note = view->y_to_note(y);
961 const uint8_t chan = mtv->get_channel_for_add();
962 const uint8_t velocity = get_velocity_for_add(beat_time);
964 const boost::shared_ptr<NoteType> new_note(
965 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
967 if (_model->contains (new_note)) {
971 view->update_note_range(new_note->note());
973 start_note_diff_command(_("add note"));
975 note_diff_add_note (new_note, true, false);
979 play_midi_note (new_note);
983 MidiRegionView::clear_events ()
985 // clear selection without signaling
986 clear_selection_internal ();
989 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
990 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
996 _note_group->clear (true);
998 _patch_changes.clear();
1000 _optimization_iterator = _events.end();
1004 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
1008 content_connection.disconnect ();
1009 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
1010 /* Don't signal as nobody else needs to know until selection has been altered. */
1013 if (_enable_display) {
1019 MidiRegionView::start_note_diff_command (string name)
1021 if (!_note_diff_command) {
1022 trackview.editor().begin_reversible_command (name);
1023 _note_diff_command = _model->new_note_diff_command (name);
1028 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1030 if (_note_diff_command) {
1031 _note_diff_command->add (note);
1034 _marked_for_selection.insert(note);
1036 if (show_velocity) {
1037 _marked_for_velocity.insert(note);
1042 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1044 if (_note_diff_command && ev->note()) {
1045 _note_diff_command->remove(ev->note());
1050 MidiRegionView::note_diff_add_change (NoteBase* ev,
1051 MidiModel::NoteDiffCommand::Property property,
1054 if (_note_diff_command) {
1055 _note_diff_command->change (ev->note(), property, val);
1060 MidiRegionView::note_diff_add_change (NoteBase* ev,
1061 MidiModel::NoteDiffCommand::Property property,
1062 Temporal::Beats val)
1064 if (_note_diff_command) {
1065 _note_diff_command->change (ev->note(), property, val);
1070 MidiRegionView::apply_diff (bool as_subcommand, bool was_copy)
1072 bool commit = false;
1074 if (!_note_diff_command) {
1078 bool add_or_remove = _note_diff_command->adds_or_removes();
1080 if (!was_copy && add_or_remove) {
1081 // Mark all selected notes for selection when model reloads
1082 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1083 _marked_for_selection.insert((*i)->note());
1087 if (as_subcommand) {
1088 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1090 _model->apply_command (*trackview.session(), _note_diff_command);
1094 _note_diff_command = 0;
1096 if (add_or_remove) {
1097 _marked_for_selection.clear();
1100 _marked_for_velocity.clear();
1103 trackview.editor().commit_reversible_command ();
1108 MidiRegionView::abort_command()
1110 delete _note_diff_command;
1111 _note_diff_command = 0;
1112 trackview.editor().abort_reversible_command();
1113 clear_editor_note_selection();
1117 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1120 if (_optimization_iterator != _events.end()) {
1121 ++_optimization_iterator;
1124 if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) {
1125 return _optimization_iterator->second;
1128 _optimization_iterator = _events.find (note);
1129 if (_optimization_iterator != _events.end()) {
1130 return _optimization_iterator->second;
1136 /** This version finds any canvas note matching the supplied note. */
1138 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1140 Events::iterator it;
1142 for (it = _events.begin(); it != _events.end(); ++it) {
1143 if (it->first->id() == id) {
1151 boost::shared_ptr<PatchChange>
1152 MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
1154 PatchChanges::const_iterator f = _patch_changes.find (p);
1156 if (f != _patch_changes.end()) {
1160 return boost::shared_ptr<PatchChange>();
1163 boost::shared_ptr<SysEx>
1164 MidiRegionView::find_canvas_sys_ex (MidiModel::SysExPtr s)
1166 SysExes::const_iterator f = _sys_exes.find (s);
1168 if (f != _sys_exes.end()) {
1172 return boost::shared_ptr<SysEx>();
1176 MidiRegionView::get_events (Events& e, Evoral::Sequence<Temporal::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1178 MidiModel::Notes notes;
1179 _model->get_notes (notes, op, val, chan_mask);
1181 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1182 NoteBase* cne = find_canvas_note (*n);
1184 e.insert (make_pair (*n, cne));
1190 MidiRegionView::redisplay_model()
1192 if (_active_notes) {
1193 // Currently recording
1194 const samplecnt_t zoom = trackview.editor().get_current_zoom();
1195 if (zoom != _last_display_zoom) {
1196 /* Update resolved canvas notes to reflect changes in zoom without
1197 touching model. Leave active notes (with length max) alone since
1198 they are being extended. */
1199 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1200 if (i->second->note()->end_time() != std::numeric_limits<Temporal::Beats>::max()) {
1201 update_note(i->second);
1204 _last_display_zoom = zoom;
1213 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1214 _optimization_iterator->second->invalidate();
1217 bool empty_when_starting = _events.empty();
1218 _optimization_iterator = _events.begin();
1219 MidiModel::Notes missing_notes;
1223 MidiModel::ReadLock lock(_model->read_lock());
1224 MidiModel::Notes& notes (_model->notes());
1227 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1229 boost::shared_ptr<NoteType> note (*n);
1232 if (note_in_region_range (note, visible)) {
1233 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1241 missing_notes.insert (note);
1246 if (!empty_when_starting) {
1247 MidiModel::Notes::iterator f;
1248 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1250 NoteBase* cne = i->second;
1252 /* remove note items that are no longer valid */
1253 if (!cne->valid()) {
1255 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1256 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1258 gr->remove_note (cne);
1263 i = _events.erase (i);
1266 bool visible = cne->item()->visible();
1268 if ((sus = dynamic_cast<Note*>(cne))) {
1271 update_sustained (sus);
1274 } else if ((hit = dynamic_cast<Hit*>(cne))) {
1286 for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
1287 boost::shared_ptr<NoteType> note (*n);
1291 if (note_in_region_range (note, visible)) {
1293 cne = add_note (note, true);
1295 cne = add_note (note, false);
1298 cne = add_note (note, false);
1301 for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1302 if ((*it) == note->id()) {
1303 add_to_selection (cne);
1308 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1309 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1310 if (gr && !gr->trackview.hidden()) {
1311 gr->redisplay_model ();
1316 display_patch_changes ();
1318 _marked_for_selection.clear ();
1319 _marked_for_velocity.clear ();
1320 _pending_note_selection.clear ();
1325 MidiRegionView::display_patch_changes ()
1327 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1328 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1330 for (uint8_t i = 0; i < 16; ++i) {
1331 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1335 /** @param active_channel true to display patch changes fully, false to display
1336 * them `greyed-out' (as on an inactive channel)
1339 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1341 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1342 boost::shared_ptr<PatchChange> p;
1344 if ((*i)->channel() != channel) {
1348 if ((p = find_canvas_patch_change (*i)) != 0) {
1350 const samplecnt_t region_samples = source_beats_to_region_samples ((*i)->time());
1352 if (region_samples < 0 || region_samples >= _region->length()) {
1355 const double x = trackview.editor().sample_to_pixel (region_samples);
1356 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1357 p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
1358 p->set_text (patch_name);
1364 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1365 add_canvas_patch_change (*i, patch_name, active_channel);
1371 MidiRegionView::display_sysexes()
1373 bool have_periodic_system_messages = false;
1374 bool display_periodic_messages = true;
1376 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1378 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1379 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1380 have_periodic_system_messages = true;
1385 if (have_periodic_system_messages) {
1386 double zoom = trackview.editor().get_current_zoom (); // samples per pixel
1388 /* get an approximate value for the number of samples per video frame */
1390 double video_frame = trackview.session()->sample_rate() * (1.0/30);
1392 /* if we are zoomed out beyond than the cutoff (i.e. more
1393 * samples per pixel than samples per 4 video frames), don't
1394 * show periodic sysex messages.
1397 if (zoom > (video_frame*4)) {
1398 display_periodic_messages = false;
1402 display_periodic_messages = false;
1405 const boost::shared_ptr<MidiRegion> mregion (midi_region());
1407 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1408 MidiModel::SysExPtr sysex_ptr = *i;
1409 Temporal::Beats time = sysex_ptr->time();
1411 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1412 if (!display_periodic_messages) {
1419 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1420 str << int((*i)->buffer()[b]);
1421 if (b != (*i)->size() -1) {
1425 string text = str.str();
1427 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_samples(time));
1429 double height = midi_stream_view()->contents_height();
1431 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1432 // SysEx canvas object!!!
1433 boost::shared_ptr<SysEx> sysex = find_canvas_sys_ex (sysex_ptr);
1436 sysex = boost::shared_ptr<SysEx>(
1437 new SysEx (*this, _note_group, text, height, x, 1.0, sysex_ptr));
1438 _sys_exes.insert (make_pair (sysex_ptr, sysex));
1440 sysex->set_height (height);
1441 sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
1444 // Show unless message is beyond the region bounds
1445 if (time - mregion->start_beats() >= mregion->length_beats() || time < mregion->start_beats()) {
1453 MidiRegionView::~MidiRegionView ()
1455 in_destructor = true;
1457 hide_verbose_cursor ();
1459 delete _list_editor;
1461 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1463 if (_active_notes) {
1470 delete _note_diff_command;
1471 delete _step_edit_cursor;
1475 MidiRegionView::region_resized (const PropertyChange& what_changed)
1477 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1479 if (what_changed.contains (ARDOUR::Properties::position)) {
1480 _region_relative_time_converter.set_origin_b(_region->position());
1481 _region_relative_time_converter_double.set_origin_b(_region->position());
1482 /* reset_width dependent_items() redisplays model */
1486 if (what_changed.contains (ARDOUR::Properties::start) ||
1487 what_changed.contains (ARDOUR::Properties::position)) {
1488 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1490 /* catch end and start trim so we can update the view*/
1491 if (!what_changed.contains (ARDOUR::Properties::start) &&
1492 what_changed.contains (ARDOUR::Properties::length)) {
1493 enable_display (true);
1494 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1495 what_changed.contains (ARDOUR::Properties::length)) {
1496 enable_display (true);
1501 MidiRegionView::reset_width_dependent_items (double pixel_width)
1503 RegionView::reset_width_dependent_items(pixel_width);
1505 if (_enable_display) {
1509 bool hide_all = false;
1510 PatchChanges::iterator x = _patch_changes.begin();
1511 if (x != _patch_changes.end()) {
1512 hide_all = x->second->width() >= _pixel_width;
1516 for (; x != _patch_changes.end(); ++x) {
1521 move_step_edit_cursor (_step_edit_cursor_position);
1522 set_step_edit_cursor_width (_step_edit_cursor_width);
1526 MidiRegionView::set_height (double height)
1528 double old_height = _height;
1529 RegionView::set_height(height);
1531 apply_note_range (midi_stream_view()->lowest_note(),
1532 midi_stream_view()->highest_note(),
1533 height != old_height);
1536 name_text->raise_to_top();
1539 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1540 (*x).second->set_height (midi_stream_view()->contents_height());
1543 if (_step_edit_cursor) {
1544 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1549 /** Apply the current note range from the stream view
1550 * by repositioning/hiding notes as necessary
1553 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1555 if (!_enable_display) {
1559 if (!force && _current_range_min == min && _current_range_max == max) {
1563 _current_range_min = min;
1564 _current_range_max = max;
1570 MidiRegionView::add_ghost (TimeAxisView& tv)
1572 double unit_position = _region->position () / samples_per_pixel;
1573 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1574 MidiGhostRegion* ghost;
1576 if (mtv && mtv->midi_view()) {
1577 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1578 to allow having midi notes on top of note lines and waveforms.
1580 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1582 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1585 ghost->set_colors ();
1586 ghost->set_height ();
1587 ghost->set_duration (_region->length() / samples_per_pixel);
1589 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1590 ghost->add_note(i->second);
1593 ghosts.push_back (ghost);
1594 enable_display (true);
1599 /** Begin tracking note state for successive calls to add_event
1602 MidiRegionView::begin_write()
1604 if (_active_notes) {
1605 delete[] _active_notes;
1607 _active_notes = new Note*[128];
1608 for (unsigned i = 0; i < 128; ++i) {
1609 _active_notes[i] = 0;
1614 /** Destroy note state for add_event
1617 MidiRegionView::end_write()
1619 delete[] _active_notes;
1621 _marked_for_selection.clear();
1622 _marked_for_velocity.clear();
1626 /** Resolve an active MIDI note (while recording).
1629 MidiRegionView::resolve_note (uint8_t note, Temporal::Beats end_time)
1631 if (midi_view()->note_mode() != Sustained) {
1635 if (_active_notes && _active_notes[note]) {
1636 /* Set note length so update_note() works. Note this is a local note
1637 for recording, not from a model, so we can safely mess with it. */
1638 _active_notes[note]->note()->set_length (end_time - _active_notes[note]->note()->time());
1640 /* End time is relative to the region being recorded. */
1641 const samplepos_t end_time_samples = region_beats_to_region_samples (end_time);
1643 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_samples));
1644 _active_notes[note]->set_outline_all ();
1645 _active_notes[note] = 0;
1650 /** Extend active notes to rightmost edge of region (if length is changed)
1653 MidiRegionView::extend_active_notes()
1655 if (!_active_notes) {
1659 for (unsigned i = 0; i < 128; ++i) {
1660 if (_active_notes[i]) {
1661 _active_notes[i]->set_x1 (trackview.editor().sample_to_pixel(_region->length()));
1667 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1669 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1673 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1675 if (!route_ui || !route_ui->midi_track()) {
1679 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1683 /* NotePlayer deletes itself */
1687 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1689 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1690 start_playing_midi_chord(notes);
1694 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1696 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1700 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1702 if (!route_ui || !route_ui->midi_track()) {
1706 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1708 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1717 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1719 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1721 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1722 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1723 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1725 visible = (note->note() >= _current_range_min) &&
1726 (note->note() <= _current_range_max);
1732 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1736 if ((sus = dynamic_cast<Note*>(note))) {
1737 update_sustained(sus, update_ghost_regions);
1738 } else if ((hit = dynamic_cast<Hit*>(note))) {
1739 update_hit(hit, update_ghost_regions);
1743 /** Update a canvas note's size from its model note.
1744 * @param ev Canvas note to update.
1745 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1748 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1750 TempoMap& map (trackview.session()->tempo_map());
1751 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1752 boost::shared_ptr<NoteType> note = ev->note();
1754 const double session_source_start = _region->quarter_note() - mr->start_beats();
1755 const samplepos_t note_start_samples = map.sample_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1757 const double x0 = max (0.,trackview.editor().sample_to_pixel (note_start_samples));
1759 const double y0 = 1 + floor(note_to_y(note->note()));
1762 if (note->length() == 0) {
1764 /* special case actual zero-length notes */
1768 } else if (note->end_time() != std::numeric_limits<Temporal::Beats>::max()) {
1772 double note_end_time = note->end_time().to_double();
1774 if (note->end_time() > mr->start_beats() + mr->length_beats()) {
1775 note_end_time = mr->start_beats() + mr->length_beats();
1778 const samplepos_t note_end_samples = map.sample_at_quarter_note (session_source_start + note_end_time) - _region->position();
1780 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_samples)) - 1;
1784 /* nascent note currently being recorded, noteOff has not yet arrived */
1786 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1789 y1 = y0 + std::max(1., floor(note_height()) - 1);
1791 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1792 ev->set_velocity (note->velocity()/127.0);
1794 if (note->end_time() == std::numeric_limits<Temporal::Beats>::max()) {
1795 if (_active_notes && note->note() < 128) {
1796 Note* const old_rect = _active_notes[note->note()];
1798 /* There is an active note on this key, so we have a stuck
1799 note. Finish the old rectangle here. */
1800 old_rect->set_x1 (x1);
1801 old_rect->set_outline_all ();
1803 _active_notes[note->note()] = ev;
1805 /* outline all but right edge */
1806 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1807 ArdourCanvas::Rectangle::TOP|
1808 ArdourCanvas::Rectangle::LEFT|
1809 ArdourCanvas::Rectangle::BOTTOM));
1811 /* outline all edges */
1812 ev->set_outline_all ();
1815 // Update color in case velocity has changed
1816 const uint32_t base_col = ev->base_color();
1817 ev->set_fill_color(base_col);
1818 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1823 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1825 boost::shared_ptr<NoteType> note = ev->note();
1827 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1828 const samplepos_t note_start_samples = trackview.session()->tempo_map().sample_at_quarter_note (note_time_qn) - _region->position();
1830 const double x = trackview.editor().sample_to_pixel(note_start_samples);
1831 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1832 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1834 // see DnD note in MidiRegionView::apply_note_range() above
1835 if (y <= 0 || y >= _height) {
1841 ev->set_position (ArdourCanvas::Duple (x, y));
1842 ev->set_height (diamond_size);
1844 // Update color in case velocity has changed
1845 const uint32_t base_col = ev->base_color();
1846 ev->set_fill_color(base_col);
1847 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1851 /** Add a MIDI note to the view (with length).
1853 * If in sustained mode, notes with an end at numeric_limits<Beats>::max() will be
1854 * considered active notes, and resolve_note should be called when the
1855 * corresponding note off event arrives, to properly display the note.
1858 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1860 NoteBase* event = 0;
1862 if (midi_view()->note_mode() == Sustained) {
1864 Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak
1866 update_sustained (ev_rect);
1870 } else if (midi_view()->note_mode() == Percussive) {
1872 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1874 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak
1876 update_hit (ev_diamond);
1885 MidiGhostRegion* gr;
1887 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1888 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1889 gr->add_note(event);
1893 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1894 note_selected(event, true);
1897 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1898 event->show_velocity();
1901 event->on_channel_selection_change (get_selected_channels());
1902 _events.insert (make_pair (event->note(), event));
1911 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1912 MidiStreamView* const view = mtv->midi_view();
1914 view->update_note_range (note->note());
1919 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1920 Temporal::Beats pos, Temporal::Beats len)
1922 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1924 /* potentially extend region to hold new note */
1926 samplepos_t end_sample = source_beats_to_absolute_samples (new_note->end_time());
1927 samplepos_t region_end = _region->last_sample();
1929 if (end_sample > region_end) {
1930 /* XX sets length in beats from audio space. make musical */
1931 _region->set_length (end_sample - _region->position(), 0);
1934 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1935 MidiStreamView* const view = mtv->midi_view();
1937 view->update_note_range(new_note->note());
1939 _marked_for_selection.clear ();
1941 start_note_diff_command (_("step add"));
1943 clear_editor_note_selection ();
1944 note_diff_add_note (new_note, true, false);
1948 // last_step_edit_note = new_note;
1952 MidiRegionView::step_sustain (Temporal::Beats beats)
1954 change_note_lengths (false, false, beats, false, true);
1957 /** Add a new patch change flag to the canvas.
1958 * @param patch the patch change to add
1959 * @param the text to display in the flag
1960 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1963 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1965 samplecnt_t region_samples = source_beats_to_region_samples (patch->time());
1966 const double x = trackview.editor().sample_to_pixel (region_samples);
1968 double const height = midi_stream_view()->contents_height();
1970 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1971 // so we need to do something more sophisticated to keep its color
1972 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1974 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1975 new PatchChange(*this, group,
1981 _patch_change_outline,
1985 if (patch_change->item().width() < _pixel_width) {
1986 // Show unless patch change is beyond the region bounds
1987 if (region_samples < 0 || region_samples >= _region->length()) {
1988 patch_change->hide();
1990 patch_change->show();
1993 patch_change->hide ();
1996 _patch_changes.insert (make_pair (patch, patch_change));
2000 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
2002 /* remove the canvas item */
2003 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2004 if (x->second->patch() == pc->patch()) {
2005 _patch_changes.erase (x);
2011 MIDI::Name::PatchPrimaryKey
2012 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
2014 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
2017 /// Return true iff @p pc applies to the given time on the given channel.
2019 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Temporal::Beats time, uint8_t channel)
2021 return pc->time() <= time && pc->channel() == channel;
2025 MidiRegionView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
2027 // The earliest event not before time
2028 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
2030 // Go backwards until we find the latest PC for this channel, or the start
2031 while (i != _model->patch_changes().begin() &&
2032 (i == _model->patch_changes().end() ||
2033 !patch_applies(*i, time, channel))) {
2037 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
2038 key.set_bank((*i)->bank());
2039 key.set_program((*i)->program ());
2047 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
2049 string name = _("alter patch change");
2050 trackview.editor().begin_reversible_command (name);
2052 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2054 if (pc.patch()->program() != new_patch.program()) {
2055 c->change_program (pc.patch (), new_patch.program());
2058 int const new_bank = new_patch.bank();
2059 if (pc.patch()->bank() != new_bank) {
2060 c->change_bank (pc.patch (), new_bank);
2063 _model->apply_command (*trackview.session(), c);
2064 trackview.editor().commit_reversible_command ();
2066 remove_canvas_patch_change (&pc);
2067 display_patch_changes ();
2071 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Temporal::Beats> & new_change)
2073 string name = _("alter patch change");
2074 trackview.editor().begin_reversible_command (name);
2075 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2077 if (old_change->time() != new_change.time()) {
2078 c->change_time (old_change, new_change.time());
2081 if (old_change->channel() != new_change.channel()) {
2082 c->change_channel (old_change, new_change.channel());
2085 if (old_change->program() != new_change.program()) {
2086 c->change_program (old_change, new_change.program());
2089 if (old_change->bank() != new_change.bank()) {
2090 c->change_bank (old_change, new_change.bank());
2093 _model->apply_command (*trackview.session(), c);
2094 trackview.editor().commit_reversible_command ();
2096 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2097 if (x->second->patch() == old_change) {
2098 _patch_changes.erase (x);
2103 display_patch_changes ();
2106 /** Add a patch change to the region.
2107 * @param t Time in samples relative to region position
2108 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2109 * MidiTimeAxisView::get_channel_for_add())
2112 MidiRegionView::add_patch_change (samplecnt_t t, Evoral::PatchChange<Temporal::Beats> const & patch)
2114 string name = _("add patch change");
2116 trackview.editor().begin_reversible_command (name);
2117 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2118 c->add (MidiModel::PatchChangePtr (
2119 new Evoral::PatchChange<Temporal::Beats> (
2120 absolute_samples_to_source_beats (_region->position() + t),
2121 patch.channel(), patch.program(), patch.bank()
2126 _model->apply_command (*trackview.session(), c);
2127 trackview.editor().commit_reversible_command ();
2129 display_patch_changes ();
2133 MidiRegionView::move_patch_change (PatchChange& pc, Temporal::Beats t)
2135 trackview.editor().begin_reversible_command (_("move patch change"));
2136 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2137 c->change_time (pc.patch (), t);
2138 _model->apply_command (*trackview.session(), c);
2139 trackview.editor().commit_reversible_command ();
2141 display_patch_changes ();
2145 MidiRegionView::delete_patch_change (PatchChange* pc)
2147 trackview.editor().begin_reversible_command (_("delete patch change"));
2149 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2150 c->remove (pc->patch ());
2151 _model->apply_command (*trackview.session(), c);
2152 trackview.editor().commit_reversible_command ();
2154 remove_canvas_patch_change (pc);
2155 display_patch_changes ();
2159 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2161 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2163 key.set_bank(key.bank() + delta);
2165 key.set_program(key.program() + delta);
2167 change_patch_change(patch, key);
2171 MidiRegionView::note_deleted (NoteBase* cne)
2173 if (_entered_note && cne == _entered_note) {
2177 if (_selection.empty()) {
2181 _selection.erase (cne);
2185 MidiRegionView::delete_selection()
2187 if (_selection.empty()) {
2191 if (trackview.editor().drags()->active()) {
2195 start_note_diff_command (_("delete selection"));
2197 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2198 if ((*i)->selected()) {
2199 _note_diff_command->remove((*i)->note());
2207 hide_verbose_cursor ();
2211 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2213 start_note_diff_command (_("delete note"));
2214 _note_diff_command->remove (n);
2217 hide_verbose_cursor ();
2221 MidiRegionView::clear_editor_note_selection ()
2223 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2224 PublicEditor& editor(trackview.editor());
2225 editor.get_selection().clear_midi_notes();
2229 MidiRegionView::clear_selection ()
2231 clear_selection_internal();
2232 PublicEditor& editor(trackview.editor());
2233 editor.get_selection().remove(this);
2237 MidiRegionView::clear_selection_internal ()
2239 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2241 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2242 (*i)->set_selected(false);
2243 (*i)->hide_velocity();
2248 // Clearing selection entirely, ungrab keyboard
2249 Keyboard::magic_widget_drop_focus();
2250 _grabbed_keyboard = false;
2255 MidiRegionView::unique_select(NoteBase* ev)
2257 clear_editor_note_selection();
2258 add_to_selection(ev);
2262 MidiRegionView::select_all_notes ()
2264 clear_editor_note_selection ();
2266 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2267 add_to_selection (i->second);
2272 MidiRegionView::select_range (samplepos_t start, samplepos_t end)
2274 clear_editor_note_selection ();
2276 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2277 samplepos_t t = source_beats_to_absolute_samples(i->first->time());
2278 if (t >= start && t <= end) {
2279 add_to_selection (i->second);
2285 MidiRegionView::invert_selection ()
2287 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2288 if (i->second->selected()) {
2289 remove_from_selection(i->second);
2291 add_to_selection (i->second);
2296 /** Used for selection undo/redo.
2297 The requested notes most likely won't exist in the view until the next model redisplay.
2300 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2303 list<Evoral::event_id_t>::iterator n;
2305 for (n = notes.begin(); n != notes.end(); ++n) {
2306 if ((cne = find_canvas_note(*n)) != 0) {
2307 add_to_selection (cne);
2309 _pending_note_selection.insert(*n);
2315 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2317 bool have_selection = !_selection.empty();
2318 uint8_t low_note = 127;
2319 uint8_t high_note = 0;
2320 MidiModel::Notes& notes (_model->notes());
2321 _optimization_iterator = _events.begin();
2323 if (extend && !have_selection) {
2327 /* scan existing selection to get note range */
2329 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2330 if ((*i)->note()->note() < low_note) {
2331 low_note = (*i)->note()->note();
2333 if ((*i)->note()->note() > high_note) {
2334 high_note = (*i)->note()->note();
2339 clear_editor_note_selection ();
2341 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2342 /* only note previously selected is the one we are
2343 * reselecting. treat this as cancelling the selection.
2350 low_note = min (low_note, notenum);
2351 high_note = max (high_note, notenum);
2354 _no_sound_notes = true;
2356 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2358 boost::shared_ptr<NoteType> note (*n);
2360 bool select = false;
2362 if (((1 << note->channel()) & channel_mask) != 0) {
2364 if ((note->note() >= low_note && note->note() <= high_note)) {
2367 } else if (note->note() == notenum) {
2373 if ((cne = find_canvas_note (note)) != 0) {
2374 // extend is false because we've taken care of it,
2375 // since it extends by time range, not pitch.
2376 note_selected (cne, add, false);
2380 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2384 _no_sound_notes = false;
2388 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2390 MidiModel::Notes& notes (_model->notes());
2391 _optimization_iterator = _events.begin();
2393 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2395 boost::shared_ptr<NoteType> note (*n);
2398 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2399 if ((cne = find_canvas_note (note)) != 0) {
2400 if (cne->selected()) {
2401 note_deselected (cne);
2403 note_selected (cne, true, false);
2411 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2414 clear_editor_note_selection();
2415 add_to_selection (ev);
2420 if (!ev->selected()) {
2421 add_to_selection (ev);
2425 /* find end of latest note selected, select all between that and the start of "ev" */
2427 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2428 Temporal::Beats latest = Temporal::Beats();
2430 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2431 if ((*i)->note()->end_time() > latest) {
2432 latest = (*i)->note()->end_time();
2434 if ((*i)->note()->time() < earliest) {
2435 earliest = (*i)->note()->time();
2439 if (ev->note()->end_time() > latest) {
2440 latest = ev->note()->end_time();
2443 if (ev->note()->time() < earliest) {
2444 earliest = ev->note()->time();
2447 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2449 /* find notes entirely within OR spanning the earliest..latest range */
2451 if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
2452 (i->first->time() <= earliest && i->first->end_time() >= latest)) {
2453 add_to_selection (i->second);
2460 MidiRegionView::note_deselected(NoteBase* ev)
2462 remove_from_selection (ev);
2466 MidiRegionView::update_drag_selection(samplepos_t start, samplepos_t end, double gy0, double gy1, bool extend)
2468 PublicEditor& editor = trackview.editor();
2470 // Convert to local coordinates
2471 const samplepos_t p = _region->position();
2472 const double y = midi_view()->y_position();
2473 const double x0 = editor.sample_to_pixel(max((samplepos_t)0, start - p));
2474 const double x1 = editor.sample_to_pixel(max((samplepos_t)0, end - p));
2475 const double y0 = max(0.0, gy0 - y);
2476 const double y1 = max(0.0, gy1 - y);
2478 // TODO: Make this faster by storing the last updated selection rect, and only
2479 // adjusting things that are in the area that appears/disappeared.
2480 // We probably need a tree to be able to find events in O(log(n)) time.
2482 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2483 if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
2484 // Rectangles intersect
2485 if (!i->second->selected()) {
2486 add_to_selection (i->second);
2488 } else if (i->second->selected() && !extend) {
2489 // Rectangles do not intersect
2490 remove_from_selection (i->second);
2494 typedef RouteTimeAxisView::AutomationTracks ATracks;
2495 typedef std::list<Selectable*> Selectables;
2497 /* Add control points to selection. */
2498 const ATracks& atracks = midi_view()->automation_tracks();
2499 Selectables selectables;
2500 editor.get_selection().clear_points();
2501 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2502 a->second->get_selectables(start, end, gy0, gy1, selectables);
2503 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2504 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2506 editor.get_selection().add(cp);
2509 a->second->set_selected_points(editor.get_selection().points);
2514 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2520 // TODO: Make this faster by storing the last updated selection rect, and only
2521 // adjusting things that are in the area that appears/disappeared.
2522 // We probably need a tree to be able to find events in O(log(n)) time.
2524 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2525 if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
2526 // within y- (note-) range
2527 if (!i->second->selected()) {
2528 add_to_selection (i->second);
2530 } else if (i->second->selected() && !extend) {
2531 remove_from_selection (i->second);
2537 MidiRegionView::remove_from_selection (NoteBase* ev)
2539 Selection::iterator i = _selection.find (ev);
2541 if (i != _selection.end()) {
2542 _selection.erase (i);
2543 if (_selection.empty() && _grabbed_keyboard) {
2545 Keyboard::magic_widget_drop_focus();
2546 _grabbed_keyboard = false;
2550 ev->set_selected (false);
2551 ev->hide_velocity ();
2553 if (_selection.empty()) {
2554 PublicEditor& editor (trackview.editor());
2555 editor.get_selection().remove (this);
2560 MidiRegionView::add_to_selection (NoteBase* ev)
2562 const bool selection_was_empty = _selection.empty();
2564 if (_selection.insert (ev).second) {
2565 ev->set_selected (true);
2566 start_playing_midi_note ((ev)->note());
2567 if (selection_was_empty && _entered) {
2568 // Grab keyboard for moving notes with arrow keys
2569 Keyboard::magic_widget_grab_focus();
2570 _grabbed_keyboard = true;
2574 if (selection_was_empty) {
2575 PublicEditor& editor (trackview.editor());
2576 editor.get_selection().add (this);
2581 MidiRegionView::earliest_in_selection ()
2583 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2585 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2586 if ((*i)->note()->time() < earliest) {
2587 earliest = (*i)->note()->time();
2595 MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
2597 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2598 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2599 TempoMap& tmap (editor->session()->tempo_map());
2600 PossibleChord to_play;
2601 Temporal::Beats earliest = earliest_in_selection();
2603 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2605 if (n->note()->time() == earliest) {
2606 to_play.push_back (n->note());
2608 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2610 if (midi_view()->note_mode() == Sustained) {
2611 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2612 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2614 /* Hit::x0() is offset by _position.x, unlike Note::x0() */
2615 Hit* hit = dynamic_cast<Hit*>(n);
2617 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2618 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2622 (*i)->move_event(dx, dy);
2625 if (midi_view()->note_mode() == Sustained) {
2626 Note* sus = dynamic_cast<Note*> (*i);
2627 double const len_dx = editor->sample_to_pixel_unrounded (
2628 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2630 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2634 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2636 if (to_play.size() > 1) {
2638 PossibleChord shifted;
2640 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2641 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2642 moved_note->set_note (moved_note->note() + cumulative_dy);
2643 shifted.push_back (moved_note);
2646 start_playing_midi_chord (shifted);
2648 } else if (!to_play.empty()) {
2650 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2651 moved_note->set_note (moved_note->note() + cumulative_dy);
2652 start_playing_midi_note (moved_note);
2658 MidiRegionView::copy_selection (NoteBase* primary)
2660 _copy_drag_events.clear ();
2662 if (_selection.empty()) {
2669 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2670 boost::shared_ptr<NoteType> g (new NoteType (*((*i)->note())));
2671 if (midi_view()->note_mode() == Sustained) {
2672 Note* n = new Note (*this, _note_group, g);
2673 update_sustained (n, false);
2676 Hit* h = new Hit (*this, _note_group, 10, g);
2677 update_hit (h, false);
2681 if ((*i) == primary) {
2685 _copy_drag_events.push_back (note);
2692 MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
2694 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2695 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2696 TempoMap& tmap (editor->session()->tempo_map());
2697 PossibleChord to_play;
2698 Temporal::Beats earliest = earliest_in_selection();
2700 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2702 if (n->note()->time() == earliest) {
2703 to_play.push_back (n->note());
2705 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2707 if (midi_view()->note_mode() == Sustained) {
2708 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2709 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2711 Hit* hit = dynamic_cast<Hit*>(n);
2713 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2714 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2718 (*i)->move_event(dx, dy);
2720 if (midi_view()->note_mode() == Sustained) {
2721 Note* sus = dynamic_cast<Note*> (*i);
2722 double const len_dx = editor->sample_to_pixel_unrounded (
2723 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2725 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2729 if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2731 if (to_play.size() > 1) {
2733 PossibleChord shifted;
2735 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2736 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2737 moved_note->set_note (moved_note->note() + cumulative_dy);
2738 shifted.push_back (moved_note);
2741 start_playing_midi_chord (shifted);
2743 } else if (!to_play.empty()) {
2745 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2746 moved_note->set_note (moved_note->note() + cumulative_dy);
2747 start_playing_midi_note (moved_note);
2753 MidiRegionView::note_dropped(NoteBase *, double d_qn, int8_t dnote, bool copy)
2755 uint8_t lowest_note_in_selection = 127;
2756 uint8_t highest_note_in_selection = 0;
2757 uint8_t highest_note_difference = 0;
2760 // find highest and lowest notes first
2762 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2763 uint8_t pitch = (*i)->note()->note();
2764 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2765 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2769 cerr << "dnote: " << (int) dnote << endl;
2770 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2771 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2772 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2773 << int(highest_note_in_selection) << endl;
2774 cerr << "selection size: " << _selection.size() << endl;
2775 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2778 // Make sure the note pitch does not exceed the MIDI standard range
2779 if (highest_note_in_selection + dnote > 127) {
2780 highest_note_difference = highest_note_in_selection - 127;
2783 start_note_diff_command (_("move notes"));
2785 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2787 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2793 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2795 uint8_t original_pitch = (*i)->note()->note();
2796 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2798 // keep notes in standard midi range
2799 clamp_to_0_127(new_pitch);
2801 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2802 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2804 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2808 clear_editor_note_selection ();
2810 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2811 uint8_t pitch = (*i)->note()->note();
2812 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2813 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2816 // Make sure the note pitch does not exceed the MIDI standard range
2817 if (highest_note_in_selection + dnote > 127) {
2818 highest_note_difference = highest_note_in_selection - 127;
2821 start_note_diff_command (_("copy notes"));
2823 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
2826 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2832 (*i)->note()->set_time (new_time);
2836 uint8_t original_pitch = (*i)->note()->note();
2837 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2839 (*i)->note()->set_note (new_pitch);
2841 // keep notes in standard midi range
2842 clamp_to_0_127(new_pitch);
2844 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2845 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2847 note_diff_add_note ((*i)->note(), true);
2852 _copy_drag_events.clear ();
2855 apply_diff (false, copy);
2857 // care about notes being moved beyond the upper/lower bounds on the canvas
2858 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2859 highest_note_in_selection > midi_stream_view()->highest_note()) {
2860 midi_stream_view()->set_note_range (MidiStreamView::ContentsRange);
2864 /** @param x Pixel relative to the region position.
2865 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2866 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2867 * @return Snapped sample relative to the region position.
2870 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2872 PublicEditor& editor (trackview.editor());
2873 return snap_sample_to_sample (editor.pixel_to_sample (x), ensure_snap).sample;
2876 /** @param x Pixel relative to the region position.
2877 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2878 * @return Snapped pixel relative to the region position.
2881 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2883 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2887 MidiRegionView::get_position_pixels()
2889 samplepos_t region_sample = get_position();
2890 return trackview.editor().sample_to_pixel(region_sample);
2894 MidiRegionView::get_end_position_pixels()
2896 samplepos_t sample = get_position() + get_duration ();
2897 return trackview.editor().sample_to_pixel(sample);
2901 MidiRegionView::source_beats_to_absolute_samples(Temporal::Beats beats) const
2903 /* the time converter will return the sample corresponding to `beats'
2904 relative to the start of the source. The start of the source
2905 is an implied position given by region->position - region->start
2907 const samplepos_t source_start = _region->position() - _region->start();
2908 return source_start + _source_relative_time_converter.to (beats);
2912 MidiRegionView::absolute_samples_to_source_beats(samplepos_t samples) const
2914 /* the `samples' argument needs to be converted into a sample count
2915 relative to the start of the source before being passed in to the
2918 const samplepos_t source_start = _region->position() - _region->start();
2919 return _source_relative_time_converter.from (samples - source_start);
2923 MidiRegionView::region_beats_to_region_samples(Temporal::Beats beats) const
2925 return _region_relative_time_converter.to(beats);
2929 MidiRegionView::region_samples_to_region_beats(samplepos_t samples) const
2931 return _region_relative_time_converter.from(samples);
2935 MidiRegionView::region_samples_to_region_beats_double (samplepos_t samples) const
2937 return _region_relative_time_converter_double.from(samples);
2941 MidiRegionView::begin_resizing (bool /*at_front*/)
2943 _resize_data.clear();
2945 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2946 Note *note = dynamic_cast<Note*> (*i);
2948 // only insert CanvasNotes into the map
2950 NoteResizeData *resize_data = new NoteResizeData();
2951 resize_data->note = note;
2953 // create a new SimpleRect from the note which will be the resize preview
2954 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2955 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2957 // calculate the colors: get the color settings
2958 uint32_t fill_color = NoteBase::meter_style_fill_color (note->note()->velocity(), true);
2960 // make the resize preview notes more transparent and bright
2961 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2963 // calculate color based on note velocity
2964 resize_rect->set_fill_color (UINT_INTERPOLATE(
2965 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2969 resize_rect->set_outline_color (NoteBase::calculate_outline (
2970 UIConfiguration::instance().color ("midi note selected outline")));
2972 resize_data->resize_rect = resize_rect;
2973 _resize_data.push_back(resize_data);
2978 /** Update resizing notes while user drags.
2979 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2980 * @param at_front which end of the note (true == note on, false == note off)
2981 * @param delta_x change in mouse position since the start of the drag
2982 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2983 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2984 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2985 * as the \a primary note.
2986 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2987 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2990 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2992 TempoMap& tmap (trackview.session()->tempo_map());
2993 bool cursor_set = false;
2994 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2996 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2997 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2998 Note* canvas_note = (*i)->note;
3003 current_x = canvas_note->x0() + delta_x + snap_delta;
3005 current_x = primary->x0() + delta_x + snap_delta;
3009 current_x = canvas_note->x1() + delta_x + snap_delta;
3011 current_x = primary->x1() + delta_x + snap_delta;
3015 if (current_x < 0) {
3016 /* This works even with snapping because RegionView::snap_sample_to_sample()
3017 * snaps forward if the snapped sample is before the beginning of the region
3021 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3022 current_x = trackview.editor().sample_to_pixel(_region->length());
3027 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3029 resize_rect->set_x0 (current_x - snap_delta);
3031 resize_rect->set_x1 (canvas_note->x1());
3034 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3036 resize_rect->set_x1 (current_x - snap_delta);
3038 resize_rect->set_x0 (canvas_note->x0());
3042 /* Convert snap delta from pixels to beats. */
3043 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3044 double snap_delta_beats = 0.0;
3047 /* negative beat offsets aren't allowed */
3048 if (snap_delta_samps > 0) {
3049 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3050 } else if (snap_delta_samps < 0) {
3051 snap_delta_beats = region_samples_to_region_beats_double (- snap_delta_samps);
3056 int32_t divisions = 0;
3059 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
3060 divisions = trackview.editor().get_grid_music_divisions (0);
3062 snapped_x = trackview.editor ().pixel_to_sample (current_x);
3065 const Temporal::Beats beats = Temporal::Beats (tmap.exact_beat_at_sample (snapped_x + midi_region()->position(), divisions)
3066 - midi_region()->beat())
3067 + midi_region()->start_beats();
3069 Temporal::Beats len = Temporal::Beats();
3072 if (beats < canvas_note->note()->end_time()) {
3073 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
3074 len += canvas_note->note()->length();
3077 if (beats >= canvas_note->note()->time()) {
3078 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
3082 len = std::max(Temporal::Beats(1 / 512.0), len);
3085 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
3086 show_verbose_cursor (buf, 0, 0);
3090 trackview.editor().set_snapped_cursor_position ( snapped_x + midi_region()->position() );
3097 /** Finish resizing notes when the user releases the mouse button.
3098 * Parameters the same as for \a update_resizing().
3101 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
3103 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
3104 TempoMap& tmap (trackview.session()->tempo_map());
3106 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
3107 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
3109 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3110 Note* canvas_note = (*i)->note;
3111 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
3113 /* Get the new x position for this resize, which is in pixels relative
3114 * to the region position.
3121 current_x = canvas_note->x0() + delta_x + snap_delta;
3123 current_x = primary->x0() + delta_x + snap_delta;
3127 current_x = canvas_note->x1() + delta_x + snap_delta;
3129 current_x = primary->x1() + delta_x + snap_delta;
3133 if (current_x < 0) {
3136 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3137 current_x = trackview.editor().sample_to_pixel(_region->length());
3140 /* Convert snap delta from pixels to beats with sign. */
3141 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3142 double snap_delta_beats = 0.0;
3145 if (snap_delta_samps > 0) {
3146 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3147 } else if (snap_delta_samps < 0) {
3148 snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
3152 uint32_t divisions = 0;
3153 /* Convert the new x position to a sample within the source */
3154 samplepos_t current_fr;
3156 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
3157 divisions = trackview.editor().get_grid_music_divisions (0);
3159 current_fr = trackview.editor().pixel_to_sample (current_x);
3162 /* and then to beats */
3163 const double e_qaf = tmap.exact_qn_at_sample (current_fr + midi_region()->position(), divisions);
3164 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
3165 const Temporal::Beats x_beats = Temporal::Beats (e_qaf - quarter_note_start);
3167 if (at_front && x_beats < canvas_note->note()->end_time()) {
3168 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
3169 Temporal::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
3170 len += canvas_note->note()->length();
3173 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3178 Temporal::Beats len = std::max(Temporal::Beats(1 / 512.0),
3179 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
3180 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3187 _resize_data.clear();
3192 MidiRegionView::abort_resizing ()
3194 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3195 delete (*i)->resize_rect;
3199 _resize_data.clear ();
3203 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
3205 uint8_t new_velocity;
3208 new_velocity = event->note()->velocity() + velocity;
3209 clamp_to_0_127(new_velocity);
3211 new_velocity = velocity;
3214 event->set_selected (event->selected()); // change color
3216 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
3220 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
3225 new_note = event->note()->note() + note;
3230 clamp_to_0_127 (new_note);
3231 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3235 MidiRegionView::trim_note (NoteBase* event, Temporal::Beats front_delta, Temporal::Beats end_delta)
3237 bool change_start = false;
3238 bool change_length = false;
3239 Temporal::Beats new_start;
3240 Temporal::Beats new_length;
3242 /* NOTE: the semantics of the two delta arguments are slightly subtle:
3244 front_delta: if positive - move the start of the note later in time (shortening it)
3245 if negative - move the start of the note earlier in time (lengthening it)
3247 end_delta: if positive - move the end of the note later in time (lengthening it)
3248 if negative - move the end of the note earlier in time (shortening it)
3251 if (!!front_delta) {
3252 if (front_delta < 0) {
3254 if (event->note()->time() < -front_delta) {
3255 new_start = Temporal::Beats();
3257 new_start = event->note()->time() + front_delta; // moves earlier
3260 /* start moved toward zero, so move the end point out to where it used to be.
3261 Note that front_delta is negative, so this increases the length.
3264 new_length = event->note()->length() - front_delta;
3265 change_start = true;
3266 change_length = true;
3270 Temporal::Beats new_pos = event->note()->time() + front_delta;
3272 if (new_pos < event->note()->end_time()) {
3273 new_start = event->note()->time() + front_delta;
3274 /* start moved toward the end, so move the end point back to where it used to be */
3275 new_length = event->note()->length() - front_delta;
3276 change_start = true;
3277 change_length = true;
3284 bool can_change = true;
3285 if (end_delta < 0) {
3286 if (event->note()->length() < -end_delta) {
3292 new_length = event->note()->length() + end_delta;
3293 change_length = true;
3298 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3301 if (change_length) {
3302 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3307 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3309 uint8_t new_channel;
3313 if (event->note()->channel() < -chn) {
3316 new_channel = event->note()->channel() + chn;
3319 new_channel = event->note()->channel() + chn;
3322 new_channel = (uint8_t) chn;
3325 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3329 MidiRegionView::change_note_time (NoteBase* event, Temporal::Beats delta, bool relative)
3331 Temporal::Beats new_time;
3335 if (event->note()->time() < -delta) {
3336 new_time = Temporal::Beats();
3338 new_time = event->note()->time() + delta;
3341 new_time = event->note()->time() + delta;
3347 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3351 MidiRegionView::change_note_length (NoteBase* event, Temporal::Beats t)
3353 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3357 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3362 if (_selection.empty()) {
3377 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3378 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3384 start_note_diff_command (_("change velocities"));
3386 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3387 Selection::iterator next = i;
3391 if (i == _selection.begin()) {
3392 change_note_velocity (*i, delta, true);
3393 value = (*i)->note()->velocity() + delta;
3395 change_note_velocity (*i, value, false);
3399 change_note_velocity (*i, delta, true);
3408 if (!_selection.empty()) {
3410 snprintf (buf, sizeof (buf), "Vel %d",
3411 (int) (*_selection.begin())->note()->velocity());
3412 show_verbose_cursor (buf, 10, 10);
3418 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3420 if (_selection.empty()) {
3437 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3439 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3443 if ((int8_t) (*i)->note()->note() + delta > 127) {
3450 start_note_diff_command (_("transpose"));
3452 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3453 Selection::iterator next = i;
3455 change_note_note (*i, delta, true);
3463 MidiRegionView::change_note_lengths (bool fine, bool shorter, Temporal::Beats delta, bool start, bool end)
3467 delta = Temporal::Beats(1.0/128.0);
3469 /* grab the current grid distance */
3470 delta = get_grid_beats(_region->position());
3478 start_note_diff_command (_("change note lengths"));
3480 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3481 Selection::iterator next = i;
3484 /* note the negation of the delta for start */
3487 (start ? -delta : Temporal::Beats()),
3488 (end ? delta : Temporal::Beats()));
3497 MidiRegionView::nudge_notes (bool forward, bool fine)
3499 if (_selection.empty()) {
3503 /* pick a note as the point along the timeline to get the nudge distance.
3504 its not necessarily the earliest note, so we may want to pull the notes out
3505 into a vector and sort before using the first one.
3508 const samplepos_t ref_point = source_beats_to_absolute_samples ((*(_selection.begin()))->note()->time());
3509 Temporal::Beats delta;
3513 /* non-fine, move by 1 bar regardless of snap */
3514 delta = Temporal::Beats(trackview.session()->tempo_map().meter_at_sample (ref_point).divisions_per_bar());
3516 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3518 /* grid is off - use nudge distance */
3521 const samplecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3522 delta = region_samples_to_region_beats (fabs ((double)distance));
3528 MusicSample next_pos (ref_point, 0);
3530 if (max_samplepos - 1 < next_pos.sample) {
3531 next_pos.sample += 1;
3534 if (next_pos.sample == 0) {
3537 next_pos.sample -= 1;
3540 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), SnapToGrid_Unscaled, false);
3541 const samplecnt_t distance = ref_point - next_pos.sample;
3542 delta = region_samples_to_region_beats (fabs ((double)distance));
3553 start_note_diff_command (_("nudge"));
3555 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3556 Selection::iterator next = i;
3558 change_note_time (*i, delta, true);
3566 MidiRegionView::change_channel(uint8_t channel)
3568 start_note_diff_command(_("change channel"));
3569 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3570 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3578 MidiRegionView::note_entered(NoteBase* ev)
3582 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3584 if (_mouse_state == SelectTouchDragging) {
3586 note_selected (ev, true);
3588 } else if (editor->current_mouse_mode() == MouseContent) {
3590 remove_ghost_note ();
3591 show_verbose_cursor (ev->note ());
3593 } else if (editor->current_mouse_mode() == MouseDraw) {
3595 remove_ghost_note ();
3596 show_verbose_cursor (ev->note ());
3601 MidiRegionView::note_left (NoteBase*)
3605 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3606 (*i)->hide_velocity ();
3609 hide_verbose_cursor ();
3613 MidiRegionView::patch_entered (PatchChange* p)
3616 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3617 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3618 << _("Channel ") << ((int) p->patch()->channel() + 1);
3619 show_verbose_cursor (s.str(), 10, 20);
3620 p->item().grab_focus();
3624 MidiRegionView::patch_left (PatchChange *)
3626 hide_verbose_cursor ();
3627 /* focus will transfer back via the enter-notify event sent to this
3633 MidiRegionView::sysex_entered (SysEx* p)
3637 // need a way to extract text from p->_flag->_text
3639 // show_verbose_cursor (s.str(), 10, 20);
3640 p->item().grab_focus();
3644 MidiRegionView::sysex_left (SysEx *)
3646 hide_verbose_cursor ();
3647 /* focus will transfer back via the enter-notify event sent to this
3653 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3655 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3656 Editing::MouseMode mm = editor->current_mouse_mode();
3657 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3659 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3660 if (can_set_cursor && ctx) {
3661 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3662 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3663 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3664 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3666 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3672 MidiRegionView::get_fill_color() const
3674 const std::string mod_name = (_dragging ? "dragging region" :
3675 trackview.editor().internal_editing() ? "editable region" :
3678 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3679 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3680 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3681 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3683 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3687 MidiRegionView::midi_channel_mode_changed ()
3689 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3690 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3691 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3693 if (mode == ForceChannel) {
3694 mask = 0xFFFF; // Show all notes as active (below)
3697 // Update notes for selection
3698 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3699 i->second->on_channel_selection_change (mask);
3702 _patch_changes.clear ();
3703 display_patch_changes ();
3707 MidiRegionView::instrument_settings_changed ()
3713 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3715 if (_selection.empty()) {
3719 PublicEditor& editor (trackview.editor());
3723 /* XXX what to do ? */
3727 editor.get_cut_buffer().add (selection_as_cut_buffer());
3735 start_note_diff_command();
3737 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3744 note_diff_remove_note (*i);
3754 MidiRegionView::selection_as_cut_buffer () const
3758 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3759 NoteType* n = (*i)->note().get();
3760 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3763 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3769 /** This method handles undo */
3771 MidiRegionView::paste (samplepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3773 bool commit = false;
3774 // Paste notes, if available
3775 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3776 if (m != selection.midi_notes.end()) {
3777 ctx.counts.increase_n_notes();
3778 if (!(*m)->empty()) {
3781 paste_internal(pos, ctx.count, ctx.times, **m);
3784 // Paste control points to automation children, if available
3785 typedef RouteTimeAxisView::AutomationTracks ATracks;
3786 const ATracks& atracks = midi_view()->automation_tracks();
3787 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3788 if (a->second->paste(pos, selection, ctx, sub_num)) {
3790 trackview.editor().begin_reversible_command (Operations::paste);
3797 trackview.editor().commit_reversible_command ();
3802 /** This method handles undo */
3804 MidiRegionView::paste_internal (samplepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3810 start_note_diff_command (_("paste"));
3812 const Temporal::Beats snap_beats = get_grid_beats(pos);
3813 const Temporal::Beats first_time = (*mcb.notes().begin())->time();
3814 const Temporal::Beats last_time = (*mcb.notes().rbegin())->end_time();
3815 const Temporal::Beats duration = last_time - first_time;
3816 const Temporal::Beats snap_duration = duration.snap_to(snap_beats);
3817 const Temporal::Beats paste_offset = snap_duration * paste_count;
3818 const Temporal::Beats quarter_note = absolute_samples_to_source_beats(pos) + paste_offset;
3819 Temporal::Beats end_point = Temporal::Beats();
3821 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3824 duration, pos, _region->position(),
3827 clear_editor_note_selection ();
3829 for (int n = 0; n < (int) times; ++n) {
3831 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3833 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3834 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3835 copied_note->set_id (Evoral::next_event_id());
3837 /* make all newly added notes selected */
3839 note_diff_add_note (copied_note, true);
3840 end_point = copied_note->end_time();
3844 /* if we pasted past the current end of the region, extend the region */
3846 samplepos_t end_sample = source_beats_to_absolute_samples (end_point);
3847 samplepos_t region_end = _region->position() + _region->length() - 1;
3849 if (end_sample > region_end) {
3851 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_sample));
3853 _region->clear_changes ();
3854 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3855 _region->set_length (end_sample - _region->position(), trackview.editor().get_grid_music_divisions (0));
3856 trackview.session()->add_command (new StatefulDiffCommand (_region));
3862 struct EventNoteTimeEarlyFirstComparator {
3863 bool operator() (NoteBase* a, NoteBase* b) {
3864 return a->note()->time() < b->note()->time();
3869 MidiRegionView::goto_next_note (bool add_to_selection)
3871 bool use_next = false;
3873 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3874 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3875 NoteBase* first_note = 0;
3877 MidiModel::ReadLock lock(_model->read_lock());
3878 MidiModel::Notes& notes (_model->notes());
3880 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
3882 if ((cne = find_canvas_note (*n))) {
3884 if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
3888 if (cne->selected()) {
3891 } else if (use_next) {
3892 if (channel_mask & (1 << (*n)->channel())) {
3893 if (!add_to_selection) {
3894 unique_select (cne);
3896 note_selected (cne, true, false);
3905 /* use the first one */
3907 if (!_events.empty() && first_note) {
3908 unique_select (first_note);
3913 MidiRegionView::goto_previous_note (bool add_to_selection)
3915 bool use_next = false;
3917 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3918 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3919 NoteBase* last_note = 0;
3921 MidiModel::ReadLock lock(_model->read_lock());
3922 MidiModel::Notes& notes (_model->notes());
3924 for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
3926 if ((cne = find_canvas_note (*n))) {
3928 if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
3932 if (cne->selected()) {
3936 } else if (use_next) {
3937 if (channel_mask & (1 << (*n)->channel())) {
3938 if (!add_to_selection) {
3939 unique_select (cne);
3941 note_selected (cne, true, false);
3950 /* use the last one */
3952 if (!_events.empty() && last_note) {
3953 unique_select (last_note);
3958 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3960 bool had_selected = false;
3962 /* we previously time sorted events here, but Notes is a multiset sorted by time */
3964 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3965 if (i->second->selected()) {
3966 selected.insert (i->first);
3967 had_selected = true;
3971 if (allow_all_if_none_selected && !had_selected) {
3972 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3973 selected.insert (i->first);
3979 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3981 x = std::max(0.0, x);
3983 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3988 _note_group->canvas_to_item (x, y);
3990 PublicEditor& editor = trackview.editor ();
3992 samplepos_t const unsnapped_sample = editor.pixel_to_sample (x);
3994 const int32_t divisions = editor.get_grid_music_divisions (state);
3995 const bool shift_snap = midi_view()->note_mode() != Percussive;
3996 const Temporal::Beats snapped_beats = snap_sample_to_grid_underneath (unsnapped_sample, divisions, shift_snap);
3998 /* prevent Percussive mode from displaying a ghost hit at region end */
3999 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
4000 _ghost_note->hide();
4001 hide_verbose_cursor ();
4005 /* ghost note may have been snapped before region */
4006 if (_ghost_note && snapped_beats.to_double() < 0.0) {
4007 _ghost_note->hide();
4010 } else if (_ghost_note) {
4011 _ghost_note->show();
4014 /* calculate time in beats relative to start of source */
4015 const Temporal::Beats length = get_grid_beats(unsnapped_sample + _region->position());
4017 _ghost_note->note()->set_time (snapped_beats);
4018 _ghost_note->note()->set_length (length);
4019 _ghost_note->note()->set_note (y_to_note (y));
4020 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
4021 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
4022 /* the ghost note does not appear in ghost regions, so pass false in here */
4023 update_note (_ghost_note, false);
4025 show_verbose_cursor (_ghost_note->note ());
4029 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
4031 remove_ghost_note ();
4033 boost::shared_ptr<NoteType> g (new NoteType);
4034 if (midi_view()->note_mode() == Sustained) {
4035 _ghost_note = new Note (*this, _note_group, g);
4037 _ghost_note = new Hit (*this, _note_group, 10, g);
4039 _ghost_note->set_ignore_events (true);
4040 _ghost_note->set_outline_color (0x000000aa);
4041 update_ghost_note (x, y, state);
4042 _ghost_note->show ();
4044 show_verbose_cursor (_ghost_note->note ());
4048 MidiRegionView::remove_ghost_note ()
4055 MidiRegionView::hide_verbose_cursor ()
4057 trackview.editor().verbose_cursor()->hide ();
4058 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4060 mtv->set_note_highlight (NO_MIDI_NOTE);
4065 MidiRegionView::snap_changed ()
4071 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
4075 MidiRegionView::drop_down_keys ()
4077 _mouse_state = None;
4081 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
4083 /* XXX: This is dead code. What was it for? */
4085 double note = y_to_note(y);
4087 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4089 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
4091 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
4092 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
4093 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
4094 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
4099 bool add_mrv_selection = false;
4101 if (_selection.empty()) {
4102 add_mrv_selection = true;
4105 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
4106 if (_selection.insert (i->second).second) {
4107 i->second->set_selected (true);
4111 if (add_mrv_selection) {
4112 PublicEditor& editor (trackview.editor());
4113 editor.get_selection().add (this);
4118 MidiRegionView::color_handler ()
4120 RegionView::color_handler ();
4122 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
4123 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
4125 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
4126 i->second->set_selected (i->second->selected()); // will change color
4129 /* XXX probably more to do here */
4133 MidiRegionView::enable_display (bool yn)
4135 RegionView::enable_display (yn);
4139 MidiRegionView::show_step_edit_cursor (Temporal::Beats pos)
4141 if (_step_edit_cursor == 0) {
4142 ArdourCanvas::Item* const group = get_canvas_group();
4144 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
4145 _step_edit_cursor->set_y0 (0);
4146 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
4147 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
4148 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
4151 move_step_edit_cursor (pos);
4152 _step_edit_cursor->show ();
4156 MidiRegionView::move_step_edit_cursor (Temporal::Beats pos)
4158 _step_edit_cursor_position = pos;
4160 if (_step_edit_cursor) {
4161 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_samples (pos));
4162 _step_edit_cursor->set_x0 (pixel);
4163 set_step_edit_cursor_width (_step_edit_cursor_width);
4168 MidiRegionView::hide_step_edit_cursor ()
4170 if (_step_edit_cursor) {
4171 _step_edit_cursor->hide ();
4176 MidiRegionView::set_step_edit_cursor_width (Temporal::Beats beats)
4178 _step_edit_cursor_width = beats;
4180 if (_step_edit_cursor) {
4181 _step_edit_cursor->set_x1 (_step_edit_cursor->x0()
4182 + trackview.editor().sample_to_pixel (
4183 region_beats_to_region_samples (_step_edit_cursor_position + beats)
4184 - region_beats_to_region_samples (_step_edit_cursor_position)));
4188 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
4189 * @param w Source that the data will end up in.
4192 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
4194 if (!_active_notes) {
4195 /* we aren't actively being recorded to */
4199 boost::shared_ptr<MidiSource> src = w.lock ();
4200 if (!src || src != midi_region()->midi_source()) {
4201 /* recorded data was not destined for our source */
4205 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
4207 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
4209 samplepos_t back = max_samplepos;
4211 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
4212 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
4214 if (ev.is_channel_event()) {
4215 if (get_channel_mode() == FilterChannels) {
4216 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
4222 /* convert from session samples to source beats */
4223 Temporal::Beats const time_beats = _source_relative_time_converter.from(
4224 ev.time() - src->natural_position() + _region->start());
4226 if (ev.type() == MIDI_CMD_NOTE_ON) {
4228 boost::shared_ptr<NoteType> note (new NoteType (ev.channel(), time_beats, std::numeric_limits<Temporal::Beats>::max() - time_beats, ev.note(), ev.velocity()));
4230 assert (note->end_time() == std::numeric_limits<Temporal::Beats>::max());
4232 add_note (note, true);
4234 /* fix up our note range */
4235 if (ev.note() < _current_range_min) {
4236 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
4237 } else if (ev.note() > _current_range_max) {
4238 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
4241 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
4242 resolve_note (ev.note (), time_beats);
4248 midi_stream_view()->check_record_layers (region(), back);
4252 MidiRegionView::trim_front_starting ()
4254 /* We used to eparent the note group to the region view's parent, so that it didn't change.
4260 MidiRegionView::trim_front_ending ()
4262 if (_region->start() < 0) {
4263 /* Trim drag made start time -ve; fix this */
4264 midi_region()->fix_negative_start ();
4269 MidiRegionView::edit_patch_change (PatchChange* pc)
4271 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4273 int response = d.run();
4276 case Gtk::RESPONSE_ACCEPT:
4278 case Gtk::RESPONSE_REJECT:
4279 delete_patch_change (pc);
4285 change_patch_change (pc->patch(), d.patch ());
4289 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4292 // sysyex object doesn't have a pointer to a sysex event
4293 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4294 // c->remove (sysex->sysex());
4295 // _model->apply_command (*trackview.session(), c);
4297 //_sys_exes.clear ();
4298 // display_sysexes();
4302 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4304 using namespace MIDI::Name;
4307 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4309 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4311 MIDI::Name::PatchPrimaryKey patch_key;
4312 get_patch_key_at(n->time(), n->channel(), patch_key);
4313 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4316 patch_key.program(),
4322 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4324 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4325 (int) n->channel() + 1,
4326 (int) n->velocity());
4332 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4333 uint8_t new_value) const
4335 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4337 mtv->set_note_highlight (new_value);
4340 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4344 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4346 show_verbose_cursor_for_new_note_value(n, n->note());
4350 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4352 trackview.editor().verbose_cursor()->set (text);
4353 trackview.editor().verbose_cursor()->show ();
4354 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4358 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4360 if (_model->notes().empty()) {
4361 return 0x40; // No notes, use default
4364 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4365 if (m == _model->notes().begin()) {
4366 // Before the start, use the velocity of the first note
4367 return (*m)->velocity();
4368 } else if (m == _model->notes().end()) {
4369 // Past the end, use the velocity of the last note
4371 return (*m)->velocity();
4374 // Interpolate velocity of surrounding notes
4375 MidiModel::Notes::const_iterator n = m;
4378 const double frac = ((time - (*n)->time()).to_double() /
4379 ((*m)->time() - (*n)->time()).to_double());
4381 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4384 /** @param p A session samplepos.
4385 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4386 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4387 * @return beat duration of p snapped to the grid subdivision underneath it.
4390 MidiRegionView::snap_sample_to_grid_underneath (samplepos_t p, int32_t divisions, bool shift_snap) const
4392 TempoMap& map (trackview.session()->tempo_map());
4393 double eqaf = map.exact_qn_at_sample (p + _region->position(), divisions);
4395 if (divisions != 0 && shift_snap) {
4396 const double qaf = map.quarter_note_at_sample (p + _region->position());
4397 /* Hack so that we always snap to the note that we are over, instead of snapping
4398 to the next one if we're more than halfway through the one we're over.
4400 const Temporal::Beats grid_beats = get_grid_beats (p + _region->position());
4401 const double rem = eqaf - qaf;
4403 eqaf -= grid_beats.to_double();
4406 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4408 return Temporal::Beats (eqaf - session_start_off);
4412 MidiRegionView::get_channel_mode () const
4414 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4415 return rtav->midi_track()->get_playback_channel_mode();
4419 MidiRegionView::get_selected_channels () const
4421 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4422 return rtav->midi_track()->get_playback_channel_mask();
4427 MidiRegionView::get_grid_beats(samplepos_t pos) const
4429 PublicEditor& editor = trackview.editor();
4430 bool success = false;
4431 Temporal::Beats beats = editor.get_grid_type_as_beats (success, pos);
4433 beats = Temporal::Beats(1);
4438 MidiRegionView::y_to_note (double y) const
4440 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4441 + _current_range_min;
4445 } else if (n > 127) {
4449 /* min due to rounding and/or off-by-one errors */
4450 return min ((uint8_t) n, _current_range_max);
4454 MidiRegionView::note_to_y(uint8_t note) const
4456 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
4460 MidiRegionView::session_relative_qn (double qn) const
4462 return qn + (region()->quarter_note() - midi_region()->start_beats());