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.hpp"
52 #include "evoral/Event.hpp"
53 #include "evoral/Control.hpp"
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 midi_view()->midi_track()->midi_playlist()->region_edited (_region, _note_diff_command);
1089 if (as_subcommand) {
1090 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1092 _model->apply_command (*trackview.session(), _note_diff_command);
1096 _note_diff_command = 0;
1098 if (add_or_remove) {
1099 _marked_for_selection.clear();
1102 _marked_for_velocity.clear();
1104 trackview.editor().commit_reversible_command ();
1109 MidiRegionView::abort_command()
1111 delete _note_diff_command;
1112 _note_diff_command = 0;
1113 trackview.editor().abort_reversible_command();
1114 clear_editor_note_selection();
1118 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1121 if (_optimization_iterator != _events.end()) {
1122 ++_optimization_iterator;
1125 if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) {
1126 return _optimization_iterator->second;
1129 _optimization_iterator = _events.find (note);
1130 if (_optimization_iterator != _events.end()) {
1131 return _optimization_iterator->second;
1137 /** This version finds any canvas note matching the supplied note. */
1139 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1141 Events::iterator it;
1143 for (it = _events.begin(); it != _events.end(); ++it) {
1144 if (it->first->id() == id) {
1152 boost::shared_ptr<PatchChange>
1153 MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
1155 PatchChanges::const_iterator f = _patch_changes.find (p);
1157 if (f != _patch_changes.end()) {
1161 return boost::shared_ptr<PatchChange>();
1164 boost::shared_ptr<SysEx>
1165 MidiRegionView::find_canvas_sys_ex (MidiModel::SysExPtr s)
1167 SysExes::const_iterator f = _sys_exes.find (s);
1169 if (f != _sys_exes.end()) {
1173 return boost::shared_ptr<SysEx>();
1177 MidiRegionView::get_events (Events& e, Evoral::Sequence<Temporal::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1179 MidiModel::Notes notes;
1180 _model->get_notes (notes, op, val, chan_mask);
1182 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1183 NoteBase* cne = find_canvas_note (*n);
1185 e.insert (make_pair (*n, cne));
1191 MidiRegionView::redisplay_model()
1193 if (_active_notes) {
1194 // Currently recording
1195 const samplecnt_t zoom = trackview.editor().get_current_zoom();
1196 if (zoom != _last_display_zoom) {
1197 /* Update resolved canvas notes to reflect changes in zoom without
1198 touching model. Leave active notes (with length 0) alone since
1199 they are being extended. */
1200 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1201 if (i->second->note()->length() > 0) {
1202 update_note(i->second);
1205 _last_display_zoom = zoom;
1214 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1215 _optimization_iterator->second->invalidate();
1218 bool empty_when_starting = _events.empty();
1219 _optimization_iterator = _events.begin();
1220 MidiModel::Notes missing_notes;
1224 MidiModel::ReadLock lock(_model->read_lock());
1225 MidiModel::Notes& notes (_model->notes());
1228 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1230 boost::shared_ptr<NoteType> note (*n);
1233 if (note_in_region_range (note, visible)) {
1234 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1242 missing_notes.insert (note);
1247 if (!empty_when_starting) {
1248 MidiModel::Notes::iterator f;
1249 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1251 NoteBase* cne = i->second;
1253 /* remove note items that are no longer valid */
1254 if (!cne->valid()) {
1256 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1257 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1259 gr->remove_note (cne);
1264 i = _events.erase (i);
1267 bool visible = cne->item()->visible();
1269 if ((sus = dynamic_cast<Note*>(cne))) {
1272 update_sustained (sus);
1275 } else if ((hit = dynamic_cast<Hit*>(cne))) {
1287 for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
1288 boost::shared_ptr<NoteType> note (*n);
1292 if (note_in_region_range (note, visible)) {
1294 cne = add_note (note, true);
1296 cne = add_note (note, false);
1299 cne = add_note (note, false);
1302 for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1303 if ((*it) == note->id()) {
1304 add_to_selection (cne);
1309 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1310 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1311 if (gr && !gr->trackview.hidden()) {
1312 gr->redisplay_model ();
1317 display_patch_changes ();
1319 _marked_for_selection.clear ();
1320 _marked_for_velocity.clear ();
1321 _pending_note_selection.clear ();
1326 MidiRegionView::display_patch_changes ()
1328 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1329 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1331 for (uint8_t i = 0; i < 16; ++i) {
1332 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1336 /** @param active_channel true to display patch changes fully, false to display
1337 * them `greyed-out' (as on an inactive channel)
1340 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1342 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1343 boost::shared_ptr<PatchChange> p;
1345 if ((*i)->channel() != channel) {
1349 if ((p = find_canvas_patch_change (*i)) != 0) {
1351 const samplecnt_t region_samples = source_beats_to_region_samples ((*i)->time());
1353 if (region_samples < 0 || region_samples >= _region->length()) {
1356 const double x = trackview.editor().sample_to_pixel (region_samples);
1357 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1358 p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
1359 p->set_text (patch_name);
1365 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1366 add_canvas_patch_change (*i, patch_name, active_channel);
1372 MidiRegionView::display_sysexes()
1374 bool have_periodic_system_messages = false;
1375 bool display_periodic_messages = true;
1377 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1379 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1380 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1381 have_periodic_system_messages = true;
1386 if (have_periodic_system_messages) {
1387 double zoom = trackview.editor().get_current_zoom (); // samples per pixel
1389 /* get an approximate value for the number of samples per video frame */
1391 double video_frame = trackview.session()->sample_rate() * (1.0/30);
1393 /* if we are zoomed out beyond than the cutoff (i.e. more
1394 * samples per pixel than samples per 4 video frames), don't
1395 * show periodic sysex messages.
1398 if (zoom > (video_frame*4)) {
1399 display_periodic_messages = false;
1403 display_periodic_messages = false;
1406 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1407 MidiModel::SysExPtr sysex_ptr = *i;
1408 Temporal::Beats time = sysex_ptr->time();
1410 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1411 if (!display_periodic_messages) {
1418 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1419 str << int((*i)->buffer()[b]);
1420 if (b != (*i)->size() -1) {
1424 string text = str.str();
1426 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_samples(time));
1428 double height = midi_stream_view()->contents_height();
1430 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1431 // SysEx canvas object!!!
1432 boost::shared_ptr<SysEx> sysex = find_canvas_sys_ex (sysex_ptr);
1435 sysex = boost::shared_ptr<SysEx>(
1436 new SysEx (*this, _note_group, text, height, x, 1.0, sysex_ptr));
1437 _sys_exes.insert (make_pair (sysex_ptr, sysex));
1439 sysex->set_height (height);
1440 sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
1443 // Show unless message is beyond the region bounds
1444 // XXX REQUIRES APPROPRIATE OPERATORS FOR Temporal::Beats and samplepos? say what?
1445 #warning paul fix this
1446 // if (time - _region->start() >= _region->length() || time < _region->start()) {
1454 MidiRegionView::~MidiRegionView ()
1456 in_destructor = true;
1458 hide_verbose_cursor ();
1460 delete _list_editor;
1462 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1464 if (_active_notes) {
1471 delete _note_diff_command;
1472 delete _step_edit_cursor;
1476 MidiRegionView::region_resized (const PropertyChange& what_changed)
1478 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1480 if (what_changed.contains (ARDOUR::Properties::position)) {
1481 _region_relative_time_converter.set_origin_b(_region->position());
1482 _region_relative_time_converter_double.set_origin_b(_region->position());
1483 /* reset_width dependent_items() redisplays model */
1487 if (what_changed.contains (ARDOUR::Properties::start) ||
1488 what_changed.contains (ARDOUR::Properties::position)) {
1489 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1491 /* catch end and start trim so we can update the view*/
1492 if (!what_changed.contains (ARDOUR::Properties::start) &&
1493 what_changed.contains (ARDOUR::Properties::length)) {
1494 enable_display (true);
1495 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1496 what_changed.contains (ARDOUR::Properties::length)) {
1497 enable_display (true);
1502 MidiRegionView::reset_width_dependent_items (double pixel_width)
1504 RegionView::reset_width_dependent_items(pixel_width);
1506 if (_enable_display) {
1510 bool hide_all = false;
1511 PatchChanges::iterator x = _patch_changes.begin();
1512 if (x != _patch_changes.end()) {
1513 hide_all = x->second->width() >= _pixel_width;
1517 for (; x != _patch_changes.end(); ++x) {
1522 move_step_edit_cursor (_step_edit_cursor_position);
1523 set_step_edit_cursor_width (_step_edit_cursor_width);
1527 MidiRegionView::set_height (double height)
1529 double old_height = _height;
1530 RegionView::set_height(height);
1532 apply_note_range (midi_stream_view()->lowest_note(),
1533 midi_stream_view()->highest_note(),
1534 height != old_height);
1537 name_text->raise_to_top();
1540 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1541 (*x).second->set_height (midi_stream_view()->contents_height());
1544 if (_step_edit_cursor) {
1545 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1550 /** Apply the current note range from the stream view
1551 * by repositioning/hiding notes as necessary
1554 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1556 if (!_enable_display) {
1560 if (!force && _current_range_min == min && _current_range_max == max) {
1564 _current_range_min = min;
1565 _current_range_max = max;
1571 MidiRegionView::add_ghost (TimeAxisView& tv)
1573 double unit_position = _region->position () / samples_per_pixel;
1574 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1575 MidiGhostRegion* ghost;
1577 if (mtv && mtv->midi_view()) {
1578 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1579 to allow having midi notes on top of note lines and waveforms.
1581 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1583 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1586 ghost->set_colors ();
1587 ghost->set_height ();
1588 ghost->set_duration (_region->length() / samples_per_pixel);
1590 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1591 ghost->add_note(i->second);
1594 ghosts.push_back (ghost);
1595 enable_display (true);
1600 /** Begin tracking note state for successive calls to add_event
1603 MidiRegionView::begin_write()
1605 if (_active_notes) {
1606 delete[] _active_notes;
1608 _active_notes = new Note*[128];
1609 for (unsigned i = 0; i < 128; ++i) {
1610 _active_notes[i] = 0;
1615 /** Destroy note state for add_event
1618 MidiRegionView::end_write()
1620 delete[] _active_notes;
1622 _marked_for_selection.clear();
1623 _marked_for_velocity.clear();
1627 /** Resolve an active MIDI note (while recording).
1630 MidiRegionView::resolve_note(uint8_t note, Temporal::Beats end_time)
1632 if (midi_view()->note_mode() != Sustained) {
1636 if (_active_notes && _active_notes[note]) {
1637 /* Set note length so update_note() works. Note this is a local note
1638 for recording, not from a model, so we can safely mess with it. */
1639 _active_notes[note]->note()->set_length(
1640 end_time - _active_notes[note]->note()->time());
1642 /* End time is relative to the region being recorded. */
1643 const samplepos_t end_time_samples = region_beats_to_region_samples(end_time);
1645 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_samples));
1646 _active_notes[note]->set_outline_all ();
1647 _active_notes[note] = 0;
1652 /** Extend active notes to rightmost edge of region (if length is changed)
1655 MidiRegionView::extend_active_notes()
1657 if (!_active_notes) {
1661 for (unsigned i = 0; i < 128; ++i) {
1662 if (_active_notes[i]) {
1663 _active_notes[i]->set_x1(
1664 trackview.editor().sample_to_pixel(_region->length()));
1670 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1672 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1676 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1678 if (!route_ui || !route_ui->midi_track()) {
1682 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1686 /* NotePlayer deletes itself */
1690 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1692 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1693 start_playing_midi_chord(notes);
1697 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1699 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1703 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1705 if (!route_ui || !route_ui->midi_track()) {
1709 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1711 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1720 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1722 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1724 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1725 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1726 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1728 visible = (note->note() >= _current_range_min) &&
1729 (note->note() <= _current_range_max);
1735 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1739 if ((sus = dynamic_cast<Note*>(note))) {
1740 update_sustained(sus, update_ghost_regions);
1741 } else if ((hit = dynamic_cast<Hit*>(note))) {
1742 update_hit(hit, update_ghost_regions);
1746 /** Update a canvas note's size from its model note.
1747 * @param ev Canvas note to update.
1748 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1751 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1753 TempoMap& map (trackview.session()->tempo_map());
1754 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1755 boost::shared_ptr<NoteType> note = ev->note();
1757 const double session_source_start = _region->quarter_note() - mr->start_beats();
1758 const samplepos_t note_start_samples = map.sample_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1760 const double x0 = trackview.editor().sample_to_pixel (note_start_samples);
1762 const double y0 = 1 + floor(note_to_y(note->note()));
1765 /* trim note display to not overlap the end of its region */
1766 if (note->length().to_double() > 0.0) {
1767 double note_end_time = note->end_time().to_double();
1769 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1770 note_end_time = mr->start_beats() + mr->length_beats();
1773 const samplepos_t note_end_samples = map.sample_at_quarter_note (session_source_start + note_end_time) - _region->position();
1775 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_samples)) - 1;
1777 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1780 y1 = y0 + std::max(1., floor(note_height()) - 1);
1782 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1783 ev->set_velocity (note->velocity()/127.0);
1785 if (!note->length()) {
1786 if (_active_notes && note->note() < 128) {
1787 Note* const old_rect = _active_notes[note->note()];
1789 /* There is an active note on this key, so we have a stuck
1790 note. Finish the old rectangle here. */
1791 old_rect->set_x1 (x1);
1792 old_rect->set_outline_all ();
1794 _active_notes[note->note()] = ev;
1796 /* outline all but right edge */
1797 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1798 ArdourCanvas::Rectangle::TOP|
1799 ArdourCanvas::Rectangle::LEFT|
1800 ArdourCanvas::Rectangle::BOTTOM));
1802 /* outline all edges */
1803 ev->set_outline_all ();
1806 // Update color in case velocity has changed
1807 const uint32_t base_col = ev->base_color();
1808 ev->set_fill_color(base_col);
1809 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1814 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1816 boost::shared_ptr<NoteType> note = ev->note();
1818 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1819 const samplepos_t note_start_samples = trackview.session()->tempo_map().sample_at_quarter_note (note_time_qn) - _region->position();
1821 const double x = trackview.editor().sample_to_pixel(note_start_samples);
1822 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1823 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1825 // see DnD note in MidiRegionView::apply_note_range() above
1826 if (y <= 0 || y >= _height) {
1832 ev->set_position (ArdourCanvas::Duple (x, y));
1833 ev->set_height (diamond_size);
1835 // Update color in case velocity has changed
1836 const uint32_t base_col = ev->base_color();
1837 ev->set_fill_color(base_col);
1838 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1842 /** Add a MIDI note to the view (with length).
1844 * If in sustained mode, notes with length 0 will be considered active
1845 * notes, and resolve_note should be called when the corresponding note off
1846 * event arrives, to properly display the note.
1849 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1851 NoteBase* event = 0;
1853 if (midi_view()->note_mode() == Sustained) {
1855 Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak
1857 update_sustained (ev_rect);
1861 } else if (midi_view()->note_mode() == Percussive) {
1863 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1865 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak
1867 update_hit (ev_diamond);
1876 MidiGhostRegion* gr;
1878 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1879 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1880 gr->add_note(event);
1884 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1885 note_selected(event, true);
1888 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1889 event->show_velocity();
1892 event->on_channel_selection_change (get_selected_channels());
1893 _events.insert (make_pair (event->note(), event));
1902 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1903 MidiStreamView* const view = mtv->midi_view();
1905 view->update_note_range (note->note());
1910 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1911 Temporal::Beats pos, Temporal::Beats len)
1913 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1915 /* potentially extend region to hold new note */
1917 samplepos_t end_sample = source_beats_to_absolute_samples (new_note->end_time());
1918 samplepos_t region_end = _region->last_sample();
1920 if (end_sample > region_end) {
1921 /* XX sets length in beats from audio space. make musical */
1922 _region->set_length (end_sample - _region->position(), 0);
1925 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1926 MidiStreamView* const view = mtv->midi_view();
1928 view->update_note_range(new_note->note());
1930 _marked_for_selection.clear ();
1932 start_note_diff_command (_("step add"));
1934 clear_editor_note_selection ();
1935 note_diff_add_note (new_note, true, false);
1939 // last_step_edit_note = new_note;
1943 MidiRegionView::step_sustain (Temporal::Beats beats)
1945 change_note_lengths (false, false, beats, false, true);
1948 /** Add a new patch change flag to the canvas.
1949 * @param patch the patch change to add
1950 * @param the text to display in the flag
1951 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1954 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1956 samplecnt_t region_samples = source_beats_to_region_samples (patch->time());
1957 const double x = trackview.editor().sample_to_pixel (region_samples);
1959 double const height = midi_stream_view()->contents_height();
1961 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1962 // so we need to do something more sophisticated to keep its color
1963 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1965 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1966 new PatchChange(*this, group,
1972 _patch_change_outline,
1976 if (patch_change->item().width() < _pixel_width) {
1977 // Show unless patch change is beyond the region bounds
1978 if (region_samples < 0 || region_samples >= _region->length()) {
1979 patch_change->hide();
1981 patch_change->show();
1984 patch_change->hide ();
1987 _patch_changes.insert (make_pair (patch, patch_change));
1991 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
1993 /* remove the canvas item */
1994 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1995 if (x->second->patch() == pc->patch()) {
1996 _patch_changes.erase (x);
2002 MIDI::Name::PatchPrimaryKey
2003 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
2005 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
2008 /// Return true iff @p pc applies to the given time on the given channel.
2010 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Temporal::Beats time, uint8_t channel)
2012 return pc->time() <= time && pc->channel() == channel;
2016 MidiRegionView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
2018 // The earliest event not before time
2019 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
2021 // Go backwards until we find the latest PC for this channel, or the start
2022 while (i != _model->patch_changes().begin() &&
2023 (i == _model->patch_changes().end() ||
2024 !patch_applies(*i, time, channel))) {
2028 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
2029 key.set_bank((*i)->bank());
2030 key.set_program((*i)->program ());
2038 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
2040 string name = _("alter patch change");
2041 trackview.editor().begin_reversible_command (name);
2043 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2045 if (pc.patch()->program() != new_patch.program()) {
2046 c->change_program (pc.patch (), new_patch.program());
2049 int const new_bank = new_patch.bank();
2050 if (pc.patch()->bank() != new_bank) {
2051 c->change_bank (pc.patch (), new_bank);
2054 _model->apply_command (*trackview.session(), c);
2055 trackview.editor().commit_reversible_command ();
2057 remove_canvas_patch_change (&pc);
2058 display_patch_changes ();
2062 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Temporal::Beats> & new_change)
2064 string name = _("alter patch change");
2065 trackview.editor().begin_reversible_command (name);
2066 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2068 if (old_change->time() != new_change.time()) {
2069 c->change_time (old_change, new_change.time());
2072 if (old_change->channel() != new_change.channel()) {
2073 c->change_channel (old_change, new_change.channel());
2076 if (old_change->program() != new_change.program()) {
2077 c->change_program (old_change, new_change.program());
2080 if (old_change->bank() != new_change.bank()) {
2081 c->change_bank (old_change, new_change.bank());
2084 _model->apply_command (*trackview.session(), c);
2085 trackview.editor().commit_reversible_command ();
2087 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2088 if (x->second->patch() == old_change) {
2089 _patch_changes.erase (x);
2094 display_patch_changes ();
2097 /** Add a patch change to the region.
2098 * @param t Time in samples relative to region position
2099 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2100 * MidiTimeAxisView::get_channel_for_add())
2103 MidiRegionView::add_patch_change (samplecnt_t t, Evoral::PatchChange<Temporal::Beats> const & patch)
2105 string name = _("add patch change");
2107 trackview.editor().begin_reversible_command (name);
2108 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2109 c->add (MidiModel::PatchChangePtr (
2110 new Evoral::PatchChange<Temporal::Beats> (
2111 absolute_samples_to_source_beats (_region->position() + t),
2112 patch.channel(), patch.program(), patch.bank()
2117 _model->apply_command (*trackview.session(), c);
2118 trackview.editor().commit_reversible_command ();
2120 display_patch_changes ();
2124 MidiRegionView::move_patch_change (PatchChange& pc, Temporal::Beats t)
2126 trackview.editor().begin_reversible_command (_("move patch change"));
2127 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2128 c->change_time (pc.patch (), t);
2129 _model->apply_command (*trackview.session(), c);
2130 trackview.editor().commit_reversible_command ();
2132 display_patch_changes ();
2136 MidiRegionView::delete_patch_change (PatchChange* pc)
2138 trackview.editor().begin_reversible_command (_("delete patch change"));
2140 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2141 c->remove (pc->patch ());
2142 _model->apply_command (*trackview.session(), c);
2143 trackview.editor().commit_reversible_command ();
2145 remove_canvas_patch_change (pc);
2146 display_patch_changes ();
2150 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2152 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2154 key.set_bank(key.bank() + delta);
2156 key.set_program(key.program() + delta);
2158 change_patch_change(patch, key);
2162 MidiRegionView::note_deleted (NoteBase* cne)
2164 if (_entered_note && cne == _entered_note) {
2168 if (_selection.empty()) {
2172 _selection.erase (cne);
2176 MidiRegionView::delete_selection()
2178 if (_selection.empty()) {
2182 if (trackview.editor().drags()->active()) {
2186 start_note_diff_command (_("delete selection"));
2188 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2189 if ((*i)->selected()) {
2190 _note_diff_command->remove((*i)->note());
2198 hide_verbose_cursor ();
2202 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2204 start_note_diff_command (_("delete note"));
2205 _note_diff_command->remove (n);
2208 hide_verbose_cursor ();
2212 MidiRegionView::clear_editor_note_selection ()
2214 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2215 PublicEditor& editor(trackview.editor());
2216 editor.get_selection().clear_midi_notes();
2220 MidiRegionView::clear_selection ()
2222 clear_selection_internal();
2223 PublicEditor& editor(trackview.editor());
2224 editor.get_selection().remove(this);
2228 MidiRegionView::clear_selection_internal ()
2230 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2232 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2233 (*i)->set_selected(false);
2234 (*i)->hide_velocity();
2239 // Clearing selection entirely, ungrab keyboard
2240 Keyboard::magic_widget_drop_focus();
2241 _grabbed_keyboard = false;
2246 MidiRegionView::unique_select(NoteBase* ev)
2248 clear_editor_note_selection();
2249 add_to_selection(ev);
2253 MidiRegionView::select_all_notes ()
2255 clear_editor_note_selection ();
2257 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2258 add_to_selection (i->second);
2263 MidiRegionView::select_range (samplepos_t start, samplepos_t end)
2265 clear_editor_note_selection ();
2267 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2268 samplepos_t t = source_beats_to_absolute_samples(i->first->time());
2269 if (t >= start && t <= end) {
2270 add_to_selection (i->second);
2276 MidiRegionView::invert_selection ()
2278 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2279 if (i->second->selected()) {
2280 remove_from_selection(i->second);
2282 add_to_selection (i->second);
2287 /** Used for selection undo/redo.
2288 The requested notes most likely won't exist in the view until the next model redisplay.
2291 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2294 list<Evoral::event_id_t>::iterator n;
2296 for (n = notes.begin(); n != notes.end(); ++n) {
2297 if ((cne = find_canvas_note(*n)) != 0) {
2298 add_to_selection (cne);
2300 _pending_note_selection.insert(*n);
2306 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2308 bool have_selection = !_selection.empty();
2309 uint8_t low_note = 127;
2310 uint8_t high_note = 0;
2311 MidiModel::Notes& notes (_model->notes());
2312 _optimization_iterator = _events.begin();
2314 if (extend && !have_selection) {
2318 /* scan existing selection to get note range */
2320 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2321 if ((*i)->note()->note() < low_note) {
2322 low_note = (*i)->note()->note();
2324 if ((*i)->note()->note() > high_note) {
2325 high_note = (*i)->note()->note();
2330 clear_editor_note_selection ();
2332 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2333 /* only note previously selected is the one we are
2334 * reselecting. treat this as cancelling the selection.
2341 low_note = min (low_note, notenum);
2342 high_note = max (high_note, notenum);
2345 _no_sound_notes = true;
2347 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2349 boost::shared_ptr<NoteType> note (*n);
2351 bool select = false;
2353 if (((1 << note->channel()) & channel_mask) != 0) {
2355 if ((note->note() >= low_note && note->note() <= high_note)) {
2358 } else if (note->note() == notenum) {
2364 if ((cne = find_canvas_note (note)) != 0) {
2365 // extend is false because we've taken care of it,
2366 // since it extends by time range, not pitch.
2367 note_selected (cne, add, false);
2371 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2375 _no_sound_notes = false;
2379 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2381 MidiModel::Notes& notes (_model->notes());
2382 _optimization_iterator = _events.begin();
2384 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2386 boost::shared_ptr<NoteType> note (*n);
2389 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2390 if ((cne = find_canvas_note (note)) != 0) {
2391 if (cne->selected()) {
2392 note_deselected (cne);
2394 note_selected (cne, true, false);
2402 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2405 clear_editor_note_selection();
2406 add_to_selection (ev);
2411 if (!ev->selected()) {
2412 add_to_selection (ev);
2416 /* find end of latest note selected, select all between that and the start of "ev" */
2418 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2419 Temporal::Beats latest = Temporal::Beats();
2421 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2422 if ((*i)->note()->end_time() > latest) {
2423 latest = (*i)->note()->end_time();
2425 if ((*i)->note()->time() < earliest) {
2426 earliest = (*i)->note()->time();
2430 if (ev->note()->end_time() > latest) {
2431 latest = ev->note()->end_time();
2434 if (ev->note()->time() < earliest) {
2435 earliest = ev->note()->time();
2438 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2440 /* find notes entirely within OR spanning the earliest..latest range */
2442 if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
2443 (i->first->time() <= earliest && i->first->end_time() >= latest)) {
2444 add_to_selection (i->second);
2451 MidiRegionView::note_deselected(NoteBase* ev)
2453 remove_from_selection (ev);
2457 MidiRegionView::update_drag_selection(samplepos_t start, samplepos_t end, double gy0, double gy1, bool extend)
2459 PublicEditor& editor = trackview.editor();
2461 // Convert to local coordinates
2462 const samplepos_t p = _region->position();
2463 const double y = midi_view()->y_position();
2464 const double x0 = editor.sample_to_pixel(max((samplepos_t)0, start - p));
2465 const double x1 = editor.sample_to_pixel(max((samplepos_t)0, end - p));
2466 const double y0 = max(0.0, gy0 - y);
2467 const double y1 = max(0.0, gy1 - y);
2469 // TODO: Make this faster by storing the last updated selection rect, and only
2470 // adjusting things that are in the area that appears/disappeared.
2471 // We probably need a tree to be able to find events in O(log(n)) time.
2473 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2474 if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
2475 // Rectangles intersect
2476 if (!i->second->selected()) {
2477 add_to_selection (i->second);
2479 } else if (i->second->selected() && !extend) {
2480 // Rectangles do not intersect
2481 remove_from_selection (i->second);
2485 typedef RouteTimeAxisView::AutomationTracks ATracks;
2486 typedef std::list<Selectable*> Selectables;
2488 /* Add control points to selection. */
2489 const ATracks& atracks = midi_view()->automation_tracks();
2490 Selectables selectables;
2491 editor.get_selection().clear_points();
2492 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2493 a->second->get_selectables(start, end, gy0, gy1, selectables);
2494 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2495 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2497 editor.get_selection().add(cp);
2500 a->second->set_selected_points(editor.get_selection().points);
2505 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2511 // TODO: Make this faster by storing the last updated selection rect, and only
2512 // adjusting things that are in the area that appears/disappeared.
2513 // We probably need a tree to be able to find events in O(log(n)) time.
2515 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2516 if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
2517 // within y- (note-) range
2518 if (!i->second->selected()) {
2519 add_to_selection (i->second);
2521 } else if (i->second->selected() && !extend) {
2522 remove_from_selection (i->second);
2528 MidiRegionView::remove_from_selection (NoteBase* ev)
2530 Selection::iterator i = _selection.find (ev);
2532 if (i != _selection.end()) {
2533 _selection.erase (i);
2534 if (_selection.empty() && _grabbed_keyboard) {
2536 Keyboard::magic_widget_drop_focus();
2537 _grabbed_keyboard = false;
2541 ev->set_selected (false);
2542 ev->hide_velocity ();
2544 if (_selection.empty()) {
2545 PublicEditor& editor (trackview.editor());
2546 editor.get_selection().remove (this);
2551 MidiRegionView::add_to_selection (NoteBase* ev)
2553 const bool selection_was_empty = _selection.empty();
2555 if (_selection.insert (ev).second) {
2556 ev->set_selected (true);
2557 start_playing_midi_note ((ev)->note());
2558 if (selection_was_empty && _entered) {
2559 // Grab keyboard for moving notes with arrow keys
2560 Keyboard::magic_widget_grab_focus();
2561 _grabbed_keyboard = true;
2565 if (selection_was_empty) {
2566 PublicEditor& editor (trackview.editor());
2567 editor.get_selection().add (this);
2572 MidiRegionView::earliest_in_selection ()
2574 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2576 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2577 if ((*i)->note()->time() < earliest) {
2578 earliest = (*i)->note()->time();
2586 MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
2588 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2589 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2590 TempoMap& tmap (editor->session()->tempo_map());
2591 PossibleChord to_play;
2592 Temporal::Beats earliest = earliest_in_selection();
2594 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2596 if (n->note()->time() == earliest) {
2597 to_play.push_back (n->note());
2599 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2601 if (midi_view()->note_mode() == Sustained) {
2602 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2603 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2605 /* Hit::x0() is offset by _position.x, unlike Note::x0() */
2606 Hit* hit = dynamic_cast<Hit*>(n);
2608 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2609 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2613 (*i)->move_event(dx, dy);
2616 if (midi_view()->note_mode() == Sustained) {
2617 Note* sus = dynamic_cast<Note*> (*i);
2618 double const len_dx = editor->sample_to_pixel_unrounded (
2619 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2621 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2625 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2627 if (to_play.size() > 1) {
2629 PossibleChord shifted;
2631 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2632 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2633 moved_note->set_note (moved_note->note() + cumulative_dy);
2634 shifted.push_back (moved_note);
2637 start_playing_midi_chord (shifted);
2639 } else if (!to_play.empty()) {
2641 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2642 moved_note->set_note (moved_note->note() + cumulative_dy);
2643 start_playing_midi_note (moved_note);
2649 MidiRegionView::copy_selection (NoteBase* primary)
2651 _copy_drag_events.clear ();
2653 if (_selection.empty()) {
2660 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2661 boost::shared_ptr<NoteType> g (new NoteType (*((*i)->note())));
2662 if (midi_view()->note_mode() == Sustained) {
2663 Note* n = new Note (*this, _note_group, g);
2664 update_sustained (n, false);
2667 Hit* h = new Hit (*this, _note_group, 10, g);
2668 update_hit (h, false);
2672 if ((*i) == primary) {
2676 _copy_drag_events.push_back (note);
2683 MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
2685 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2686 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2687 TempoMap& tmap (editor->session()->tempo_map());
2688 PossibleChord to_play;
2689 Temporal::Beats earliest = earliest_in_selection();
2691 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2693 if (n->note()->time() == earliest) {
2694 to_play.push_back (n->note());
2696 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2698 if (midi_view()->note_mode() == Sustained) {
2699 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2700 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2702 Hit* hit = dynamic_cast<Hit*>(n);
2704 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2705 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2709 (*i)->move_event(dx, dy);
2711 if (midi_view()->note_mode() == Sustained) {
2712 Note* sus = dynamic_cast<Note*> (*i);
2713 double const len_dx = editor->sample_to_pixel_unrounded (
2714 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2716 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2720 if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2722 if (to_play.size() > 1) {
2724 PossibleChord shifted;
2726 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2727 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2728 moved_note->set_note (moved_note->note() + cumulative_dy);
2729 shifted.push_back (moved_note);
2732 start_playing_midi_chord (shifted);
2734 } else if (!to_play.empty()) {
2736 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2737 moved_note->set_note (moved_note->note() + cumulative_dy);
2738 start_playing_midi_note (moved_note);
2744 MidiRegionView::note_dropped(NoteBase *, double d_qn, int8_t dnote, bool copy)
2746 uint8_t lowest_note_in_selection = 127;
2747 uint8_t highest_note_in_selection = 0;
2748 uint8_t highest_note_difference = 0;
2751 // find highest and lowest notes first
2753 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2754 uint8_t pitch = (*i)->note()->note();
2755 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2756 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2760 cerr << "dnote: " << (int) dnote << endl;
2761 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2762 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2763 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2764 << int(highest_note_in_selection) << endl;
2765 cerr << "selection size: " << _selection.size() << endl;
2766 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2769 // Make sure the note pitch does not exceed the MIDI standard range
2770 if (highest_note_in_selection + dnote > 127) {
2771 highest_note_difference = highest_note_in_selection - 127;
2774 start_note_diff_command (_("move notes"));
2776 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2778 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2784 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2786 uint8_t original_pitch = (*i)->note()->note();
2787 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2789 // keep notes in standard midi range
2790 clamp_to_0_127(new_pitch);
2792 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2793 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2795 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2799 clear_editor_note_selection ();
2801 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2802 uint8_t pitch = (*i)->note()->note();
2803 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2804 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2807 // Make sure the note pitch does not exceed the MIDI standard range
2808 if (highest_note_in_selection + dnote > 127) {
2809 highest_note_difference = highest_note_in_selection - 127;
2812 start_note_diff_command (_("copy notes"));
2814 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
2817 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2823 (*i)->note()->set_time (new_time);
2827 uint8_t original_pitch = (*i)->note()->note();
2828 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2830 (*i)->note()->set_note (new_pitch);
2832 // keep notes in standard midi range
2833 clamp_to_0_127(new_pitch);
2835 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2836 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2838 note_diff_add_note ((*i)->note(), true);
2843 _copy_drag_events.clear ();
2846 apply_diff (false, copy);
2848 // care about notes being moved beyond the upper/lower bounds on the canvas
2849 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2850 highest_note_in_selection > midi_stream_view()->highest_note()) {
2851 midi_stream_view()->set_note_range (MidiStreamView::ContentsRange);
2855 /** @param x Pixel relative to the region position.
2856 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2857 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2858 * @return Snapped sample relative to the region position.
2861 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2863 PublicEditor& editor (trackview.editor());
2864 return snap_sample_to_sample (editor.pixel_to_sample (x), ensure_snap).sample;
2867 /** @param x Pixel relative to the region position.
2868 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2869 * @return Snapped pixel relative to the region position.
2872 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2874 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2878 MidiRegionView::get_position_pixels()
2880 samplepos_t region_sample = get_position();
2881 return trackview.editor().sample_to_pixel(region_sample);
2885 MidiRegionView::get_end_position_pixels()
2887 samplepos_t sample = get_position() + get_duration ();
2888 return trackview.editor().sample_to_pixel(sample);
2892 MidiRegionView::source_beats_to_absolute_samples(Temporal::Beats beats) const
2894 /* the time converter will return the sample corresponding to `beats'
2895 relative to the start of the source. The start of the source
2896 is an implied position given by region->position - region->start
2898 const samplepos_t source_start = _region->position() - _region->start();
2899 return source_start + _source_relative_time_converter.to (beats);
2903 MidiRegionView::absolute_samples_to_source_beats(samplepos_t samples) const
2905 /* the `samples' argument needs to be converted into a sample count
2906 relative to the start of the source before being passed in to the
2909 const samplepos_t source_start = _region->position() - _region->start();
2910 return _source_relative_time_converter.from (samples - source_start);
2914 MidiRegionView::region_beats_to_region_samples(Temporal::Beats beats) const
2916 return _region_relative_time_converter.to(beats);
2920 MidiRegionView::region_samples_to_region_beats(samplepos_t samples) const
2922 return _region_relative_time_converter.from(samples);
2926 MidiRegionView::region_samples_to_region_beats_double (samplepos_t samples) const
2928 return _region_relative_time_converter_double.from(samples);
2932 MidiRegionView::begin_resizing (bool /*at_front*/)
2934 _resize_data.clear();
2936 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2937 Note *note = dynamic_cast<Note*> (*i);
2939 // only insert CanvasNotes into the map
2941 NoteResizeData *resize_data = new NoteResizeData();
2942 resize_data->note = note;
2944 // create a new SimpleRect from the note which will be the resize preview
2945 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2946 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2948 // calculate the colors: get the color settings
2949 uint32_t fill_color = NoteBase::meter_style_fill_color (note->note()->velocity(), true);
2951 // make the resize preview notes more transparent and bright
2952 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2954 // calculate color based on note velocity
2955 resize_rect->set_fill_color (UINT_INTERPOLATE(
2956 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2960 resize_rect->set_outline_color (NoteBase::calculate_outline (
2961 UIConfiguration::instance().color ("midi note selected outline")));
2963 resize_data->resize_rect = resize_rect;
2964 _resize_data.push_back(resize_data);
2969 /** Update resizing notes while user drags.
2970 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2971 * @param at_front which end of the note (true == note on, false == note off)
2972 * @param delta_x change in mouse position since the start of the drag
2973 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2974 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2975 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2976 * as the \a primary note.
2977 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2978 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2981 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2983 TempoMap& tmap (trackview.session()->tempo_map());
2984 bool cursor_set = false;
2985 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2987 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2988 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2989 Note* canvas_note = (*i)->note;
2994 current_x = canvas_note->x0() + delta_x + snap_delta;
2996 current_x = primary->x0() + delta_x + snap_delta;
3000 current_x = canvas_note->x1() + delta_x + snap_delta;
3002 current_x = primary->x1() + delta_x + snap_delta;
3006 if (current_x < 0) {
3007 /* This works even with snapping because RegionView::snap_sample_to_sample()
3008 * snaps forward if the snapped sample is before the beginning of the region
3012 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3013 current_x = trackview.editor().sample_to_pixel(_region->length());
3018 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3020 resize_rect->set_x0 (current_x - snap_delta);
3022 resize_rect->set_x1 (canvas_note->x1());
3025 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3027 resize_rect->set_x1 (current_x - snap_delta);
3029 resize_rect->set_x0 (canvas_note->x0());
3033 /* Convert snap delta from pixels to beats. */
3034 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3035 double snap_delta_beats = 0.0;
3038 /* negative beat offsets aren't allowed */
3039 if (snap_delta_samps > 0) {
3040 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3041 } else if (snap_delta_samps < 0) {
3042 snap_delta_beats = region_samples_to_region_beats_double (- snap_delta_samps);
3047 int32_t divisions = 0;
3050 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
3051 divisions = trackview.editor().get_grid_music_divisions (0);
3053 snapped_x = trackview.editor ().pixel_to_sample (current_x);
3056 const Temporal::Beats beats = Temporal::Beats (tmap.exact_beat_at_sample (snapped_x + midi_region()->position(), divisions)
3057 - midi_region()->beat())
3058 + midi_region()->start_beats();
3060 Temporal::Beats len = Temporal::Beats();
3063 if (beats < canvas_note->note()->end_time()) {
3064 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
3065 len += canvas_note->note()->length();
3068 if (beats >= canvas_note->note()->time()) {
3069 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
3073 len = std::max(Temporal::Beats(1 / 512.0), len);
3076 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
3077 show_verbose_cursor (buf, 0, 0);
3081 trackview.editor().set_snapped_cursor_position ( snapped_x + midi_region()->position() );
3088 /** Finish resizing notes when the user releases the mouse button.
3089 * Parameters the same as for \a update_resizing().
3092 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
3094 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
3095 TempoMap& tmap (trackview.session()->tempo_map());
3097 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
3098 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
3100 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3101 Note* canvas_note = (*i)->note;
3102 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
3104 /* Get the new x position for this resize, which is in pixels relative
3105 * to the region position.
3112 current_x = canvas_note->x0() + delta_x + snap_delta;
3114 current_x = primary->x0() + delta_x + snap_delta;
3118 current_x = canvas_note->x1() + delta_x + snap_delta;
3120 current_x = primary->x1() + delta_x + snap_delta;
3124 if (current_x < 0) {
3127 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3128 current_x = trackview.editor().sample_to_pixel(_region->length());
3131 /* Convert snap delta from pixels to beats with sign. */
3132 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3133 double snap_delta_beats = 0.0;
3136 if (snap_delta_samps > 0) {
3137 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3138 } else if (snap_delta_samps < 0) {
3139 snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
3143 uint32_t divisions = 0;
3144 /* Convert the new x position to a sample within the source */
3145 samplepos_t current_fr;
3147 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
3148 divisions = trackview.editor().get_grid_music_divisions (0);
3150 current_fr = trackview.editor().pixel_to_sample (current_x);
3153 /* and then to beats */
3154 const double e_qaf = tmap.exact_qn_at_sample (current_fr + midi_region()->position(), divisions);
3155 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
3156 const Temporal::Beats x_beats = Temporal::Beats (e_qaf - quarter_note_start);
3158 if (at_front && x_beats < canvas_note->note()->end_time()) {
3159 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
3160 Temporal::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
3161 len += canvas_note->note()->length();
3164 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3169 Temporal::Beats len = std::max(Temporal::Beats(1 / 512.0),
3170 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
3171 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3178 _resize_data.clear();
3183 MidiRegionView::abort_resizing ()
3185 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3186 delete (*i)->resize_rect;
3190 _resize_data.clear ();
3194 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
3196 uint8_t new_velocity;
3199 new_velocity = event->note()->velocity() + velocity;
3200 clamp_to_0_127(new_velocity);
3202 new_velocity = velocity;
3205 event->set_selected (event->selected()); // change color
3207 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
3211 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
3216 new_note = event->note()->note() + note;
3221 clamp_to_0_127 (new_note);
3222 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3226 MidiRegionView::trim_note (NoteBase* event, Temporal::Beats front_delta, Temporal::Beats end_delta)
3228 bool change_start = false;
3229 bool change_length = false;
3230 Temporal::Beats new_start;
3231 Temporal::Beats new_length;
3233 /* NOTE: the semantics of the two delta arguments are slightly subtle:
3235 front_delta: if positive - move the start of the note later in time (shortening it)
3236 if negative - move the start of the note earlier in time (lengthening it)
3238 end_delta: if positive - move the end of the note later in time (lengthening it)
3239 if negative - move the end of the note earlier in time (shortening it)
3242 if (!!front_delta) {
3243 if (front_delta < 0) {
3245 if (event->note()->time() < -front_delta) {
3246 new_start = Temporal::Beats();
3248 new_start = event->note()->time() + front_delta; // moves earlier
3251 /* start moved toward zero, so move the end point out to where it used to be.
3252 Note that front_delta is negative, so this increases the length.
3255 new_length = event->note()->length() - front_delta;
3256 change_start = true;
3257 change_length = true;
3261 Temporal::Beats new_pos = event->note()->time() + front_delta;
3263 if (new_pos < event->note()->end_time()) {
3264 new_start = event->note()->time() + front_delta;
3265 /* start moved toward the end, so move the end point back to where it used to be */
3266 new_length = event->note()->length() - front_delta;
3267 change_start = true;
3268 change_length = true;
3275 bool can_change = true;
3276 if (end_delta < 0) {
3277 if (event->note()->length() < -end_delta) {
3283 new_length = event->note()->length() + end_delta;
3284 change_length = true;
3289 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3292 if (change_length) {
3293 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3298 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3300 uint8_t new_channel;
3304 if (event->note()->channel() < -chn) {
3307 new_channel = event->note()->channel() + chn;
3310 new_channel = event->note()->channel() + chn;
3313 new_channel = (uint8_t) chn;
3316 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3320 MidiRegionView::change_note_time (NoteBase* event, Temporal::Beats delta, bool relative)
3322 Temporal::Beats new_time;
3326 if (event->note()->time() < -delta) {
3327 new_time = Temporal::Beats();
3329 new_time = event->note()->time() + delta;
3332 new_time = event->note()->time() + delta;
3338 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3342 MidiRegionView::change_note_length (NoteBase* event, Temporal::Beats t)
3344 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3348 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3353 if (_selection.empty()) {
3368 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3369 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3375 start_note_diff_command (_("change velocities"));
3377 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3378 Selection::iterator next = i;
3382 if (i == _selection.begin()) {
3383 change_note_velocity (*i, delta, true);
3384 value = (*i)->note()->velocity() + delta;
3386 change_note_velocity (*i, value, false);
3390 change_note_velocity (*i, delta, true);
3399 if (!_selection.empty()) {
3401 snprintf (buf, sizeof (buf), "Vel %d",
3402 (int) (*_selection.begin())->note()->velocity());
3403 show_verbose_cursor (buf, 10, 10);
3409 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3411 if (_selection.empty()) {
3428 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3430 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3434 if ((int8_t) (*i)->note()->note() + delta > 127) {
3441 start_note_diff_command (_("transpose"));
3443 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3444 Selection::iterator next = i;
3446 change_note_note (*i, delta, true);
3454 MidiRegionView::change_note_lengths (bool fine, bool shorter, Temporal::Beats delta, bool start, bool end)
3458 delta = Temporal::Beats(1.0/128.0);
3460 /* grab the current grid distance */
3461 delta = get_grid_beats(_region->position());
3469 start_note_diff_command (_("change note lengths"));
3471 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3472 Selection::iterator next = i;
3475 /* note the negation of the delta for start */
3478 (start ? -delta : Temporal::Beats()),
3479 (end ? delta : Temporal::Beats()));
3488 MidiRegionView::nudge_notes (bool forward, bool fine)
3490 if (_selection.empty()) {
3494 /* pick a note as the point along the timeline to get the nudge distance.
3495 its not necessarily the earliest note, so we may want to pull the notes out
3496 into a vector and sort before using the first one.
3499 const samplepos_t ref_point = source_beats_to_absolute_samples ((*(_selection.begin()))->note()->time());
3500 Temporal::Beats delta;
3504 /* non-fine, move by 1 bar regardless of snap */
3505 delta = Temporal::Beats(trackview.session()->tempo_map().meter_at_sample (ref_point).divisions_per_bar());
3507 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3509 /* grid is off - use nudge distance */
3512 const samplecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3513 delta = region_samples_to_region_beats (fabs ((double)distance));
3519 MusicSample next_pos (ref_point, 0);
3521 if (max_samplepos - 1 < next_pos.sample) {
3522 next_pos.sample += 1;
3525 if (next_pos.sample == 0) {
3528 next_pos.sample -= 1;
3531 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), SnapToGrid_Unscaled, false);
3532 const samplecnt_t distance = ref_point - next_pos.sample;
3533 delta = region_samples_to_region_beats (fabs ((double)distance));
3544 start_note_diff_command (_("nudge"));
3546 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3547 Selection::iterator next = i;
3549 change_note_time (*i, delta, true);
3557 MidiRegionView::change_channel(uint8_t channel)
3559 start_note_diff_command(_("change channel"));
3560 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3561 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3569 MidiRegionView::note_entered(NoteBase* ev)
3573 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3575 if (_mouse_state == SelectTouchDragging) {
3577 note_selected (ev, true);
3579 } else if (editor->current_mouse_mode() == MouseContent) {
3581 remove_ghost_note ();
3582 show_verbose_cursor (ev->note ());
3584 } else if (editor->current_mouse_mode() == MouseDraw) {
3586 remove_ghost_note ();
3587 show_verbose_cursor (ev->note ());
3592 MidiRegionView::note_left (NoteBase*)
3596 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3597 (*i)->hide_velocity ();
3600 hide_verbose_cursor ();
3604 MidiRegionView::patch_entered (PatchChange* p)
3607 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3608 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3609 << _("Channel ") << ((int) p->patch()->channel() + 1);
3610 show_verbose_cursor (s.str(), 10, 20);
3611 p->item().grab_focus();
3615 MidiRegionView::patch_left (PatchChange *)
3617 hide_verbose_cursor ();
3618 /* focus will transfer back via the enter-notify event sent to this
3624 MidiRegionView::sysex_entered (SysEx* p)
3628 // need a way to extract text from p->_flag->_text
3630 // show_verbose_cursor (s.str(), 10, 20);
3631 p->item().grab_focus();
3635 MidiRegionView::sysex_left (SysEx *)
3637 hide_verbose_cursor ();
3638 /* focus will transfer back via the enter-notify event sent to this
3644 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3646 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3647 Editing::MouseMode mm = editor->current_mouse_mode();
3648 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3650 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3651 if (can_set_cursor && ctx) {
3652 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3653 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3654 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3655 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3657 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3663 MidiRegionView::get_fill_color() const
3665 const std::string mod_name = (_dragging ? "dragging region" :
3666 trackview.editor().internal_editing() ? "editable region" :
3669 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3670 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3671 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3672 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3674 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3678 MidiRegionView::midi_channel_mode_changed ()
3680 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3681 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3682 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3684 if (mode == ForceChannel) {
3685 mask = 0xFFFF; // Show all notes as active (below)
3688 // Update notes for selection
3689 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3690 i->second->on_channel_selection_change (mask);
3693 _patch_changes.clear ();
3694 display_patch_changes ();
3698 MidiRegionView::instrument_settings_changed ()
3704 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3706 if (_selection.empty()) {
3710 PublicEditor& editor (trackview.editor());
3714 /* XXX what to do ? */
3718 editor.get_cut_buffer().add (selection_as_cut_buffer());
3726 start_note_diff_command();
3728 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3735 note_diff_remove_note (*i);
3745 MidiRegionView::selection_as_cut_buffer () const
3749 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3750 NoteType* n = (*i)->note().get();
3751 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3754 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3760 /** This method handles undo */
3762 MidiRegionView::paste (samplepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3764 bool commit = false;
3765 // Paste notes, if available
3766 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3767 if (m != selection.midi_notes.end()) {
3768 ctx.counts.increase_n_notes();
3769 if (!(*m)->empty()) {
3772 paste_internal(pos, ctx.count, ctx.times, **m);
3775 // Paste control points to automation children, if available
3776 typedef RouteTimeAxisView::AutomationTracks ATracks;
3777 const ATracks& atracks = midi_view()->automation_tracks();
3778 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3779 if (a->second->paste(pos, selection, ctx, sub_num)) {
3781 trackview.editor().begin_reversible_command (Operations::paste);
3788 trackview.editor().commit_reversible_command ();
3793 /** This method handles undo */
3795 MidiRegionView::paste_internal (samplepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3801 start_note_diff_command (_("paste"));
3803 const Temporal::Beats snap_beats = get_grid_beats(pos);
3804 const Temporal::Beats first_time = (*mcb.notes().begin())->time();
3805 const Temporal::Beats last_time = (*mcb.notes().rbegin())->end_time();
3806 const Temporal::Beats duration = last_time - first_time;
3807 const Temporal::Beats snap_duration = duration.snap_to(snap_beats);
3808 const Temporal::Beats paste_offset = snap_duration * paste_count;
3809 const Temporal::Beats quarter_note = absolute_samples_to_source_beats(pos) + paste_offset;
3810 Temporal::Beats end_point = Temporal::Beats();
3812 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3815 duration, pos, _region->position(),
3818 clear_editor_note_selection ();
3820 for (int n = 0; n < (int) times; ++n) {
3822 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3824 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3825 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3826 copied_note->set_id (Evoral::next_event_id());
3828 /* make all newly added notes selected */
3830 note_diff_add_note (copied_note, true);
3831 end_point = copied_note->end_time();
3835 /* if we pasted past the current end of the region, extend the region */
3837 samplepos_t end_sample = source_beats_to_absolute_samples (end_point);
3838 samplepos_t region_end = _region->position() + _region->length() - 1;
3840 if (end_sample > region_end) {
3842 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_sample));
3844 _region->clear_changes ();
3845 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3846 _region->set_length (end_sample - _region->position(), trackview.editor().get_grid_music_divisions (0));
3847 trackview.session()->add_command (new StatefulDiffCommand (_region));
3853 struct EventNoteTimeEarlyFirstComparator {
3854 bool operator() (NoteBase* a, NoteBase* b) {
3855 return a->note()->time() < b->note()->time();
3860 MidiRegionView::goto_next_note (bool add_to_selection)
3862 bool use_next = false;
3864 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3865 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3866 NoteBase* first_note = 0;
3868 MidiModel::ReadLock lock(_model->read_lock());
3869 MidiModel::Notes& notes (_model->notes());
3871 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
3873 if ((cne = find_canvas_note (*n))) {
3875 if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
3879 if (cne->selected()) {
3882 } else if (use_next) {
3883 if (channel_mask & (1 << (*n)->channel())) {
3884 if (!add_to_selection) {
3885 unique_select (cne);
3887 note_selected (cne, true, false);
3896 /* use the first one */
3898 if (!_events.empty() && first_note) {
3899 unique_select (first_note);
3904 MidiRegionView::goto_previous_note (bool add_to_selection)
3906 bool use_next = false;
3908 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3909 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3910 NoteBase* last_note = 0;
3912 MidiModel::ReadLock lock(_model->read_lock());
3913 MidiModel::Notes& notes (_model->notes());
3915 for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
3917 if ((cne = find_canvas_note (*n))) {
3919 if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
3923 if (cne->selected()) {
3927 } else if (use_next) {
3928 if (channel_mask & (1 << (*n)->channel())) {
3929 if (!add_to_selection) {
3930 unique_select (cne);
3932 note_selected (cne, true, false);
3941 /* use the last one */
3943 if (!_events.empty() && last_note) {
3944 unique_select (last_note);
3949 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3951 bool had_selected = false;
3953 /* we previously time sorted events here, but Notes is a multiset sorted by time */
3955 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3956 if (i->second->selected()) {
3957 selected.insert (i->first);
3958 had_selected = true;
3962 if (allow_all_if_none_selected && !had_selected) {
3963 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3964 selected.insert (i->first);
3970 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3972 x = std::max(0.0, x);
3974 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3979 _note_group->canvas_to_item (x, y);
3981 PublicEditor& editor = trackview.editor ();
3983 samplepos_t const unsnapped_sample = editor.pixel_to_sample (x);
3985 const int32_t divisions = editor.get_grid_music_divisions (state);
3986 const bool shift_snap = midi_view()->note_mode() != Percussive;
3987 const Temporal::Beats snapped_beats = snap_sample_to_grid_underneath (unsnapped_sample, divisions, shift_snap);
3989 /* prevent Percussive mode from displaying a ghost hit at region end */
3990 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3991 _ghost_note->hide();
3992 hide_verbose_cursor ();
3996 /* ghost note may have been snapped before region */
3997 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3998 _ghost_note->hide();
4001 } else if (_ghost_note) {
4002 _ghost_note->show();
4005 /* calculate time in beats relative to start of source */
4006 const Temporal::Beats length = get_grid_beats(unsnapped_sample + _region->position());
4008 _ghost_note->note()->set_time (snapped_beats);
4009 _ghost_note->note()->set_length (length);
4010 _ghost_note->note()->set_note (y_to_note (y));
4011 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
4012 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
4013 /* the ghost note does not appear in ghost regions, so pass false in here */
4014 update_note (_ghost_note, false);
4016 show_verbose_cursor (_ghost_note->note ());
4020 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
4022 remove_ghost_note ();
4024 boost::shared_ptr<NoteType> g (new NoteType);
4025 if (midi_view()->note_mode() == Sustained) {
4026 _ghost_note = new Note (*this, _note_group, g);
4028 _ghost_note = new Hit (*this, _note_group, 10, g);
4030 _ghost_note->set_ignore_events (true);
4031 _ghost_note->set_outline_color (0x000000aa);
4032 update_ghost_note (x, y, state);
4033 _ghost_note->show ();
4035 show_verbose_cursor (_ghost_note->note ());
4039 MidiRegionView::remove_ghost_note ()
4046 MidiRegionView::hide_verbose_cursor ()
4048 trackview.editor().verbose_cursor()->hide ();
4049 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4051 mtv->set_note_highlight (NO_MIDI_NOTE);
4056 MidiRegionView::snap_changed ()
4062 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
4066 MidiRegionView::drop_down_keys ()
4068 _mouse_state = None;
4072 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
4074 /* XXX: This is dead code. What was it for? */
4076 double note = y_to_note(y);
4078 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4080 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
4082 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
4083 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
4084 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
4085 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
4090 bool add_mrv_selection = false;
4092 if (_selection.empty()) {
4093 add_mrv_selection = true;
4096 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
4097 if (_selection.insert (i->second).second) {
4098 i->second->set_selected (true);
4102 if (add_mrv_selection) {
4103 PublicEditor& editor (trackview.editor());
4104 editor.get_selection().add (this);
4109 MidiRegionView::color_handler ()
4111 RegionView::color_handler ();
4113 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
4114 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
4116 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
4117 i->second->set_selected (i->second->selected()); // will change color
4120 /* XXX probably more to do here */
4124 MidiRegionView::enable_display (bool yn)
4126 RegionView::enable_display (yn);
4130 MidiRegionView::show_step_edit_cursor (Temporal::Beats pos)
4132 if (_step_edit_cursor == 0) {
4133 ArdourCanvas::Item* const group = get_canvas_group();
4135 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
4136 _step_edit_cursor->set_y0 (0);
4137 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
4138 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
4139 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
4142 move_step_edit_cursor (pos);
4143 _step_edit_cursor->show ();
4147 MidiRegionView::move_step_edit_cursor (Temporal::Beats pos)
4149 _step_edit_cursor_position = pos;
4151 if (_step_edit_cursor) {
4152 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_samples (pos));
4153 _step_edit_cursor->set_x0 (pixel);
4154 set_step_edit_cursor_width (_step_edit_cursor_width);
4159 MidiRegionView::hide_step_edit_cursor ()
4161 if (_step_edit_cursor) {
4162 _step_edit_cursor->hide ();
4167 MidiRegionView::set_step_edit_cursor_width (Temporal::Beats beats)
4169 _step_edit_cursor_width = beats;
4171 if (_step_edit_cursor) {
4172 _step_edit_cursor->set_x1 (_step_edit_cursor->x0()
4173 + trackview.editor().sample_to_pixel (
4174 region_beats_to_region_samples (_step_edit_cursor_position + beats)
4175 - region_beats_to_region_samples (_step_edit_cursor_position)));
4179 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
4180 * @param w Source that the data will end up in.
4183 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
4185 if (!_active_notes) {
4186 /* we aren't actively being recorded to */
4190 boost::shared_ptr<MidiSource> src = w.lock ();
4191 if (!src || src != midi_region()->midi_source()) {
4192 /* recorded data was not destined for our source */
4196 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
4198 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
4200 samplepos_t back = max_samplepos;
4202 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
4203 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
4205 if (ev.is_channel_event()) {
4206 if (get_channel_mode() == FilterChannels) {
4207 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
4213 /* convert from session samples to source beats */
4214 Temporal::Beats const time_beats = _source_relative_time_converter.from(
4215 ev.time() - src->natural_position() + _region->start());
4217 if (ev.type() == MIDI_CMD_NOTE_ON) {
4218 boost::shared_ptr<NoteType> note (
4219 new NoteType (ev.channel(), time_beats, Temporal::Beats(), ev.note(), ev.velocity()));
4221 add_note (note, true);
4223 /* fix up our note range */
4224 if (ev.note() < _current_range_min) {
4225 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
4226 } else if (ev.note() > _current_range_max) {
4227 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
4230 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
4231 resolve_note (ev.note (), time_beats);
4237 midi_stream_view()->check_record_layers (region(), back);
4241 MidiRegionView::trim_front_starting ()
4243 /* We used to eparent the note group to the region view's parent, so that it didn't change.
4249 MidiRegionView::trim_front_ending ()
4251 if (_region->start() < 0) {
4252 /* Trim drag made start time -ve; fix this */
4253 midi_region()->fix_negative_start ();
4258 MidiRegionView::edit_patch_change (PatchChange* pc)
4260 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4262 int response = d.run();
4265 case Gtk::RESPONSE_ACCEPT:
4267 case Gtk::RESPONSE_REJECT:
4268 delete_patch_change (pc);
4274 change_patch_change (pc->patch(), d.patch ());
4278 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4281 // sysyex object doesn't have a pointer to a sysex event
4282 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4283 // c->remove (sysex->sysex());
4284 // _model->apply_command (*trackview.session(), c);
4286 //_sys_exes.clear ();
4287 // display_sysexes();
4291 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4293 using namespace MIDI::Name;
4296 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4298 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4300 MIDI::Name::PatchPrimaryKey patch_key;
4301 get_patch_key_at(n->time(), n->channel(), patch_key);
4302 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4305 patch_key.program(),
4311 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4313 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4314 (int) n->channel() + 1,
4315 (int) n->velocity());
4321 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4322 uint8_t new_value) const
4324 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4326 mtv->set_note_highlight (new_value);
4329 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4333 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4335 show_verbose_cursor_for_new_note_value(n, n->note());
4339 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4341 trackview.editor().verbose_cursor()->set (text);
4342 trackview.editor().verbose_cursor()->show ();
4343 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4347 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4349 if (_model->notes().empty()) {
4350 return 0x40; // No notes, use default
4353 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4354 if (m == _model->notes().begin()) {
4355 // Before the start, use the velocity of the first note
4356 return (*m)->velocity();
4357 } else if (m == _model->notes().end()) {
4358 // Past the end, use the velocity of the last note
4360 return (*m)->velocity();
4363 // Interpolate velocity of surrounding notes
4364 MidiModel::Notes::const_iterator n = m;
4367 const double frac = ((time - (*n)->time()).to_double() /
4368 ((*m)->time() - (*n)->time()).to_double());
4370 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4373 /** @param p A session samplepos.
4374 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4375 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4376 * @return beat duration of p snapped to the grid subdivision underneath it.
4379 MidiRegionView::snap_sample_to_grid_underneath (samplepos_t p, int32_t divisions, bool shift_snap) const
4381 TempoMap& map (trackview.session()->tempo_map());
4382 double eqaf = map.exact_qn_at_sample (p + _region->position(), divisions);
4384 if (divisions != 0 && shift_snap) {
4385 const double qaf = map.quarter_note_at_sample (p + _region->position());
4386 /* Hack so that we always snap to the note that we are over, instead of snapping
4387 to the next one if we're more than halfway through the one we're over.
4389 const Temporal::Beats grid_beats = get_grid_beats (p + _region->position());
4390 const double rem = eqaf - qaf;
4392 eqaf -= grid_beats.to_double();
4395 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4397 return Temporal::Beats (eqaf - session_start_off);
4401 MidiRegionView::get_channel_mode () const
4403 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4404 return rtav->midi_track()->get_playback_channel_mode();
4408 MidiRegionView::get_selected_channels () const
4410 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4411 return rtav->midi_track()->get_playback_channel_mask();
4416 MidiRegionView::get_grid_beats(samplepos_t pos) const
4418 PublicEditor& editor = trackview.editor();
4419 bool success = false;
4420 Temporal::Beats beats = editor.get_grid_type_as_beats (success, pos);
4422 beats = Temporal::Beats(1);
4427 MidiRegionView::y_to_note (double y) const
4429 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4430 + _current_range_min;
4434 } else if (n > 127) {
4438 /* min due to rounding and/or off-by-one errors */
4439 return min ((uint8_t) n, _current_range_max);
4443 MidiRegionView::note_to_y(uint8_t note) const
4445 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
4449 MidiRegionView::session_relative_qn (double qn) const
4451 return qn + (region()->quarter_note() - midi_region()->start_beats());