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/MIDIEvent.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()));
129 _note_group->raise_to_top();
130 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
132 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
133 connect_to_diskstream ();
136 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
137 RouteTimeAxisView& tv,
138 boost::shared_ptr<MidiRegion> r,
140 uint32_t basic_color,
142 TimeAxisViewItem::Visibility visibility)
143 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
144 , _current_range_min(0)
145 , _current_range_max(0)
146 , _region_relative_time_converter(r->session().tempo_map(), r->position())
147 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
148 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
150 , _note_group (new ArdourCanvas::Container (group))
151 , _note_diff_command (0)
153 , _step_edit_cursor (0)
154 , _step_edit_cursor_width (1.0)
155 , _step_edit_cursor_position (0.0)
156 , _channel_selection_scoped_note (0)
159 , _sort_needed (true)
160 , _optimization_iterator (_events.end())
162 , _no_sound_notes (false)
163 , _last_display_zoom (0)
166 , _grabbed_keyboard (false)
169 , _mouse_changed_selection (false)
171 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
172 _note_group->raise_to_top();
174 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
176 connect_to_diskstream ();
180 MidiRegionView::parameter_changed (std::string const & p)
182 if (p == "display-first-midi-bank-as-zero") {
183 if (_enable_display) {
189 MidiRegionView::MidiRegionView (const MidiRegionView& other)
190 : sigc::trackable(other)
192 , _current_range_min(0)
193 , _current_range_max(0)
194 , _region_relative_time_converter(other.region_relative_time_converter())
195 , _source_relative_time_converter(other.source_relative_time_converter())
196 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
198 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
199 , _note_diff_command (0)
201 , _step_edit_cursor (0)
202 , _step_edit_cursor_width (1.0)
203 , _step_edit_cursor_position (0.0)
204 , _channel_selection_scoped_note (0)
207 , _sort_needed (true)
208 , _optimization_iterator (_events.end())
210 , _no_sound_notes (false)
211 , _last_display_zoom (0)
214 , _grabbed_keyboard (false)
217 , _mouse_changed_selection (false)
222 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
223 : RegionView (other, boost::shared_ptr<Region> (region))
224 , _current_range_min(0)
225 , _current_range_max(0)
226 , _region_relative_time_converter(other.region_relative_time_converter())
227 , _source_relative_time_converter(other.source_relative_time_converter())
228 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
230 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
231 , _note_diff_command (0)
233 , _step_edit_cursor (0)
234 , _step_edit_cursor_width (1.0)
235 , _step_edit_cursor_position (0.0)
236 , _channel_selection_scoped_note (0)
239 , _sort_needed (true)
240 , _optimization_iterator (_events.end())
242 , _no_sound_notes (false)
243 , _last_display_zoom (0)
246 , _grabbed_keyboard (false)
249 , _mouse_changed_selection (false)
255 MidiRegionView::init (bool wfd)
257 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
260 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
261 midi_region()->midi_source(0)->load_model(lm);
264 _model = midi_region()->midi_source(0)->model();
265 _enable_display = false;
266 fill_color_name = "midi frame base";
268 RegionView::init (false);
270 //set_height (trackview.current_height());
273 region_sync_changed ();
274 region_resized (ARDOUR::bounds_change);
279 _enable_display = true;
282 display_model (_model);
286 reset_width_dependent_items (_pixel_width);
288 group->raise_to_top();
290 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
291 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
294 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
295 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
297 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
298 boost::bind (&MidiRegionView::snap_changed, this),
301 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
302 boost::bind (&MidiRegionView::mouse_mode_changed, this),
305 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
306 connect_to_diskstream ();
310 MidiRegionView::instrument_info () const
312 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
313 return route_ui->route()->instrument_info();
316 const boost::shared_ptr<ARDOUR::MidiRegion>
317 MidiRegionView::midi_region() const
319 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
323 MidiRegionView::connect_to_diskstream ()
325 midi_view()->midi_track()->DataRecorded.connect(
326 *this, invalidator(*this),
327 boost::bind (&MidiRegionView::data_recorded, this, _1),
332 MidiRegionView::canvas_group_event(GdkEvent* ev)
334 if (in_destructor || _recregion) {
338 if (!trackview.editor().internal_editing()) {
339 // not in internal edit mode, so just act like a normal region
340 return RegionView::canvas_group_event (ev);
346 case GDK_ENTER_NOTIFY:
347 _last_event_x = ev->crossing.x;
348 _last_event_y = ev->crossing.y;
349 enter_notify(&ev->crossing);
350 // set entered_regionview (among other things)
351 return RegionView::canvas_group_event (ev);
353 case GDK_LEAVE_NOTIFY:
354 _last_event_x = ev->crossing.x;
355 _last_event_y = ev->crossing.y;
356 leave_notify(&ev->crossing);
357 // reset entered_regionview (among other things)
358 return RegionView::canvas_group_event (ev);
361 if (scroll (&ev->scroll)) {
367 return key_press (&ev->key);
369 case GDK_KEY_RELEASE:
370 return key_release (&ev->key);
372 case GDK_BUTTON_PRESS:
373 return button_press (&ev->button);
375 case GDK_BUTTON_RELEASE:
376 r = button_release (&ev->button);
379 case GDK_MOTION_NOTIFY:
380 _last_event_x = ev->motion.x;
381 _last_event_y = ev->motion.y;
382 return motion (&ev->motion);
388 return RegionView::canvas_group_event (ev);
392 MidiRegionView::enter_notify (GdkEventCrossing* ev)
394 enter_internal (ev->state);
401 MidiRegionView::leave_notify (GdkEventCrossing*)
410 MidiRegionView::mouse_mode_changed ()
412 // Adjust frame colour (become more transparent for internal tools)
416 if (!trackview.editor().internal_editing()) {
417 /* Switched out of internal editing mode while entered.
418 Only necessary for leave as a mouse_mode_change over a region
419 automatically triggers an enter event. */
422 else if (trackview.editor().current_mouse_mode() == MouseContent) {
423 // hide cursor and ghost note after changing to internal edit mode
424 remove_ghost_note ();
426 /* XXX This is problematic as the function is executed for every region
427 and only for one region _entered_note can be true. Still it's
428 necessary as to hide the verbose cursor when we're changing from
429 draw mode to internal edit mode. These lines are the reason why
430 in some situations no verbose cursor is shown when we enter internal
431 edit mode over a note. */
432 if (!_entered_note) {
433 hide_verbose_cursor ();
440 MidiRegionView::enter_internal (uint32_t state)
442 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
443 // Show ghost note under pencil
444 create_ghost_note(_last_event_x, _last_event_y, state);
447 if (!_selection.empty()) {
448 // Grab keyboard for moving selected notes with arrow keys
449 Keyboard::magic_widget_grab_focus();
450 _grabbed_keyboard = true;
453 // Lower frame handles below notes so they don't steal events
454 if (frame_handle_start) {
455 frame_handle_start->lower_to_bottom();
457 if (frame_handle_end) {
458 frame_handle_end->lower_to_bottom();
463 MidiRegionView::leave_internal()
465 hide_verbose_cursor ();
466 remove_ghost_note ();
469 if (_grabbed_keyboard) {
470 Keyboard::magic_widget_drop_focus();
471 _grabbed_keyboard = false;
474 // Raise frame handles above notes so they catch events
475 if (frame_handle_start) {
476 frame_handle_start->raise_to_top();
478 if (frame_handle_end) {
479 frame_handle_end->raise_to_top();
484 MidiRegionView::button_press (GdkEventButton* ev)
486 if (ev->button != 1) {
490 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
491 MouseMode m = editor->current_mouse_mode();
493 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
494 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
497 if (_mouse_state != SelectTouchDragging) {
499 _pressed_button = ev->button;
501 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
503 if (midi_view()->note_mode() == Percussive) {
504 editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
506 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
509 _mouse_state = AddDragging;
510 remove_ghost_note ();
511 hide_verbose_cursor ();
513 _mouse_state = Pressed;
519 _pressed_button = ev->button;
520 _mouse_changed_selection = false;
526 MidiRegionView::button_release (GdkEventButton* ev)
528 double event_x, event_y;
530 if (ev->button != 1) {
537 group->canvas_to_item (event_x, event_y);
540 PublicEditor& editor = trackview.editor ();
542 _press_cursor_ctx.reset();
544 switch (_mouse_state) {
545 case Pressed: // Clicked
547 switch (editor.current_mouse_mode()) {
549 /* no motion occurred - simple click */
550 clear_editor_note_selection ();
551 _mouse_changed_selection = true;
557 _mouse_changed_selection = true;
558 clear_editor_note_selection ();
573 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
574 we don't want one when we were drag-selecting either. */
575 case SelectRectDragging:
576 editor.drags()->end_grab ((GdkEvent *) ev);
585 if (_mouse_changed_selection) {
586 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
587 trackview.editor().commit_reversible_selection_op ();
594 MidiRegionView::motion (GdkEventMotion* ev)
596 PublicEditor& editor = trackview.editor ();
598 if (!_entered_note) {
600 if (_mouse_state == AddDragging) {
602 remove_ghost_note ();
605 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
606 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
607 _mouse_state != AddDragging) {
609 create_ghost_note (ev->x, ev->y, ev->state);
611 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
612 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
614 update_ghost_note (ev->x, ev->y, ev->state);
616 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
618 remove_ghost_note ();
619 hide_verbose_cursor ();
621 } else if (editor.current_mouse_mode() == MouseDraw) {
624 update_ghost_note (ev->x, ev->y, ev->state);
627 create_ghost_note (ev->x, ev->y, ev->state);
632 /* any motion immediately hides velocity text that may have been visible */
634 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
635 (*i)->hide_velocity ();
638 switch (_mouse_state) {
641 if (_pressed_button == 1) {
643 MouseMode m = editor.current_mouse_mode();
645 if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
646 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
647 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
648 clear_editor_note_selection ();
649 _mouse_changed_selection = true;
651 _mouse_state = SelectRectDragging;
653 } else if (m == MouseRange) {
654 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
655 _mouse_state = SelectVerticalDragging;
662 case SelectRectDragging:
663 case SelectVerticalDragging:
665 editor.drags()->motion_handler ((GdkEvent *) ev, false);
668 case SelectTouchDragging:
676 /* we may be dragging some non-note object (eg. patch-change, sysex)
679 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
684 MidiRegionView::scroll (GdkEventScroll* ev)
686 if (_selection.empty()) {
690 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
691 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
692 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
693 * through so that it still works for navigation.
698 hide_verbose_cursor ();
700 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
701 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
702 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
704 if (ev->direction == GDK_SCROLL_UP) {
705 change_velocities (true, fine, false, together);
706 } else if (ev->direction == GDK_SCROLL_DOWN) {
707 change_velocities (false, fine, false, together);
709 /* left, right: we don't use them */
717 MidiRegionView::key_press (GdkEventKey* ev)
719 /* since GTK bindings are generally activated on press, and since
720 detectable auto-repeat is the name of the game and only sends
721 repeated presses, carry out key actions at key press, not release.
723 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
725 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
727 if (_mouse_state != AddDragging) {
728 _mouse_state = SelectTouchDragging;
733 } else if (ev->keyval == GDK_Escape && unmodified) {
734 clear_editor_note_selection ();
737 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
739 bool start = (ev->keyval == GDK_comma);
740 bool end = (ev->keyval == GDK_period);
741 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
742 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
744 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
748 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
750 if (_selection.empty()) {
757 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
759 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
761 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
762 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
764 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
767 trackview.editor().commit_reversible_selection_op();
771 } else if (ev->keyval == GDK_Up) {
773 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
774 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
775 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
777 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
778 change_velocities (true, fine, allow_smush, together);
780 transpose (true, fine, allow_smush);
784 } else if (ev->keyval == GDK_Down) {
786 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
787 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
788 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
790 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
791 change_velocities (false, fine, allow_smush, together);
793 transpose (false, fine, allow_smush);
797 } else if (ev->keyval == GDK_Left) {
799 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
800 nudge_notes (false, fine);
803 } else if (ev->keyval == GDK_Right) {
805 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
806 nudge_notes (true, fine);
809 } else if (ev->keyval == GDK_c && unmodified) {
813 } else if (ev->keyval == GDK_v && unmodified) {
822 MidiRegionView::key_release (GdkEventKey* ev)
824 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
832 MidiRegionView::channel_edit ()
834 if (_selection.empty()) {
838 /* pick a note somewhat at random (since Selection is a set<>) to
839 * provide the "current" channel for the dialog.
842 uint8_t current_channel = (*_selection.begin())->note()->channel ();
843 MidiChannelDialog channel_dialog (current_channel);
844 int ret = channel_dialog.run ();
847 case Gtk::RESPONSE_OK:
853 uint8_t new_channel = channel_dialog.active_channel ();
855 start_note_diff_command (_("channel edit"));
857 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
858 Selection::iterator next = i;
860 change_note_channel (*i, new_channel);
868 MidiRegionView::velocity_edit ()
870 if (_selection.empty()) {
874 /* pick a note somewhat at random (since Selection is a set<>) to
875 * provide the "current" velocity for the dialog.
878 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
879 MidiVelocityDialog velocity_dialog (current_velocity);
880 int ret = velocity_dialog.run ();
883 case Gtk::RESPONSE_OK:
889 uint8_t new_velocity = velocity_dialog.velocity ();
891 start_note_diff_command (_("velocity edit"));
893 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
894 Selection::iterator next = i;
896 change_note_velocity (*i, new_velocity, false);
904 MidiRegionView::show_list_editor ()
907 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
909 _list_editor->present ();
912 /** Add a note to the model, and the view, at a canvas (click) coordinate.
913 * \param t time in frames relative to the position of the region
914 * \param y vertical position in pixels
915 * \param length duration of the note in beats
916 * \param snap_t true to snap t to the grid, otherwise false.
919 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
921 if (length < 2 * DBL_EPSILON) {
925 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
926 MidiStreamView* const view = mtv->midi_view();
927 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
933 // Start of note in frames relative to region start
934 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
935 Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
937 const double note = view->y_to_note(y);
938 const uint8_t chan = mtv->get_channel_for_add();
939 const uint8_t velocity = get_velocity_for_add(beat_time);
941 const boost::shared_ptr<NoteType> new_note(
942 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
944 if (_model->contains (new_note)) {
948 view->update_note_range(new_note->note());
950 start_note_diff_command(_("add note"));
952 clear_editor_note_selection ();
953 note_diff_add_note (new_note, true, false);
957 play_midi_note (new_note);
961 MidiRegionView::clear_events ()
963 // clear selection without signaling
964 clear_selection_internal ();
967 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
968 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
973 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
978 _patch_changes.clear();
980 _optimization_iterator = _events.end();
984 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
988 content_connection.disconnect ();
989 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
990 /* Don't signal as nobody else needs to know until selection has been altered. */
993 if (_enable_display) {
999 MidiRegionView::start_note_diff_command (string name)
1001 if (!_note_diff_command) {
1002 trackview.editor().begin_reversible_command (name);
1003 _note_diff_command = _model->new_note_diff_command (name);
1008 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1010 if (_note_diff_command) {
1011 _note_diff_command->add (note);
1014 _marked_for_selection.insert(note);
1016 if (show_velocity) {
1017 _marked_for_velocity.insert(note);
1022 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1024 if (_note_diff_command && ev->note()) {
1025 _note_diff_command->remove(ev->note());
1030 MidiRegionView::note_diff_add_change (NoteBase* ev,
1031 MidiModel::NoteDiffCommand::Property property,
1034 if (_note_diff_command) {
1035 _note_diff_command->change (ev->note(), property, val);
1040 MidiRegionView::note_diff_add_change (NoteBase* ev,
1041 MidiModel::NoteDiffCommand::Property property,
1044 if (_note_diff_command) {
1045 _note_diff_command->change (ev->note(), property, val);
1050 MidiRegionView::apply_diff (bool as_subcommand)
1053 bool commit = false;
1055 if (!_note_diff_command) {
1059 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1060 // Mark all selected notes for selection when model reloads
1061 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1062 _marked_for_selection.insert((*i)->note());
1066 midi_view()->midi_track()->midi_playlist()->region_edited(
1067 _region, _note_diff_command);
1069 if (as_subcommand) {
1070 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1072 _model->apply_command (*trackview.session(), _note_diff_command);
1076 _note_diff_command = 0;
1078 if (add_or_remove) {
1079 _marked_for_selection.clear();
1082 _marked_for_velocity.clear();
1084 trackview.editor().commit_reversible_command ();
1089 MidiRegionView::abort_command()
1091 delete _note_diff_command;
1092 _note_diff_command = 0;
1093 clear_editor_note_selection();
1097 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1099 if (_optimization_iterator != _events.end()) {
1100 ++_optimization_iterator;
1103 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1104 return *_optimization_iterator;
1107 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1108 if ((*_optimization_iterator)->note() == note) {
1109 return *_optimization_iterator;
1116 /** This version finds any canvas note matching the supplied note. */
1118 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1120 Events::iterator it;
1122 for (it = _events.begin(); it != _events.end(); ++it) {
1123 if ((*it)->note()->id() == id) {
1132 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1134 MidiModel::Notes notes;
1135 _model->get_notes (notes, op, val, chan_mask);
1137 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1138 NoteBase* cne = find_canvas_note (*n);
1146 MidiRegionView::redisplay_model()
1148 if (_active_notes) {
1149 // Currently recording
1150 const framecnt_t zoom = trackview.editor().get_current_zoom();
1151 if (zoom != _last_display_zoom) {
1152 /* Update resolved canvas notes to reflect changes in zoom without
1153 touching model. Leave active notes (with length 0) alone since
1154 they are being extended. */
1155 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1156 if ((*i)->note()->length() > 0) {
1160 _last_display_zoom = zoom;
1169 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1170 (*i)->invalidate ();
1173 MidiModel::ReadLock lock(_model->read_lock());
1175 MidiModel::Notes& notes (_model->notes());
1176 _optimization_iterator = _events.begin();
1178 bool empty_when_starting = _events.empty();
1181 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1183 boost::shared_ptr<NoteType> note (*n);
1186 if (note_in_region_range (note, visible)) {
1188 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1200 cne = add_note (note, visible);
1203 set<Evoral::event_id_t>::iterator it;
1204 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1205 if ((*it) == note->id()) {
1206 add_to_selection (cne);
1212 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1219 /* remove note items that are no longer valid */
1221 if (!empty_when_starting) {
1222 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1223 if (!(*i)->valid ()) {
1225 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1226 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1228 gr->remove_note (*i);
1233 i = _events.erase (i);
1241 _patch_changes.clear();
1245 display_patch_changes ();
1247 _marked_for_selection.clear ();
1248 _marked_for_velocity.clear ();
1249 _pending_note_selection.clear ();
1251 /* we may have caused _events to contain things out of order (e.g. if a note
1252 moved earlier or later). we don't generally need them in time order, but
1253 make a note that a sort is required for those cases that require it.
1256 _sort_needed = true;
1260 MidiRegionView::display_patch_changes ()
1262 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1263 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1265 for (uint8_t i = 0; i < 16; ++i) {
1266 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1270 /** @param active_channel true to display patch changes fully, false to display
1271 * them `greyed-out' (as on an inactive channel)
1274 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1276 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1278 if ((*i)->channel() != channel) {
1282 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1283 add_canvas_patch_change (*i, patch_name, active_channel);
1288 MidiRegionView::display_sysexes()
1290 bool have_periodic_system_messages = false;
1291 bool display_periodic_messages = true;
1293 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1295 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1296 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1297 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1300 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1301 have_periodic_system_messages = true;
1307 if (have_periodic_system_messages) {
1308 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1310 /* get an approximate value for the number of samples per video frame */
1312 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1314 /* if we are zoomed out beyond than the cutoff (i.e. more
1315 * frames per pixel than frames per 4 video frames), don't
1316 * show periodic sysex messages.
1319 if (zoom > (video_frame*4)) {
1320 display_periodic_messages = false;
1324 display_periodic_messages = false;
1327 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1329 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1330 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1332 Evoral::Beats time = (*i)->time();
1335 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1336 if (!display_periodic_messages) {
1344 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1345 str << int((*i)->buffer()[b]);
1346 if (b != (*i)->size() -1) {
1350 string text = str.str();
1352 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1354 double height = midi_stream_view()->contents_height();
1356 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1357 // SysEx canvas object!!!
1359 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1360 new SysEx (*this, _note_group, text, height, x, 1.0));
1362 // Show unless message is beyond the region bounds
1363 if (time - _region->start() >= _region->length() || time < _region->start()) {
1369 _sys_exes.push_back(sysex);
1373 MidiRegionView::~MidiRegionView ()
1375 in_destructor = true;
1377 hide_verbose_cursor ();
1379 delete _list_editor;
1381 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1383 if (_active_notes) {
1390 delete _note_diff_command;
1391 delete _step_edit_cursor;
1395 MidiRegionView::region_resized (const PropertyChange& what_changed)
1397 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1399 if (what_changed.contains (ARDOUR::Properties::position)) {
1400 _region_relative_time_converter.set_origin_b(_region->position());
1401 _region_relative_time_converter_double.set_origin_b(_region->position());
1402 /* reset_width dependent_items() redisplays model */
1406 if (what_changed.contains (ARDOUR::Properties::start) ||
1407 what_changed.contains (ARDOUR::Properties::position)) {
1408 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1410 /* catch end and start trim so we can update the view*/
1411 if (!what_changed.contains (ARDOUR::Properties::start) &&
1412 what_changed.contains (ARDOUR::Properties::length)) {
1413 enable_display (true);
1414 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1415 what_changed.contains (ARDOUR::Properties::length)) {
1416 enable_display (true);
1421 MidiRegionView::reset_width_dependent_items (double pixel_width)
1423 RegionView::reset_width_dependent_items(pixel_width);
1425 if (_enable_display) {
1429 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1430 if ((*x)->canvas_item()->width() >= _pixel_width) {
1437 move_step_edit_cursor (_step_edit_cursor_position);
1438 set_step_edit_cursor_width (_step_edit_cursor_width);
1442 MidiRegionView::set_height (double height)
1444 double old_height = _height;
1445 RegionView::set_height(height);
1447 apply_note_range (midi_stream_view()->lowest_note(),
1448 midi_stream_view()->highest_note(),
1449 height != old_height);
1452 name_text->raise_to_top();
1455 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1456 (*x)->set_height (midi_stream_view()->contents_height());
1459 if (_step_edit_cursor) {
1460 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1465 /** Apply the current note range from the stream view
1466 * by repositioning/hiding notes as necessary
1469 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1471 if (!_enable_display) {
1475 if (!force && _current_range_min == min && _current_range_max == max) {
1479 _current_range_min = min;
1480 _current_range_max = max;
1486 MidiRegionView::add_ghost (TimeAxisView& tv)
1488 double unit_position = _region->position () / samples_per_pixel;
1489 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1490 MidiGhostRegion* ghost;
1492 if (mtv && mtv->midi_view()) {
1493 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1494 to allow having midi notes on top of note lines and waveforms.
1496 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1498 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1501 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1502 ghost->add_note(*i);
1505 ghost->set_colors ();
1506 ghost->set_height ();
1507 ghost->set_duration (_region->length() / samples_per_pixel);
1508 ghosts.push_back (ghost);
1514 /** Begin tracking note state for successive calls to add_event
1517 MidiRegionView::begin_write()
1519 if (_active_notes) {
1520 delete[] _active_notes;
1522 _active_notes = new Note*[128];
1523 for (unsigned i = 0; i < 128; ++i) {
1524 _active_notes[i] = 0;
1529 /** Destroy note state for add_event
1532 MidiRegionView::end_write()
1534 delete[] _active_notes;
1536 _marked_for_selection.clear();
1537 _marked_for_velocity.clear();
1541 /** Resolve an active MIDI note (while recording).
1544 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1546 if (midi_view()->note_mode() != Sustained) {
1550 if (_active_notes && _active_notes[note]) {
1551 /* Set note length so update_note() works. Note this is a local note
1552 for recording, not from a model, so we can safely mess with it. */
1553 _active_notes[note]->note()->set_length(
1554 end_time - _active_notes[note]->note()->time());
1556 /* End time is relative to the region being recorded. */
1557 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1559 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1560 _active_notes[note]->set_outline_all ();
1561 _active_notes[note] = 0;
1566 /** Extend active notes to rightmost edge of region (if length is changed)
1569 MidiRegionView::extend_active_notes()
1571 if (!_active_notes) {
1575 for (unsigned i = 0; i < 128; ++i) {
1576 if (_active_notes[i]) {
1577 _active_notes[i]->set_x1(
1578 trackview.editor().sample_to_pixel(_region->length()));
1584 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1586 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1590 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1592 if (!route_ui || !route_ui->midi_track()) {
1596 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1600 /* NotePlayer deletes itself */
1604 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1606 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1607 start_playing_midi_chord(notes);
1611 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1613 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1617 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1619 if (!route_ui || !route_ui->midi_track()) {
1623 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1625 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1634 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1636 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1638 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1639 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1640 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1642 visible = (note->note() >= _current_range_min) &&
1643 (note->note() <= _current_range_max);
1649 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1653 if ((sus = dynamic_cast<Note*>(note))) {
1654 update_sustained(sus, update_ghost_regions);
1655 } else if ((hit = dynamic_cast<Hit*>(note))) {
1656 update_hit(hit, update_ghost_regions);
1660 /** Update a canvas note's size from its model note.
1661 * @param ev Canvas note to update.
1662 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1665 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1667 TempoMap& map (trackview.session()->tempo_map());
1668 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1669 boost::shared_ptr<NoteType> note = ev->note();
1671 const double session_source_start = _region->quarter_note() - mr->start_beats();
1672 const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1674 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1676 const double y0 = 1 + floor(note_to_y(note->note()));
1679 /* trim note display to not overlap the end of its region */
1680 if (note->length().to_double() > 0.0) {
1681 double note_end_time = note->end_time().to_double();
1683 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1684 note_end_time = mr->start_beats() + mr->length_beats();
1687 const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
1689 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1691 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1694 y1 = y0 + std::max(1., floor(note_height()) - 1);
1696 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1698 if (!note->length()) {
1699 if (_active_notes && note->note() < 128) {
1700 Note* const old_rect = _active_notes[note->note()];
1702 /* There is an active note on this key, so we have a stuck
1703 note. Finish the old rectangle here. */
1704 old_rect->set_x1 (x1);
1705 old_rect->set_outline_all ();
1707 _active_notes[note->note()] = ev;
1709 /* outline all but right edge */
1710 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1711 ArdourCanvas::Rectangle::TOP|
1712 ArdourCanvas::Rectangle::LEFT|
1713 ArdourCanvas::Rectangle::BOTTOM));
1715 /* outline all edges */
1716 ev->set_outline_all ();
1719 // Update color in case velocity has changed
1720 const uint32_t base_col = ev->base_color();
1721 ev->set_fill_color(base_col);
1722 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1724 if (update_ghost_regions) {
1725 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1726 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1728 gr->update_note (ev);
1735 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1737 boost::shared_ptr<NoteType> note = ev->note();
1739 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1740 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1742 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1743 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1744 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1746 // see DnD note in MidiRegionView::apply_note_range() above
1747 if (y <= 0 || y >= _height) {
1753 ev->set_position (ArdourCanvas::Duple (x, y));
1754 ev->set_height (diamond_size);
1756 // Update color in case velocity has changed
1757 const uint32_t base_col = ev->base_color();
1758 ev->set_fill_color(base_col);
1759 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1761 if (update_ghost_regions) {
1762 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1763 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1765 gr->update_hit (ev);
1771 /** Add a MIDI note to the view (with length).
1773 * If in sustained mode, notes with length 0 will be considered active
1774 * notes, and resolve_note should be called when the corresponding note off
1775 * event arrives, to properly display the note.
1778 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1780 NoteBase* event = 0;
1782 if (midi_view()->note_mode() == Sustained) {
1784 Note* ev_rect = new Note (*this, _note_group, note);
1786 update_sustained (ev_rect);
1790 } else if (midi_view()->note_mode() == Percussive) {
1792 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1794 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1796 update_hit (ev_diamond);
1805 MidiGhostRegion* gr;
1807 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1808 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1809 gr->add_note(event);
1813 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1814 note_selected(event, true);
1817 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1818 event->show_velocity();
1821 event->on_channel_selection_change (get_selected_channels());
1822 _events.push_back(event);
1831 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1832 MidiStreamView* const view = mtv->midi_view();
1834 view->update_note_range (note->note());
1839 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1840 Evoral::Beats pos, Evoral::Beats len)
1842 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1844 /* potentially extend region to hold new note */
1846 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1847 framepos_t region_end = _region->last_frame();
1849 if (end_frame > region_end) {
1850 /* XX sets length in beats from audio space. make musical */
1851 _region->set_length (end_frame - _region->position(), 0);
1854 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1855 MidiStreamView* const view = mtv->midi_view();
1857 view->update_note_range(new_note->note());
1859 _marked_for_selection.clear ();
1861 start_note_diff_command (_("step add"));
1863 clear_editor_note_selection ();
1864 note_diff_add_note (new_note, true, false);
1868 // last_step_edit_note = new_note;
1872 MidiRegionView::step_sustain (Evoral::Beats beats)
1874 change_note_lengths (false, false, beats, false, true);
1877 /** Add a new patch change flag to the canvas.
1878 * @param patch the patch change to add
1879 * @param the text to display in the flag
1880 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1883 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1885 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1886 const double x = trackview.editor().sample_to_pixel (region_frames);
1888 double const height = midi_stream_view()->contents_height();
1890 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1891 // so we need to do something more sophisticated to keep its color
1892 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1895 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1896 new PatchChange(*this, group,
1903 if (patch_change->item().width() < _pixel_width) {
1904 // Show unless patch change is beyond the region bounds
1905 if (region_frames < 0 || region_frames >= _region->length()) {
1906 patch_change->hide();
1908 patch_change->show();
1911 patch_change->hide ();
1914 _patch_changes.push_back (patch_change);
1917 MIDI::Name::PatchPrimaryKey
1918 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1920 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1923 /// Return true iff @p pc applies to the given time on the given channel.
1925 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1927 return pc->time() <= time && pc->channel() == channel;
1931 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1933 // The earliest event not before time
1934 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1936 // Go backwards until we find the latest PC for this channel, or the start
1937 while (i != _model->patch_changes().begin() &&
1938 (i == _model->patch_changes().end() ||
1939 !patch_applies(*i, time, channel))) {
1943 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1944 key.set_bank((*i)->bank());
1945 key.set_program((*i)->program ());
1953 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1955 string name = _("alter patch change");
1956 trackview.editor().begin_reversible_command (name);
1957 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1959 if (pc.patch()->program() != new_patch.program()) {
1960 c->change_program (pc.patch (), new_patch.program());
1963 int const new_bank = new_patch.bank();
1964 if (pc.patch()->bank() != new_bank) {
1965 c->change_bank (pc.patch (), new_bank);
1968 _model->apply_command (*trackview.session(), c);
1969 trackview.editor().commit_reversible_command ();
1971 _patch_changes.clear ();
1972 display_patch_changes ();
1976 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
1978 string name = _("alter patch change");
1979 trackview.editor().begin_reversible_command (name);
1980 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1982 if (old_change->time() != new_change.time()) {
1983 c->change_time (old_change, new_change.time());
1986 if (old_change->channel() != new_change.channel()) {
1987 c->change_channel (old_change, new_change.channel());
1990 if (old_change->program() != new_change.program()) {
1991 c->change_program (old_change, new_change.program());
1994 if (old_change->bank() != new_change.bank()) {
1995 c->change_bank (old_change, new_change.bank());
1998 _model->apply_command (*trackview.session(), c);
1999 trackview.editor().commit_reversible_command ();
2001 _patch_changes.clear ();
2002 display_patch_changes ();
2005 /** Add a patch change to the region.
2006 * @param t Time in frames relative to region position
2007 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2008 * MidiTimeAxisView::get_channel_for_add())
2011 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2013 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2014 string name = _("add patch change");
2016 trackview.editor().begin_reversible_command (name);
2017 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2018 c->add (MidiModel::PatchChangePtr (
2019 new Evoral::PatchChange<Evoral::Beats> (
2020 absolute_frames_to_source_beats (_region->position() + t),
2021 mtv->get_channel_for_add(), patch.program(), patch.bank()
2026 _model->apply_command (*trackview.session(), c);
2027 trackview.editor().commit_reversible_command ();
2029 _patch_changes.clear ();
2030 display_patch_changes ();
2034 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2036 trackview.editor().begin_reversible_command (_("move patch change"));
2037 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2038 c->change_time (pc.patch (), t);
2039 _model->apply_command (*trackview.session(), c);
2040 trackview.editor().commit_reversible_command ();
2042 _patch_changes.clear ();
2043 display_patch_changes ();
2047 MidiRegionView::delete_patch_change (PatchChange* pc)
2049 trackview.editor().begin_reversible_command (_("delete patch change"));
2050 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2051 c->remove (pc->patch ());
2052 _model->apply_command (*trackview.session(), c);
2053 trackview.editor().commit_reversible_command ();
2055 _patch_changes.clear ();
2056 display_patch_changes ();
2060 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2062 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2064 key.set_bank(key.bank() + delta);
2066 key.set_program(key.program() + delta);
2068 change_patch_change(patch, key);
2072 MidiRegionView::note_deleted (NoteBase* cne)
2074 if (_entered_note && cne == _entered_note) {
2078 if (_selection.empty()) {
2082 _selection.erase (cne);
2086 MidiRegionView::delete_selection()
2088 if (_selection.empty()) {
2092 if (trackview.editor().drags()->active()) {
2096 start_note_diff_command (_("delete selection"));
2098 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2099 if ((*i)->selected()) {
2100 _note_diff_command->remove((*i)->note());
2108 hide_verbose_cursor ();
2112 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2114 start_note_diff_command (_("delete note"));
2115 _note_diff_command->remove (n);
2118 hide_verbose_cursor ();
2122 MidiRegionView::clear_editor_note_selection ()
2124 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2125 PublicEditor& editor(trackview.editor());
2126 editor.get_selection().clear_midi_notes();
2130 MidiRegionView::clear_selection ()
2132 clear_selection_internal();
2133 PublicEditor& editor(trackview.editor());
2134 editor.get_selection().remove(this);
2138 MidiRegionView::clear_selection_internal ()
2140 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2142 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2143 (*i)->set_selected(false);
2144 (*i)->hide_velocity();
2149 // Clearing selection entirely, ungrab keyboard
2150 Keyboard::magic_widget_drop_focus();
2151 _grabbed_keyboard = false;
2156 MidiRegionView::unique_select(NoteBase* ev)
2158 clear_editor_note_selection();
2159 add_to_selection(ev);
2163 MidiRegionView::select_all_notes ()
2165 clear_editor_note_selection ();
2167 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2168 add_to_selection (*i);
2173 MidiRegionView::select_range (framepos_t start, framepos_t end)
2175 clear_editor_note_selection ();
2177 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2178 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2179 if (t >= start && t <= end) {
2180 add_to_selection (*i);
2186 MidiRegionView::invert_selection ()
2188 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2189 if ((*i)->selected()) {
2190 remove_from_selection(*i);
2192 add_to_selection (*i);
2197 /** Used for selection undo/redo.
2198 The requested notes most likely won't exist in the view until the next model redisplay.
2201 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2204 list<Evoral::event_id_t>::iterator n;
2206 for (n = notes.begin(); n != notes.end(); ++n) {
2207 if ((cne = find_canvas_note(*n)) != 0) {
2208 add_to_selection (cne);
2210 _pending_note_selection.insert(*n);
2216 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2218 bool have_selection = !_selection.empty();
2219 uint8_t low_note = 127;
2220 uint8_t high_note = 0;
2221 MidiModel::Notes& notes (_model->notes());
2222 _optimization_iterator = _events.begin();
2224 if (extend && !have_selection) {
2228 /* scan existing selection to get note range */
2230 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2231 if ((*i)->note()->note() < low_note) {
2232 low_note = (*i)->note()->note();
2234 if ((*i)->note()->note() > high_note) {
2235 high_note = (*i)->note()->note();
2240 clear_editor_note_selection ();
2242 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2243 /* only note previously selected is the one we are
2244 * reselecting. treat this as cancelling the selection.
2251 low_note = min (low_note, notenum);
2252 high_note = max (high_note, notenum);
2255 _no_sound_notes = true;
2257 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2259 boost::shared_ptr<NoteType> note (*n);
2261 bool select = false;
2263 if (((1 << note->channel()) & channel_mask) != 0) {
2265 if ((note->note() >= low_note && note->note() <= high_note)) {
2268 } else if (note->note() == notenum) {
2274 if ((cne = find_canvas_note (note)) != 0) {
2275 // extend is false because we've taken care of it,
2276 // since it extends by time range, not pitch.
2277 note_selected (cne, add, false);
2281 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2285 _no_sound_notes = false;
2289 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2291 MidiModel::Notes& notes (_model->notes());
2292 _optimization_iterator = _events.begin();
2294 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2296 boost::shared_ptr<NoteType> note (*n);
2299 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2300 if ((cne = find_canvas_note (note)) != 0) {
2301 if (cne->selected()) {
2302 note_deselected (cne);
2304 note_selected (cne, true, false);
2312 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2315 clear_editor_note_selection();
2316 add_to_selection (ev);
2321 if (!ev->selected()) {
2322 add_to_selection (ev);
2326 /* find end of latest note selected, select all between that and the start of "ev" */
2328 Evoral::Beats earliest = Evoral::MaxBeats;
2329 Evoral::Beats latest = Evoral::Beats();
2331 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2332 if ((*i)->note()->end_time() > latest) {
2333 latest = (*i)->note()->end_time();
2335 if ((*i)->note()->time() < earliest) {
2336 earliest = (*i)->note()->time();
2340 if (ev->note()->end_time() > latest) {
2341 latest = ev->note()->end_time();
2344 if (ev->note()->time() < earliest) {
2345 earliest = ev->note()->time();
2348 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2350 /* find notes entirely within OR spanning the earliest..latest range */
2352 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2353 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2354 add_to_selection (*i);
2362 MidiRegionView::note_deselected(NoteBase* ev)
2364 remove_from_selection (ev);
2368 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2370 PublicEditor& editor = trackview.editor();
2372 // Convert to local coordinates
2373 const framepos_t p = _region->position();
2374 const double y = midi_view()->y_position();
2375 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2376 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2377 const double y0 = max(0.0, gy0 - y);
2378 const double y1 = max(0.0, gy1 - y);
2380 // TODO: Make this faster by storing the last updated selection rect, and only
2381 // adjusting things that are in the area that appears/disappeared.
2382 // We probably need a tree to be able to find events in O(log(n)) time.
2384 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2385 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2386 // Rectangles intersect
2387 if (!(*i)->selected()) {
2388 add_to_selection (*i);
2390 } else if ((*i)->selected() && !extend) {
2391 // Rectangles do not intersect
2392 remove_from_selection (*i);
2396 typedef RouteTimeAxisView::AutomationTracks ATracks;
2397 typedef std::list<Selectable*> Selectables;
2399 /* Add control points to selection. */
2400 const ATracks& atracks = midi_view()->automation_tracks();
2401 Selectables selectables;
2402 editor.get_selection().clear_points();
2403 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2404 a->second->get_selectables(start, end, gy0, gy1, selectables);
2405 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2406 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2408 editor.get_selection().add(cp);
2411 a->second->set_selected_points(editor.get_selection().points);
2416 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2422 // TODO: Make this faster by storing the last updated selection rect, and only
2423 // adjusting things that are in the area that appears/disappeared.
2424 // We probably need a tree to be able to find events in O(log(n)) time.
2426 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2427 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2428 // within y- (note-) range
2429 if (!(*i)->selected()) {
2430 add_to_selection (*i);
2432 } else if ((*i)->selected() && !extend) {
2433 remove_from_selection (*i);
2439 MidiRegionView::remove_from_selection (NoteBase* ev)
2441 Selection::iterator i = _selection.find (ev);
2443 if (i != _selection.end()) {
2444 _selection.erase (i);
2445 if (_selection.empty() && _grabbed_keyboard) {
2447 Keyboard::magic_widget_drop_focus();
2448 _grabbed_keyboard = false;
2452 ev->set_selected (false);
2453 ev->hide_velocity ();
2455 if (_selection.empty()) {
2456 PublicEditor& editor (trackview.editor());
2457 editor.get_selection().remove (this);
2462 MidiRegionView::add_to_selection (NoteBase* ev)
2464 const bool selection_was_empty = _selection.empty();
2466 if (_selection.insert (ev).second) {
2467 ev->set_selected (true);
2468 start_playing_midi_note ((ev)->note());
2469 if (selection_was_empty && _entered) {
2470 // Grab keyboard for moving notes with arrow keys
2471 Keyboard::magic_widget_grab_focus();
2472 _grabbed_keyboard = true;
2476 if (selection_was_empty) {
2477 PublicEditor& editor (trackview.editor());
2478 editor.get_selection().add (this);
2483 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2485 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2486 PossibleChord to_play;
2487 Evoral::Beats earliest = Evoral::MaxBeats;
2489 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2490 if ((*i)->note()->time() < earliest) {
2491 earliest = (*i)->note()->time();
2495 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2496 if ((*i)->note()->time() == earliest) {
2497 to_play.push_back ((*i)->note());
2499 (*i)->move_event(dx, dy);
2502 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2504 if (to_play.size() > 1) {
2506 PossibleChord shifted;
2508 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2509 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2510 moved_note->set_note (moved_note->note() + cumulative_dy);
2511 shifted.push_back (moved_note);
2514 start_playing_midi_chord (shifted);
2516 } else if (!to_play.empty()) {
2518 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2519 moved_note->set_note (moved_note->note() + cumulative_dy);
2520 start_playing_midi_note (moved_note);
2526 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2528 uint8_t lowest_note_in_selection = 127;
2529 uint8_t highest_note_in_selection = 0;
2530 uint8_t highest_note_difference = 0;
2532 // find highest and lowest notes first
2534 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2535 uint8_t pitch = (*i)->note()->note();
2536 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2537 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2541 cerr << "dnote: " << (int) dnote << endl;
2542 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2543 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2544 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2545 << int(highest_note_in_selection) << endl;
2546 cerr << "selection size: " << _selection.size() << endl;
2547 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2550 // Make sure the note pitch does not exceed the MIDI standard range
2551 if (highest_note_in_selection + dnote > 127) {
2552 highest_note_difference = highest_note_in_selection - 127;
2554 TempoMap& map (trackview.session()->tempo_map());
2556 start_note_diff_command (_("move notes"));
2558 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2560 double const start_qn = _region->quarter_note() - midi_region()->start_beats();
2561 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2562 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2567 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2569 uint8_t original_pitch = (*i)->note()->note();
2570 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2572 // keep notes in standard midi range
2573 clamp_to_0_127(new_pitch);
2575 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2576 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2578 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2583 // care about notes being moved beyond the upper/lower bounds on the canvas
2584 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2585 highest_note_in_selection > midi_stream_view()->highest_note()) {
2586 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2590 /** @param x Pixel relative to the region position.
2591 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2592 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2593 * @return Snapped frame relative to the region position.
2596 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2598 PublicEditor& editor (trackview.editor());
2599 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2602 /** @param x Pixel relative to the region position.
2603 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2604 * @return Snapped pixel relative to the region position.
2607 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2609 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2613 MidiRegionView::get_position_pixels()
2615 framepos_t region_frame = get_position();
2616 return trackview.editor().sample_to_pixel(region_frame);
2620 MidiRegionView::get_end_position_pixels()
2622 framepos_t frame = get_position() + get_duration ();
2623 return trackview.editor().sample_to_pixel(frame);
2627 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2629 /* the time converter will return the frame corresponding to `beats'
2630 relative to the start of the source. The start of the source
2631 is an implied position given by region->position - region->start
2633 const framepos_t source_start = _region->position() - _region->start();
2634 return source_start + _source_relative_time_converter.to (beats);
2638 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2640 /* the `frames' argument needs to be converted into a frame count
2641 relative to the start of the source before being passed in to the
2644 const framepos_t source_start = _region->position() - _region->start();
2645 return _source_relative_time_converter.from (frames - source_start);
2649 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2651 return _region_relative_time_converter.to(beats);
2655 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2657 return _region_relative_time_converter.from(frames);
2661 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2663 return _region_relative_time_converter_double.from(frames);
2667 MidiRegionView::begin_resizing (bool /*at_front*/)
2669 _resize_data.clear();
2671 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2672 Note *note = dynamic_cast<Note*> (*i);
2674 // only insert CanvasNotes into the map
2676 NoteResizeData *resize_data = new NoteResizeData();
2677 resize_data->note = note;
2679 // create a new SimpleRect from the note which will be the resize preview
2680 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2681 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2683 // calculate the colors: get the color settings
2684 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2685 UIConfiguration::instance().color ("midi note selected"),
2688 // make the resize preview notes more transparent and bright
2689 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2691 // calculate color based on note velocity
2692 resize_rect->set_fill_color (UINT_INTERPOLATE(
2693 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2697 resize_rect->set_outline_color (NoteBase::calculate_outline (
2698 UIConfiguration::instance().color ("midi note selected")));
2700 resize_data->resize_rect = resize_rect;
2701 _resize_data.push_back(resize_data);
2706 /** Update resizing notes while user drags.
2707 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2708 * @param at_front which end of the note (true == note on, false == note off)
2709 * @param delta_x change in mouse position since the start of the drag
2710 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2711 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2712 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2713 * as the \a primary note.
2714 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2715 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2718 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2720 TempoMap& tmap (trackview.session()->tempo_map());
2721 bool cursor_set = false;
2722 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2724 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2725 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2726 Note* canvas_note = (*i)->note;
2731 current_x = canvas_note->x0() + delta_x + snap_delta;
2733 current_x = primary->x0() + delta_x + snap_delta;
2737 current_x = canvas_note->x1() + delta_x + snap_delta;
2739 current_x = primary->x1() + delta_x + snap_delta;
2743 if (current_x < 0) {
2744 // This works even with snapping because RegionView::snap_frame_to_frame()
2745 // snaps forward if the snapped sample is before the beginning of the region
2748 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2749 current_x = trackview.editor().sample_to_pixel(_region->length());
2754 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2756 resize_rect->set_x0 (current_x - snap_delta);
2758 resize_rect->set_x1 (canvas_note->x1());
2761 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2763 resize_rect->set_x1 (current_x - snap_delta);
2765 resize_rect->set_x0 (canvas_note->x0());
2769 /* Convert snap delta from pixels to beats. */
2770 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2771 double snap_delta_beats = 0.0;
2774 /* negative beat offsets aren't allowed */
2775 if (snap_delta_samps > 0) {
2776 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2777 } else if (snap_delta_samps < 0) {
2778 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2783 int32_t divisions = 0;
2786 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2787 divisions = trackview.editor().get_grid_music_divisions (0);
2789 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2791 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2792 - midi_region()->beat()) + midi_region()->start_beats();
2794 Evoral::Beats len = Evoral::Beats();
2797 if (beats < canvas_note->note()->end_time()) {
2798 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2799 len += canvas_note->note()->length();
2802 if (beats >= canvas_note->note()->time()) {
2803 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2807 len = std::max(Evoral::Beats(1 / 512.0), len);
2810 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2811 show_verbose_cursor (buf, 0, 0);
2820 /** Finish resizing notes when the user releases the mouse button.
2821 * Parameters the same as for \a update_resizing().
2824 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2826 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2827 TempoMap& tmap (trackview.session()->tempo_map());
2829 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2830 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2832 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2833 Note* canvas_note = (*i)->note;
2834 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2836 /* Get the new x position for this resize, which is in pixels relative
2837 * to the region position.
2844 current_x = canvas_note->x0() + delta_x + snap_delta;
2846 current_x = primary->x0() + delta_x + snap_delta;
2850 current_x = canvas_note->x1() + delta_x + snap_delta;
2852 current_x = primary->x1() + delta_x + snap_delta;
2856 if (current_x < 0) {
2859 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2860 current_x = trackview.editor().sample_to_pixel(_region->length());
2863 /* Convert snap delta from pixels to beats with sign. */
2864 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2865 double snap_delta_beats = 0.0;
2868 if (snap_delta_samps > 0) {
2869 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2870 } else if (snap_delta_samps < 0) {
2871 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2875 uint32_t divisions = 0;
2876 /* Convert the new x position to a frame within the source */
2877 framepos_t current_fr;
2879 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2880 divisions = trackview.editor().get_grid_music_divisions (0);
2882 current_fr = trackview.editor().pixel_to_sample (current_x);
2885 /* and then to beats */
2886 const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
2887 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
2888 const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
2890 if (at_front && x_beats < canvas_note->note()->end_time()) {
2891 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2892 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2893 len += canvas_note->note()->length();
2896 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2901 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2902 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2903 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2910 _resize_data.clear();
2915 MidiRegionView::abort_resizing ()
2917 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2918 delete (*i)->resize_rect;
2922 _resize_data.clear ();
2926 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2928 uint8_t new_velocity;
2931 new_velocity = event->note()->velocity() + velocity;
2932 clamp_to_0_127(new_velocity);
2934 new_velocity = velocity;
2937 event->set_selected (event->selected()); // change color
2939 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2943 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2948 new_note = event->note()->note() + note;
2953 clamp_to_0_127 (new_note);
2954 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2958 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2960 bool change_start = false;
2961 bool change_length = false;
2962 Evoral::Beats new_start;
2963 Evoral::Beats new_length;
2965 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2967 front_delta: if positive - move the start of the note later in time (shortening it)
2968 if negative - move the start of the note earlier in time (lengthening it)
2970 end_delta: if positive - move the end of the note later in time (lengthening it)
2971 if negative - move the end of the note earlier in time (shortening it)
2974 if (!!front_delta) {
2975 if (front_delta < 0) {
2977 if (event->note()->time() < -front_delta) {
2978 new_start = Evoral::Beats();
2980 new_start = event->note()->time() + front_delta; // moves earlier
2983 /* start moved toward zero, so move the end point out to where it used to be.
2984 Note that front_delta is negative, so this increases the length.
2987 new_length = event->note()->length() - front_delta;
2988 change_start = true;
2989 change_length = true;
2993 Evoral::Beats new_pos = event->note()->time() + front_delta;
2995 if (new_pos < event->note()->end_time()) {
2996 new_start = event->note()->time() + front_delta;
2997 /* start moved toward the end, so move the end point back to where it used to be */
2998 new_length = event->note()->length() - front_delta;
2999 change_start = true;
3000 change_length = true;
3007 bool can_change = true;
3008 if (end_delta < 0) {
3009 if (event->note()->length() < -end_delta) {
3015 new_length = event->note()->length() + end_delta;
3016 change_length = true;
3021 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3024 if (change_length) {
3025 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3030 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3032 uint8_t new_channel;
3036 if (event->note()->channel() < -chn) {
3039 new_channel = event->note()->channel() + chn;
3042 new_channel = event->note()->channel() + chn;
3045 new_channel = (uint8_t) chn;
3048 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3052 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3054 Evoral::Beats new_time;
3058 if (event->note()->time() < -delta) {
3059 new_time = Evoral::Beats();
3061 new_time = event->note()->time() + delta;
3064 new_time = event->note()->time() + delta;
3070 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3074 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3076 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3080 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3085 if (_selection.empty()) {
3100 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3101 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3107 start_note_diff_command (_("change velocities"));
3109 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3110 Selection::iterator next = i;
3114 if (i == _selection.begin()) {
3115 change_note_velocity (*i, delta, true);
3116 value = (*i)->note()->velocity() + delta;
3118 change_note_velocity (*i, value, false);
3122 change_note_velocity (*i, delta, true);
3131 if (!_selection.empty()) {
3133 snprintf (buf, sizeof (buf), "Vel %d",
3134 (int) (*_selection.begin())->note()->velocity());
3135 show_verbose_cursor (buf, 10, 10);
3141 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3143 if (_selection.empty()) {
3160 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3162 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3166 if ((int8_t) (*i)->note()->note() + delta > 127) {
3173 start_note_diff_command (_("transpose"));
3175 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3176 Selection::iterator next = i;
3178 change_note_note (*i, delta, true);
3186 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3190 delta = Evoral::Beats(1.0/128.0);
3192 /* grab the current grid distance */
3193 delta = get_grid_beats(_region->position());
3201 start_note_diff_command (_("change note lengths"));
3203 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3204 Selection::iterator next = i;
3207 /* note the negation of the delta for start */
3210 (start ? -delta : Evoral::Beats()),
3211 (end ? delta : Evoral::Beats()));
3220 MidiRegionView::nudge_notes (bool forward, bool fine)
3222 if (_selection.empty()) {
3226 /* pick a note as the point along the timeline to get the nudge distance.
3227 its not necessarily the earliest note, so we may want to pull the notes out
3228 into a vector and sort before using the first one.
3231 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3232 Evoral::Beats delta;
3236 /* non-fine, move by 1 bar regardless of snap */
3237 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3239 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3241 /* grid is off - use nudge distance */
3244 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3245 delta = region_frames_to_region_beats (fabs ((double)distance));
3251 framepos_t next_pos = ref_point;
3254 if (max_framepos - 1 < next_pos) {
3258 if (next_pos == 0) {
3264 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3265 const framecnt_t distance = ref_point - next_pos;
3266 delta = region_frames_to_region_beats (fabs ((double)distance));
3277 start_note_diff_command (_("nudge"));
3279 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3280 Selection::iterator next = i;
3282 change_note_time (*i, delta, true);
3290 MidiRegionView::change_channel(uint8_t channel)
3292 start_note_diff_command(_("change channel"));
3293 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3294 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3302 MidiRegionView::note_entered(NoteBase* ev)
3306 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3308 if (_mouse_state == SelectTouchDragging) {
3310 note_selected (ev, true);
3312 } else if (editor->current_mouse_mode() == MouseContent) {
3314 remove_ghost_note ();
3315 show_verbose_cursor (ev->note ());
3317 } else if (editor->current_mouse_mode() == MouseDraw) {
3319 remove_ghost_note ();
3320 show_verbose_cursor (ev->note ());
3325 MidiRegionView::note_left (NoteBase*)
3329 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3330 (*i)->hide_velocity ();
3333 hide_verbose_cursor ();
3337 MidiRegionView::patch_entered (PatchChange* p)
3340 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3341 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3342 << _("Channel ") << ((int) p->patch()->channel() + 1);
3343 show_verbose_cursor (s.str(), 10, 20);
3344 p->item().grab_focus();
3348 MidiRegionView::patch_left (PatchChange *)
3350 hide_verbose_cursor ();
3351 /* focus will transfer back via the enter-notify event sent to this
3357 MidiRegionView::sysex_entered (SysEx* p)
3361 // need a way to extract text from p->_flag->_text
3363 // show_verbose_cursor (s.str(), 10, 20);
3364 p->item().grab_focus();
3368 MidiRegionView::sysex_left (SysEx *)
3370 hide_verbose_cursor ();
3371 /* focus will transfer back via the enter-notify event sent to this
3377 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3379 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3380 Editing::MouseMode mm = editor->current_mouse_mode();
3381 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3383 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3384 if (can_set_cursor && ctx) {
3385 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3386 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3387 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3388 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3390 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3396 MidiRegionView::get_fill_color() const
3398 const std::string mod_name = (_dragging ? "dragging region" :
3399 trackview.editor().internal_editing() ? "editable region" :
3402 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3403 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3404 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3405 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3407 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3411 MidiRegionView::midi_channel_mode_changed ()
3413 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3414 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3415 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3417 if (mode == ForceChannel) {
3418 mask = 0xFFFF; // Show all notes as active (below)
3421 // Update notes for selection
3422 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3423 (*i)->on_channel_selection_change (mask);
3426 _patch_changes.clear ();
3427 display_patch_changes ();
3431 MidiRegionView::instrument_settings_changed ()
3437 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3439 if (_selection.empty()) {
3443 PublicEditor& editor (trackview.editor());
3447 /* XXX what to do ? */
3451 editor.get_cut_buffer().add (selection_as_cut_buffer());
3459 start_note_diff_command();
3461 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3468 note_diff_remove_note (*i);
3478 MidiRegionView::selection_as_cut_buffer () const
3482 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3483 NoteType* n = (*i)->note().get();
3484 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3487 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3493 /** This method handles undo */
3495 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3497 bool commit = false;
3498 // Paste notes, if available
3499 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3500 if (m != selection.midi_notes.end()) {
3501 ctx.counts.increase_n_notes();
3502 if (!(*m)->empty()) {
3505 paste_internal(pos, ctx.count, ctx.times, **m);
3508 // Paste control points to automation children, if available
3509 typedef RouteTimeAxisView::AutomationTracks ATracks;
3510 const ATracks& atracks = midi_view()->automation_tracks();
3511 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3512 if (a->second->paste(pos, selection, ctx, sub_num)) {
3514 trackview.editor().begin_reversible_command (Operations::paste);
3521 trackview.editor().commit_reversible_command ();
3526 /** This method handles undo */
3528 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3534 start_note_diff_command (_("paste"));
3536 const Evoral::Beats snap_beats = get_grid_beats(pos);
3537 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3538 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3539 const Evoral::Beats duration = last_time - first_time;
3540 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3541 const Evoral::Beats paste_offset = snap_duration * paste_count;
3542 const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
3543 Evoral::Beats end_point = Evoral::Beats();
3545 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3548 duration, pos, _region->position(),
3551 clear_editor_note_selection ();
3553 for (int n = 0; n < (int) times; ++n) {
3555 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3557 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3558 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3559 copied_note->set_id (Evoral::next_event_id());
3561 /* make all newly added notes selected */
3563 note_diff_add_note (copied_note, true);
3564 end_point = copied_note->end_time();
3568 /* if we pasted past the current end of the region, extend the region */
3570 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3571 framepos_t region_end = _region->position() + _region->length() - 1;
3573 if (end_frame > region_end) {
3575 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3577 _region->clear_changes ();
3578 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3579 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3580 trackview.session()->add_command (new StatefulDiffCommand (_region));
3586 struct EventNoteTimeEarlyFirstComparator {
3587 bool operator() (NoteBase* a, NoteBase* b) {
3588 return a->note()->time() < b->note()->time();
3593 MidiRegionView::time_sort_events ()
3595 if (!_sort_needed) {
3599 EventNoteTimeEarlyFirstComparator cmp;
3602 _sort_needed = false;
3606 MidiRegionView::goto_next_note (bool add_to_selection)
3608 bool use_next = false;
3610 if (_events.back()->selected()) {
3614 time_sort_events ();
3616 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3617 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3619 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3620 if ((*i)->selected()) {
3623 } else if (use_next) {
3624 if (channel_mask & (1 << (*i)->note()->channel())) {
3625 if (!add_to_selection) {
3628 note_selected (*i, true, false);
3635 /* use the first one */
3637 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3638 unique_select (_events.front());
3643 MidiRegionView::goto_previous_note (bool add_to_selection)
3645 bool use_next = false;
3647 if (_events.front()->selected()) {
3651 time_sort_events ();
3653 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3654 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3656 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3657 if ((*i)->selected()) {
3660 } else if (use_next) {
3661 if (channel_mask & (1 << (*i)->note()->channel())) {
3662 if (!add_to_selection) {
3665 note_selected (*i, true, false);
3672 /* use the last one */
3674 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3675 unique_select (*(_events.rbegin()));
3680 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3682 bool had_selected = false;
3684 time_sort_events ();
3686 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3687 if ((*i)->selected()) {
3688 selected.insert ((*i)->note());
3689 had_selected = true;
3693 if (allow_all_if_none_selected && !had_selected) {
3694 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3695 selected.insert ((*i)->note());
3701 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3703 x = std::max(0.0, x);
3705 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3710 _note_group->canvas_to_item (x, y);
3712 PublicEditor& editor = trackview.editor ();
3714 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3716 const int32_t divisions = editor.get_grid_music_divisions (state);
3717 const bool shift_snap = midi_view()->note_mode() != Percussive;
3718 const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap);
3720 /* prevent Percussive mode from displaying a ghost hit at region end */
3721 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3722 _ghost_note->hide();
3723 hide_verbose_cursor ();
3727 /* ghost note may have been snapped before region */
3728 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3729 _ghost_note->hide();
3732 } else if (_ghost_note) {
3733 _ghost_note->show();
3736 /* calculate time in beats relative to start of source */
3737 const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3739 _ghost_note->note()->set_time (snapped_beats);
3740 _ghost_note->note()->set_length (length);
3741 _ghost_note->note()->set_note (y_to_note (y));
3742 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3743 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3744 /* the ghost note does not appear in ghost regions, so pass false in here */
3745 update_note (_ghost_note, false);
3747 show_verbose_cursor (_ghost_note->note ());
3751 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3753 remove_ghost_note ();
3755 boost::shared_ptr<NoteType> g (new NoteType);
3756 if (midi_view()->note_mode() == Sustained) {
3757 _ghost_note = new Note (*this, _note_group, g);
3759 _ghost_note = new Hit (*this, _note_group, 10, g);
3761 _ghost_note->set_ignore_events (true);
3762 _ghost_note->set_outline_color (0x000000aa);
3763 update_ghost_note (x, y, state);
3764 _ghost_note->show ();
3766 show_verbose_cursor (_ghost_note->note ());
3770 MidiRegionView::remove_ghost_note ()
3777 MidiRegionView::hide_verbose_cursor ()
3779 trackview.editor().verbose_cursor()->hide ();
3780 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3782 mtv->set_note_highlight (NO_MIDI_NOTE);
3787 MidiRegionView::snap_changed ()
3793 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3797 MidiRegionView::drop_down_keys ()
3799 _mouse_state = None;
3803 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3805 /* XXX: This is dead code. What was it for? */
3807 double note = y_to_note(y);
3809 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3811 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3813 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3814 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3815 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3816 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3821 bool add_mrv_selection = false;
3823 if (_selection.empty()) {
3824 add_mrv_selection = true;
3827 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3828 if (_selection.insert (*i).second) {
3829 (*i)->set_selected (true);
3833 if (add_mrv_selection) {
3834 PublicEditor& editor (trackview.editor());
3835 editor.get_selection().add (this);
3840 MidiRegionView::color_handler ()
3842 RegionView::color_handler ();
3844 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3845 (*i)->set_selected ((*i)->selected()); // will change color
3848 /* XXX probably more to do here */
3852 MidiRegionView::enable_display (bool yn)
3854 RegionView::enable_display (yn);
3858 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3860 if (_step_edit_cursor == 0) {
3861 ArdourCanvas::Item* const group = get_canvas_group();
3863 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3864 _step_edit_cursor->set_y0 (0);
3865 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3866 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3867 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3870 move_step_edit_cursor (pos);
3871 _step_edit_cursor->show ();
3875 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3877 _step_edit_cursor_position = pos;
3879 if (_step_edit_cursor) {
3880 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3881 _step_edit_cursor->set_x0 (pixel);
3882 set_step_edit_cursor_width (_step_edit_cursor_width);
3887 MidiRegionView::hide_step_edit_cursor ()
3889 if (_step_edit_cursor) {
3890 _step_edit_cursor->hide ();
3895 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3897 _step_edit_cursor_width = beats;
3899 if (_step_edit_cursor) {
3900 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3901 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3902 - region_beats_to_region_frames (_step_edit_cursor_position)));
3906 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3907 * @param w Source that the data will end up in.
3910 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3912 if (!_active_notes) {
3913 /* we aren't actively being recorded to */
3917 boost::shared_ptr<MidiSource> src = w.lock ();
3918 if (!src || src != midi_region()->midi_source()) {
3919 /* recorded data was not destined for our source */
3923 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3925 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3927 framepos_t back = max_framepos;
3929 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3930 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3932 if (ev.is_channel_event()) {
3933 if (get_channel_mode() == FilterChannels) {
3934 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3940 /* convert from session frames to source beats */
3941 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3942 ev.time() - src->timeline_position() + _region->start());
3944 if (ev.type() == MIDI_CMD_NOTE_ON) {
3945 boost::shared_ptr<NoteType> note (
3946 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3948 add_note (note, true);
3950 /* fix up our note range */
3951 if (ev.note() < _current_range_min) {
3952 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3953 } else if (ev.note() > _current_range_max) {
3954 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3957 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3958 resolve_note (ev.note (), time_beats);
3964 midi_stream_view()->check_record_layers (region(), back);
3968 MidiRegionView::trim_front_starting ()
3970 /* We used to eparent the note group to the region view's parent, so that it didn't change.
3976 MidiRegionView::trim_front_ending ()
3978 if (_region->start() < 0) {
3979 /* Trim drag made start time -ve; fix this */
3980 midi_region()->fix_negative_start ();
3985 MidiRegionView::edit_patch_change (PatchChange* pc)
3987 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3989 int response = d.run();
3992 case Gtk::RESPONSE_ACCEPT:
3994 case Gtk::RESPONSE_REJECT:
3995 delete_patch_change (pc);
4001 change_patch_change (pc->patch(), d.patch ());
4005 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4008 // sysyex object doesn't have a pointer to a sysex event
4009 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4010 // c->remove (sysex->sysex());
4011 // _model->apply_command (*trackview.session(), c);
4013 //_sys_exes.clear ();
4014 // display_sysexes();
4018 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4020 using namespace MIDI::Name;
4023 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4025 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4027 MIDI::Name::PatchPrimaryKey patch_key;
4028 get_patch_key_at(n->time(), n->channel(), patch_key);
4029 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4032 patch_key.program(),
4038 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4040 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4041 (int) n->channel() + 1,
4042 (int) n->velocity());
4048 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4049 uint8_t new_value) const
4051 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4053 mtv->set_note_highlight (new_value);
4056 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4060 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4062 show_verbose_cursor_for_new_note_value(n, n->note());
4066 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4068 trackview.editor().verbose_cursor()->set (text);
4069 trackview.editor().verbose_cursor()->show ();
4070 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4074 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4076 if (_model->notes().empty()) {
4077 return 0x40; // No notes, use default
4080 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4081 if (m == _model->notes().begin()) {
4082 // Before the start, use the velocity of the first note
4083 return (*m)->velocity();
4084 } else if (m == _model->notes().end()) {
4085 // Past the end, use the velocity of the last note
4087 return (*m)->velocity();
4090 // Interpolate velocity of surrounding notes
4091 MidiModel::Notes::const_iterator n = m;
4094 const double frac = ((time - (*n)->time()).to_double() /
4095 ((*m)->time() - (*n)->time()).to_double());
4097 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4100 /** @param p A session framepos.
4101 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4102 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4103 * @return beat duration of p snapped to the grid subdivision underneath it.
4106 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
4108 TempoMap& map (trackview.session()->tempo_map());
4109 double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4111 if (divisions != 0 && shift_snap) {
4112 const double qaf = map.quarter_note_at_frame (p + _region->position());
4113 /* Hack so that we always snap to the note that we are over, instead of snapping
4114 to the next one if we're more than halfway through the one we're over.
4116 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4117 const double rem = eqaf - qaf;
4119 eqaf -= grid_beats.to_double();
4122 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4124 return Evoral::Beats (eqaf - session_start_off);
4128 MidiRegionView::get_channel_mode () const
4130 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4131 return rtav->midi_track()->get_playback_channel_mode();
4135 MidiRegionView::get_selected_channels () const
4137 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4138 return rtav->midi_track()->get_playback_channel_mask();
4143 MidiRegionView::get_grid_beats(framepos_t pos) const
4145 PublicEditor& editor = trackview.editor();
4146 bool success = false;
4147 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4149 beats = Evoral::Beats(1);
4154 MidiRegionView::y_to_note (double y) const
4156 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4157 + _current_range_min;
4161 } else if (n > 127) {
4165 /* min due to rounding and/or off-by-one errors */
4166 return min ((uint8_t) n, _current_range_max);
4170 MidiRegionView::note_to_y(uint8_t note) const
4172 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;