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) {
197 MidiRegionView::MidiRegionView (const MidiRegionView& other)
198 : sigc::trackable(other)
200 , _current_range_min(0)
201 , _current_range_max(0)
202 , _region_relative_time_converter(other.region_relative_time_converter())
203 , _source_relative_time_converter(other.source_relative_time_converter())
204 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
206 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
207 , _note_diff_command (0)
209 , _step_edit_cursor (0)
210 , _step_edit_cursor_width (1.0)
211 , _step_edit_cursor_position (0.0)
212 , _channel_selection_scoped_note (0)
215 , _sort_needed (true)
216 , _optimization_iterator (_events.end())
218 , _no_sound_notes (false)
219 , _last_display_zoom (0)
222 , _grabbed_keyboard (false)
225 , _mouse_changed_selection (false)
230 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
231 : RegionView (other, boost::shared_ptr<Region> (region))
232 , _current_range_min(0)
233 , _current_range_max(0)
234 , _region_relative_time_converter(other.region_relative_time_converter())
235 , _source_relative_time_converter(other.source_relative_time_converter())
236 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
238 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
239 , _note_diff_command (0)
241 , _step_edit_cursor (0)
242 , _step_edit_cursor_width (1.0)
243 , _step_edit_cursor_position (0.0)
244 , _channel_selection_scoped_note (0)
247 , _sort_needed (true)
248 , _optimization_iterator (_events.end())
250 , _no_sound_notes (false)
251 , _last_display_zoom (0)
254 , _grabbed_keyboard (false)
257 , _mouse_changed_selection (false)
263 MidiRegionView::init (bool wfd)
265 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
268 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
269 midi_region()->midi_source(0)->load_model(lm);
272 _model = midi_region()->midi_source(0)->model();
273 _enable_display = false;
274 fill_color_name = "midi frame base";
276 RegionView::init (false);
278 //set_height (trackview.current_height());
281 region_sync_changed ();
282 region_resized (ARDOUR::bounds_change);
287 _enable_display = true;
290 display_model (_model);
294 reset_width_dependent_items (_pixel_width);
296 group->raise_to_top();
298 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
299 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
302 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
303 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
305 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
306 boost::bind (&MidiRegionView::snap_changed, this),
309 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
310 boost::bind (&MidiRegionView::mouse_mode_changed, this),
313 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
314 connect_to_diskstream ();
318 MidiRegionView::instrument_info () const
320 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
321 return route_ui->route()->instrument_info();
324 const boost::shared_ptr<ARDOUR::MidiRegion>
325 MidiRegionView::midi_region() const
327 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
331 MidiRegionView::connect_to_diskstream ()
333 midi_view()->midi_track()->DataRecorded.connect(
334 *this, invalidator(*this),
335 boost::bind (&MidiRegionView::data_recorded, this, _1),
340 MidiRegionView::canvas_group_event(GdkEvent* ev)
342 if (in_destructor || _recregion) {
346 if (!trackview.editor().internal_editing()) {
347 // not in internal edit mode, so just act like a normal region
348 return RegionView::canvas_group_event (ev);
354 case GDK_ENTER_NOTIFY:
355 _last_event_x = ev->crossing.x;
356 _last_event_y = ev->crossing.y;
357 enter_notify(&ev->crossing);
358 // set entered_regionview (among other things)
359 return RegionView::canvas_group_event (ev);
361 case GDK_LEAVE_NOTIFY:
362 _last_event_x = ev->crossing.x;
363 _last_event_y = ev->crossing.y;
364 leave_notify(&ev->crossing);
365 // reset entered_regionview (among other things)
366 return RegionView::canvas_group_event (ev);
369 if (scroll (&ev->scroll)) {
375 return key_press (&ev->key);
377 case GDK_KEY_RELEASE:
378 return key_release (&ev->key);
380 case GDK_BUTTON_PRESS:
381 return button_press (&ev->button);
383 case GDK_BUTTON_RELEASE:
384 r = button_release (&ev->button);
387 case GDK_MOTION_NOTIFY:
388 _last_event_x = ev->motion.x;
389 _last_event_y = ev->motion.y;
390 return motion (&ev->motion);
396 return RegionView::canvas_group_event (ev);
400 MidiRegionView::enter_notify (GdkEventCrossing* ev)
402 enter_internal (ev->state);
409 MidiRegionView::leave_notify (GdkEventCrossing*)
418 MidiRegionView::mouse_mode_changed ()
420 // Adjust frame colour (become more transparent for internal tools)
424 if (!trackview.editor().internal_editing()) {
425 /* Switched out of internal editing mode while entered.
426 Only necessary for leave as a mouse_mode_change over a region
427 automatically triggers an enter event. */
430 else if (trackview.editor().current_mouse_mode() == MouseContent) {
431 // hide cursor and ghost note after changing to internal edit mode
432 remove_ghost_note ();
434 /* XXX This is problematic as the function is executed for every region
435 and only for one region _entered_note can be true. Still it's
436 necessary as to hide the verbose cursor when we're changing from
437 draw mode to internal edit mode. These lines are the reason why
438 in some situations no verbose cursor is shown when we enter internal
439 edit mode over a note. */
440 if (!_entered_note) {
441 hide_verbose_cursor ();
448 MidiRegionView::enter_internal (uint32_t state)
450 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
451 // Show ghost note under pencil
452 create_ghost_note(_last_event_x, _last_event_y, state);
455 if (!_selection.empty()) {
456 // Grab keyboard for moving selected notes with arrow keys
457 Keyboard::magic_widget_grab_focus();
458 _grabbed_keyboard = true;
461 // Lower frame handles below notes so they don't steal events
462 if (frame_handle_start) {
463 frame_handle_start->lower_to_bottom();
465 if (frame_handle_end) {
466 frame_handle_end->lower_to_bottom();
471 MidiRegionView::leave_internal()
473 hide_verbose_cursor ();
474 remove_ghost_note ();
477 if (_grabbed_keyboard) {
478 Keyboard::magic_widget_drop_focus();
479 _grabbed_keyboard = false;
482 // Raise frame handles above notes so they catch events
483 if (frame_handle_start) {
484 frame_handle_start->raise_to_top();
486 if (frame_handle_end) {
487 frame_handle_end->raise_to_top();
492 MidiRegionView::button_press (GdkEventButton* ev)
494 if (ev->button != 1) {
498 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
499 MouseMode m = editor->current_mouse_mode();
501 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
502 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
505 if (_mouse_state != SelectTouchDragging) {
507 _pressed_button = ev->button;
509 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
511 if (midi_view()->note_mode() == Percussive) {
512 editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
514 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
517 _mouse_state = AddDragging;
518 remove_ghost_note ();
519 hide_verbose_cursor ();
521 _mouse_state = Pressed;
527 _pressed_button = ev->button;
528 _mouse_changed_selection = false;
534 MidiRegionView::button_release (GdkEventButton* ev)
536 double event_x, event_y;
538 if (ev->button != 1) {
545 group->canvas_to_item (event_x, event_y);
548 PublicEditor& editor = trackview.editor ();
550 _press_cursor_ctx.reset();
552 switch (_mouse_state) {
553 case Pressed: // Clicked
555 switch (editor.current_mouse_mode()) {
557 /* no motion occurred - simple click */
558 clear_editor_note_selection ();
559 _mouse_changed_selection = true;
565 _mouse_changed_selection = true;
566 clear_editor_note_selection ();
581 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
582 we don't want one when we were drag-selecting either. */
583 case SelectRectDragging:
584 editor.drags()->end_grab ((GdkEvent *) ev);
593 if (_mouse_changed_selection) {
594 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
595 trackview.editor().commit_reversible_selection_op ();
602 MidiRegionView::motion (GdkEventMotion* ev)
604 PublicEditor& editor = trackview.editor ();
606 if (!_entered_note) {
608 if (_mouse_state == AddDragging) {
610 remove_ghost_note ();
613 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
614 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
615 _mouse_state != AddDragging) {
617 create_ghost_note (ev->x, ev->y, ev->state);
619 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
620 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
622 update_ghost_note (ev->x, ev->y, ev->state);
624 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
626 remove_ghost_note ();
627 hide_verbose_cursor ();
629 } else if (editor.current_mouse_mode() == MouseDraw) {
632 update_ghost_note (ev->x, ev->y, ev->state);
635 create_ghost_note (ev->x, ev->y, ev->state);
640 /* any motion immediately hides velocity text that may have been visible */
642 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
643 (*i)->hide_velocity ();
646 switch (_mouse_state) {
649 if (_pressed_button == 1) {
651 MouseMode m = editor.current_mouse_mode();
653 if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
654 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
655 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
656 clear_editor_note_selection ();
657 _mouse_changed_selection = true;
659 _mouse_state = SelectRectDragging;
661 } else if (m == MouseRange) {
662 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
663 _mouse_state = SelectVerticalDragging;
670 case SelectRectDragging:
671 case SelectVerticalDragging:
673 editor.drags()->motion_handler ((GdkEvent *) ev, false);
676 case SelectTouchDragging:
684 /* we may be dragging some non-note object (eg. patch-change, sysex)
687 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
692 MidiRegionView::scroll (GdkEventScroll* ev)
694 if (_selection.empty()) {
698 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
699 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
700 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
701 * through so that it still works for navigation.
706 hide_verbose_cursor ();
708 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
709 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
710 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
712 if (ev->direction == GDK_SCROLL_UP) {
713 change_velocities (true, fine, false, together);
714 } else if (ev->direction == GDK_SCROLL_DOWN) {
715 change_velocities (false, fine, false, together);
717 /* left, right: we don't use them */
725 MidiRegionView::key_press (GdkEventKey* ev)
727 /* since GTK bindings are generally activated on press, and since
728 detectable auto-repeat is the name of the game and only sends
729 repeated presses, carry out key actions at key press, not release.
731 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
733 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
735 if (_mouse_state != AddDragging) {
736 _mouse_state = SelectTouchDragging;
741 } else if (ev->keyval == GDK_Escape && unmodified) {
742 clear_editor_note_selection ();
745 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
747 bool start = (ev->keyval == GDK_comma);
748 bool end = (ev->keyval == GDK_period);
749 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
750 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
752 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
756 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
758 if (_selection.empty()) {
765 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
767 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
769 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
770 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
772 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
775 trackview.editor().commit_reversible_selection_op();
779 } else if (ev->keyval == GDK_Up) {
781 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
782 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
783 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
785 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
786 change_velocities (true, fine, allow_smush, together);
788 transpose (true, fine, allow_smush);
792 } else if (ev->keyval == GDK_Down) {
794 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
795 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
796 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
798 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
799 change_velocities (false, fine, allow_smush, together);
801 transpose (false, fine, allow_smush);
805 } else if (ev->keyval == GDK_Left) {
807 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
808 nudge_notes (false, fine);
811 } else if (ev->keyval == GDK_Right) {
813 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
814 nudge_notes (true, fine);
817 } else if (ev->keyval == GDK_c && unmodified) {
821 } else if (ev->keyval == GDK_v && unmodified) {
830 MidiRegionView::key_release (GdkEventKey* ev)
832 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
840 MidiRegionView::channel_edit ()
842 if (_selection.empty()) {
846 /* pick a note somewhat at random (since Selection is a set<>) to
847 * provide the "current" channel for the dialog.
850 uint8_t current_channel = (*_selection.begin())->note()->channel ();
851 MidiChannelDialog channel_dialog (current_channel);
852 int ret = channel_dialog.run ();
855 case Gtk::RESPONSE_OK:
861 uint8_t new_channel = channel_dialog.active_channel ();
863 start_note_diff_command (_("channel edit"));
865 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
866 Selection::iterator next = i;
868 change_note_channel (*i, new_channel);
876 MidiRegionView::velocity_edit ()
878 if (_selection.empty()) {
882 /* pick a note somewhat at random (since Selection is a set<>) to
883 * provide the "current" velocity for the dialog.
886 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
887 MidiVelocityDialog velocity_dialog (current_velocity);
888 int ret = velocity_dialog.run ();
891 case Gtk::RESPONSE_OK:
897 uint8_t new_velocity = velocity_dialog.velocity ();
899 start_note_diff_command (_("velocity edit"));
901 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
902 Selection::iterator next = i;
904 change_note_velocity (*i, new_velocity, false);
912 MidiRegionView::show_list_editor ()
915 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
917 _list_editor->present ();
920 /** Add a note to the model, and the view, at a canvas (click) coordinate.
921 * \param t time in frames relative to the position of the region
922 * \param y vertical position in pixels
923 * \param length duration of the note in beats
924 * \param snap_t true to snap t to the grid, otherwise false.
927 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
929 if (length < 2 * DBL_EPSILON) {
933 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
934 MidiStreamView* const view = mtv->midi_view();
935 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
941 // Start of note in frames relative to region start
942 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
943 Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
945 const double note = view->y_to_note(y);
946 const uint8_t chan = mtv->get_channel_for_add();
947 const uint8_t velocity = get_velocity_for_add(beat_time);
949 const boost::shared_ptr<NoteType> new_note(
950 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
952 if (_model->contains (new_note)) {
956 view->update_note_range(new_note->note());
958 start_note_diff_command(_("add note"));
960 clear_editor_note_selection ();
961 note_diff_add_note (new_note, true, false);
965 play_midi_note (new_note);
969 MidiRegionView::clear_events ()
971 // clear selection without signaling
972 clear_selection_internal ();
975 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
976 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
981 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
986 _patch_changes.clear();
988 _optimization_iterator = _events.end();
992 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
996 content_connection.disconnect ();
997 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
998 /* Don't signal as nobody else needs to know until selection has been altered. */
1001 if (_enable_display) {
1007 MidiRegionView::start_note_diff_command (string name)
1009 if (!_note_diff_command) {
1010 trackview.editor().begin_reversible_command (name);
1011 _note_diff_command = _model->new_note_diff_command (name);
1016 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1018 if (_note_diff_command) {
1019 _note_diff_command->add (note);
1022 _marked_for_selection.insert(note);
1024 if (show_velocity) {
1025 _marked_for_velocity.insert(note);
1030 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1032 if (_note_diff_command && ev->note()) {
1033 _note_diff_command->remove(ev->note());
1038 MidiRegionView::note_diff_add_change (NoteBase* ev,
1039 MidiModel::NoteDiffCommand::Property property,
1042 if (_note_diff_command) {
1043 _note_diff_command->change (ev->note(), property, val);
1048 MidiRegionView::note_diff_add_change (NoteBase* ev,
1049 MidiModel::NoteDiffCommand::Property property,
1052 if (_note_diff_command) {
1053 _note_diff_command->change (ev->note(), property, val);
1058 MidiRegionView::apply_diff (bool as_subcommand)
1061 bool commit = false;
1063 if (!_note_diff_command) {
1067 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1068 // Mark all selected notes for selection when model reloads
1069 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1070 _marked_for_selection.insert((*i)->note());
1074 midi_view()->midi_track()->midi_playlist()->region_edited(
1075 _region, _note_diff_command);
1077 if (as_subcommand) {
1078 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1080 _model->apply_command (*trackview.session(), _note_diff_command);
1084 _note_diff_command = 0;
1086 if (add_or_remove) {
1087 _marked_for_selection.clear();
1090 _marked_for_velocity.clear();
1092 trackview.editor().commit_reversible_command ();
1097 MidiRegionView::abort_command()
1099 delete _note_diff_command;
1100 _note_diff_command = 0;
1101 clear_editor_note_selection();
1105 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1107 if (_optimization_iterator != _events.end()) {
1108 ++_optimization_iterator;
1111 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1112 return *_optimization_iterator;
1115 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1116 if ((*_optimization_iterator)->note() == note) {
1117 return *_optimization_iterator;
1124 /** This version finds any canvas note matching the supplied note. */
1126 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1128 Events::iterator it;
1130 for (it = _events.begin(); it != _events.end(); ++it) {
1131 if ((*it)->note()->id() == id) {
1140 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1142 MidiModel::Notes notes;
1143 _model->get_notes (notes, op, val, chan_mask);
1145 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1146 NoteBase* cne = find_canvas_note (*n);
1154 MidiRegionView::redisplay_model()
1156 if (_active_notes) {
1157 // Currently recording
1158 const framecnt_t zoom = trackview.editor().get_current_zoom();
1159 if (zoom != _last_display_zoom) {
1160 /* Update resolved canvas notes to reflect changes in zoom without
1161 touching model. Leave active notes (with length 0) alone since
1162 they are being extended. */
1163 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1164 if ((*i)->note()->length() > 0) {
1168 _last_display_zoom = zoom;
1177 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1178 (*i)->invalidate ();
1181 MidiModel::ReadLock lock(_model->read_lock());
1183 MidiModel::Notes& notes (_model->notes());
1184 _optimization_iterator = _events.begin();
1186 bool empty_when_starting = _events.empty();
1189 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1191 boost::shared_ptr<NoteType> note (*n);
1194 if (note_in_region_range (note, visible)) {
1196 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1208 cne = add_note (note, visible);
1211 set<Evoral::event_id_t>::iterator it;
1212 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1213 if ((*it) == note->id()) {
1214 add_to_selection (cne);
1220 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1227 /* remove note items that are no longer valid */
1229 if (!empty_when_starting) {
1230 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1231 if (!(*i)->valid ()) {
1233 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1234 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1236 gr->remove_note (*i);
1241 i = _events.erase (i);
1249 _patch_changes.clear();
1253 display_patch_changes ();
1255 _marked_for_selection.clear ();
1256 _marked_for_velocity.clear ();
1257 _pending_note_selection.clear ();
1259 /* we may have caused _events to contain things out of order (e.g. if a note
1260 moved earlier or later). we don't generally need them in time order, but
1261 make a note that a sort is required for those cases that require it.
1264 _sort_needed = true;
1268 MidiRegionView::display_patch_changes ()
1270 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1271 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1273 for (uint8_t i = 0; i < 16; ++i) {
1274 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1278 /** @param active_channel true to display patch changes fully, false to display
1279 * them `greyed-out' (as on an inactive channel)
1282 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1284 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1286 if ((*i)->channel() != channel) {
1290 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1291 add_canvas_patch_change (*i, patch_name, active_channel);
1296 MidiRegionView::display_sysexes()
1298 bool have_periodic_system_messages = false;
1299 bool display_periodic_messages = true;
1301 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1303 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1304 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1305 have_periodic_system_messages = true;
1310 if (have_periodic_system_messages) {
1311 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1313 /* get an approximate value for the number of samples per video frame */
1315 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1317 /* if we are zoomed out beyond than the cutoff (i.e. more
1318 * frames per pixel than frames per 4 video frames), don't
1319 * show periodic sysex messages.
1322 if (zoom > (video_frame*4)) {
1323 display_periodic_messages = false;
1327 display_periodic_messages = false;
1330 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1331 Evoral::Beats time = (*i)->time();
1333 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1334 if (!display_periodic_messages) {
1341 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1342 str << int((*i)->buffer()[b]);
1343 if (b != (*i)->size() -1) {
1347 string text = str.str();
1349 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1351 double height = midi_stream_view()->contents_height();
1353 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1354 // SysEx canvas object!!!
1356 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1357 new SysEx (*this, _note_group, text, height, x, 1.0));
1359 // Show unless message is beyond the region bounds
1360 if (time - _region->start() >= _region->length() || time < _region->start()) {
1366 _sys_exes.push_back(sysex);
1370 MidiRegionView::~MidiRegionView ()
1372 in_destructor = true;
1374 hide_verbose_cursor ();
1376 delete _list_editor;
1378 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1380 if (_active_notes) {
1387 delete _note_diff_command;
1388 delete _step_edit_cursor;
1392 MidiRegionView::region_resized (const PropertyChange& what_changed)
1394 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1396 if (what_changed.contains (ARDOUR::Properties::position)) {
1397 _region_relative_time_converter.set_origin_b(_region->position());
1398 _region_relative_time_converter_double.set_origin_b(_region->position());
1399 /* reset_width dependent_items() redisplays model */
1403 if (what_changed.contains (ARDOUR::Properties::start) ||
1404 what_changed.contains (ARDOUR::Properties::position)) {
1405 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1407 /* catch end and start trim so we can update the view*/
1408 if (!what_changed.contains (ARDOUR::Properties::start) &&
1409 what_changed.contains (ARDOUR::Properties::length)) {
1410 enable_display (true);
1411 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1412 what_changed.contains (ARDOUR::Properties::length)) {
1413 enable_display (true);
1418 MidiRegionView::reset_width_dependent_items (double pixel_width)
1420 RegionView::reset_width_dependent_items(pixel_width);
1422 if (_enable_display) {
1426 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1427 if ((*x)->canvas_item()->width() >= _pixel_width) {
1434 move_step_edit_cursor (_step_edit_cursor_position);
1435 set_step_edit_cursor_width (_step_edit_cursor_width);
1439 MidiRegionView::set_height (double height)
1441 double old_height = _height;
1442 RegionView::set_height(height);
1444 apply_note_range (midi_stream_view()->lowest_note(),
1445 midi_stream_view()->highest_note(),
1446 height != old_height);
1449 name_text->raise_to_top();
1452 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1453 (*x)->set_height (midi_stream_view()->contents_height());
1456 if (_step_edit_cursor) {
1457 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1462 /** Apply the current note range from the stream view
1463 * by repositioning/hiding notes as necessary
1466 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1468 if (!_enable_display) {
1472 if (!force && _current_range_min == min && _current_range_max == max) {
1476 _current_range_min = min;
1477 _current_range_max = max;
1483 MidiRegionView::add_ghost (TimeAxisView& tv)
1485 double unit_position = _region->position () / samples_per_pixel;
1486 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1487 MidiGhostRegion* ghost;
1489 if (mtv && mtv->midi_view()) {
1490 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1491 to allow having midi notes on top of note lines and waveforms.
1493 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1495 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1498 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1499 ghost->add_note(*i);
1502 ghost->set_colors ();
1503 ghost->set_height ();
1504 ghost->set_duration (_region->length() / samples_per_pixel);
1505 ghosts.push_back (ghost);
1511 /** Begin tracking note state for successive calls to add_event
1514 MidiRegionView::begin_write()
1516 if (_active_notes) {
1517 delete[] _active_notes;
1519 _active_notes = new Note*[128];
1520 for (unsigned i = 0; i < 128; ++i) {
1521 _active_notes[i] = 0;
1526 /** Destroy note state for add_event
1529 MidiRegionView::end_write()
1531 delete[] _active_notes;
1533 _marked_for_selection.clear();
1534 _marked_for_velocity.clear();
1538 /** Resolve an active MIDI note (while recording).
1541 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1543 if (midi_view()->note_mode() != Sustained) {
1547 if (_active_notes && _active_notes[note]) {
1548 /* Set note length so update_note() works. Note this is a local note
1549 for recording, not from a model, so we can safely mess with it. */
1550 _active_notes[note]->note()->set_length(
1551 end_time - _active_notes[note]->note()->time());
1553 /* End time is relative to the region being recorded. */
1554 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1556 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1557 _active_notes[note]->set_outline_all ();
1558 _active_notes[note] = 0;
1563 /** Extend active notes to rightmost edge of region (if length is changed)
1566 MidiRegionView::extend_active_notes()
1568 if (!_active_notes) {
1572 for (unsigned i = 0; i < 128; ++i) {
1573 if (_active_notes[i]) {
1574 _active_notes[i]->set_x1(
1575 trackview.editor().sample_to_pixel(_region->length()));
1581 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1583 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1587 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1589 if (!route_ui || !route_ui->midi_track()) {
1593 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1597 /* NotePlayer deletes itself */
1601 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1603 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1604 start_playing_midi_chord(notes);
1608 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1610 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1614 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1616 if (!route_ui || !route_ui->midi_track()) {
1620 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1622 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1631 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1633 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1635 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1636 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1637 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1639 visible = (note->note() >= _current_range_min) &&
1640 (note->note() <= _current_range_max);
1646 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1650 if ((sus = dynamic_cast<Note*>(note))) {
1651 update_sustained(sus, update_ghost_regions);
1652 } else if ((hit = dynamic_cast<Hit*>(note))) {
1653 update_hit(hit, update_ghost_regions);
1657 /** Update a canvas note's size from its model note.
1658 * @param ev Canvas note to update.
1659 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1662 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1664 TempoMap& map (trackview.session()->tempo_map());
1665 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1666 boost::shared_ptr<NoteType> note = ev->note();
1668 const double session_source_start = _region->quarter_note() - mr->start_beats();
1669 const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1671 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1673 const double y0 = 1 + floor(note_to_y(note->note()));
1676 /* trim note display to not overlap the end of its region */
1677 if (note->length().to_double() > 0.0) {
1678 double note_end_time = note->end_time().to_double();
1680 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1681 note_end_time = mr->start_beats() + mr->length_beats();
1684 const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
1686 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1688 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1691 y1 = y0 + std::max(1., floor(note_height()) - 1);
1693 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1695 if (!note->length()) {
1696 if (_active_notes && note->note() < 128) {
1697 Note* const old_rect = _active_notes[note->note()];
1699 /* There is an active note on this key, so we have a stuck
1700 note. Finish the old rectangle here. */
1701 old_rect->set_x1 (x1);
1702 old_rect->set_outline_all ();
1704 _active_notes[note->note()] = ev;
1706 /* outline all but right edge */
1707 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1708 ArdourCanvas::Rectangle::TOP|
1709 ArdourCanvas::Rectangle::LEFT|
1710 ArdourCanvas::Rectangle::BOTTOM));
1712 /* outline all edges */
1713 ev->set_outline_all ();
1716 // Update color in case velocity has changed
1717 const uint32_t base_col = ev->base_color();
1718 ev->set_fill_color(base_col);
1719 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1721 if (update_ghost_regions) {
1722 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1723 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1725 gr->update_note (ev);
1732 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1734 boost::shared_ptr<NoteType> note = ev->note();
1736 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1737 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1739 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1740 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1741 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1743 // see DnD note in MidiRegionView::apply_note_range() above
1744 if (y <= 0 || y >= _height) {
1750 ev->set_position (ArdourCanvas::Duple (x, y));
1751 ev->set_height (diamond_size);
1753 // Update color in case velocity has changed
1754 const uint32_t base_col = ev->base_color();
1755 ev->set_fill_color(base_col);
1756 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1758 if (update_ghost_regions) {
1759 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1760 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1762 gr->update_hit (ev);
1768 /** Add a MIDI note to the view (with length).
1770 * If in sustained mode, notes with length 0 will be considered active
1771 * notes, and resolve_note should be called when the corresponding note off
1772 * event arrives, to properly display the note.
1775 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1777 NoteBase* event = 0;
1779 if (midi_view()->note_mode() == Sustained) {
1781 Note* ev_rect = new Note (*this, _note_group, note);
1783 update_sustained (ev_rect);
1787 } else if (midi_view()->note_mode() == Percussive) {
1789 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1791 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1793 update_hit (ev_diamond);
1802 MidiGhostRegion* gr;
1804 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1805 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1806 gr->add_note(event);
1810 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1811 note_selected(event, true);
1814 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1815 event->show_velocity();
1818 event->on_channel_selection_change (get_selected_channels());
1819 _events.push_back(event);
1828 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1829 MidiStreamView* const view = mtv->midi_view();
1831 view->update_note_range (note->note());
1836 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1837 Evoral::Beats pos, Evoral::Beats len)
1839 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1841 /* potentially extend region to hold new note */
1843 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1844 framepos_t region_end = _region->last_frame();
1846 if (end_frame > region_end) {
1847 /* XX sets length in beats from audio space. make musical */
1848 _region->set_length (end_frame - _region->position(), 0);
1851 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1852 MidiStreamView* const view = mtv->midi_view();
1854 view->update_note_range(new_note->note());
1856 _marked_for_selection.clear ();
1858 start_note_diff_command (_("step add"));
1860 clear_editor_note_selection ();
1861 note_diff_add_note (new_note, true, false);
1865 // last_step_edit_note = new_note;
1869 MidiRegionView::step_sustain (Evoral::Beats beats)
1871 change_note_lengths (false, false, beats, false, true);
1874 /** Add a new patch change flag to the canvas.
1875 * @param patch the patch change to add
1876 * @param the text to display in the flag
1877 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1880 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1882 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1883 const double x = trackview.editor().sample_to_pixel (region_frames);
1885 double const height = midi_stream_view()->contents_height();
1887 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1888 // so we need to do something more sophisticated to keep its color
1889 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1891 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1892 new PatchChange(*this, group,
1898 _patch_change_outline,
1902 if (patch_change->item().width() < _pixel_width) {
1903 // Show unless patch change is beyond the region bounds
1904 if (region_frames < 0 || region_frames >= _region->length()) {
1905 patch_change->hide();
1907 patch_change->show();
1910 patch_change->hide ();
1913 _patch_changes.push_back (patch_change);
1916 MIDI::Name::PatchPrimaryKey
1917 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1919 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1922 /// Return true iff @p pc applies to the given time on the given channel.
1924 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1926 return pc->time() <= time && pc->channel() == channel;
1930 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1932 // The earliest event not before time
1933 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1935 // Go backwards until we find the latest PC for this channel, or the start
1936 while (i != _model->patch_changes().begin() &&
1937 (i == _model->patch_changes().end() ||
1938 !patch_applies(*i, time, channel))) {
1942 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1943 key.set_bank((*i)->bank());
1944 key.set_program((*i)->program ());
1952 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1954 string name = _("alter patch change");
1955 trackview.editor().begin_reversible_command (name);
1956 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1958 if (pc.patch()->program() != new_patch.program()) {
1959 c->change_program (pc.patch (), new_patch.program());
1962 int const new_bank = new_patch.bank();
1963 if (pc.patch()->bank() != new_bank) {
1964 c->change_bank (pc.patch (), new_bank);
1967 _model->apply_command (*trackview.session(), c);
1968 trackview.editor().commit_reversible_command ();
1970 _patch_changes.clear ();
1971 display_patch_changes ();
1975 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
1977 string name = _("alter patch change");
1978 trackview.editor().begin_reversible_command (name);
1979 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1981 if (old_change->time() != new_change.time()) {
1982 c->change_time (old_change, new_change.time());
1985 if (old_change->channel() != new_change.channel()) {
1986 c->change_channel (old_change, new_change.channel());
1989 if (old_change->program() != new_change.program()) {
1990 c->change_program (old_change, new_change.program());
1993 if (old_change->bank() != new_change.bank()) {
1994 c->change_bank (old_change, new_change.bank());
1997 _model->apply_command (*trackview.session(), c);
1998 trackview.editor().commit_reversible_command ();
2000 _patch_changes.clear ();
2001 display_patch_changes ();
2004 /** Add a patch change to the region.
2005 * @param t Time in frames relative to region position
2006 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2007 * MidiTimeAxisView::get_channel_for_add())
2010 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2012 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2013 string name = _("add patch change");
2015 trackview.editor().begin_reversible_command (name);
2016 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2017 c->add (MidiModel::PatchChangePtr (
2018 new Evoral::PatchChange<Evoral::Beats> (
2019 absolute_frames_to_source_beats (_region->position() + t),
2020 mtv->get_channel_for_add(), patch.program(), patch.bank()
2025 _model->apply_command (*trackview.session(), c);
2026 trackview.editor().commit_reversible_command ();
2028 _patch_changes.clear ();
2029 display_patch_changes ();
2033 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2035 trackview.editor().begin_reversible_command (_("move patch change"));
2036 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2037 c->change_time (pc.patch (), t);
2038 _model->apply_command (*trackview.session(), c);
2039 trackview.editor().commit_reversible_command ();
2041 _patch_changes.clear ();
2042 display_patch_changes ();
2046 MidiRegionView::delete_patch_change (PatchChange* pc)
2048 trackview.editor().begin_reversible_command (_("delete patch change"));
2049 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2050 c->remove (pc->patch ());
2051 _model->apply_command (*trackview.session(), c);
2052 trackview.editor().commit_reversible_command ();
2054 _patch_changes.clear ();
2055 display_patch_changes ();
2059 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2061 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2063 key.set_bank(key.bank() + delta);
2065 key.set_program(key.program() + delta);
2067 change_patch_change(patch, key);
2071 MidiRegionView::note_deleted (NoteBase* cne)
2073 if (_entered_note && cne == _entered_note) {
2077 if (_selection.empty()) {
2081 _selection.erase (cne);
2085 MidiRegionView::delete_selection()
2087 if (_selection.empty()) {
2091 if (trackview.editor().drags()->active()) {
2095 start_note_diff_command (_("delete selection"));
2097 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2098 if ((*i)->selected()) {
2099 _note_diff_command->remove((*i)->note());
2107 hide_verbose_cursor ();
2111 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2113 start_note_diff_command (_("delete note"));
2114 _note_diff_command->remove (n);
2117 hide_verbose_cursor ();
2121 MidiRegionView::clear_editor_note_selection ()
2123 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2124 PublicEditor& editor(trackview.editor());
2125 editor.get_selection().clear_midi_notes();
2129 MidiRegionView::clear_selection ()
2131 clear_selection_internal();
2132 PublicEditor& editor(trackview.editor());
2133 editor.get_selection().remove(this);
2137 MidiRegionView::clear_selection_internal ()
2139 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2141 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2142 (*i)->set_selected(false);
2143 (*i)->hide_velocity();
2148 // Clearing selection entirely, ungrab keyboard
2149 Keyboard::magic_widget_drop_focus();
2150 _grabbed_keyboard = false;
2155 MidiRegionView::unique_select(NoteBase* ev)
2157 clear_editor_note_selection();
2158 add_to_selection(ev);
2162 MidiRegionView::select_all_notes ()
2164 clear_editor_note_selection ();
2166 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2167 add_to_selection (*i);
2172 MidiRegionView::select_range (framepos_t start, framepos_t end)
2174 clear_editor_note_selection ();
2176 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2177 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2178 if (t >= start && t <= end) {
2179 add_to_selection (*i);
2185 MidiRegionView::invert_selection ()
2187 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2188 if ((*i)->selected()) {
2189 remove_from_selection(*i);
2191 add_to_selection (*i);
2196 /** Used for selection undo/redo.
2197 The requested notes most likely won't exist in the view until the next model redisplay.
2200 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2203 list<Evoral::event_id_t>::iterator n;
2205 for (n = notes.begin(); n != notes.end(); ++n) {
2206 if ((cne = find_canvas_note(*n)) != 0) {
2207 add_to_selection (cne);
2209 _pending_note_selection.insert(*n);
2215 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2217 bool have_selection = !_selection.empty();
2218 uint8_t low_note = 127;
2219 uint8_t high_note = 0;
2220 MidiModel::Notes& notes (_model->notes());
2221 _optimization_iterator = _events.begin();
2223 if (extend && !have_selection) {
2227 /* scan existing selection to get note range */
2229 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2230 if ((*i)->note()->note() < low_note) {
2231 low_note = (*i)->note()->note();
2233 if ((*i)->note()->note() > high_note) {
2234 high_note = (*i)->note()->note();
2239 clear_editor_note_selection ();
2241 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2242 /* only note previously selected is the one we are
2243 * reselecting. treat this as cancelling the selection.
2250 low_note = min (low_note, notenum);
2251 high_note = max (high_note, notenum);
2254 _no_sound_notes = true;
2256 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2258 boost::shared_ptr<NoteType> note (*n);
2260 bool select = false;
2262 if (((1 << note->channel()) & channel_mask) != 0) {
2264 if ((note->note() >= low_note && note->note() <= high_note)) {
2267 } else if (note->note() == notenum) {
2273 if ((cne = find_canvas_note (note)) != 0) {
2274 // extend is false because we've taken care of it,
2275 // since it extends by time range, not pitch.
2276 note_selected (cne, add, false);
2280 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2284 _no_sound_notes = false;
2288 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2290 MidiModel::Notes& notes (_model->notes());
2291 _optimization_iterator = _events.begin();
2293 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2295 boost::shared_ptr<NoteType> note (*n);
2298 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2299 if ((cne = find_canvas_note (note)) != 0) {
2300 if (cne->selected()) {
2301 note_deselected (cne);
2303 note_selected (cne, true, false);
2311 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2314 clear_editor_note_selection();
2315 add_to_selection (ev);
2320 if (!ev->selected()) {
2321 add_to_selection (ev);
2325 /* find end of latest note selected, select all between that and the start of "ev" */
2327 Evoral::Beats earliest = Evoral::MaxBeats;
2328 Evoral::Beats latest = Evoral::Beats();
2330 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2331 if ((*i)->note()->end_time() > latest) {
2332 latest = (*i)->note()->end_time();
2334 if ((*i)->note()->time() < earliest) {
2335 earliest = (*i)->note()->time();
2339 if (ev->note()->end_time() > latest) {
2340 latest = ev->note()->end_time();
2343 if (ev->note()->time() < earliest) {
2344 earliest = ev->note()->time();
2347 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2349 /* find notes entirely within OR spanning the earliest..latest range */
2351 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2352 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2353 add_to_selection (*i);
2361 MidiRegionView::note_deselected(NoteBase* ev)
2363 remove_from_selection (ev);
2367 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2369 PublicEditor& editor = trackview.editor();
2371 // Convert to local coordinates
2372 const framepos_t p = _region->position();
2373 const double y = midi_view()->y_position();
2374 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2375 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2376 const double y0 = max(0.0, gy0 - y);
2377 const double y1 = max(0.0, gy1 - y);
2379 // TODO: Make this faster by storing the last updated selection rect, and only
2380 // adjusting things that are in the area that appears/disappeared.
2381 // We probably need a tree to be able to find events in O(log(n)) time.
2383 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2384 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2385 // Rectangles intersect
2386 if (!(*i)->selected()) {
2387 add_to_selection (*i);
2389 } else if ((*i)->selected() && !extend) {
2390 // Rectangles do not intersect
2391 remove_from_selection (*i);
2395 typedef RouteTimeAxisView::AutomationTracks ATracks;
2396 typedef std::list<Selectable*> Selectables;
2398 /* Add control points to selection. */
2399 const ATracks& atracks = midi_view()->automation_tracks();
2400 Selectables selectables;
2401 editor.get_selection().clear_points();
2402 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2403 a->second->get_selectables(start, end, gy0, gy1, selectables);
2404 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2405 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2407 editor.get_selection().add(cp);
2410 a->second->set_selected_points(editor.get_selection().points);
2415 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2421 // TODO: Make this faster by storing the last updated selection rect, and only
2422 // adjusting things that are in the area that appears/disappeared.
2423 // We probably need a tree to be able to find events in O(log(n)) time.
2425 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2426 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2427 // within y- (note-) range
2428 if (!(*i)->selected()) {
2429 add_to_selection (*i);
2431 } else if ((*i)->selected() && !extend) {
2432 remove_from_selection (*i);
2438 MidiRegionView::remove_from_selection (NoteBase* ev)
2440 Selection::iterator i = _selection.find (ev);
2442 if (i != _selection.end()) {
2443 _selection.erase (i);
2444 if (_selection.empty() && _grabbed_keyboard) {
2446 Keyboard::magic_widget_drop_focus();
2447 _grabbed_keyboard = false;
2451 ev->set_selected (false);
2452 ev->hide_velocity ();
2454 if (_selection.empty()) {
2455 PublicEditor& editor (trackview.editor());
2456 editor.get_selection().remove (this);
2461 MidiRegionView::add_to_selection (NoteBase* ev)
2463 const bool selection_was_empty = _selection.empty();
2465 if (_selection.insert (ev).second) {
2466 ev->set_selected (true);
2467 start_playing_midi_note ((ev)->note());
2468 if (selection_was_empty && _entered) {
2469 // Grab keyboard for moving notes with arrow keys
2470 Keyboard::magic_widget_grab_focus();
2471 _grabbed_keyboard = true;
2475 if (selection_was_empty) {
2476 PublicEditor& editor (trackview.editor());
2477 editor.get_selection().add (this);
2482 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2484 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2485 PossibleChord to_play;
2486 Evoral::Beats earliest = Evoral::MaxBeats;
2488 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2489 if ((*i)->note()->time() < earliest) {
2490 earliest = (*i)->note()->time();
2494 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2495 if ((*i)->note()->time() == earliest) {
2496 to_play.push_back ((*i)->note());
2498 (*i)->move_event(dx, dy);
2501 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2503 if (to_play.size() > 1) {
2505 PossibleChord shifted;
2507 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2508 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2509 moved_note->set_note (moved_note->note() + cumulative_dy);
2510 shifted.push_back (moved_note);
2513 start_playing_midi_chord (shifted);
2515 } else if (!to_play.empty()) {
2517 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2518 moved_note->set_note (moved_note->note() + cumulative_dy);
2519 start_playing_midi_note (moved_note);
2525 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2527 uint8_t lowest_note_in_selection = 127;
2528 uint8_t highest_note_in_selection = 0;
2529 uint8_t highest_note_difference = 0;
2531 // find highest and lowest notes first
2533 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2534 uint8_t pitch = (*i)->note()->note();
2535 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2536 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2540 cerr << "dnote: " << (int) dnote << endl;
2541 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2542 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2543 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2544 << int(highest_note_in_selection) << endl;
2545 cerr << "selection size: " << _selection.size() << endl;
2546 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2549 // Make sure the note pitch does not exceed the MIDI standard range
2550 if (highest_note_in_selection + dnote > 127) {
2551 highest_note_difference = highest_note_in_selection - 127;
2553 TempoMap& map (trackview.session()->tempo_map());
2555 start_note_diff_command (_("move notes"));
2557 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2559 double const start_qn = _region->quarter_note() - midi_region()->start_beats();
2560 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2561 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2566 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2568 uint8_t original_pitch = (*i)->note()->note();
2569 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2571 // keep notes in standard midi range
2572 clamp_to_0_127(new_pitch);
2574 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2575 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2577 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2582 // care about notes being moved beyond the upper/lower bounds on the canvas
2583 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2584 highest_note_in_selection > midi_stream_view()->highest_note()) {
2585 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2589 /** @param x Pixel relative to the region position.
2590 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2591 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2592 * @return Snapped frame relative to the region position.
2595 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2597 PublicEditor& editor (trackview.editor());
2598 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2601 /** @param x Pixel relative to the region position.
2602 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2603 * @return Snapped pixel relative to the region position.
2606 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2608 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2612 MidiRegionView::get_position_pixels()
2614 framepos_t region_frame = get_position();
2615 return trackview.editor().sample_to_pixel(region_frame);
2619 MidiRegionView::get_end_position_pixels()
2621 framepos_t frame = get_position() + get_duration ();
2622 return trackview.editor().sample_to_pixel(frame);
2626 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2628 /* the time converter will return the frame corresponding to `beats'
2629 relative to the start of the source. The start of the source
2630 is an implied position given by region->position - region->start
2632 const framepos_t source_start = _region->position() - _region->start();
2633 return source_start + _source_relative_time_converter.to (beats);
2637 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2639 /* the `frames' argument needs to be converted into a frame count
2640 relative to the start of the source before being passed in to the
2643 const framepos_t source_start = _region->position() - _region->start();
2644 return _source_relative_time_converter.from (frames - source_start);
2648 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2650 return _region_relative_time_converter.to(beats);
2654 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2656 return _region_relative_time_converter.from(frames);
2660 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2662 return _region_relative_time_converter_double.from(frames);
2666 MidiRegionView::begin_resizing (bool /*at_front*/)
2668 _resize_data.clear();
2670 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2671 Note *note = dynamic_cast<Note*> (*i);
2673 // only insert CanvasNotes into the map
2675 NoteResizeData *resize_data = new NoteResizeData();
2676 resize_data->note = note;
2678 // create a new SimpleRect from the note which will be the resize preview
2679 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2680 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2682 // calculate the colors: get the color settings
2683 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2684 UIConfiguration::instance().color ("midi note selected"),
2687 // make the resize preview notes more transparent and bright
2688 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2690 // calculate color based on note velocity
2691 resize_rect->set_fill_color (UINT_INTERPOLATE(
2692 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2696 resize_rect->set_outline_color (NoteBase::calculate_outline (
2697 UIConfiguration::instance().color ("midi note selected")));
2699 resize_data->resize_rect = resize_rect;
2700 _resize_data.push_back(resize_data);
2705 /** Update resizing notes while user drags.
2706 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2707 * @param at_front which end of the note (true == note on, false == note off)
2708 * @param delta_x change in mouse position since the start of the drag
2709 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2710 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2711 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2712 * as the \a primary note.
2713 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2714 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2717 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2719 TempoMap& tmap (trackview.session()->tempo_map());
2720 bool cursor_set = false;
2721 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2723 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2724 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2725 Note* canvas_note = (*i)->note;
2730 current_x = canvas_note->x0() + delta_x + snap_delta;
2732 current_x = primary->x0() + delta_x + snap_delta;
2736 current_x = canvas_note->x1() + delta_x + snap_delta;
2738 current_x = primary->x1() + delta_x + snap_delta;
2742 if (current_x < 0) {
2743 // This works even with snapping because RegionView::snap_frame_to_frame()
2744 // snaps forward if the snapped sample is before the beginning of the region
2747 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2748 current_x = trackview.editor().sample_to_pixel(_region->length());
2753 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2755 resize_rect->set_x0 (current_x - snap_delta);
2757 resize_rect->set_x1 (canvas_note->x1());
2760 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2762 resize_rect->set_x1 (current_x - snap_delta);
2764 resize_rect->set_x0 (canvas_note->x0());
2768 /* Convert snap delta from pixels to beats. */
2769 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2770 double snap_delta_beats = 0.0;
2773 /* negative beat offsets aren't allowed */
2774 if (snap_delta_samps > 0) {
2775 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2776 } else if (snap_delta_samps < 0) {
2777 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2782 int32_t divisions = 0;
2785 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2786 divisions = trackview.editor().get_grid_music_divisions (0);
2788 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2790 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2791 - midi_region()->beat()) + midi_region()->start_beats();
2793 Evoral::Beats len = Evoral::Beats();
2796 if (beats < canvas_note->note()->end_time()) {
2797 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2798 len += canvas_note->note()->length();
2801 if (beats >= canvas_note->note()->time()) {
2802 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2806 len = std::max(Evoral::Beats(1 / 512.0), len);
2809 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2810 show_verbose_cursor (buf, 0, 0);
2819 /** Finish resizing notes when the user releases the mouse button.
2820 * Parameters the same as for \a update_resizing().
2823 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2825 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2826 TempoMap& tmap (trackview.session()->tempo_map());
2828 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2829 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2831 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2832 Note* canvas_note = (*i)->note;
2833 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2835 /* Get the new x position for this resize, which is in pixels relative
2836 * to the region position.
2843 current_x = canvas_note->x0() + delta_x + snap_delta;
2845 current_x = primary->x0() + delta_x + snap_delta;
2849 current_x = canvas_note->x1() + delta_x + snap_delta;
2851 current_x = primary->x1() + delta_x + snap_delta;
2855 if (current_x < 0) {
2858 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2859 current_x = trackview.editor().sample_to_pixel(_region->length());
2862 /* Convert snap delta from pixels to beats with sign. */
2863 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2864 double snap_delta_beats = 0.0;
2867 if (snap_delta_samps > 0) {
2868 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2869 } else if (snap_delta_samps < 0) {
2870 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2874 uint32_t divisions = 0;
2875 /* Convert the new x position to a frame within the source */
2876 framepos_t current_fr;
2878 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2879 divisions = trackview.editor().get_grid_music_divisions (0);
2881 current_fr = trackview.editor().pixel_to_sample (current_x);
2884 /* and then to beats */
2885 const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
2886 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
2887 const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
2889 if (at_front && x_beats < canvas_note->note()->end_time()) {
2890 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2891 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2892 len += canvas_note->note()->length();
2895 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2900 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2901 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2902 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2909 _resize_data.clear();
2914 MidiRegionView::abort_resizing ()
2916 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2917 delete (*i)->resize_rect;
2921 _resize_data.clear ();
2925 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2927 uint8_t new_velocity;
2930 new_velocity = event->note()->velocity() + velocity;
2931 clamp_to_0_127(new_velocity);
2933 new_velocity = velocity;
2936 event->set_selected (event->selected()); // change color
2938 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2942 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2947 new_note = event->note()->note() + note;
2952 clamp_to_0_127 (new_note);
2953 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2957 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2959 bool change_start = false;
2960 bool change_length = false;
2961 Evoral::Beats new_start;
2962 Evoral::Beats new_length;
2964 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2966 front_delta: if positive - move the start of the note later in time (shortening it)
2967 if negative - move the start of the note earlier in time (lengthening it)
2969 end_delta: if positive - move the end of the note later in time (lengthening it)
2970 if negative - move the end of the note earlier in time (shortening it)
2973 if (!!front_delta) {
2974 if (front_delta < 0) {
2976 if (event->note()->time() < -front_delta) {
2977 new_start = Evoral::Beats();
2979 new_start = event->note()->time() + front_delta; // moves earlier
2982 /* start moved toward zero, so move the end point out to where it used to be.
2983 Note that front_delta is negative, so this increases the length.
2986 new_length = event->note()->length() - front_delta;
2987 change_start = true;
2988 change_length = true;
2992 Evoral::Beats new_pos = event->note()->time() + front_delta;
2994 if (new_pos < event->note()->end_time()) {
2995 new_start = event->note()->time() + front_delta;
2996 /* start moved toward the end, so move the end point back to where it used to be */
2997 new_length = event->note()->length() - front_delta;
2998 change_start = true;
2999 change_length = true;
3006 bool can_change = true;
3007 if (end_delta < 0) {
3008 if (event->note()->length() < -end_delta) {
3014 new_length = event->note()->length() + end_delta;
3015 change_length = true;
3020 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3023 if (change_length) {
3024 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3029 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3031 uint8_t new_channel;
3035 if (event->note()->channel() < -chn) {
3038 new_channel = event->note()->channel() + chn;
3041 new_channel = event->note()->channel() + chn;
3044 new_channel = (uint8_t) chn;
3047 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3051 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3053 Evoral::Beats new_time;
3057 if (event->note()->time() < -delta) {
3058 new_time = Evoral::Beats();
3060 new_time = event->note()->time() + delta;
3063 new_time = event->note()->time() + delta;
3069 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3073 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3075 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3079 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3084 if (_selection.empty()) {
3099 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3100 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3106 start_note_diff_command (_("change velocities"));
3108 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3109 Selection::iterator next = i;
3113 if (i == _selection.begin()) {
3114 change_note_velocity (*i, delta, true);
3115 value = (*i)->note()->velocity() + delta;
3117 change_note_velocity (*i, value, false);
3121 change_note_velocity (*i, delta, true);
3130 if (!_selection.empty()) {
3132 snprintf (buf, sizeof (buf), "Vel %d",
3133 (int) (*_selection.begin())->note()->velocity());
3134 show_verbose_cursor (buf, 10, 10);
3140 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3142 if (_selection.empty()) {
3159 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3161 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3165 if ((int8_t) (*i)->note()->note() + delta > 127) {
3172 start_note_diff_command (_("transpose"));
3174 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3175 Selection::iterator next = i;
3177 change_note_note (*i, delta, true);
3185 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3189 delta = Evoral::Beats(1.0/128.0);
3191 /* grab the current grid distance */
3192 delta = get_grid_beats(_region->position());
3200 start_note_diff_command (_("change note lengths"));
3202 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3203 Selection::iterator next = i;
3206 /* note the negation of the delta for start */
3209 (start ? -delta : Evoral::Beats()),
3210 (end ? delta : Evoral::Beats()));
3219 MidiRegionView::nudge_notes (bool forward, bool fine)
3221 if (_selection.empty()) {
3225 /* pick a note as the point along the timeline to get the nudge distance.
3226 its not necessarily the earliest note, so we may want to pull the notes out
3227 into a vector and sort before using the first one.
3230 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3231 Evoral::Beats delta;
3235 /* non-fine, move by 1 bar regardless of snap */
3236 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3238 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3240 /* grid is off - use nudge distance */
3243 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3244 delta = region_frames_to_region_beats (fabs ((double)distance));
3250 framepos_t next_pos = ref_point;
3253 if (max_framepos - 1 < next_pos) {
3257 if (next_pos == 0) {
3263 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3264 const framecnt_t distance = ref_point - next_pos;
3265 delta = region_frames_to_region_beats (fabs ((double)distance));
3276 start_note_diff_command (_("nudge"));
3278 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3279 Selection::iterator next = i;
3281 change_note_time (*i, delta, true);
3289 MidiRegionView::change_channel(uint8_t channel)
3291 start_note_diff_command(_("change channel"));
3292 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3293 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3301 MidiRegionView::note_entered(NoteBase* ev)
3305 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3307 if (_mouse_state == SelectTouchDragging) {
3309 note_selected (ev, true);
3311 } else if (editor->current_mouse_mode() == MouseContent) {
3313 remove_ghost_note ();
3314 show_verbose_cursor (ev->note ());
3316 } else if (editor->current_mouse_mode() == MouseDraw) {
3318 remove_ghost_note ();
3319 show_verbose_cursor (ev->note ());
3324 MidiRegionView::note_left (NoteBase*)
3328 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3329 (*i)->hide_velocity ();
3332 hide_verbose_cursor ();
3336 MidiRegionView::patch_entered (PatchChange* p)
3339 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3340 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3341 << _("Channel ") << ((int) p->patch()->channel() + 1);
3342 show_verbose_cursor (s.str(), 10, 20);
3343 p->item().grab_focus();
3347 MidiRegionView::patch_left (PatchChange *)
3349 hide_verbose_cursor ();
3350 /* focus will transfer back via the enter-notify event sent to this
3356 MidiRegionView::sysex_entered (SysEx* p)
3360 // need a way to extract text from p->_flag->_text
3362 // show_verbose_cursor (s.str(), 10, 20);
3363 p->item().grab_focus();
3367 MidiRegionView::sysex_left (SysEx *)
3369 hide_verbose_cursor ();
3370 /* focus will transfer back via the enter-notify event sent to this
3376 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3378 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3379 Editing::MouseMode mm = editor->current_mouse_mode();
3380 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3382 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3383 if (can_set_cursor && ctx) {
3384 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3385 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3386 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3387 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3389 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3395 MidiRegionView::get_fill_color() const
3397 const std::string mod_name = (_dragging ? "dragging region" :
3398 trackview.editor().internal_editing() ? "editable region" :
3401 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3402 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3403 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3404 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3406 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3410 MidiRegionView::midi_channel_mode_changed ()
3412 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3413 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3414 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3416 if (mode == ForceChannel) {
3417 mask = 0xFFFF; // Show all notes as active (below)
3420 // Update notes for selection
3421 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3422 (*i)->on_channel_selection_change (mask);
3425 _patch_changes.clear ();
3426 display_patch_changes ();
3430 MidiRegionView::instrument_settings_changed ()
3436 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3438 if (_selection.empty()) {
3442 PublicEditor& editor (trackview.editor());
3446 /* XXX what to do ? */
3450 editor.get_cut_buffer().add (selection_as_cut_buffer());
3458 start_note_diff_command();
3460 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3467 note_diff_remove_note (*i);
3477 MidiRegionView::selection_as_cut_buffer () const
3481 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3482 NoteType* n = (*i)->note().get();
3483 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3486 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3492 /** This method handles undo */
3494 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3496 bool commit = false;
3497 // Paste notes, if available
3498 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3499 if (m != selection.midi_notes.end()) {
3500 ctx.counts.increase_n_notes();
3501 if (!(*m)->empty()) {
3504 paste_internal(pos, ctx.count, ctx.times, **m);
3507 // Paste control points to automation children, if available
3508 typedef RouteTimeAxisView::AutomationTracks ATracks;
3509 const ATracks& atracks = midi_view()->automation_tracks();
3510 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3511 if (a->second->paste(pos, selection, ctx, sub_num)) {
3513 trackview.editor().begin_reversible_command (Operations::paste);
3520 trackview.editor().commit_reversible_command ();
3525 /** This method handles undo */
3527 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3533 start_note_diff_command (_("paste"));
3535 const Evoral::Beats snap_beats = get_grid_beats(pos);
3536 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3537 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3538 const Evoral::Beats duration = last_time - first_time;
3539 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3540 const Evoral::Beats paste_offset = snap_duration * paste_count;
3541 const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
3542 Evoral::Beats end_point = Evoral::Beats();
3544 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3547 duration, pos, _region->position(),
3550 clear_editor_note_selection ();
3552 for (int n = 0; n < (int) times; ++n) {
3554 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3556 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3557 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3558 copied_note->set_id (Evoral::next_event_id());
3560 /* make all newly added notes selected */
3562 note_diff_add_note (copied_note, true);
3563 end_point = copied_note->end_time();
3567 /* if we pasted past the current end of the region, extend the region */
3569 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3570 framepos_t region_end = _region->position() + _region->length() - 1;
3572 if (end_frame > region_end) {
3574 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3576 _region->clear_changes ();
3577 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3578 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3579 trackview.session()->add_command (new StatefulDiffCommand (_region));
3585 struct EventNoteTimeEarlyFirstComparator {
3586 bool operator() (NoteBase* a, NoteBase* b) {
3587 return a->note()->time() < b->note()->time();
3592 MidiRegionView::time_sort_events ()
3594 if (!_sort_needed) {
3598 EventNoteTimeEarlyFirstComparator cmp;
3601 _sort_needed = false;
3605 MidiRegionView::goto_next_note (bool add_to_selection)
3607 bool use_next = false;
3609 if (_events.back()->selected()) {
3613 time_sort_events ();
3615 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3616 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3618 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3619 if ((*i)->selected()) {
3622 } else if (use_next) {
3623 if (channel_mask & (1 << (*i)->note()->channel())) {
3624 if (!add_to_selection) {
3627 note_selected (*i, true, false);
3634 /* use the first one */
3636 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3637 unique_select (_events.front());
3642 MidiRegionView::goto_previous_note (bool add_to_selection)
3644 bool use_next = false;
3646 if (_events.front()->selected()) {
3650 time_sort_events ();
3652 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3653 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3655 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3656 if ((*i)->selected()) {
3659 } else if (use_next) {
3660 if (channel_mask & (1 << (*i)->note()->channel())) {
3661 if (!add_to_selection) {
3664 note_selected (*i, true, false);
3671 /* use the last one */
3673 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3674 unique_select (*(_events.rbegin()));
3679 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3681 bool had_selected = false;
3683 time_sort_events ();
3685 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3686 if ((*i)->selected()) {
3687 selected.insert ((*i)->note());
3688 had_selected = true;
3692 if (allow_all_if_none_selected && !had_selected) {
3693 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3694 selected.insert ((*i)->note());
3700 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3702 x = std::max(0.0, x);
3704 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3709 _note_group->canvas_to_item (x, y);
3711 PublicEditor& editor = trackview.editor ();
3713 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3715 const int32_t divisions = editor.get_grid_music_divisions (state);
3716 const bool shift_snap = midi_view()->note_mode() != Percussive;
3717 const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap);
3719 /* prevent Percussive mode from displaying a ghost hit at region end */
3720 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3721 _ghost_note->hide();
3722 hide_verbose_cursor ();
3726 /* ghost note may have been snapped before region */
3727 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3728 _ghost_note->hide();
3731 } else if (_ghost_note) {
3732 _ghost_note->show();
3735 /* calculate time in beats relative to start of source */
3736 const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3738 _ghost_note->note()->set_time (snapped_beats);
3739 _ghost_note->note()->set_length (length);
3740 _ghost_note->note()->set_note (y_to_note (y));
3741 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3742 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3743 /* the ghost note does not appear in ghost regions, so pass false in here */
3744 update_note (_ghost_note, false);
3746 show_verbose_cursor (_ghost_note->note ());
3750 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3752 remove_ghost_note ();
3754 boost::shared_ptr<NoteType> g (new NoteType);
3755 if (midi_view()->note_mode() == Sustained) {
3756 _ghost_note = new Note (*this, _note_group, g);
3758 _ghost_note = new Hit (*this, _note_group, 10, g);
3760 _ghost_note->set_ignore_events (true);
3761 _ghost_note->set_outline_color (0x000000aa);
3762 update_ghost_note (x, y, state);
3763 _ghost_note->show ();
3765 show_verbose_cursor (_ghost_note->note ());
3769 MidiRegionView::remove_ghost_note ()
3776 MidiRegionView::hide_verbose_cursor ()
3778 trackview.editor().verbose_cursor()->hide ();
3779 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3781 mtv->set_note_highlight (NO_MIDI_NOTE);
3786 MidiRegionView::snap_changed ()
3792 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3796 MidiRegionView::drop_down_keys ()
3798 _mouse_state = None;
3802 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3804 /* XXX: This is dead code. What was it for? */
3806 double note = y_to_note(y);
3808 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3810 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3812 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3813 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3814 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3815 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3820 bool add_mrv_selection = false;
3822 if (_selection.empty()) {
3823 add_mrv_selection = true;
3826 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3827 if (_selection.insert (*i).second) {
3828 (*i)->set_selected (true);
3832 if (add_mrv_selection) {
3833 PublicEditor& editor (trackview.editor());
3834 editor.get_selection().add (this);
3839 MidiRegionView::color_handler ()
3841 RegionView::color_handler ();
3843 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
3844 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
3846 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3847 (*i)->set_selected ((*i)->selected()); // will change color
3850 /* XXX probably more to do here */
3854 MidiRegionView::enable_display (bool yn)
3856 RegionView::enable_display (yn);
3860 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3862 if (_step_edit_cursor == 0) {
3863 ArdourCanvas::Item* const group = get_canvas_group();
3865 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3866 _step_edit_cursor->set_y0 (0);
3867 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3868 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3869 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3872 move_step_edit_cursor (pos);
3873 _step_edit_cursor->show ();
3877 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3879 _step_edit_cursor_position = pos;
3881 if (_step_edit_cursor) {
3882 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3883 _step_edit_cursor->set_x0 (pixel);
3884 set_step_edit_cursor_width (_step_edit_cursor_width);
3889 MidiRegionView::hide_step_edit_cursor ()
3891 if (_step_edit_cursor) {
3892 _step_edit_cursor->hide ();
3897 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3899 _step_edit_cursor_width = beats;
3901 if (_step_edit_cursor) {
3902 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3903 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3904 - region_beats_to_region_frames (_step_edit_cursor_position)));
3908 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3909 * @param w Source that the data will end up in.
3912 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3914 if (!_active_notes) {
3915 /* we aren't actively being recorded to */
3919 boost::shared_ptr<MidiSource> src = w.lock ();
3920 if (!src || src != midi_region()->midi_source()) {
3921 /* recorded data was not destined for our source */
3925 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3927 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3929 framepos_t back = max_framepos;
3931 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3932 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
3934 if (ev.is_channel_event()) {
3935 if (get_channel_mode() == FilterChannels) {
3936 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3942 /* convert from session frames to source beats */
3943 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3944 ev.time() - src->timeline_position() + _region->start());
3946 if (ev.type() == MIDI_CMD_NOTE_ON) {
3947 boost::shared_ptr<NoteType> note (
3948 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3950 add_note (note, true);
3952 /* fix up our note range */
3953 if (ev.note() < _current_range_min) {
3954 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3955 } else if (ev.note() > _current_range_max) {
3956 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3959 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3960 resolve_note (ev.note (), time_beats);
3966 midi_stream_view()->check_record_layers (region(), back);
3970 MidiRegionView::trim_front_starting ()
3972 /* We used to eparent the note group to the region view's parent, so that it didn't change.
3978 MidiRegionView::trim_front_ending ()
3980 if (_region->start() < 0) {
3981 /* Trim drag made start time -ve; fix this */
3982 midi_region()->fix_negative_start ();
3987 MidiRegionView::edit_patch_change (PatchChange* pc)
3989 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3991 int response = d.run();
3994 case Gtk::RESPONSE_ACCEPT:
3996 case Gtk::RESPONSE_REJECT:
3997 delete_patch_change (pc);
4003 change_patch_change (pc->patch(), d.patch ());
4007 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4010 // sysyex object doesn't have a pointer to a sysex event
4011 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4012 // c->remove (sysex->sysex());
4013 // _model->apply_command (*trackview.session(), c);
4015 //_sys_exes.clear ();
4016 // display_sysexes();
4020 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4022 using namespace MIDI::Name;
4025 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4027 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4029 MIDI::Name::PatchPrimaryKey patch_key;
4030 get_patch_key_at(n->time(), n->channel(), patch_key);
4031 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4034 patch_key.program(),
4040 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4042 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4043 (int) n->channel() + 1,
4044 (int) n->velocity());
4050 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4051 uint8_t new_value) const
4053 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4055 mtv->set_note_highlight (new_value);
4058 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4062 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4064 show_verbose_cursor_for_new_note_value(n, n->note());
4068 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4070 trackview.editor().verbose_cursor()->set (text);
4071 trackview.editor().verbose_cursor()->show ();
4072 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4076 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4078 if (_model->notes().empty()) {
4079 return 0x40; // No notes, use default
4082 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4083 if (m == _model->notes().begin()) {
4084 // Before the start, use the velocity of the first note
4085 return (*m)->velocity();
4086 } else if (m == _model->notes().end()) {
4087 // Past the end, use the velocity of the last note
4089 return (*m)->velocity();
4092 // Interpolate velocity of surrounding notes
4093 MidiModel::Notes::const_iterator n = m;
4096 const double frac = ((time - (*n)->time()).to_double() /
4097 ((*m)->time() - (*n)->time()).to_double());
4099 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4102 /** @param p A session framepos.
4103 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4104 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4105 * @return beat duration of p snapped to the grid subdivision underneath it.
4108 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
4110 TempoMap& map (trackview.session()->tempo_map());
4111 double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4113 if (divisions != 0 && shift_snap) {
4114 const double qaf = map.quarter_note_at_frame (p + _region->position());
4115 /* Hack so that we always snap to the note that we are over, instead of snapping
4116 to the next one if we're more than halfway through the one we're over.
4118 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4119 const double rem = eqaf - qaf;
4121 eqaf -= grid_beats.to_double();
4124 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4126 return Evoral::Beats (eqaf - session_start_off);
4130 MidiRegionView::get_channel_mode () const
4132 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4133 return rtav->midi_track()->get_playback_channel_mode();
4137 MidiRegionView::get_selected_channels () const
4139 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4140 return rtav->midi_track()->get_playback_channel_mask();
4145 MidiRegionView::get_grid_beats(framepos_t pos) const
4147 PublicEditor& editor = trackview.editor();
4148 bool success = false;
4149 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4151 beats = Evoral::Beats(1);
4156 MidiRegionView::y_to_note (double y) const
4158 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4159 + _current_range_min;
4163 } else if (n > 127) {
4167 /* min due to rounding and/or off-by-one errors */
4168 return min ((uint8_t) n, _current_range_max);
4172 MidiRegionView::note_to_y(uint8_t note) const
4174 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;