2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "midi++/midnam_patch.h"
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/Event.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
81 #include "patch_change.h"
83 #include "ui_config.h"
87 using namespace ARDOUR;
89 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
95 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
96 RouteTimeAxisView& tv,
97 boost::shared_ptr<MidiRegion> r,
100 : RegionView (parent, tv, r, spu, basic_color)
101 , _current_range_min(0)
102 , _current_range_max(0)
103 , _region_relative_time_converter(r->session().tempo_map(), r->position())
104 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
105 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
107 , _note_group (new ArdourCanvas::Container (group))
108 , _note_diff_command (0)
110 , _step_edit_cursor (0)
111 , _step_edit_cursor_width (1.0)
112 , _step_edit_cursor_position (0.0)
113 , _channel_selection_scoped_note (0)
116 , _sort_needed (true)
117 , _optimization_iterator (_events.end())
119 , _no_sound_notes (false)
120 , _last_display_zoom (0)
123 , _grabbed_keyboard (false)
126 , _mouse_changed_selection (false)
128 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
130 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
131 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
133 _note_group->raise_to_top();
134 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
136 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
137 connect_to_diskstream ();
140 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
141 RouteTimeAxisView& tv,
142 boost::shared_ptr<MidiRegion> r,
144 uint32_t basic_color,
146 TimeAxisViewItem::Visibility visibility)
147 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
148 , _current_range_min(0)
149 , _current_range_max(0)
150 , _region_relative_time_converter(r->session().tempo_map(), r->position())
151 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
152 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
154 , _note_group (new ArdourCanvas::Container (group))
155 , _note_diff_command (0)
157 , _step_edit_cursor (0)
158 , _step_edit_cursor_width (1.0)
159 , _step_edit_cursor_position (0.0)
160 , _channel_selection_scoped_note (0)
163 , _sort_needed (true)
164 , _optimization_iterator (_events.end())
166 , _no_sound_notes (false)
167 , _last_display_zoom (0)
170 , _grabbed_keyboard (false)
173 , _mouse_changed_selection (false)
175 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
177 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
178 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
180 _note_group->raise_to_top();
182 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
184 connect_to_diskstream ();
188 MidiRegionView::parameter_changed (std::string const & p)
190 if (p == "display-first-midi-bank-as-zero") {
191 if (_enable_display) {
194 } else if (p == "color-regions-using-track-color") {
199 MidiRegionView::MidiRegionView (const MidiRegionView& other)
200 : sigc::trackable(other)
202 , _current_range_min(0)
203 , _current_range_max(0)
204 , _region_relative_time_converter(other.region_relative_time_converter())
205 , _source_relative_time_converter(other.source_relative_time_converter())
206 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
208 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
209 , _note_diff_command (0)
211 , _step_edit_cursor (0)
212 , _step_edit_cursor_width (1.0)
213 , _step_edit_cursor_position (0.0)
214 , _channel_selection_scoped_note (0)
217 , _sort_needed (true)
218 , _optimization_iterator (_events.end())
220 , _no_sound_notes (false)
221 , _last_display_zoom (0)
224 , _grabbed_keyboard (false)
227 , _mouse_changed_selection (false)
232 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
233 : RegionView (other, boost::shared_ptr<Region> (region))
234 , _current_range_min(0)
235 , _current_range_max(0)
236 , _region_relative_time_converter(other.region_relative_time_converter())
237 , _source_relative_time_converter(other.source_relative_time_converter())
238 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
240 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
241 , _note_diff_command (0)
243 , _step_edit_cursor (0)
244 , _step_edit_cursor_width (1.0)
245 , _step_edit_cursor_position (0.0)
246 , _channel_selection_scoped_note (0)
249 , _sort_needed (true)
250 , _optimization_iterator (_events.end())
252 , _no_sound_notes (false)
253 , _last_display_zoom (0)
256 , _grabbed_keyboard (false)
259 , _mouse_changed_selection (false)
265 MidiRegionView::init (bool wfd)
267 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
270 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
271 midi_region()->midi_source(0)->load_model(lm);
274 _model = midi_region()->midi_source(0)->model();
275 _enable_display = false;
276 fill_color_name = "midi frame base";
278 RegionView::init (false);
280 //set_height (trackview.current_height());
283 region_sync_changed ();
284 region_resized (ARDOUR::bounds_change);
289 _enable_display = true;
292 display_model (_model);
296 reset_width_dependent_items (_pixel_width);
298 group->raise_to_top();
300 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
301 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
304 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
305 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
307 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
308 boost::bind (&MidiRegionView::snap_changed, this),
311 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
312 boost::bind (&MidiRegionView::mouse_mode_changed, this),
315 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
316 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
317 connect_to_diskstream ();
321 MidiRegionView::instrument_info () const
323 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
324 return route_ui->route()->instrument_info();
327 const boost::shared_ptr<ARDOUR::MidiRegion>
328 MidiRegionView::midi_region() const
330 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
334 MidiRegionView::connect_to_diskstream ()
336 midi_view()->midi_track()->DataRecorded.connect(
337 *this, invalidator(*this),
338 boost::bind (&MidiRegionView::data_recorded, this, _1),
343 MidiRegionView::canvas_group_event(GdkEvent* ev)
345 if (in_destructor || _recregion) {
349 if (!trackview.editor().internal_editing()) {
350 // not in internal edit mode, so just act like a normal region
351 return RegionView::canvas_group_event (ev);
357 case GDK_ENTER_NOTIFY:
358 _last_event_x = ev->crossing.x;
359 _last_event_y = ev->crossing.y;
360 enter_notify(&ev->crossing);
361 // set entered_regionview (among other things)
362 return RegionView::canvas_group_event (ev);
364 case GDK_LEAVE_NOTIFY:
365 _last_event_x = ev->crossing.x;
366 _last_event_y = ev->crossing.y;
367 leave_notify(&ev->crossing);
368 // reset entered_regionview (among other things)
369 return RegionView::canvas_group_event (ev);
372 if (scroll (&ev->scroll)) {
378 return key_press (&ev->key);
380 case GDK_KEY_RELEASE:
381 return key_release (&ev->key);
383 case GDK_BUTTON_PRESS:
384 return button_press (&ev->button);
386 case GDK_BUTTON_RELEASE:
387 r = button_release (&ev->button);
390 case GDK_MOTION_NOTIFY:
391 _last_event_x = ev->motion.x;
392 _last_event_y = ev->motion.y;
393 return motion (&ev->motion);
399 return RegionView::canvas_group_event (ev);
403 MidiRegionView::enter_notify (GdkEventCrossing* ev)
405 enter_internal (ev->state);
412 MidiRegionView::leave_notify (GdkEventCrossing*)
421 MidiRegionView::mouse_mode_changed ()
423 // Adjust frame colour (become more transparent for internal tools)
427 if (!trackview.editor().internal_editing()) {
428 /* Switched out of internal editing mode while entered.
429 Only necessary for leave as a mouse_mode_change over a region
430 automatically triggers an enter event. */
433 else if (trackview.editor().current_mouse_mode() == MouseContent) {
434 // hide cursor and ghost note after changing to internal edit mode
435 remove_ghost_note ();
437 /* XXX This is problematic as the function is executed for every region
438 and only for one region _entered_note can be true. Still it's
439 necessary as to hide the verbose cursor when we're changing from
440 draw mode to internal edit mode. These lines are the reason why
441 in some situations no verbose cursor is shown when we enter internal
442 edit mode over a note. */
443 if (!_entered_note) {
444 hide_verbose_cursor ();
451 MidiRegionView::enter_internal (uint32_t state)
453 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
454 // Show ghost note under pencil
455 create_ghost_note(_last_event_x, _last_event_y, state);
458 if (!_selection.empty()) {
459 // Grab keyboard for moving selected notes with arrow keys
460 Keyboard::magic_widget_grab_focus();
461 _grabbed_keyboard = true;
464 // Lower frame handles below notes so they don't steal events
465 if (frame_handle_start) {
466 frame_handle_start->lower_to_bottom();
468 if (frame_handle_end) {
469 frame_handle_end->lower_to_bottom();
474 MidiRegionView::leave_internal()
476 hide_verbose_cursor ();
477 remove_ghost_note ();
480 if (_grabbed_keyboard) {
481 Keyboard::magic_widget_drop_focus();
482 _grabbed_keyboard = false;
485 // Raise frame handles above notes so they catch events
486 if (frame_handle_start) {
487 frame_handle_start->raise_to_top();
489 if (frame_handle_end) {
490 frame_handle_end->raise_to_top();
495 MidiRegionView::button_press (GdkEventButton* ev)
497 if (ev->button != 1) {
501 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
502 MouseMode m = editor->current_mouse_mode();
504 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
505 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
508 if (_mouse_state != SelectTouchDragging) {
510 _pressed_button = ev->button;
512 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
514 if (midi_view()->note_mode() == Percussive) {
515 editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
517 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
520 _mouse_state = AddDragging;
521 remove_ghost_note ();
522 hide_verbose_cursor ();
524 _mouse_state = Pressed;
530 _pressed_button = ev->button;
531 _mouse_changed_selection = false;
537 MidiRegionView::button_release (GdkEventButton* ev)
539 double event_x, event_y;
541 if (ev->button != 1) {
548 group->canvas_to_item (event_x, event_y);
551 PublicEditor& editor = trackview.editor ();
553 _press_cursor_ctx.reset();
555 switch (_mouse_state) {
556 case Pressed: // Clicked
558 switch (editor.current_mouse_mode()) {
560 /* no motion occurred - simple click */
561 clear_editor_note_selection ();
562 _mouse_changed_selection = true;
568 _mouse_changed_selection = true;
569 clear_editor_note_selection ();
584 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
585 we don't want one when we were drag-selecting either. */
586 case SelectRectDragging:
587 editor.drags()->end_grab ((GdkEvent *) ev);
596 if (_mouse_changed_selection) {
597 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
598 trackview.editor().commit_reversible_selection_op ();
605 MidiRegionView::motion (GdkEventMotion* ev)
607 PublicEditor& editor = trackview.editor ();
609 if (!_entered_note) {
611 if (_mouse_state == AddDragging) {
613 remove_ghost_note ();
616 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
617 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
618 _mouse_state != AddDragging) {
620 create_ghost_note (ev->x, ev->y, ev->state);
622 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
623 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
625 update_ghost_note (ev->x, ev->y, ev->state);
627 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
629 remove_ghost_note ();
630 hide_verbose_cursor ();
632 } else if (editor.current_mouse_mode() == MouseDraw) {
635 update_ghost_note (ev->x, ev->y, ev->state);
638 create_ghost_note (ev->x, ev->y, ev->state);
643 /* any motion immediately hides velocity text that may have been visible */
645 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
646 (*i)->hide_velocity ();
649 switch (_mouse_state) {
652 if (_pressed_button == 1) {
654 MouseMode m = editor.current_mouse_mode();
656 if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
657 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
658 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
659 clear_editor_note_selection ();
660 _mouse_changed_selection = true;
662 _mouse_state = SelectRectDragging;
664 } else if (m == MouseRange) {
665 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
666 _mouse_state = SelectVerticalDragging;
673 case SelectRectDragging:
674 case SelectVerticalDragging:
676 editor.drags()->motion_handler ((GdkEvent *) ev, false);
679 case SelectTouchDragging:
687 /* we may be dragging some non-note object (eg. patch-change, sysex)
690 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
695 MidiRegionView::scroll (GdkEventScroll* ev)
697 if (_selection.empty()) {
701 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
702 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
703 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
704 * through so that it still works for navigation.
709 hide_verbose_cursor ();
711 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
712 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
713 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
715 if (ev->direction == GDK_SCROLL_UP) {
716 change_velocities (true, fine, false, together);
717 } else if (ev->direction == GDK_SCROLL_DOWN) {
718 change_velocities (false, fine, false, together);
720 /* left, right: we don't use them */
728 MidiRegionView::key_press (GdkEventKey* ev)
730 /* since GTK bindings are generally activated on press, and since
731 detectable auto-repeat is the name of the game and only sends
732 repeated presses, carry out key actions at key press, not release.
734 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
736 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
738 if (_mouse_state != AddDragging) {
739 _mouse_state = SelectTouchDragging;
744 } else if (ev->keyval == GDK_Escape && unmodified) {
745 clear_editor_note_selection ();
748 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
750 bool start = (ev->keyval == GDK_comma);
751 bool end = (ev->keyval == GDK_period);
752 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
753 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
755 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
759 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
761 if (_selection.empty()) {
768 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
770 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
772 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
773 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
775 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
778 trackview.editor().commit_reversible_selection_op();
782 } else if (ev->keyval == GDK_Up) {
784 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
785 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
786 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
788 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
789 change_velocities (true, fine, allow_smush, together);
791 transpose (true, fine, allow_smush);
795 } else if (ev->keyval == GDK_Down) {
797 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
798 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
799 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
801 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
802 change_velocities (false, fine, allow_smush, together);
804 transpose (false, fine, allow_smush);
808 } else if (ev->keyval == GDK_Left) {
810 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
811 nudge_notes (false, fine);
814 } else if (ev->keyval == GDK_Right) {
816 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
817 nudge_notes (true, fine);
820 } else if (ev->keyval == GDK_c && unmodified) {
824 } else if (ev->keyval == GDK_v && unmodified) {
833 MidiRegionView::key_release (GdkEventKey* ev)
835 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
843 MidiRegionView::channel_edit ()
845 if (_selection.empty()) {
849 /* pick a note somewhat at random (since Selection is a set<>) to
850 * provide the "current" channel for the dialog.
853 uint8_t current_channel = (*_selection.begin())->note()->channel ();
854 MidiChannelDialog channel_dialog (current_channel);
855 int ret = channel_dialog.run ();
858 case Gtk::RESPONSE_OK:
864 uint8_t new_channel = channel_dialog.active_channel ();
866 start_note_diff_command (_("channel edit"));
868 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
869 Selection::iterator next = i;
871 change_note_channel (*i, new_channel);
879 MidiRegionView::velocity_edit ()
881 if (_selection.empty()) {
885 /* pick a note somewhat at random (since Selection is a set<>) to
886 * provide the "current" velocity for the dialog.
889 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
890 MidiVelocityDialog velocity_dialog (current_velocity);
891 int ret = velocity_dialog.run ();
894 case Gtk::RESPONSE_OK:
900 uint8_t new_velocity = velocity_dialog.velocity ();
902 start_note_diff_command (_("velocity edit"));
904 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
905 Selection::iterator next = i;
907 change_note_velocity (*i, new_velocity, false);
915 MidiRegionView::show_list_editor ()
918 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
920 _list_editor->present ();
923 /** Add a note to the model, and the view, at a canvas (click) coordinate.
924 * \param t time in frames relative to the position of the region
925 * \param y vertical position in pixels
926 * \param length duration of the note in beats
927 * \param snap_t true to snap t to the grid, otherwise false.
930 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
932 if (length < 2 * DBL_EPSILON) {
936 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
937 MidiStreamView* const view = mtv->midi_view();
938 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
944 // Start of note in frames relative to region start
945 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
946 Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
948 const double note = view->y_to_note(y);
949 const uint8_t chan = mtv->get_channel_for_add();
950 const uint8_t velocity = get_velocity_for_add(beat_time);
952 const boost::shared_ptr<NoteType> new_note(
953 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
955 if (_model->contains (new_note)) {
959 view->update_note_range(new_note->note());
961 start_note_diff_command(_("add note"));
963 clear_editor_note_selection ();
964 note_diff_add_note (new_note, true, false);
968 play_midi_note (new_note);
972 MidiRegionView::clear_events ()
974 // clear selection without signaling
975 clear_selection_internal ();
978 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
979 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
985 _note_group->clear (true);
987 _patch_changes.clear();
989 _optimization_iterator = _events.end();
993 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
997 content_connection.disconnect ();
998 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
999 /* Don't signal as nobody else needs to know until selection has been altered. */
1002 if (_enable_display) {
1008 MidiRegionView::start_note_diff_command (string name)
1010 if (!_note_diff_command) {
1011 trackview.editor().begin_reversible_command (name);
1012 _note_diff_command = _model->new_note_diff_command (name);
1017 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1019 if (_note_diff_command) {
1020 _note_diff_command->add (note);
1023 _marked_for_selection.insert(note);
1025 if (show_velocity) {
1026 _marked_for_velocity.insert(note);
1031 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1033 if (_note_diff_command && ev->note()) {
1034 _note_diff_command->remove(ev->note());
1039 MidiRegionView::note_diff_add_change (NoteBase* ev,
1040 MidiModel::NoteDiffCommand::Property property,
1043 if (_note_diff_command) {
1044 _note_diff_command->change (ev->note(), property, val);
1049 MidiRegionView::note_diff_add_change (NoteBase* ev,
1050 MidiModel::NoteDiffCommand::Property property,
1053 if (_note_diff_command) {
1054 _note_diff_command->change (ev->note(), property, val);
1059 MidiRegionView::apply_diff (bool as_subcommand)
1062 bool commit = false;
1064 if (!_note_diff_command) {
1068 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1069 // Mark all selected notes for selection when model reloads
1070 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1071 _marked_for_selection.insert((*i)->note());
1075 midi_view()->midi_track()->midi_playlist()->region_edited(
1076 _region, _note_diff_command);
1078 if (as_subcommand) {
1079 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1081 _model->apply_command (*trackview.session(), _note_diff_command);
1085 _note_diff_command = 0;
1087 if (add_or_remove) {
1088 _marked_for_selection.clear();
1091 _marked_for_velocity.clear();
1093 trackview.editor().commit_reversible_command ();
1098 MidiRegionView::abort_command()
1100 delete _note_diff_command;
1101 _note_diff_command = 0;
1102 clear_editor_note_selection();
1106 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1108 if (_optimization_iterator != _events.end()) {
1109 ++_optimization_iterator;
1112 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1113 return *_optimization_iterator;
1116 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1117 if ((*_optimization_iterator)->note() == note) {
1118 return *_optimization_iterator;
1125 /** This version finds any canvas note matching the supplied note. */
1127 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1129 Events::iterator it;
1131 for (it = _events.begin(); it != _events.end(); ++it) {
1132 if ((*it)->note()->id() == id) {
1140 boost::shared_ptr<PatchChange>
1141 MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
1143 PatchChanges::const_iterator f = _patch_changes.find (p);
1145 if (f != _patch_changes.end()) {
1149 return boost::shared_ptr<PatchChange>();
1153 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1155 MidiModel::Notes notes;
1156 _model->get_notes (notes, op, val, chan_mask);
1158 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1159 NoteBase* cne = find_canvas_note (*n);
1167 MidiRegionView::redisplay_model()
1169 if (_active_notes) {
1170 // Currently recording
1171 const framecnt_t zoom = trackview.editor().get_current_zoom();
1172 if (zoom != _last_display_zoom) {
1173 /* Update resolved canvas notes to reflect changes in zoom without
1174 touching model. Leave active notes (with length 0) alone since
1175 they are being extended. */
1176 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1177 if ((*i)->note()->length() > 0) {
1181 _last_display_zoom = zoom;
1190 bool empty_when_starting = _events.empty();
1191 MidiModel::ReadLock lock(_model->read_lock());
1192 MidiModel::Notes missing_notes = _model->notes(); // copy
1196 if (!empty_when_starting) {
1197 MidiModel::Notes::iterator f;
1198 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1199 NoteBase* cne = (*i);
1200 boost::shared_ptr<NoteType> note = cne->note();
1202 /* if event item's note exists in the model, we can just update it.
1203 * don't mark it as missing.
1205 if ((f = missing_notes.find (note)) != missing_notes.end()) {
1208 missing_notes.erase (f);
1210 if (cne->selected()) {
1211 _marked_for_selection.insert (note);
1220 /* remove note items that are no longer valid */
1221 if (!cne->valid()) {
1223 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1224 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1226 gr->remove_note (cne);
1231 i = _events.erase (i);
1235 bool update = false;
1237 if (note_in_region_range (note, visible)) {
1247 if ((sus = dynamic_cast<Note*>(cne))) {
1250 update_sustained (sus);
1253 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1254 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1255 if (gr && !gr->trackview.hidden()) {
1256 gr->update_note (sus, !update);
1259 } else if ((hit = dynamic_cast<Hit*>(cne))) {
1265 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1266 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1267 if (gr && !gr->trackview.hidden()) {
1268 gr->update_hit (hit, !update);
1278 for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
1279 boost::shared_ptr<NoteType> note (*n);
1283 if (note_in_region_range (note, visible)) {
1285 cne = add_note (note, true);
1287 cne = add_note (note, false);
1290 cne = add_note (note, false);
1293 for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1294 if ((*it) == note->id()) {
1295 add_to_selection (cne);
1303 display_patch_changes ();
1305 _marked_for_selection.clear ();
1306 _marked_for_velocity.clear ();
1307 _pending_note_selection.clear ();
1309 /* we may have caused _events to contain things out of order (e.g. if a note
1310 moved earlier or later). we don't generally need them in time order, but
1311 make a note that a sort is required for those cases that require it.
1314 _sort_needed = true;
1318 MidiRegionView::display_patch_changes ()
1320 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1321 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1323 for (uint8_t i = 0; i < 16; ++i) {
1324 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1328 /** @param active_channel true to display patch changes fully, false to display
1329 * them `greyed-out' (as on an inactive channel)
1332 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1334 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1335 boost::shared_ptr<PatchChange> p;
1337 if ((*i)->channel() != channel) {
1341 if ((p = find_canvas_patch_change (*i)) != 0) {
1343 const framecnt_t region_frames = source_beats_to_region_frames ((*i)->time());
1345 if (region_frames < 0 || region_frames >= _region->length()) {
1348 const double x = trackview.editor().sample_to_pixel (region_frames);
1349 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1350 p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
1351 p->flag()->set_text (patch_name);
1357 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1358 add_canvas_patch_change (*i, patch_name, active_channel);
1364 MidiRegionView::display_sysexes()
1366 bool have_periodic_system_messages = false;
1367 bool display_periodic_messages = true;
1369 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1371 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1372 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1373 have_periodic_system_messages = true;
1378 if (have_periodic_system_messages) {
1379 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1381 /* get an approximate value for the number of samples per video frame */
1383 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1385 /* if we are zoomed out beyond than the cutoff (i.e. more
1386 * frames per pixel than frames per 4 video frames), don't
1387 * show periodic sysex messages.
1390 if (zoom > (video_frame*4)) {
1391 display_periodic_messages = false;
1395 display_periodic_messages = false;
1398 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1399 Evoral::Beats time = (*i)->time();
1401 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1402 if (!display_periodic_messages) {
1409 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1410 str << int((*i)->buffer()[b]);
1411 if (b != (*i)->size() -1) {
1415 string text = str.str();
1417 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1419 double height = midi_stream_view()->contents_height();
1421 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1422 // SysEx canvas object!!!
1424 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1425 new SysEx (*this, _note_group, text, height, x, 1.0));
1427 // Show unless message is beyond the region bounds
1428 if (time - _region->start() >= _region->length() || time < _region->start()) {
1434 _sys_exes.push_back(sysex);
1438 MidiRegionView::~MidiRegionView ()
1440 in_destructor = true;
1442 hide_verbose_cursor ();
1444 delete _list_editor;
1446 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1448 if (_active_notes) {
1455 delete _note_diff_command;
1456 delete _step_edit_cursor;
1460 MidiRegionView::region_resized (const PropertyChange& what_changed)
1462 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1464 if (what_changed.contains (ARDOUR::Properties::position)) {
1465 _region_relative_time_converter.set_origin_b(_region->position());
1466 _region_relative_time_converter_double.set_origin_b(_region->position());
1467 /* reset_width dependent_items() redisplays model */
1471 if (what_changed.contains (ARDOUR::Properties::start) ||
1472 what_changed.contains (ARDOUR::Properties::position)) {
1473 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1475 /* catch end and start trim so we can update the view*/
1476 if (!what_changed.contains (ARDOUR::Properties::start) &&
1477 what_changed.contains (ARDOUR::Properties::length)) {
1478 enable_display (true);
1479 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1480 what_changed.contains (ARDOUR::Properties::length)) {
1481 enable_display (true);
1486 MidiRegionView::reset_width_dependent_items (double pixel_width)
1488 RegionView::reset_width_dependent_items(pixel_width);
1490 if (_enable_display) {
1494 bool hide_all = false;
1495 PatchChanges::iterator x = _patch_changes.begin();
1496 if (x != _patch_changes.end()) {
1497 hide_all = (*x).second->flag()->width() >= _pixel_width;
1501 for (; x != _patch_changes.end(); ++x) {
1502 (*x).second->hide();
1506 move_step_edit_cursor (_step_edit_cursor_position);
1507 set_step_edit_cursor_width (_step_edit_cursor_width);
1511 MidiRegionView::set_height (double height)
1513 double old_height = _height;
1514 RegionView::set_height(height);
1516 apply_note_range (midi_stream_view()->lowest_note(),
1517 midi_stream_view()->highest_note(),
1518 height != old_height);
1521 name_text->raise_to_top();
1524 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1525 (*x).second->set_height (midi_stream_view()->contents_height());
1528 if (_step_edit_cursor) {
1529 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1534 /** Apply the current note range from the stream view
1535 * by repositioning/hiding notes as necessary
1538 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1540 if (!_enable_display) {
1544 if (!force && _current_range_min == min && _current_range_max == max) {
1548 _current_range_min = min;
1549 _current_range_max = max;
1555 MidiRegionView::add_ghost (TimeAxisView& tv)
1557 double unit_position = _region->position () / samples_per_pixel;
1558 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1559 MidiGhostRegion* ghost;
1561 if (mtv && mtv->midi_view()) {
1562 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1563 to allow having midi notes on top of note lines and waveforms.
1565 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1567 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1570 ghost->set_colors ();
1571 ghost->set_height ();
1572 ghost->set_duration (_region->length() / samples_per_pixel);
1574 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1575 ghost->add_note(*i);
1578 ghosts.push_back (ghost);
1584 /** Begin tracking note state for successive calls to add_event
1587 MidiRegionView::begin_write()
1589 if (_active_notes) {
1590 delete[] _active_notes;
1592 _active_notes = new Note*[128];
1593 for (unsigned i = 0; i < 128; ++i) {
1594 _active_notes[i] = 0;
1599 /** Destroy note state for add_event
1602 MidiRegionView::end_write()
1604 delete[] _active_notes;
1606 _marked_for_selection.clear();
1607 _marked_for_velocity.clear();
1611 /** Resolve an active MIDI note (while recording).
1614 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1616 if (midi_view()->note_mode() != Sustained) {
1620 if (_active_notes && _active_notes[note]) {
1621 /* Set note length so update_note() works. Note this is a local note
1622 for recording, not from a model, so we can safely mess with it. */
1623 _active_notes[note]->note()->set_length(
1624 end_time - _active_notes[note]->note()->time());
1626 /* End time is relative to the region being recorded. */
1627 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1629 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1630 _active_notes[note]->set_outline_all ();
1631 _active_notes[note] = 0;
1636 /** Extend active notes to rightmost edge of region (if length is changed)
1639 MidiRegionView::extend_active_notes()
1641 if (!_active_notes) {
1645 for (unsigned i = 0; i < 128; ++i) {
1646 if (_active_notes[i]) {
1647 _active_notes[i]->set_x1(
1648 trackview.editor().sample_to_pixel(_region->length()));
1654 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1656 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1660 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1662 if (!route_ui || !route_ui->midi_track()) {
1666 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1670 /* NotePlayer deletes itself */
1674 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1676 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1677 start_playing_midi_chord(notes);
1681 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1683 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1687 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1689 if (!route_ui || !route_ui->midi_track()) {
1693 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1695 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1704 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1706 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1708 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1709 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1710 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1712 visible = (note->note() >= _current_range_min) &&
1713 (note->note() <= _current_range_max);
1719 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1723 if ((sus = dynamic_cast<Note*>(note))) {
1724 update_sustained(sus, update_ghost_regions);
1725 } else if ((hit = dynamic_cast<Hit*>(note))) {
1726 update_hit(hit, update_ghost_regions);
1730 /** Update a canvas note's size from its model note.
1731 * @param ev Canvas note to update.
1732 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1735 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1737 TempoMap& map (trackview.session()->tempo_map());
1738 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1739 boost::shared_ptr<NoteType> note = ev->note();
1741 const double session_source_start = _region->quarter_note() - mr->start_beats();
1742 const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1744 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1746 const double y0 = 1 + floor(note_to_y(note->note()));
1749 /* trim note display to not overlap the end of its region */
1750 if (note->length().to_double() > 0.0) {
1751 double note_end_time = note->end_time().to_double();
1753 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1754 note_end_time = mr->start_beats() + mr->length_beats();
1757 const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
1759 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1761 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1764 y1 = y0 + std::max(1., floor(note_height()) - 1);
1766 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1768 if (!note->length()) {
1769 if (_active_notes && note->note() < 128) {
1770 Note* const old_rect = _active_notes[note->note()];
1772 /* There is an active note on this key, so we have a stuck
1773 note. Finish the old rectangle here. */
1774 old_rect->set_x1 (x1);
1775 old_rect->set_outline_all ();
1777 _active_notes[note->note()] = ev;
1779 /* outline all but right edge */
1780 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1781 ArdourCanvas::Rectangle::TOP|
1782 ArdourCanvas::Rectangle::LEFT|
1783 ArdourCanvas::Rectangle::BOTTOM));
1785 /* outline all edges */
1786 ev->set_outline_all ();
1789 // Update color in case velocity has changed
1790 const uint32_t base_col = ev->base_color();
1791 ev->set_fill_color(base_col);
1792 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1797 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1799 boost::shared_ptr<NoteType> note = ev->note();
1801 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1802 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1804 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1805 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1806 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1808 // see DnD note in MidiRegionView::apply_note_range() above
1809 if (y <= 0 || y >= _height) {
1815 ev->set_position (ArdourCanvas::Duple (x, y));
1816 ev->set_height (diamond_size);
1818 // Update color in case velocity has changed
1819 const uint32_t base_col = ev->base_color();
1820 ev->set_fill_color(base_col);
1821 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1825 /** Add a MIDI note to the view (with length).
1827 * If in sustained mode, notes with length 0 will be considered active
1828 * notes, and resolve_note should be called when the corresponding note off
1829 * event arrives, to properly display the note.
1832 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1834 NoteBase* event = 0;
1836 if (midi_view()->note_mode() == Sustained) {
1838 Note* ev_rect = new Note (*this, _note_group, note);
1840 update_sustained (ev_rect);
1844 } else if (midi_view()->note_mode() == Percussive) {
1846 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1848 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1850 update_hit (ev_diamond);
1859 MidiGhostRegion* gr;
1861 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1862 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1863 gr->add_note(event);
1867 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1868 note_selected(event, true);
1871 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1872 event->show_velocity();
1875 event->on_channel_selection_change (get_selected_channels());
1876 _events.push_back(event);
1885 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1886 MidiStreamView* const view = mtv->midi_view();
1888 view->update_note_range (note->note());
1893 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1894 Evoral::Beats pos, Evoral::Beats len)
1896 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1898 /* potentially extend region to hold new note */
1900 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1901 framepos_t region_end = _region->last_frame();
1903 if (end_frame > region_end) {
1904 /* XX sets length in beats from audio space. make musical */
1905 _region->set_length (end_frame - _region->position(), 0);
1908 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1909 MidiStreamView* const view = mtv->midi_view();
1911 view->update_note_range(new_note->note());
1913 _marked_for_selection.clear ();
1915 start_note_diff_command (_("step add"));
1917 clear_editor_note_selection ();
1918 note_diff_add_note (new_note, true, false);
1922 // last_step_edit_note = new_note;
1926 MidiRegionView::step_sustain (Evoral::Beats beats)
1928 change_note_lengths (false, false, beats, false, true);
1931 /** Add a new patch change flag to the canvas.
1932 * @param patch the patch change to add
1933 * @param the text to display in the flag
1934 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1937 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1939 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1940 const double x = trackview.editor().sample_to_pixel (region_frames);
1942 double const height = midi_stream_view()->contents_height();
1944 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1945 // so we need to do something more sophisticated to keep its color
1946 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1948 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1949 new PatchChange(*this, group,
1955 _patch_change_outline,
1959 if (patch_change->item().width() < _pixel_width) {
1960 // Show unless patch change is beyond the region bounds
1961 if (region_frames < 0 || region_frames >= _region->length()) {
1962 patch_change->hide();
1964 patch_change->show();
1967 patch_change->hide ();
1970 _patch_changes.insert (make_pair (patch, patch_change));
1974 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
1976 /* remove the canvas item */
1977 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1978 if (x->second->patch() == pc->patch()) {
1979 _patch_changes.erase (x);
1985 MIDI::Name::PatchPrimaryKey
1986 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1988 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1991 /// Return true iff @p pc applies to the given time on the given channel.
1993 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1995 return pc->time() <= time && pc->channel() == channel;
1999 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
2001 // The earliest event not before time
2002 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
2004 // Go backwards until we find the latest PC for this channel, or the start
2005 while (i != _model->patch_changes().begin() &&
2006 (i == _model->patch_changes().end() ||
2007 !patch_applies(*i, time, channel))) {
2011 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
2012 key.set_bank((*i)->bank());
2013 key.set_program((*i)->program ());
2021 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
2023 string name = _("alter patch change");
2024 trackview.editor().begin_reversible_command (name);
2026 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2028 if (pc.patch()->program() != new_patch.program()) {
2029 c->change_program (pc.patch (), new_patch.program());
2032 int const new_bank = new_patch.bank();
2033 if (pc.patch()->bank() != new_bank) {
2034 c->change_bank (pc.patch (), new_bank);
2037 _model->apply_command (*trackview.session(), c);
2038 trackview.editor().commit_reversible_command ();
2040 remove_canvas_patch_change (&pc);
2041 display_patch_changes ();
2045 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2047 string name = _("alter patch change");
2048 trackview.editor().begin_reversible_command (name);
2049 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2051 if (old_change->time() != new_change.time()) {
2052 c->change_time (old_change, new_change.time());
2055 if (old_change->channel() != new_change.channel()) {
2056 c->change_channel (old_change, new_change.channel());
2059 if (old_change->program() != new_change.program()) {
2060 c->change_program (old_change, new_change.program());
2063 if (old_change->bank() != new_change.bank()) {
2064 c->change_bank (old_change, new_change.bank());
2067 _model->apply_command (*trackview.session(), c);
2068 trackview.editor().commit_reversible_command ();
2070 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2071 if (x->second->patch() == old_change) {
2072 _patch_changes.erase (x);
2077 display_patch_changes ();
2080 /** Add a patch change to the region.
2081 * @param t Time in frames relative to region position
2082 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2083 * MidiTimeAxisView::get_channel_for_add())
2086 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2088 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2089 string name = _("add patch change");
2091 trackview.editor().begin_reversible_command (name);
2092 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2093 c->add (MidiModel::PatchChangePtr (
2094 new Evoral::PatchChange<Evoral::Beats> (
2095 absolute_frames_to_source_beats (_region->position() + t),
2096 mtv->get_channel_for_add(), patch.program(), patch.bank()
2101 _model->apply_command (*trackview.session(), c);
2102 trackview.editor().commit_reversible_command ();
2104 display_patch_changes ();
2108 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2110 trackview.editor().begin_reversible_command (_("move patch change"));
2111 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2112 c->change_time (pc.patch (), t);
2113 _model->apply_command (*trackview.session(), c);
2114 trackview.editor().commit_reversible_command ();
2116 display_patch_changes ();
2120 MidiRegionView::delete_patch_change (PatchChange* pc)
2122 trackview.editor().begin_reversible_command (_("delete patch change"));
2124 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2125 c->remove (pc->patch ());
2126 _model->apply_command (*trackview.session(), c);
2127 trackview.editor().commit_reversible_command ();
2129 remove_canvas_patch_change (pc);
2130 display_patch_changes ();
2134 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2136 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2138 key.set_bank(key.bank() + delta);
2140 key.set_program(key.program() + delta);
2142 change_patch_change(patch, key);
2146 MidiRegionView::note_deleted (NoteBase* cne)
2148 if (_entered_note && cne == _entered_note) {
2152 if (_selection.empty()) {
2156 _selection.erase (cne);
2160 MidiRegionView::delete_selection()
2162 if (_selection.empty()) {
2166 if (trackview.editor().drags()->active()) {
2170 start_note_diff_command (_("delete selection"));
2172 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2173 if ((*i)->selected()) {
2174 _note_diff_command->remove((*i)->note());
2182 hide_verbose_cursor ();
2186 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2188 start_note_diff_command (_("delete note"));
2189 _note_diff_command->remove (n);
2192 hide_verbose_cursor ();
2196 MidiRegionView::clear_editor_note_selection ()
2198 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2199 PublicEditor& editor(trackview.editor());
2200 editor.get_selection().clear_midi_notes();
2204 MidiRegionView::clear_selection ()
2206 clear_selection_internal();
2207 PublicEditor& editor(trackview.editor());
2208 editor.get_selection().remove(this);
2212 MidiRegionView::clear_selection_internal ()
2214 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2216 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2217 (*i)->set_selected(false);
2218 (*i)->hide_velocity();
2223 // Clearing selection entirely, ungrab keyboard
2224 Keyboard::magic_widget_drop_focus();
2225 _grabbed_keyboard = false;
2230 MidiRegionView::unique_select(NoteBase* ev)
2232 clear_editor_note_selection();
2233 add_to_selection(ev);
2237 MidiRegionView::select_all_notes ()
2239 clear_editor_note_selection ();
2241 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2242 add_to_selection (*i);
2247 MidiRegionView::select_range (framepos_t start, framepos_t end)
2249 clear_editor_note_selection ();
2251 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2252 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2253 if (t >= start && t <= end) {
2254 add_to_selection (*i);
2260 MidiRegionView::invert_selection ()
2262 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2263 if ((*i)->selected()) {
2264 remove_from_selection(*i);
2266 add_to_selection (*i);
2271 /** Used for selection undo/redo.
2272 The requested notes most likely won't exist in the view until the next model redisplay.
2275 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2278 list<Evoral::event_id_t>::iterator n;
2280 for (n = notes.begin(); n != notes.end(); ++n) {
2281 if ((cne = find_canvas_note(*n)) != 0) {
2282 add_to_selection (cne);
2284 _pending_note_selection.insert(*n);
2290 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2292 bool have_selection = !_selection.empty();
2293 uint8_t low_note = 127;
2294 uint8_t high_note = 0;
2295 MidiModel::Notes& notes (_model->notes());
2296 _optimization_iterator = _events.begin();
2298 if (extend && !have_selection) {
2302 /* scan existing selection to get note range */
2304 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2305 if ((*i)->note()->note() < low_note) {
2306 low_note = (*i)->note()->note();
2308 if ((*i)->note()->note() > high_note) {
2309 high_note = (*i)->note()->note();
2314 clear_editor_note_selection ();
2316 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2317 /* only note previously selected is the one we are
2318 * reselecting. treat this as cancelling the selection.
2325 low_note = min (low_note, notenum);
2326 high_note = max (high_note, notenum);
2329 _no_sound_notes = true;
2331 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2333 boost::shared_ptr<NoteType> note (*n);
2335 bool select = false;
2337 if (((1 << note->channel()) & channel_mask) != 0) {
2339 if ((note->note() >= low_note && note->note() <= high_note)) {
2342 } else if (note->note() == notenum) {
2348 if ((cne = find_canvas_note (note)) != 0) {
2349 // extend is false because we've taken care of it,
2350 // since it extends by time range, not pitch.
2351 note_selected (cne, add, false);
2355 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2359 _no_sound_notes = false;
2363 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2365 MidiModel::Notes& notes (_model->notes());
2366 _optimization_iterator = _events.begin();
2368 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2370 boost::shared_ptr<NoteType> note (*n);
2373 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2374 if ((cne = find_canvas_note (note)) != 0) {
2375 if (cne->selected()) {
2376 note_deselected (cne);
2378 note_selected (cne, true, false);
2386 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2389 clear_editor_note_selection();
2390 add_to_selection (ev);
2395 if (!ev->selected()) {
2396 add_to_selection (ev);
2400 /* find end of latest note selected, select all between that and the start of "ev" */
2402 Evoral::Beats earliest = Evoral::MaxBeats;
2403 Evoral::Beats latest = Evoral::Beats();
2405 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2406 if ((*i)->note()->end_time() > latest) {
2407 latest = (*i)->note()->end_time();
2409 if ((*i)->note()->time() < earliest) {
2410 earliest = (*i)->note()->time();
2414 if (ev->note()->end_time() > latest) {
2415 latest = ev->note()->end_time();
2418 if (ev->note()->time() < earliest) {
2419 earliest = ev->note()->time();
2422 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2424 /* find notes entirely within OR spanning the earliest..latest range */
2426 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2427 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2428 add_to_selection (*i);
2436 MidiRegionView::note_deselected(NoteBase* ev)
2438 remove_from_selection (ev);
2442 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2444 PublicEditor& editor = trackview.editor();
2446 // Convert to local coordinates
2447 const framepos_t p = _region->position();
2448 const double y = midi_view()->y_position();
2449 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2450 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2451 const double y0 = max(0.0, gy0 - y);
2452 const double y1 = max(0.0, gy1 - y);
2454 // TODO: Make this faster by storing the last updated selection rect, and only
2455 // adjusting things that are in the area that appears/disappeared.
2456 // We probably need a tree to be able to find events in O(log(n)) time.
2458 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2459 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2460 // Rectangles intersect
2461 if (!(*i)->selected()) {
2462 add_to_selection (*i);
2464 } else if ((*i)->selected() && !extend) {
2465 // Rectangles do not intersect
2466 remove_from_selection (*i);
2470 typedef RouteTimeAxisView::AutomationTracks ATracks;
2471 typedef std::list<Selectable*> Selectables;
2473 /* Add control points to selection. */
2474 const ATracks& atracks = midi_view()->automation_tracks();
2475 Selectables selectables;
2476 editor.get_selection().clear_points();
2477 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2478 a->second->get_selectables(start, end, gy0, gy1, selectables);
2479 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2480 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2482 editor.get_selection().add(cp);
2485 a->second->set_selected_points(editor.get_selection().points);
2490 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2496 // TODO: Make this faster by storing the last updated selection rect, and only
2497 // adjusting things that are in the area that appears/disappeared.
2498 // We probably need a tree to be able to find events in O(log(n)) time.
2500 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2501 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2502 // within y- (note-) range
2503 if (!(*i)->selected()) {
2504 add_to_selection (*i);
2506 } else if ((*i)->selected() && !extend) {
2507 remove_from_selection (*i);
2513 MidiRegionView::remove_from_selection (NoteBase* ev)
2515 Selection::iterator i = _selection.find (ev);
2517 if (i != _selection.end()) {
2518 _selection.erase (i);
2519 if (_selection.empty() && _grabbed_keyboard) {
2521 Keyboard::magic_widget_drop_focus();
2522 _grabbed_keyboard = false;
2526 ev->set_selected (false);
2527 ev->hide_velocity ();
2529 if (_selection.empty()) {
2530 PublicEditor& editor (trackview.editor());
2531 editor.get_selection().remove (this);
2536 MidiRegionView::add_to_selection (NoteBase* ev)
2538 const bool selection_was_empty = _selection.empty();
2540 if (_selection.insert (ev).second) {
2541 ev->set_selected (true);
2542 start_playing_midi_note ((ev)->note());
2543 if (selection_was_empty && _entered) {
2544 // Grab keyboard for moving notes with arrow keys
2545 Keyboard::magic_widget_grab_focus();
2546 _grabbed_keyboard = true;
2550 if (selection_was_empty) {
2551 PublicEditor& editor (trackview.editor());
2552 editor.get_selection().add (this);
2557 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2559 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2560 PossibleChord to_play;
2561 Evoral::Beats earliest = Evoral::MaxBeats;
2563 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2564 if ((*i)->note()->time() < earliest) {
2565 earliest = (*i)->note()->time();
2569 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2570 if ((*i)->note()->time() == earliest) {
2571 to_play.push_back ((*i)->note());
2573 (*i)->move_event(dx, dy);
2576 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2578 if (to_play.size() > 1) {
2580 PossibleChord shifted;
2582 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2583 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2584 moved_note->set_note (moved_note->note() + cumulative_dy);
2585 shifted.push_back (moved_note);
2588 start_playing_midi_chord (shifted);
2590 } else if (!to_play.empty()) {
2592 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2593 moved_note->set_note (moved_note->note() + cumulative_dy);
2594 start_playing_midi_note (moved_note);
2600 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2602 uint8_t lowest_note_in_selection = 127;
2603 uint8_t highest_note_in_selection = 0;
2604 uint8_t highest_note_difference = 0;
2606 // find highest and lowest notes first
2608 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2609 uint8_t pitch = (*i)->note()->note();
2610 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2611 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2615 cerr << "dnote: " << (int) dnote << endl;
2616 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2617 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2618 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2619 << int(highest_note_in_selection) << endl;
2620 cerr << "selection size: " << _selection.size() << endl;
2621 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2624 // Make sure the note pitch does not exceed the MIDI standard range
2625 if (highest_note_in_selection + dnote > 127) {
2626 highest_note_difference = highest_note_in_selection - 127;
2628 TempoMap& map (trackview.session()->tempo_map());
2630 start_note_diff_command (_("move notes"));
2632 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2634 double const start_qn = _region->quarter_note() - midi_region()->start_beats();
2635 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2636 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2641 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2643 uint8_t original_pitch = (*i)->note()->note();
2644 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2646 // keep notes in standard midi range
2647 clamp_to_0_127(new_pitch);
2649 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2650 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2652 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2657 // care about notes being moved beyond the upper/lower bounds on the canvas
2658 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2659 highest_note_in_selection > midi_stream_view()->highest_note()) {
2660 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2664 /** @param x Pixel relative to the region position.
2665 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2666 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2667 * @return Snapped frame relative to the region position.
2670 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2672 PublicEditor& editor (trackview.editor());
2673 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2676 /** @param x Pixel relative to the region position.
2677 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2678 * @return Snapped pixel relative to the region position.
2681 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2683 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2687 MidiRegionView::get_position_pixels()
2689 framepos_t region_frame = get_position();
2690 return trackview.editor().sample_to_pixel(region_frame);
2694 MidiRegionView::get_end_position_pixels()
2696 framepos_t frame = get_position() + get_duration ();
2697 return trackview.editor().sample_to_pixel(frame);
2701 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2703 /* the time converter will return the frame corresponding to `beats'
2704 relative to the start of the source. The start of the source
2705 is an implied position given by region->position - region->start
2707 const framepos_t source_start = _region->position() - _region->start();
2708 return source_start + _source_relative_time_converter.to (beats);
2712 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2714 /* the `frames' argument needs to be converted into a frame count
2715 relative to the start of the source before being passed in to the
2718 const framepos_t source_start = _region->position() - _region->start();
2719 return _source_relative_time_converter.from (frames - source_start);
2723 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2725 return _region_relative_time_converter.to(beats);
2729 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2731 return _region_relative_time_converter.from(frames);
2735 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2737 return _region_relative_time_converter_double.from(frames);
2741 MidiRegionView::begin_resizing (bool /*at_front*/)
2743 _resize_data.clear();
2745 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2746 Note *note = dynamic_cast<Note*> (*i);
2748 // only insert CanvasNotes into the map
2750 NoteResizeData *resize_data = new NoteResizeData();
2751 resize_data->note = note;
2753 // create a new SimpleRect from the note which will be the resize preview
2754 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2755 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2757 // calculate the colors: get the color settings
2758 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2759 UIConfiguration::instance().color ("midi note selected"),
2762 // make the resize preview notes more transparent and bright
2763 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2765 // calculate color based on note velocity
2766 resize_rect->set_fill_color (UINT_INTERPOLATE(
2767 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2771 resize_rect->set_outline_color (NoteBase::calculate_outline (
2772 UIConfiguration::instance().color ("midi note selected")));
2774 resize_data->resize_rect = resize_rect;
2775 _resize_data.push_back(resize_data);
2780 /** Update resizing notes while user drags.
2781 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2782 * @param at_front which end of the note (true == note on, false == note off)
2783 * @param delta_x change in mouse position since the start of the drag
2784 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2785 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2786 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2787 * as the \a primary note.
2788 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2789 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2792 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2794 TempoMap& tmap (trackview.session()->tempo_map());
2795 bool cursor_set = false;
2796 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2798 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2799 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2800 Note* canvas_note = (*i)->note;
2805 current_x = canvas_note->x0() + delta_x + snap_delta;
2807 current_x = primary->x0() + delta_x + snap_delta;
2811 current_x = canvas_note->x1() + delta_x + snap_delta;
2813 current_x = primary->x1() + delta_x + snap_delta;
2817 if (current_x < 0) {
2818 // This works even with snapping because RegionView::snap_frame_to_frame()
2819 // snaps forward if the snapped sample is before the beginning of the region
2822 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2823 current_x = trackview.editor().sample_to_pixel(_region->length());
2828 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2830 resize_rect->set_x0 (current_x - snap_delta);
2832 resize_rect->set_x1 (canvas_note->x1());
2835 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2837 resize_rect->set_x1 (current_x - snap_delta);
2839 resize_rect->set_x0 (canvas_note->x0());
2843 /* Convert snap delta from pixels to beats. */
2844 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2845 double snap_delta_beats = 0.0;
2848 /* negative beat offsets aren't allowed */
2849 if (snap_delta_samps > 0) {
2850 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2851 } else if (snap_delta_samps < 0) {
2852 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2857 int32_t divisions = 0;
2860 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2861 divisions = trackview.editor().get_grid_music_divisions (0);
2863 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2865 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2866 - midi_region()->beat()) + midi_region()->start_beats();
2868 Evoral::Beats len = Evoral::Beats();
2871 if (beats < canvas_note->note()->end_time()) {
2872 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2873 len += canvas_note->note()->length();
2876 if (beats >= canvas_note->note()->time()) {
2877 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2881 len = std::max(Evoral::Beats(1 / 512.0), len);
2884 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2885 show_verbose_cursor (buf, 0, 0);
2894 /** Finish resizing notes when the user releases the mouse button.
2895 * Parameters the same as for \a update_resizing().
2898 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2900 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2901 TempoMap& tmap (trackview.session()->tempo_map());
2903 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2904 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2906 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2907 Note* canvas_note = (*i)->note;
2908 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2910 /* Get the new x position for this resize, which is in pixels relative
2911 * to the region position.
2918 current_x = canvas_note->x0() + delta_x + snap_delta;
2920 current_x = primary->x0() + delta_x + snap_delta;
2924 current_x = canvas_note->x1() + delta_x + snap_delta;
2926 current_x = primary->x1() + delta_x + snap_delta;
2930 if (current_x < 0) {
2933 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2934 current_x = trackview.editor().sample_to_pixel(_region->length());
2937 /* Convert snap delta from pixels to beats with sign. */
2938 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2939 double snap_delta_beats = 0.0;
2942 if (snap_delta_samps > 0) {
2943 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2944 } else if (snap_delta_samps < 0) {
2945 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2949 uint32_t divisions = 0;
2950 /* Convert the new x position to a frame within the source */
2951 framepos_t current_fr;
2953 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2954 divisions = trackview.editor().get_grid_music_divisions (0);
2956 current_fr = trackview.editor().pixel_to_sample (current_x);
2959 /* and then to beats */
2960 const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
2961 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
2962 const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
2964 if (at_front && x_beats < canvas_note->note()->end_time()) {
2965 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2966 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2967 len += canvas_note->note()->length();
2970 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2975 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2976 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2977 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2984 _resize_data.clear();
2989 MidiRegionView::abort_resizing ()
2991 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2992 delete (*i)->resize_rect;
2996 _resize_data.clear ();
3000 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
3002 uint8_t new_velocity;
3005 new_velocity = event->note()->velocity() + velocity;
3006 clamp_to_0_127(new_velocity);
3008 new_velocity = velocity;
3011 event->set_selected (event->selected()); // change color
3013 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
3017 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
3022 new_note = event->note()->note() + note;
3027 clamp_to_0_127 (new_note);
3028 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3032 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
3034 bool change_start = false;
3035 bool change_length = false;
3036 Evoral::Beats new_start;
3037 Evoral::Beats new_length;
3039 /* NOTE: the semantics of the two delta arguments are slightly subtle:
3041 front_delta: if positive - move the start of the note later in time (shortening it)
3042 if negative - move the start of the note earlier in time (lengthening it)
3044 end_delta: if positive - move the end of the note later in time (lengthening it)
3045 if negative - move the end of the note earlier in time (shortening it)
3048 if (!!front_delta) {
3049 if (front_delta < 0) {
3051 if (event->note()->time() < -front_delta) {
3052 new_start = Evoral::Beats();
3054 new_start = event->note()->time() + front_delta; // moves earlier
3057 /* start moved toward zero, so move the end point out to where it used to be.
3058 Note that front_delta is negative, so this increases the length.
3061 new_length = event->note()->length() - front_delta;
3062 change_start = true;
3063 change_length = true;
3067 Evoral::Beats new_pos = event->note()->time() + front_delta;
3069 if (new_pos < event->note()->end_time()) {
3070 new_start = event->note()->time() + front_delta;
3071 /* start moved toward the end, so move the end point back to where it used to be */
3072 new_length = event->note()->length() - front_delta;
3073 change_start = true;
3074 change_length = true;
3081 bool can_change = true;
3082 if (end_delta < 0) {
3083 if (event->note()->length() < -end_delta) {
3089 new_length = event->note()->length() + end_delta;
3090 change_length = true;
3095 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3098 if (change_length) {
3099 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3104 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3106 uint8_t new_channel;
3110 if (event->note()->channel() < -chn) {
3113 new_channel = event->note()->channel() + chn;
3116 new_channel = event->note()->channel() + chn;
3119 new_channel = (uint8_t) chn;
3122 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3126 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3128 Evoral::Beats new_time;
3132 if (event->note()->time() < -delta) {
3133 new_time = Evoral::Beats();
3135 new_time = event->note()->time() + delta;
3138 new_time = event->note()->time() + delta;
3144 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3148 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3150 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3154 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3159 if (_selection.empty()) {
3174 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3175 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3181 start_note_diff_command (_("change velocities"));
3183 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3184 Selection::iterator next = i;
3188 if (i == _selection.begin()) {
3189 change_note_velocity (*i, delta, true);
3190 value = (*i)->note()->velocity() + delta;
3192 change_note_velocity (*i, value, false);
3196 change_note_velocity (*i, delta, true);
3205 if (!_selection.empty()) {
3207 snprintf (buf, sizeof (buf), "Vel %d",
3208 (int) (*_selection.begin())->note()->velocity());
3209 show_verbose_cursor (buf, 10, 10);
3215 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3217 if (_selection.empty()) {
3234 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3236 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3240 if ((int8_t) (*i)->note()->note() + delta > 127) {
3247 start_note_diff_command (_("transpose"));
3249 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3250 Selection::iterator next = i;
3252 change_note_note (*i, delta, true);
3260 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3264 delta = Evoral::Beats(1.0/128.0);
3266 /* grab the current grid distance */
3267 delta = get_grid_beats(_region->position());
3275 start_note_diff_command (_("change note lengths"));
3277 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3278 Selection::iterator next = i;
3281 /* note the negation of the delta for start */
3284 (start ? -delta : Evoral::Beats()),
3285 (end ? delta : Evoral::Beats()));
3294 MidiRegionView::nudge_notes (bool forward, bool fine)
3296 if (_selection.empty()) {
3300 /* pick a note as the point along the timeline to get the nudge distance.
3301 its not necessarily the earliest note, so we may want to pull the notes out
3302 into a vector and sort before using the first one.
3305 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3306 Evoral::Beats delta;
3310 /* non-fine, move by 1 bar regardless of snap */
3311 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3313 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3315 /* grid is off - use nudge distance */
3318 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3319 delta = region_frames_to_region_beats (fabs ((double)distance));
3325 framepos_t next_pos = ref_point;
3328 if (max_framepos - 1 < next_pos) {
3332 if (next_pos == 0) {
3338 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3339 const framecnt_t distance = ref_point - next_pos;
3340 delta = region_frames_to_region_beats (fabs ((double)distance));
3351 start_note_diff_command (_("nudge"));
3353 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3354 Selection::iterator next = i;
3356 change_note_time (*i, delta, true);
3364 MidiRegionView::change_channel(uint8_t channel)
3366 start_note_diff_command(_("change channel"));
3367 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3368 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3376 MidiRegionView::note_entered(NoteBase* ev)
3380 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3382 if (_mouse_state == SelectTouchDragging) {
3384 note_selected (ev, true);
3386 } else if (editor->current_mouse_mode() == MouseContent) {
3388 remove_ghost_note ();
3389 show_verbose_cursor (ev->note ());
3391 } else if (editor->current_mouse_mode() == MouseDraw) {
3393 remove_ghost_note ();
3394 show_verbose_cursor (ev->note ());
3399 MidiRegionView::note_left (NoteBase*)
3403 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3404 (*i)->hide_velocity ();
3407 hide_verbose_cursor ();
3411 MidiRegionView::patch_entered (PatchChange* p)
3414 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3415 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3416 << _("Channel ") << ((int) p->patch()->channel() + 1);
3417 show_verbose_cursor (s.str(), 10, 20);
3418 p->item().grab_focus();
3422 MidiRegionView::patch_left (PatchChange *)
3424 hide_verbose_cursor ();
3425 /* focus will transfer back via the enter-notify event sent to this
3431 MidiRegionView::sysex_entered (SysEx* p)
3435 // need a way to extract text from p->_flag->_text
3437 // show_verbose_cursor (s.str(), 10, 20);
3438 p->item().grab_focus();
3442 MidiRegionView::sysex_left (SysEx *)
3444 hide_verbose_cursor ();
3445 /* focus will transfer back via the enter-notify event sent to this
3451 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3453 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3454 Editing::MouseMode mm = editor->current_mouse_mode();
3455 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3457 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3458 if (can_set_cursor && ctx) {
3459 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3460 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3461 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3462 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3464 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3470 MidiRegionView::get_fill_color() const
3472 const std::string mod_name = (_dragging ? "dragging region" :
3473 trackview.editor().internal_editing() ? "editable region" :
3476 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3477 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3478 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3479 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3481 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3485 MidiRegionView::midi_channel_mode_changed ()
3487 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3488 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3489 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3491 if (mode == ForceChannel) {
3492 mask = 0xFFFF; // Show all notes as active (below)
3495 // Update notes for selection
3496 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3497 (*i)->on_channel_selection_change (mask);
3500 _patch_changes.clear ();
3501 display_patch_changes ();
3505 MidiRegionView::instrument_settings_changed ()
3511 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3513 if (_selection.empty()) {
3517 PublicEditor& editor (trackview.editor());
3521 /* XXX what to do ? */
3525 editor.get_cut_buffer().add (selection_as_cut_buffer());
3533 start_note_diff_command();
3535 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3542 note_diff_remove_note (*i);
3552 MidiRegionView::selection_as_cut_buffer () const
3556 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3557 NoteType* n = (*i)->note().get();
3558 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3561 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3567 /** This method handles undo */
3569 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3571 bool commit = false;
3572 // Paste notes, if available
3573 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3574 if (m != selection.midi_notes.end()) {
3575 ctx.counts.increase_n_notes();
3576 if (!(*m)->empty()) {
3579 paste_internal(pos, ctx.count, ctx.times, **m);
3582 // Paste control points to automation children, if available
3583 typedef RouteTimeAxisView::AutomationTracks ATracks;
3584 const ATracks& atracks = midi_view()->automation_tracks();
3585 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3586 if (a->second->paste(pos, selection, ctx, sub_num)) {
3588 trackview.editor().begin_reversible_command (Operations::paste);
3595 trackview.editor().commit_reversible_command ();
3600 /** This method handles undo */
3602 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3608 start_note_diff_command (_("paste"));
3610 const Evoral::Beats snap_beats = get_grid_beats(pos);
3611 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3612 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3613 const Evoral::Beats duration = last_time - first_time;
3614 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3615 const Evoral::Beats paste_offset = snap_duration * paste_count;
3616 const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
3617 Evoral::Beats end_point = Evoral::Beats();
3619 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3622 duration, pos, _region->position(),
3625 clear_editor_note_selection ();
3627 for (int n = 0; n < (int) times; ++n) {
3629 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3631 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3632 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3633 copied_note->set_id (Evoral::next_event_id());
3635 /* make all newly added notes selected */
3637 note_diff_add_note (copied_note, true);
3638 end_point = copied_note->end_time();
3642 /* if we pasted past the current end of the region, extend the region */
3644 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3645 framepos_t region_end = _region->position() + _region->length() - 1;
3647 if (end_frame > region_end) {
3649 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3651 _region->clear_changes ();
3652 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3653 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3654 trackview.session()->add_command (new StatefulDiffCommand (_region));
3660 struct EventNoteTimeEarlyFirstComparator {
3661 bool operator() (NoteBase* a, NoteBase* b) {
3662 return a->note()->time() < b->note()->time();
3667 MidiRegionView::time_sort_events ()
3669 if (!_sort_needed) {
3673 EventNoteTimeEarlyFirstComparator cmp;
3676 _sort_needed = false;
3680 MidiRegionView::goto_next_note (bool add_to_selection)
3682 bool use_next = false;
3684 if (_events.back()->selected()) {
3688 time_sort_events ();
3690 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3691 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3693 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3694 if ((*i)->selected()) {
3697 } else if (use_next) {
3698 if (channel_mask & (1 << (*i)->note()->channel())) {
3699 if (!add_to_selection) {
3702 note_selected (*i, true, false);
3709 /* use the first one */
3711 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3712 unique_select (_events.front());
3717 MidiRegionView::goto_previous_note (bool add_to_selection)
3719 bool use_next = false;
3721 if (_events.front()->selected()) {
3725 time_sort_events ();
3727 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3728 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3730 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3731 if ((*i)->selected()) {
3734 } else if (use_next) {
3735 if (channel_mask & (1 << (*i)->note()->channel())) {
3736 if (!add_to_selection) {
3739 note_selected (*i, true, false);
3746 /* use the last one */
3748 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3749 unique_select (*(_events.rbegin()));
3754 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3756 bool had_selected = false;
3758 time_sort_events ();
3760 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3761 if ((*i)->selected()) {
3762 selected.insert ((*i)->note());
3763 had_selected = true;
3767 if (allow_all_if_none_selected && !had_selected) {
3768 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3769 selected.insert ((*i)->note());
3775 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3777 x = std::max(0.0, x);
3779 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3784 _note_group->canvas_to_item (x, y);
3786 PublicEditor& editor = trackview.editor ();
3788 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3790 const int32_t divisions = editor.get_grid_music_divisions (state);
3791 const bool shift_snap = midi_view()->note_mode() != Percussive;
3792 const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap);
3794 /* prevent Percussive mode from displaying a ghost hit at region end */
3795 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3796 _ghost_note->hide();
3797 hide_verbose_cursor ();
3801 /* ghost note may have been snapped before region */
3802 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3803 _ghost_note->hide();
3806 } else if (_ghost_note) {
3807 _ghost_note->show();
3810 /* calculate time in beats relative to start of source */
3811 const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3813 _ghost_note->note()->set_time (snapped_beats);
3814 _ghost_note->note()->set_length (length);
3815 _ghost_note->note()->set_note (y_to_note (y));
3816 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3817 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3818 /* the ghost note does not appear in ghost regions, so pass false in here */
3819 update_note (_ghost_note, false);
3821 show_verbose_cursor (_ghost_note->note ());
3825 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3827 remove_ghost_note ();
3829 boost::shared_ptr<NoteType> g (new NoteType);
3830 if (midi_view()->note_mode() == Sustained) {
3831 _ghost_note = new Note (*this, _note_group, g);
3833 _ghost_note = new Hit (*this, _note_group, 10, g);
3835 _ghost_note->set_ignore_events (true);
3836 _ghost_note->set_outline_color (0x000000aa);
3837 update_ghost_note (x, y, state);
3838 _ghost_note->show ();
3840 show_verbose_cursor (_ghost_note->note ());
3844 MidiRegionView::remove_ghost_note ()
3851 MidiRegionView::hide_verbose_cursor ()
3853 trackview.editor().verbose_cursor()->hide ();
3854 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3856 mtv->set_note_highlight (NO_MIDI_NOTE);
3861 MidiRegionView::snap_changed ()
3867 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3871 MidiRegionView::drop_down_keys ()
3873 _mouse_state = None;
3877 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3879 /* XXX: This is dead code. What was it for? */
3881 double note = y_to_note(y);
3883 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3885 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3887 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3888 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3889 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3890 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3895 bool add_mrv_selection = false;
3897 if (_selection.empty()) {
3898 add_mrv_selection = true;
3901 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3902 if (_selection.insert (*i).second) {
3903 (*i)->set_selected (true);
3907 if (add_mrv_selection) {
3908 PublicEditor& editor (trackview.editor());
3909 editor.get_selection().add (this);
3914 MidiRegionView::color_handler ()
3916 RegionView::color_handler ();
3918 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
3919 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
3921 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3922 (*i)->set_selected ((*i)->selected()); // will change color
3925 /* XXX probably more to do here */
3929 MidiRegionView::enable_display (bool yn)
3931 RegionView::enable_display (yn);
3935 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3937 if (_step_edit_cursor == 0) {
3938 ArdourCanvas::Item* const group = get_canvas_group();
3940 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3941 _step_edit_cursor->set_y0 (0);
3942 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3943 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3944 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3947 move_step_edit_cursor (pos);
3948 _step_edit_cursor->show ();
3952 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3954 _step_edit_cursor_position = pos;
3956 if (_step_edit_cursor) {
3957 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3958 _step_edit_cursor->set_x0 (pixel);
3959 set_step_edit_cursor_width (_step_edit_cursor_width);
3964 MidiRegionView::hide_step_edit_cursor ()
3966 if (_step_edit_cursor) {
3967 _step_edit_cursor->hide ();
3972 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3974 _step_edit_cursor_width = beats;
3976 if (_step_edit_cursor) {
3977 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3978 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3979 - region_beats_to_region_frames (_step_edit_cursor_position)));
3983 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3984 * @param w Source that the data will end up in.
3987 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3989 if (!_active_notes) {
3990 /* we aren't actively being recorded to */
3994 boost::shared_ptr<MidiSource> src = w.lock ();
3995 if (!src || src != midi_region()->midi_source()) {
3996 /* recorded data was not destined for our source */
4000 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
4002 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
4004 framepos_t back = max_framepos;
4006 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
4007 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
4009 if (ev.is_channel_event()) {
4010 if (get_channel_mode() == FilterChannels) {
4011 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
4017 /* convert from session frames to source beats */
4018 Evoral::Beats const time_beats = _source_relative_time_converter.from(
4019 ev.time() - src->timeline_position() + _region->start());
4021 if (ev.type() == MIDI_CMD_NOTE_ON) {
4022 boost::shared_ptr<NoteType> note (
4023 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
4025 add_note (note, true);
4027 /* fix up our note range */
4028 if (ev.note() < _current_range_min) {
4029 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
4030 } else if (ev.note() > _current_range_max) {
4031 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
4034 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
4035 resolve_note (ev.note (), time_beats);
4041 midi_stream_view()->check_record_layers (region(), back);
4045 MidiRegionView::trim_front_starting ()
4047 /* We used to eparent the note group to the region view's parent, so that it didn't change.
4053 MidiRegionView::trim_front_ending ()
4055 if (_region->start() < 0) {
4056 /* Trim drag made start time -ve; fix this */
4057 midi_region()->fix_negative_start ();
4062 MidiRegionView::edit_patch_change (PatchChange* pc)
4064 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4066 int response = d.run();
4069 case Gtk::RESPONSE_ACCEPT:
4071 case Gtk::RESPONSE_REJECT:
4072 delete_patch_change (pc);
4078 change_patch_change (pc->patch(), d.patch ());
4082 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4085 // sysyex object doesn't have a pointer to a sysex event
4086 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4087 // c->remove (sysex->sysex());
4088 // _model->apply_command (*trackview.session(), c);
4090 //_sys_exes.clear ();
4091 // display_sysexes();
4095 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4097 using namespace MIDI::Name;
4100 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4102 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4104 MIDI::Name::PatchPrimaryKey patch_key;
4105 get_patch_key_at(n->time(), n->channel(), patch_key);
4106 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4109 patch_key.program(),
4115 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4117 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4118 (int) n->channel() + 1,
4119 (int) n->velocity());
4125 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4126 uint8_t new_value) const
4128 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4130 mtv->set_note_highlight (new_value);
4133 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4137 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4139 show_verbose_cursor_for_new_note_value(n, n->note());
4143 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4145 trackview.editor().verbose_cursor()->set (text);
4146 trackview.editor().verbose_cursor()->show ();
4147 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4151 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4153 if (_model->notes().empty()) {
4154 return 0x40; // No notes, use default
4157 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4158 if (m == _model->notes().begin()) {
4159 // Before the start, use the velocity of the first note
4160 return (*m)->velocity();
4161 } else if (m == _model->notes().end()) {
4162 // Past the end, use the velocity of the last note
4164 return (*m)->velocity();
4167 // Interpolate velocity of surrounding notes
4168 MidiModel::Notes::const_iterator n = m;
4171 const double frac = ((time - (*n)->time()).to_double() /
4172 ((*m)->time() - (*n)->time()).to_double());
4174 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4177 /** @param p A session framepos.
4178 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4179 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4180 * @return beat duration of p snapped to the grid subdivision underneath it.
4183 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
4185 TempoMap& map (trackview.session()->tempo_map());
4186 double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4188 if (divisions != 0 && shift_snap) {
4189 const double qaf = map.quarter_note_at_frame (p + _region->position());
4190 /* Hack so that we always snap to the note that we are over, instead of snapping
4191 to the next one if we're more than halfway through the one we're over.
4193 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4194 const double rem = eqaf - qaf;
4196 eqaf -= grid_beats.to_double();
4199 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4201 return Evoral::Beats (eqaf - session_start_off);
4205 MidiRegionView::get_channel_mode () const
4207 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4208 return rtav->midi_track()->get_playback_channel_mode();
4212 MidiRegionView::get_selected_channels () const
4214 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4215 return rtav->midi_track()->get_playback_channel_mask();
4220 MidiRegionView::get_grid_beats(framepos_t pos) const
4222 PublicEditor& editor = trackview.editor();
4223 bool success = false;
4224 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4226 beats = Evoral::Beats(1);
4231 MidiRegionView::y_to_note (double y) const
4233 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4234 + _current_range_min;
4238 } else if (n > 127) {
4242 /* min due to rounding and/or off-by-one errors */
4243 return min ((uint8_t) n, _current_range_max);
4247 MidiRegionView::note_to_y(uint8_t note) const
4249 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;