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 0) alone since
1198 they are being extended. */
1199 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1200 if (i->second->note()->length() > 0) {
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(
1639 end_time - _active_notes[note]->note()->time());
1641 /* End time is relative to the region being recorded. */
1642 const samplepos_t end_time_samples = region_beats_to_region_samples(end_time);
1644 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_samples));
1645 _active_notes[note]->set_outline_all ();
1646 _active_notes[note] = 0;
1651 /** Extend active notes to rightmost edge of region (if length is changed)
1654 MidiRegionView::extend_active_notes()
1656 if (!_active_notes) {
1660 for (unsigned i = 0; i < 128; ++i) {
1661 if (_active_notes[i]) {
1662 _active_notes[i]->set_x1(
1663 trackview.editor().sample_to_pixel(_region->length()));
1669 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1671 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1675 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1677 if (!route_ui || !route_ui->midi_track()) {
1681 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1685 /* NotePlayer deletes itself */
1689 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1691 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1692 start_playing_midi_chord(notes);
1696 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1698 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1702 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1704 if (!route_ui || !route_ui->midi_track()) {
1708 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1710 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1719 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1721 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1723 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1724 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1725 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1727 visible = (note->note() >= _current_range_min) &&
1728 (note->note() <= _current_range_max);
1734 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1738 if ((sus = dynamic_cast<Note*>(note))) {
1739 update_sustained(sus, update_ghost_regions);
1740 } else if ((hit = dynamic_cast<Hit*>(note))) {
1741 update_hit(hit, update_ghost_regions);
1745 /** Update a canvas note's size from its model note.
1746 * @param ev Canvas note to update.
1747 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1750 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1752 TempoMap& map (trackview.session()->tempo_map());
1753 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1754 boost::shared_ptr<NoteType> note = ev->note();
1756 const double session_source_start = _region->quarter_note() - mr->start_beats();
1757 const samplepos_t note_start_samples = map.sample_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1759 const double x0 = trackview.editor().sample_to_pixel (note_start_samples);
1761 const double y0 = 1 + floor(note_to_y(note->note()));
1764 /* trim note display to not overlap the end of its region */
1765 if (note->length().to_double() > 0.0) {
1766 double note_end_time = note->end_time().to_double();
1768 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1769 note_end_time = mr->start_beats() + mr->length_beats();
1772 const samplepos_t note_end_samples = map.sample_at_quarter_note (session_source_start + note_end_time) - _region->position();
1774 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_samples)) - 1;
1776 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1779 y1 = y0 + std::max(1., floor(note_height()) - 1);
1781 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1782 ev->set_velocity (note->velocity()/127.0);
1784 if (!note->length()) {
1785 if (_active_notes && note->note() < 128) {
1786 Note* const old_rect = _active_notes[note->note()];
1788 /* There is an active note on this key, so we have a stuck
1789 note. Finish the old rectangle here. */
1790 old_rect->set_x1 (x1);
1791 old_rect->set_outline_all ();
1793 _active_notes[note->note()] = ev;
1795 /* outline all but right edge */
1796 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1797 ArdourCanvas::Rectangle::TOP|
1798 ArdourCanvas::Rectangle::LEFT|
1799 ArdourCanvas::Rectangle::BOTTOM));
1801 /* outline all edges */
1802 ev->set_outline_all ();
1805 // Update color in case velocity has changed
1806 const uint32_t base_col = ev->base_color();
1807 ev->set_fill_color(base_col);
1808 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1813 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1815 boost::shared_ptr<NoteType> note = ev->note();
1817 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1818 const samplepos_t note_start_samples = trackview.session()->tempo_map().sample_at_quarter_note (note_time_qn) - _region->position();
1820 const double x = trackview.editor().sample_to_pixel(note_start_samples);
1821 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1822 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1824 // see DnD note in MidiRegionView::apply_note_range() above
1825 if (y <= 0 || y >= _height) {
1831 ev->set_position (ArdourCanvas::Duple (x, y));
1832 ev->set_height (diamond_size);
1834 // Update color in case velocity has changed
1835 const uint32_t base_col = ev->base_color();
1836 ev->set_fill_color(base_col);
1837 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1841 /** Add a MIDI note to the view (with length).
1843 * If in sustained mode, notes with length 0 will be considered active
1844 * notes, and resolve_note should be called when the corresponding note off
1845 * event arrives, to properly display the note.
1848 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1850 NoteBase* event = 0;
1852 if (midi_view()->note_mode() == Sustained) {
1854 Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak
1856 update_sustained (ev_rect);
1860 } else if (midi_view()->note_mode() == Percussive) {
1862 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1864 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak
1866 update_hit (ev_diamond);
1875 MidiGhostRegion* gr;
1877 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1878 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1879 gr->add_note(event);
1883 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1884 note_selected(event, true);
1887 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1888 event->show_velocity();
1891 event->on_channel_selection_change (get_selected_channels());
1892 _events.insert (make_pair (event->note(), event));
1901 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1902 MidiStreamView* const view = mtv->midi_view();
1904 view->update_note_range (note->note());
1909 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1910 Temporal::Beats pos, Temporal::Beats len)
1912 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1914 /* potentially extend region to hold new note */
1916 samplepos_t end_sample = source_beats_to_absolute_samples (new_note->end_time());
1917 samplepos_t region_end = _region->last_sample();
1919 if (end_sample > region_end) {
1920 /* XX sets length in beats from audio space. make musical */
1921 _region->set_length (end_sample - _region->position(), 0);
1924 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1925 MidiStreamView* const view = mtv->midi_view();
1927 view->update_note_range(new_note->note());
1929 _marked_for_selection.clear ();
1931 start_note_diff_command (_("step add"));
1933 clear_editor_note_selection ();
1934 note_diff_add_note (new_note, true, false);
1938 // last_step_edit_note = new_note;
1942 MidiRegionView::step_sustain (Temporal::Beats beats)
1944 change_note_lengths (false, false, beats, false, true);
1947 /** Add a new patch change flag to the canvas.
1948 * @param patch the patch change to add
1949 * @param the text to display in the flag
1950 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1953 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1955 samplecnt_t region_samples = source_beats_to_region_samples (patch->time());
1956 const double x = trackview.editor().sample_to_pixel (region_samples);
1958 double const height = midi_stream_view()->contents_height();
1960 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1961 // so we need to do something more sophisticated to keep its color
1962 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1964 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1965 new PatchChange(*this, group,
1971 _patch_change_outline,
1975 if (patch_change->item().width() < _pixel_width) {
1976 // Show unless patch change is beyond the region bounds
1977 if (region_samples < 0 || region_samples >= _region->length()) {
1978 patch_change->hide();
1980 patch_change->show();
1983 patch_change->hide ();
1986 _patch_changes.insert (make_pair (patch, patch_change));
1990 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
1992 /* remove the canvas item */
1993 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1994 if (x->second->patch() == pc->patch()) {
1995 _patch_changes.erase (x);
2001 MIDI::Name::PatchPrimaryKey
2002 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
2004 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
2007 /// Return true iff @p pc applies to the given time on the given channel.
2009 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Temporal::Beats time, uint8_t channel)
2011 return pc->time() <= time && pc->channel() == channel;
2015 MidiRegionView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
2017 // The earliest event not before time
2018 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
2020 // Go backwards until we find the latest PC for this channel, or the start
2021 while (i != _model->patch_changes().begin() &&
2022 (i == _model->patch_changes().end() ||
2023 !patch_applies(*i, time, channel))) {
2027 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
2028 key.set_bank((*i)->bank());
2029 key.set_program((*i)->program ());
2037 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
2039 string name = _("alter patch change");
2040 trackview.editor().begin_reversible_command (name);
2042 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2044 if (pc.patch()->program() != new_patch.program()) {
2045 c->change_program (pc.patch (), new_patch.program());
2048 int const new_bank = new_patch.bank();
2049 if (pc.patch()->bank() != new_bank) {
2050 c->change_bank (pc.patch (), new_bank);
2053 _model->apply_command (*trackview.session(), c);
2054 trackview.editor().commit_reversible_command ();
2056 remove_canvas_patch_change (&pc);
2057 display_patch_changes ();
2061 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Temporal::Beats> & new_change)
2063 string name = _("alter patch change");
2064 trackview.editor().begin_reversible_command (name);
2065 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2067 if (old_change->time() != new_change.time()) {
2068 c->change_time (old_change, new_change.time());
2071 if (old_change->channel() != new_change.channel()) {
2072 c->change_channel (old_change, new_change.channel());
2075 if (old_change->program() != new_change.program()) {
2076 c->change_program (old_change, new_change.program());
2079 if (old_change->bank() != new_change.bank()) {
2080 c->change_bank (old_change, new_change.bank());
2083 _model->apply_command (*trackview.session(), c);
2084 trackview.editor().commit_reversible_command ();
2086 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2087 if (x->second->patch() == old_change) {
2088 _patch_changes.erase (x);
2093 display_patch_changes ();
2096 /** Add a patch change to the region.
2097 * @param t Time in samples relative to region position
2098 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2099 * MidiTimeAxisView::get_channel_for_add())
2102 MidiRegionView::add_patch_change (samplecnt_t t, Evoral::PatchChange<Temporal::Beats> const & patch)
2104 string name = _("add patch change");
2106 trackview.editor().begin_reversible_command (name);
2107 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2108 c->add (MidiModel::PatchChangePtr (
2109 new Evoral::PatchChange<Temporal::Beats> (
2110 absolute_samples_to_source_beats (_region->position() + t),
2111 patch.channel(), patch.program(), patch.bank()
2116 _model->apply_command (*trackview.session(), c);
2117 trackview.editor().commit_reversible_command ();
2119 display_patch_changes ();
2123 MidiRegionView::move_patch_change (PatchChange& pc, Temporal::Beats t)
2125 trackview.editor().begin_reversible_command (_("move patch change"));
2126 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2127 c->change_time (pc.patch (), t);
2128 _model->apply_command (*trackview.session(), c);
2129 trackview.editor().commit_reversible_command ();
2131 display_patch_changes ();
2135 MidiRegionView::delete_patch_change (PatchChange* pc)
2137 trackview.editor().begin_reversible_command (_("delete patch change"));
2139 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2140 c->remove (pc->patch ());
2141 _model->apply_command (*trackview.session(), c);
2142 trackview.editor().commit_reversible_command ();
2144 remove_canvas_patch_change (pc);
2145 display_patch_changes ();
2149 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2151 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2153 key.set_bank(key.bank() + delta);
2155 key.set_program(key.program() + delta);
2157 change_patch_change(patch, key);
2161 MidiRegionView::note_deleted (NoteBase* cne)
2163 if (_entered_note && cne == _entered_note) {
2167 if (_selection.empty()) {
2171 _selection.erase (cne);
2175 MidiRegionView::delete_selection()
2177 if (_selection.empty()) {
2181 if (trackview.editor().drags()->active()) {
2185 start_note_diff_command (_("delete selection"));
2187 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2188 if ((*i)->selected()) {
2189 _note_diff_command->remove((*i)->note());
2197 hide_verbose_cursor ();
2201 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2203 start_note_diff_command (_("delete note"));
2204 _note_diff_command->remove (n);
2207 hide_verbose_cursor ();
2211 MidiRegionView::clear_editor_note_selection ()
2213 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2214 PublicEditor& editor(trackview.editor());
2215 editor.get_selection().clear_midi_notes();
2219 MidiRegionView::clear_selection ()
2221 clear_selection_internal();
2222 PublicEditor& editor(trackview.editor());
2223 editor.get_selection().remove(this);
2227 MidiRegionView::clear_selection_internal ()
2229 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2231 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2232 (*i)->set_selected(false);
2233 (*i)->hide_velocity();
2238 // Clearing selection entirely, ungrab keyboard
2239 Keyboard::magic_widget_drop_focus();
2240 _grabbed_keyboard = false;
2245 MidiRegionView::unique_select(NoteBase* ev)
2247 clear_editor_note_selection();
2248 add_to_selection(ev);
2252 MidiRegionView::select_all_notes ()
2254 clear_editor_note_selection ();
2256 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2257 add_to_selection (i->second);
2262 MidiRegionView::select_range (samplepos_t start, samplepos_t end)
2264 clear_editor_note_selection ();
2266 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2267 samplepos_t t = source_beats_to_absolute_samples(i->first->time());
2268 if (t >= start && t <= end) {
2269 add_to_selection (i->second);
2275 MidiRegionView::invert_selection ()
2277 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2278 if (i->second->selected()) {
2279 remove_from_selection(i->second);
2281 add_to_selection (i->second);
2286 /** Used for selection undo/redo.
2287 The requested notes most likely won't exist in the view until the next model redisplay.
2290 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2293 list<Evoral::event_id_t>::iterator n;
2295 for (n = notes.begin(); n != notes.end(); ++n) {
2296 if ((cne = find_canvas_note(*n)) != 0) {
2297 add_to_selection (cne);
2299 _pending_note_selection.insert(*n);
2305 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2307 bool have_selection = !_selection.empty();
2308 uint8_t low_note = 127;
2309 uint8_t high_note = 0;
2310 MidiModel::Notes& notes (_model->notes());
2311 _optimization_iterator = _events.begin();
2313 if (extend && !have_selection) {
2317 /* scan existing selection to get note range */
2319 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2320 if ((*i)->note()->note() < low_note) {
2321 low_note = (*i)->note()->note();
2323 if ((*i)->note()->note() > high_note) {
2324 high_note = (*i)->note()->note();
2329 clear_editor_note_selection ();
2331 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2332 /* only note previously selected is the one we are
2333 * reselecting. treat this as cancelling the selection.
2340 low_note = min (low_note, notenum);
2341 high_note = max (high_note, notenum);
2344 _no_sound_notes = true;
2346 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2348 boost::shared_ptr<NoteType> note (*n);
2350 bool select = false;
2352 if (((1 << note->channel()) & channel_mask) != 0) {
2354 if ((note->note() >= low_note && note->note() <= high_note)) {
2357 } else if (note->note() == notenum) {
2363 if ((cne = find_canvas_note (note)) != 0) {
2364 // extend is false because we've taken care of it,
2365 // since it extends by time range, not pitch.
2366 note_selected (cne, add, false);
2370 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2374 _no_sound_notes = false;
2378 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2380 MidiModel::Notes& notes (_model->notes());
2381 _optimization_iterator = _events.begin();
2383 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2385 boost::shared_ptr<NoteType> note (*n);
2388 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2389 if ((cne = find_canvas_note (note)) != 0) {
2390 if (cne->selected()) {
2391 note_deselected (cne);
2393 note_selected (cne, true, false);
2401 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2404 clear_editor_note_selection();
2405 add_to_selection (ev);
2410 if (!ev->selected()) {
2411 add_to_selection (ev);
2415 /* find end of latest note selected, select all between that and the start of "ev" */
2417 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2418 Temporal::Beats latest = Temporal::Beats();
2420 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2421 if ((*i)->note()->end_time() > latest) {
2422 latest = (*i)->note()->end_time();
2424 if ((*i)->note()->time() < earliest) {
2425 earliest = (*i)->note()->time();
2429 if (ev->note()->end_time() > latest) {
2430 latest = ev->note()->end_time();
2433 if (ev->note()->time() < earliest) {
2434 earliest = ev->note()->time();
2437 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2439 /* find notes entirely within OR spanning the earliest..latest range */
2441 if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
2442 (i->first->time() <= earliest && i->first->end_time() >= latest)) {
2443 add_to_selection (i->second);
2450 MidiRegionView::note_deselected(NoteBase* ev)
2452 remove_from_selection (ev);
2456 MidiRegionView::update_drag_selection(samplepos_t start, samplepos_t end, double gy0, double gy1, bool extend)
2458 PublicEditor& editor = trackview.editor();
2460 // Convert to local coordinates
2461 const samplepos_t p = _region->position();
2462 const double y = midi_view()->y_position();
2463 const double x0 = editor.sample_to_pixel(max((samplepos_t)0, start - p));
2464 const double x1 = editor.sample_to_pixel(max((samplepos_t)0, end - p));
2465 const double y0 = max(0.0, gy0 - y);
2466 const double y1 = max(0.0, gy1 - y);
2468 // TODO: Make this faster by storing the last updated selection rect, and only
2469 // adjusting things that are in the area that appears/disappeared.
2470 // We probably need a tree to be able to find events in O(log(n)) time.
2472 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2473 if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
2474 // Rectangles intersect
2475 if (!i->second->selected()) {
2476 add_to_selection (i->second);
2478 } else if (i->second->selected() && !extend) {
2479 // Rectangles do not intersect
2480 remove_from_selection (i->second);
2484 typedef RouteTimeAxisView::AutomationTracks ATracks;
2485 typedef std::list<Selectable*> Selectables;
2487 /* Add control points to selection. */
2488 const ATracks& atracks = midi_view()->automation_tracks();
2489 Selectables selectables;
2490 editor.get_selection().clear_points();
2491 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2492 a->second->get_selectables(start, end, gy0, gy1, selectables);
2493 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2494 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2496 editor.get_selection().add(cp);
2499 a->second->set_selected_points(editor.get_selection().points);
2504 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2510 // TODO: Make this faster by storing the last updated selection rect, and only
2511 // adjusting things that are in the area that appears/disappeared.
2512 // We probably need a tree to be able to find events in O(log(n)) time.
2514 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2515 if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
2516 // within y- (note-) range
2517 if (!i->second->selected()) {
2518 add_to_selection (i->second);
2520 } else if (i->second->selected() && !extend) {
2521 remove_from_selection (i->second);
2527 MidiRegionView::remove_from_selection (NoteBase* ev)
2529 Selection::iterator i = _selection.find (ev);
2531 if (i != _selection.end()) {
2532 _selection.erase (i);
2533 if (_selection.empty() && _grabbed_keyboard) {
2535 Keyboard::magic_widget_drop_focus();
2536 _grabbed_keyboard = false;
2540 ev->set_selected (false);
2541 ev->hide_velocity ();
2543 if (_selection.empty()) {
2544 PublicEditor& editor (trackview.editor());
2545 editor.get_selection().remove (this);
2550 MidiRegionView::add_to_selection (NoteBase* ev)
2552 const bool selection_was_empty = _selection.empty();
2554 if (_selection.insert (ev).second) {
2555 ev->set_selected (true);
2556 start_playing_midi_note ((ev)->note());
2557 if (selection_was_empty && _entered) {
2558 // Grab keyboard for moving notes with arrow keys
2559 Keyboard::magic_widget_grab_focus();
2560 _grabbed_keyboard = true;
2564 if (selection_was_empty) {
2565 PublicEditor& editor (trackview.editor());
2566 editor.get_selection().add (this);
2571 MidiRegionView::earliest_in_selection ()
2573 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2575 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2576 if ((*i)->note()->time() < earliest) {
2577 earliest = (*i)->note()->time();
2585 MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
2587 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2588 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2589 TempoMap& tmap (editor->session()->tempo_map());
2590 PossibleChord to_play;
2591 Temporal::Beats earliest = earliest_in_selection();
2593 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2595 if (n->note()->time() == earliest) {
2596 to_play.push_back (n->note());
2598 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2600 if (midi_view()->note_mode() == Sustained) {
2601 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2602 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2604 /* Hit::x0() is offset by _position.x, unlike Note::x0() */
2605 Hit* hit = dynamic_cast<Hit*>(n);
2607 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2608 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2612 (*i)->move_event(dx, dy);
2615 if (midi_view()->note_mode() == Sustained) {
2616 Note* sus = dynamic_cast<Note*> (*i);
2617 double const len_dx = editor->sample_to_pixel_unrounded (
2618 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2620 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2624 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2626 if (to_play.size() > 1) {
2628 PossibleChord shifted;
2630 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2631 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2632 moved_note->set_note (moved_note->note() + cumulative_dy);
2633 shifted.push_back (moved_note);
2636 start_playing_midi_chord (shifted);
2638 } else if (!to_play.empty()) {
2640 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2641 moved_note->set_note (moved_note->note() + cumulative_dy);
2642 start_playing_midi_note (moved_note);
2648 MidiRegionView::copy_selection (NoteBase* primary)
2650 _copy_drag_events.clear ();
2652 if (_selection.empty()) {
2659 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2660 boost::shared_ptr<NoteType> g (new NoteType (*((*i)->note())));
2661 if (midi_view()->note_mode() == Sustained) {
2662 Note* n = new Note (*this, _note_group, g);
2663 update_sustained (n, false);
2666 Hit* h = new Hit (*this, _note_group, 10, g);
2667 update_hit (h, false);
2671 if ((*i) == primary) {
2675 _copy_drag_events.push_back (note);
2682 MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
2684 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2685 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2686 TempoMap& tmap (editor->session()->tempo_map());
2687 PossibleChord to_play;
2688 Temporal::Beats earliest = earliest_in_selection();
2690 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2692 if (n->note()->time() == earliest) {
2693 to_play.push_back (n->note());
2695 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2697 if (midi_view()->note_mode() == Sustained) {
2698 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2699 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2701 Hit* hit = dynamic_cast<Hit*>(n);
2703 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2704 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2708 (*i)->move_event(dx, dy);
2710 if (midi_view()->note_mode() == Sustained) {
2711 Note* sus = dynamic_cast<Note*> (*i);
2712 double const len_dx = editor->sample_to_pixel_unrounded (
2713 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2715 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2719 if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2721 if (to_play.size() > 1) {
2723 PossibleChord shifted;
2725 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2726 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2727 moved_note->set_note (moved_note->note() + cumulative_dy);
2728 shifted.push_back (moved_note);
2731 start_playing_midi_chord (shifted);
2733 } else if (!to_play.empty()) {
2735 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2736 moved_note->set_note (moved_note->note() + cumulative_dy);
2737 start_playing_midi_note (moved_note);
2743 MidiRegionView::note_dropped(NoteBase *, double d_qn, int8_t dnote, bool copy)
2745 uint8_t lowest_note_in_selection = 127;
2746 uint8_t highest_note_in_selection = 0;
2747 uint8_t highest_note_difference = 0;
2750 // find highest and lowest notes first
2752 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2753 uint8_t pitch = (*i)->note()->note();
2754 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2755 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2759 cerr << "dnote: " << (int) dnote << endl;
2760 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2761 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2762 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2763 << int(highest_note_in_selection) << endl;
2764 cerr << "selection size: " << _selection.size() << endl;
2765 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2768 // Make sure the note pitch does not exceed the MIDI standard range
2769 if (highest_note_in_selection + dnote > 127) {
2770 highest_note_difference = highest_note_in_selection - 127;
2773 start_note_diff_command (_("move notes"));
2775 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2777 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2783 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2785 uint8_t original_pitch = (*i)->note()->note();
2786 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2788 // keep notes in standard midi range
2789 clamp_to_0_127(new_pitch);
2791 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2792 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2794 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2798 clear_editor_note_selection ();
2800 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2801 uint8_t pitch = (*i)->note()->note();
2802 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2803 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2806 // Make sure the note pitch does not exceed the MIDI standard range
2807 if (highest_note_in_selection + dnote > 127) {
2808 highest_note_difference = highest_note_in_selection - 127;
2811 start_note_diff_command (_("copy notes"));
2813 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
2816 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2822 (*i)->note()->set_time (new_time);
2826 uint8_t original_pitch = (*i)->note()->note();
2827 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2829 (*i)->note()->set_note (new_pitch);
2831 // keep notes in standard midi range
2832 clamp_to_0_127(new_pitch);
2834 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2835 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2837 note_diff_add_note ((*i)->note(), true);
2842 _copy_drag_events.clear ();
2845 apply_diff (false, copy);
2847 // care about notes being moved beyond the upper/lower bounds on the canvas
2848 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2849 highest_note_in_selection > midi_stream_view()->highest_note()) {
2850 midi_stream_view()->set_note_range (MidiStreamView::ContentsRange);
2854 /** @param x Pixel relative to the region position.
2855 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2856 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2857 * @return Snapped sample relative to the region position.
2860 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2862 PublicEditor& editor (trackview.editor());
2863 return snap_sample_to_sample (editor.pixel_to_sample (x), ensure_snap).sample;
2866 /** @param x Pixel relative to the region position.
2867 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2868 * @return Snapped pixel relative to the region position.
2871 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2873 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2877 MidiRegionView::get_position_pixels()
2879 samplepos_t region_sample = get_position();
2880 return trackview.editor().sample_to_pixel(region_sample);
2884 MidiRegionView::get_end_position_pixels()
2886 samplepos_t sample = get_position() + get_duration ();
2887 return trackview.editor().sample_to_pixel(sample);
2891 MidiRegionView::source_beats_to_absolute_samples(Temporal::Beats beats) const
2893 /* the time converter will return the sample corresponding to `beats'
2894 relative to the start of the source. The start of the source
2895 is an implied position given by region->position - region->start
2897 const samplepos_t source_start = _region->position() - _region->start();
2898 return source_start + _source_relative_time_converter.to (beats);
2902 MidiRegionView::absolute_samples_to_source_beats(samplepos_t samples) const
2904 /* the `samples' argument needs to be converted into a sample count
2905 relative to the start of the source before being passed in to the
2908 const samplepos_t source_start = _region->position() - _region->start();
2909 return _source_relative_time_converter.from (samples - source_start);
2913 MidiRegionView::region_beats_to_region_samples(Temporal::Beats beats) const
2915 return _region_relative_time_converter.to(beats);
2919 MidiRegionView::region_samples_to_region_beats(samplepos_t samples) const
2921 return _region_relative_time_converter.from(samples);
2925 MidiRegionView::region_samples_to_region_beats_double (samplepos_t samples) const
2927 return _region_relative_time_converter_double.from(samples);
2931 MidiRegionView::begin_resizing (bool /*at_front*/)
2933 _resize_data.clear();
2935 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2936 Note *note = dynamic_cast<Note*> (*i);
2938 // only insert CanvasNotes into the map
2940 NoteResizeData *resize_data = new NoteResizeData();
2941 resize_data->note = note;
2943 // create a new SimpleRect from the note which will be the resize preview
2944 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2945 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2947 // calculate the colors: get the color settings
2948 uint32_t fill_color = NoteBase::meter_style_fill_color (note->note()->velocity(), true);
2950 // make the resize preview notes more transparent and bright
2951 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2953 // calculate color based on note velocity
2954 resize_rect->set_fill_color (UINT_INTERPOLATE(
2955 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2959 resize_rect->set_outline_color (NoteBase::calculate_outline (
2960 UIConfiguration::instance().color ("midi note selected outline")));
2962 resize_data->resize_rect = resize_rect;
2963 _resize_data.push_back(resize_data);
2968 /** Update resizing notes while user drags.
2969 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2970 * @param at_front which end of the note (true == note on, false == note off)
2971 * @param delta_x change in mouse position since the start of the drag
2972 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2973 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2974 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2975 * as the \a primary note.
2976 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2977 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2980 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2982 TempoMap& tmap (trackview.session()->tempo_map());
2983 bool cursor_set = false;
2984 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2986 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2987 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2988 Note* canvas_note = (*i)->note;
2993 current_x = canvas_note->x0() + delta_x + snap_delta;
2995 current_x = primary->x0() + delta_x + snap_delta;
2999 current_x = canvas_note->x1() + delta_x + snap_delta;
3001 current_x = primary->x1() + delta_x + snap_delta;
3005 if (current_x < 0) {
3006 /* This works even with snapping because RegionView::snap_sample_to_sample()
3007 * snaps forward if the snapped sample is before the beginning of the region
3011 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3012 current_x = trackview.editor().sample_to_pixel(_region->length());
3017 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3019 resize_rect->set_x0 (current_x - snap_delta);
3021 resize_rect->set_x1 (canvas_note->x1());
3024 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3026 resize_rect->set_x1 (current_x - snap_delta);
3028 resize_rect->set_x0 (canvas_note->x0());
3032 /* Convert snap delta from pixels to beats. */
3033 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3034 double snap_delta_beats = 0.0;
3037 /* negative beat offsets aren't allowed */
3038 if (snap_delta_samps > 0) {
3039 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3040 } else if (snap_delta_samps < 0) {
3041 snap_delta_beats = region_samples_to_region_beats_double (- snap_delta_samps);
3046 int32_t divisions = 0;
3049 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
3050 divisions = trackview.editor().get_grid_music_divisions (0);
3052 snapped_x = trackview.editor ().pixel_to_sample (current_x);
3055 const Temporal::Beats beats = Temporal::Beats (tmap.exact_beat_at_sample (snapped_x + midi_region()->position(), divisions)
3056 - midi_region()->beat())
3057 + midi_region()->start_beats();
3059 Temporal::Beats len = Temporal::Beats();
3062 if (beats < canvas_note->note()->end_time()) {
3063 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
3064 len += canvas_note->note()->length();
3067 if (beats >= canvas_note->note()->time()) {
3068 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
3072 len = std::max(Temporal::Beats(1 / 512.0), len);
3075 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
3076 show_verbose_cursor (buf, 0, 0);
3080 trackview.editor().set_snapped_cursor_position ( snapped_x + midi_region()->position() );
3087 /** Finish resizing notes when the user releases the mouse button.
3088 * Parameters the same as for \a update_resizing().
3091 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
3093 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
3094 TempoMap& tmap (trackview.session()->tempo_map());
3096 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
3097 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
3099 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3100 Note* canvas_note = (*i)->note;
3101 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
3103 /* Get the new x position for this resize, which is in pixels relative
3104 * to the region position.
3111 current_x = canvas_note->x0() + delta_x + snap_delta;
3113 current_x = primary->x0() + delta_x + snap_delta;
3117 current_x = canvas_note->x1() + delta_x + snap_delta;
3119 current_x = primary->x1() + delta_x + snap_delta;
3123 if (current_x < 0) {
3126 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3127 current_x = trackview.editor().sample_to_pixel(_region->length());
3130 /* Convert snap delta from pixels to beats with sign. */
3131 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3132 double snap_delta_beats = 0.0;
3135 if (snap_delta_samps > 0) {
3136 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3137 } else if (snap_delta_samps < 0) {
3138 snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
3142 uint32_t divisions = 0;
3143 /* Convert the new x position to a sample within the source */
3144 samplepos_t current_fr;
3146 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
3147 divisions = trackview.editor().get_grid_music_divisions (0);
3149 current_fr = trackview.editor().pixel_to_sample (current_x);
3152 /* and then to beats */
3153 const double e_qaf = tmap.exact_qn_at_sample (current_fr + midi_region()->position(), divisions);
3154 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
3155 const Temporal::Beats x_beats = Temporal::Beats (e_qaf - quarter_note_start);
3157 if (at_front && x_beats < canvas_note->note()->end_time()) {
3158 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
3159 Temporal::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
3160 len += canvas_note->note()->length();
3163 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3168 Temporal::Beats len = std::max(Temporal::Beats(1 / 512.0),
3169 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
3170 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3177 _resize_data.clear();
3182 MidiRegionView::abort_resizing ()
3184 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3185 delete (*i)->resize_rect;
3189 _resize_data.clear ();
3193 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
3195 uint8_t new_velocity;
3198 new_velocity = event->note()->velocity() + velocity;
3199 clamp_to_0_127(new_velocity);
3201 new_velocity = velocity;
3204 event->set_selected (event->selected()); // change color
3206 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
3210 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
3215 new_note = event->note()->note() + note;
3220 clamp_to_0_127 (new_note);
3221 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3225 MidiRegionView::trim_note (NoteBase* event, Temporal::Beats front_delta, Temporal::Beats end_delta)
3227 bool change_start = false;
3228 bool change_length = false;
3229 Temporal::Beats new_start;
3230 Temporal::Beats new_length;
3232 /* NOTE: the semantics of the two delta arguments are slightly subtle:
3234 front_delta: if positive - move the start of the note later in time (shortening it)
3235 if negative - move the start of the note earlier in time (lengthening it)
3237 end_delta: if positive - move the end of the note later in time (lengthening it)
3238 if negative - move the end of the note earlier in time (shortening it)
3241 if (!!front_delta) {
3242 if (front_delta < 0) {
3244 if (event->note()->time() < -front_delta) {
3245 new_start = Temporal::Beats();
3247 new_start = event->note()->time() + front_delta; // moves earlier
3250 /* start moved toward zero, so move the end point out to where it used to be.
3251 Note that front_delta is negative, so this increases the length.
3254 new_length = event->note()->length() - front_delta;
3255 change_start = true;
3256 change_length = true;
3260 Temporal::Beats new_pos = event->note()->time() + front_delta;
3262 if (new_pos < event->note()->end_time()) {
3263 new_start = event->note()->time() + front_delta;
3264 /* start moved toward the end, so move the end point back to where it used to be */
3265 new_length = event->note()->length() - front_delta;
3266 change_start = true;
3267 change_length = true;
3274 bool can_change = true;
3275 if (end_delta < 0) {
3276 if (event->note()->length() < -end_delta) {
3282 new_length = event->note()->length() + end_delta;
3283 change_length = true;
3288 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3291 if (change_length) {
3292 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3297 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3299 uint8_t new_channel;
3303 if (event->note()->channel() < -chn) {
3306 new_channel = event->note()->channel() + chn;
3309 new_channel = event->note()->channel() + chn;
3312 new_channel = (uint8_t) chn;
3315 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3319 MidiRegionView::change_note_time (NoteBase* event, Temporal::Beats delta, bool relative)
3321 Temporal::Beats new_time;
3325 if (event->note()->time() < -delta) {
3326 new_time = Temporal::Beats();
3328 new_time = event->note()->time() + delta;
3331 new_time = event->note()->time() + delta;
3337 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3341 MidiRegionView::change_note_length (NoteBase* event, Temporal::Beats t)
3343 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3347 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3352 if (_selection.empty()) {
3367 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3368 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3374 start_note_diff_command (_("change velocities"));
3376 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3377 Selection::iterator next = i;
3381 if (i == _selection.begin()) {
3382 change_note_velocity (*i, delta, true);
3383 value = (*i)->note()->velocity() + delta;
3385 change_note_velocity (*i, value, false);
3389 change_note_velocity (*i, delta, true);
3398 if (!_selection.empty()) {
3400 snprintf (buf, sizeof (buf), "Vel %d",
3401 (int) (*_selection.begin())->note()->velocity());
3402 show_verbose_cursor (buf, 10, 10);
3408 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3410 if (_selection.empty()) {
3427 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3429 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3433 if ((int8_t) (*i)->note()->note() + delta > 127) {
3440 start_note_diff_command (_("transpose"));
3442 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3443 Selection::iterator next = i;
3445 change_note_note (*i, delta, true);
3453 MidiRegionView::change_note_lengths (bool fine, bool shorter, Temporal::Beats delta, bool start, bool end)
3457 delta = Temporal::Beats(1.0/128.0);
3459 /* grab the current grid distance */
3460 delta = get_grid_beats(_region->position());
3468 start_note_diff_command (_("change note lengths"));
3470 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3471 Selection::iterator next = i;
3474 /* note the negation of the delta for start */
3477 (start ? -delta : Temporal::Beats()),
3478 (end ? delta : Temporal::Beats()));
3487 MidiRegionView::nudge_notes (bool forward, bool fine)
3489 if (_selection.empty()) {
3493 /* pick a note as the point along the timeline to get the nudge distance.
3494 its not necessarily the earliest note, so we may want to pull the notes out
3495 into a vector and sort before using the first one.
3498 const samplepos_t ref_point = source_beats_to_absolute_samples ((*(_selection.begin()))->note()->time());
3499 Temporal::Beats delta;
3503 /* non-fine, move by 1 bar regardless of snap */
3504 delta = Temporal::Beats(trackview.session()->tempo_map().meter_at_sample (ref_point).divisions_per_bar());
3506 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3508 /* grid is off - use nudge distance */
3511 const samplecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3512 delta = region_samples_to_region_beats (fabs ((double)distance));
3518 MusicSample next_pos (ref_point, 0);
3520 if (max_samplepos - 1 < next_pos.sample) {
3521 next_pos.sample += 1;
3524 if (next_pos.sample == 0) {
3527 next_pos.sample -= 1;
3530 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), SnapToGrid_Unscaled, false);
3531 const samplecnt_t distance = ref_point - next_pos.sample;
3532 delta = region_samples_to_region_beats (fabs ((double)distance));
3543 start_note_diff_command (_("nudge"));
3545 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3546 Selection::iterator next = i;
3548 change_note_time (*i, delta, true);
3556 MidiRegionView::change_channel(uint8_t channel)
3558 start_note_diff_command(_("change channel"));
3559 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3560 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3568 MidiRegionView::note_entered(NoteBase* ev)
3572 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3574 if (_mouse_state == SelectTouchDragging) {
3576 note_selected (ev, true);
3578 } else if (editor->current_mouse_mode() == MouseContent) {
3580 remove_ghost_note ();
3581 show_verbose_cursor (ev->note ());
3583 } else if (editor->current_mouse_mode() == MouseDraw) {
3585 remove_ghost_note ();
3586 show_verbose_cursor (ev->note ());
3591 MidiRegionView::note_left (NoteBase*)
3595 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3596 (*i)->hide_velocity ();
3599 hide_verbose_cursor ();
3603 MidiRegionView::patch_entered (PatchChange* p)
3606 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3607 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3608 << _("Channel ") << ((int) p->patch()->channel() + 1);
3609 show_verbose_cursor (s.str(), 10, 20);
3610 p->item().grab_focus();
3614 MidiRegionView::patch_left (PatchChange *)
3616 hide_verbose_cursor ();
3617 /* focus will transfer back via the enter-notify event sent to this
3623 MidiRegionView::sysex_entered (SysEx* p)
3627 // need a way to extract text from p->_flag->_text
3629 // show_verbose_cursor (s.str(), 10, 20);
3630 p->item().grab_focus();
3634 MidiRegionView::sysex_left (SysEx *)
3636 hide_verbose_cursor ();
3637 /* focus will transfer back via the enter-notify event sent to this
3643 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3645 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3646 Editing::MouseMode mm = editor->current_mouse_mode();
3647 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3649 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3650 if (can_set_cursor && ctx) {
3651 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3652 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3653 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3654 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3656 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3662 MidiRegionView::get_fill_color() const
3664 const std::string mod_name = (_dragging ? "dragging region" :
3665 trackview.editor().internal_editing() ? "editable region" :
3668 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3669 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3670 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3671 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3673 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3677 MidiRegionView::midi_channel_mode_changed ()
3679 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3680 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3681 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3683 if (mode == ForceChannel) {
3684 mask = 0xFFFF; // Show all notes as active (below)
3687 // Update notes for selection
3688 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3689 i->second->on_channel_selection_change (mask);
3692 _patch_changes.clear ();
3693 display_patch_changes ();
3697 MidiRegionView::instrument_settings_changed ()
3703 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3705 if (_selection.empty()) {
3709 PublicEditor& editor (trackview.editor());
3713 /* XXX what to do ? */
3717 editor.get_cut_buffer().add (selection_as_cut_buffer());
3725 start_note_diff_command();
3727 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3734 note_diff_remove_note (*i);
3744 MidiRegionView::selection_as_cut_buffer () const
3748 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3749 NoteType* n = (*i)->note().get();
3750 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3753 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3759 /** This method handles undo */
3761 MidiRegionView::paste (samplepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3763 bool commit = false;
3764 // Paste notes, if available
3765 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3766 if (m != selection.midi_notes.end()) {
3767 ctx.counts.increase_n_notes();
3768 if (!(*m)->empty()) {
3771 paste_internal(pos, ctx.count, ctx.times, **m);
3774 // Paste control points to automation children, if available
3775 typedef RouteTimeAxisView::AutomationTracks ATracks;
3776 const ATracks& atracks = midi_view()->automation_tracks();
3777 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3778 if (a->second->paste(pos, selection, ctx, sub_num)) {
3780 trackview.editor().begin_reversible_command (Operations::paste);
3787 trackview.editor().commit_reversible_command ();
3792 /** This method handles undo */
3794 MidiRegionView::paste_internal (samplepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3800 start_note_diff_command (_("paste"));
3802 const Temporal::Beats snap_beats = get_grid_beats(pos);
3803 const Temporal::Beats first_time = (*mcb.notes().begin())->time();
3804 const Temporal::Beats last_time = (*mcb.notes().rbegin())->end_time();
3805 const Temporal::Beats duration = last_time - first_time;
3806 const Temporal::Beats snap_duration = duration.snap_to(snap_beats);
3807 const Temporal::Beats paste_offset = snap_duration * paste_count;
3808 const Temporal::Beats quarter_note = absolute_samples_to_source_beats(pos) + paste_offset;
3809 Temporal::Beats end_point = Temporal::Beats();
3811 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3814 duration, pos, _region->position(),
3817 clear_editor_note_selection ();
3819 for (int n = 0; n < (int) times; ++n) {
3821 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3823 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3824 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3825 copied_note->set_id (Evoral::next_event_id());
3827 /* make all newly added notes selected */
3829 note_diff_add_note (copied_note, true);
3830 end_point = copied_note->end_time();
3834 /* if we pasted past the current end of the region, extend the region */
3836 samplepos_t end_sample = source_beats_to_absolute_samples (end_point);
3837 samplepos_t region_end = _region->position() + _region->length() - 1;
3839 if (end_sample > region_end) {
3841 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_sample));
3843 _region->clear_changes ();
3844 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3845 _region->set_length (end_sample - _region->position(), trackview.editor().get_grid_music_divisions (0));
3846 trackview.session()->add_command (new StatefulDiffCommand (_region));
3852 struct EventNoteTimeEarlyFirstComparator {
3853 bool operator() (NoteBase* a, NoteBase* b) {
3854 return a->note()->time() < b->note()->time();
3859 MidiRegionView::goto_next_note (bool add_to_selection)
3861 bool use_next = false;
3863 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3864 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3865 NoteBase* first_note = 0;
3867 MidiModel::ReadLock lock(_model->read_lock());
3868 MidiModel::Notes& notes (_model->notes());
3870 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
3872 if ((cne = find_canvas_note (*n))) {
3874 if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
3878 if (cne->selected()) {
3881 } else if (use_next) {
3882 if (channel_mask & (1 << (*n)->channel())) {
3883 if (!add_to_selection) {
3884 unique_select (cne);
3886 note_selected (cne, true, false);
3895 /* use the first one */
3897 if (!_events.empty() && first_note) {
3898 unique_select (first_note);
3903 MidiRegionView::goto_previous_note (bool add_to_selection)
3905 bool use_next = false;
3907 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3908 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3909 NoteBase* last_note = 0;
3911 MidiModel::ReadLock lock(_model->read_lock());
3912 MidiModel::Notes& notes (_model->notes());
3914 for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
3916 if ((cne = find_canvas_note (*n))) {
3918 if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
3922 if (cne->selected()) {
3926 } else if (use_next) {
3927 if (channel_mask & (1 << (*n)->channel())) {
3928 if (!add_to_selection) {
3929 unique_select (cne);
3931 note_selected (cne, true, false);
3940 /* use the last one */
3942 if (!_events.empty() && last_note) {
3943 unique_select (last_note);
3948 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3950 bool had_selected = false;
3952 /* we previously time sorted events here, but Notes is a multiset sorted by time */
3954 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3955 if (i->second->selected()) {
3956 selected.insert (i->first);
3957 had_selected = true;
3961 if (allow_all_if_none_selected && !had_selected) {
3962 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3963 selected.insert (i->first);
3969 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3971 x = std::max(0.0, x);
3973 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3978 _note_group->canvas_to_item (x, y);
3980 PublicEditor& editor = trackview.editor ();
3982 samplepos_t const unsnapped_sample = editor.pixel_to_sample (x);
3984 const int32_t divisions = editor.get_grid_music_divisions (state);
3985 const bool shift_snap = midi_view()->note_mode() != Percussive;
3986 const Temporal::Beats snapped_beats = snap_sample_to_grid_underneath (unsnapped_sample, divisions, shift_snap);
3988 /* prevent Percussive mode from displaying a ghost hit at region end */
3989 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3990 _ghost_note->hide();
3991 hide_verbose_cursor ();
3995 /* ghost note may have been snapped before region */
3996 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3997 _ghost_note->hide();
4000 } else if (_ghost_note) {
4001 _ghost_note->show();
4004 /* calculate time in beats relative to start of source */
4005 const Temporal::Beats length = get_grid_beats(unsnapped_sample + _region->position());
4007 _ghost_note->note()->set_time (snapped_beats);
4008 _ghost_note->note()->set_length (length);
4009 _ghost_note->note()->set_note (y_to_note (y));
4010 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
4011 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
4012 /* the ghost note does not appear in ghost regions, so pass false in here */
4013 update_note (_ghost_note, false);
4015 show_verbose_cursor (_ghost_note->note ());
4019 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
4021 remove_ghost_note ();
4023 boost::shared_ptr<NoteType> g (new NoteType);
4024 if (midi_view()->note_mode() == Sustained) {
4025 _ghost_note = new Note (*this, _note_group, g);
4027 _ghost_note = new Hit (*this, _note_group, 10, g);
4029 _ghost_note->set_ignore_events (true);
4030 _ghost_note->set_outline_color (0x000000aa);
4031 update_ghost_note (x, y, state);
4032 _ghost_note->show ();
4034 show_verbose_cursor (_ghost_note->note ());
4038 MidiRegionView::remove_ghost_note ()
4045 MidiRegionView::hide_verbose_cursor ()
4047 trackview.editor().verbose_cursor()->hide ();
4048 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4050 mtv->set_note_highlight (NO_MIDI_NOTE);
4055 MidiRegionView::snap_changed ()
4061 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
4065 MidiRegionView::drop_down_keys ()
4067 _mouse_state = None;
4071 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
4073 /* XXX: This is dead code. What was it for? */
4075 double note = y_to_note(y);
4077 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4079 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
4081 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
4082 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
4083 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
4084 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
4089 bool add_mrv_selection = false;
4091 if (_selection.empty()) {
4092 add_mrv_selection = true;
4095 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
4096 if (_selection.insert (i->second).second) {
4097 i->second->set_selected (true);
4101 if (add_mrv_selection) {
4102 PublicEditor& editor (trackview.editor());
4103 editor.get_selection().add (this);
4108 MidiRegionView::color_handler ()
4110 RegionView::color_handler ();
4112 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
4113 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
4115 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
4116 i->second->set_selected (i->second->selected()); // will change color
4119 /* XXX probably more to do here */
4123 MidiRegionView::enable_display (bool yn)
4125 RegionView::enable_display (yn);
4129 MidiRegionView::show_step_edit_cursor (Temporal::Beats pos)
4131 if (_step_edit_cursor == 0) {
4132 ArdourCanvas::Item* const group = get_canvas_group();
4134 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
4135 _step_edit_cursor->set_y0 (0);
4136 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
4137 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
4138 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
4141 move_step_edit_cursor (pos);
4142 _step_edit_cursor->show ();
4146 MidiRegionView::move_step_edit_cursor (Temporal::Beats pos)
4148 _step_edit_cursor_position = pos;
4150 if (_step_edit_cursor) {
4151 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_samples (pos));
4152 _step_edit_cursor->set_x0 (pixel);
4153 set_step_edit_cursor_width (_step_edit_cursor_width);
4158 MidiRegionView::hide_step_edit_cursor ()
4160 if (_step_edit_cursor) {
4161 _step_edit_cursor->hide ();
4166 MidiRegionView::set_step_edit_cursor_width (Temporal::Beats beats)
4168 _step_edit_cursor_width = beats;
4170 if (_step_edit_cursor) {
4171 _step_edit_cursor->set_x1 (_step_edit_cursor->x0()
4172 + trackview.editor().sample_to_pixel (
4173 region_beats_to_region_samples (_step_edit_cursor_position + beats)
4174 - region_beats_to_region_samples (_step_edit_cursor_position)));
4178 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
4179 * @param w Source that the data will end up in.
4182 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
4184 if (!_active_notes) {
4185 /* we aren't actively being recorded to */
4189 boost::shared_ptr<MidiSource> src = w.lock ();
4190 if (!src || src != midi_region()->midi_source()) {
4191 /* recorded data was not destined for our source */
4195 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
4197 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
4199 samplepos_t back = max_samplepos;
4201 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
4202 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
4204 if (ev.is_channel_event()) {
4205 if (get_channel_mode() == FilterChannels) {
4206 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
4212 /* convert from session samples to source beats */
4213 Temporal::Beats const time_beats = _source_relative_time_converter.from(
4214 ev.time() - src->natural_position() + _region->start());
4216 if (ev.type() == MIDI_CMD_NOTE_ON) {
4217 boost::shared_ptr<NoteType> note (
4218 new NoteType (ev.channel(), time_beats, Temporal::Beats(), ev.note(), ev.velocity()));
4220 add_note (note, true);
4222 /* fix up our note range */
4223 if (ev.note() < _current_range_min) {
4224 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
4225 } else if (ev.note() > _current_range_max) {
4226 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
4229 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
4230 resolve_note (ev.note (), time_beats);
4236 midi_stream_view()->check_record_layers (region(), back);
4240 MidiRegionView::trim_front_starting ()
4242 /* We used to eparent the note group to the region view's parent, so that it didn't change.
4248 MidiRegionView::trim_front_ending ()
4250 if (_region->start() < 0) {
4251 /* Trim drag made start time -ve; fix this */
4252 midi_region()->fix_negative_start ();
4257 MidiRegionView::edit_patch_change (PatchChange* pc)
4259 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4261 int response = d.run();
4264 case Gtk::RESPONSE_ACCEPT:
4266 case Gtk::RESPONSE_REJECT:
4267 delete_patch_change (pc);
4273 change_patch_change (pc->patch(), d.patch ());
4277 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4280 // sysyex object doesn't have a pointer to a sysex event
4281 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4282 // c->remove (sysex->sysex());
4283 // _model->apply_command (*trackview.session(), c);
4285 //_sys_exes.clear ();
4286 // display_sysexes();
4290 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4292 using namespace MIDI::Name;
4295 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4297 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4299 MIDI::Name::PatchPrimaryKey patch_key;
4300 get_patch_key_at(n->time(), n->channel(), patch_key);
4301 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4304 patch_key.program(),
4310 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4312 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4313 (int) n->channel() + 1,
4314 (int) n->velocity());
4320 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4321 uint8_t new_value) const
4323 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4325 mtv->set_note_highlight (new_value);
4328 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4332 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4334 show_verbose_cursor_for_new_note_value(n, n->note());
4338 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4340 trackview.editor().verbose_cursor()->set (text);
4341 trackview.editor().verbose_cursor()->show ();
4342 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4346 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4348 if (_model->notes().empty()) {
4349 return 0x40; // No notes, use default
4352 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4353 if (m == _model->notes().begin()) {
4354 // Before the start, use the velocity of the first note
4355 return (*m)->velocity();
4356 } else if (m == _model->notes().end()) {
4357 // Past the end, use the velocity of the last note
4359 return (*m)->velocity();
4362 // Interpolate velocity of surrounding notes
4363 MidiModel::Notes::const_iterator n = m;
4366 const double frac = ((time - (*n)->time()).to_double() /
4367 ((*m)->time() - (*n)->time()).to_double());
4369 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4372 /** @param p A session samplepos.
4373 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4374 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4375 * @return beat duration of p snapped to the grid subdivision underneath it.
4378 MidiRegionView::snap_sample_to_grid_underneath (samplepos_t p, int32_t divisions, bool shift_snap) const
4380 TempoMap& map (trackview.session()->tempo_map());
4381 double eqaf = map.exact_qn_at_sample (p + _region->position(), divisions);
4383 if (divisions != 0 && shift_snap) {
4384 const double qaf = map.quarter_note_at_sample (p + _region->position());
4385 /* Hack so that we always snap to the note that we are over, instead of snapping
4386 to the next one if we're more than halfway through the one we're over.
4388 const Temporal::Beats grid_beats = get_grid_beats (p + _region->position());
4389 const double rem = eqaf - qaf;
4391 eqaf -= grid_beats.to_double();
4394 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4396 return Temporal::Beats (eqaf - session_start_off);
4400 MidiRegionView::get_channel_mode () const
4402 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4403 return rtav->midi_track()->get_playback_channel_mode();
4407 MidiRegionView::get_selected_channels () const
4409 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4410 return rtav->midi_track()->get_playback_channel_mask();
4415 MidiRegionView::get_grid_beats(samplepos_t pos) const
4417 PublicEditor& editor = trackview.editor();
4418 bool success = false;
4419 Temporal::Beats beats = editor.get_grid_type_as_beats (success, pos);
4421 beats = Temporal::Beats(1);
4426 MidiRegionView::y_to_note (double y) const
4428 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4429 + _current_range_min;
4433 } else if (n > 127) {
4437 /* min due to rounding and/or off-by-one errors */
4438 return min ((uint8_t) n, _current_range_max);
4442 MidiRegionView::note_to_y(uint8_t note) const
4444 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
4448 MidiRegionView::session_relative_qn (double qn) const
4450 return qn + (region()->quarter_note() - midi_region()->start_beats());