2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "midi++/midnam_patch.h"
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/Event.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
81 #include "patch_change.h"
83 #include "ui_config.h"
87 using namespace ARDOUR;
89 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
95 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
96 RouteTimeAxisView& tv,
97 boost::shared_ptr<MidiRegion> r,
100 : RegionView (parent, tv, r, spu, basic_color)
101 , _current_range_min(0)
102 , _current_range_max(0)
103 , _region_relative_time_converter(r->session().tempo_map(), r->position())
104 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
105 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
107 , _note_group (new ArdourCanvas::Container (group))
108 , _note_diff_command (0)
110 , _step_edit_cursor (0)
111 , _step_edit_cursor_width (1.0)
112 , _step_edit_cursor_position (0.0)
113 , _channel_selection_scoped_note (0)
116 , _sort_needed (true)
117 , _optimization_iterator (_events.end())
119 , _no_sound_notes (false)
120 , _last_display_zoom (0)
123 , _grabbed_keyboard (false)
126 , _mouse_changed_selection (false)
128 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
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 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1297 have_periodic_system_messages = true;
1302 if (have_periodic_system_messages) {
1303 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1305 /* get an approximate value for the number of samples per video frame */
1307 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1309 /* if we are zoomed out beyond than the cutoff (i.e. more
1310 * frames per pixel than frames per 4 video frames), don't
1311 * show periodic sysex messages.
1314 if (zoom > (video_frame*4)) {
1315 display_periodic_messages = false;
1319 display_periodic_messages = false;
1322 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1323 Evoral::Beats time = (*i)->time();
1325 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1326 if (!display_periodic_messages) {
1333 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1334 str << int((*i)->buffer()[b]);
1335 if (b != (*i)->size() -1) {
1339 string text = str.str();
1341 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1343 double height = midi_stream_view()->contents_height();
1345 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1346 // SysEx canvas object!!!
1348 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1349 new SysEx (*this, _note_group, text, height, x, 1.0));
1351 // Show unless message is beyond the region bounds
1352 if (time - _region->start() >= _region->length() || time < _region->start()) {
1358 _sys_exes.push_back(sysex);
1362 MidiRegionView::~MidiRegionView ()
1364 in_destructor = true;
1366 hide_verbose_cursor ();
1368 delete _list_editor;
1370 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1372 if (_active_notes) {
1379 delete _note_diff_command;
1380 delete _step_edit_cursor;
1384 MidiRegionView::region_resized (const PropertyChange& what_changed)
1386 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1388 if (what_changed.contains (ARDOUR::Properties::position)) {
1389 _region_relative_time_converter.set_origin_b(_region->position());
1390 _region_relative_time_converter_double.set_origin_b(_region->position());
1391 /* reset_width dependent_items() redisplays model */
1395 if (what_changed.contains (ARDOUR::Properties::start) ||
1396 what_changed.contains (ARDOUR::Properties::position)) {
1397 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1399 /* catch end and start trim so we can update the view*/
1400 if (!what_changed.contains (ARDOUR::Properties::start) &&
1401 what_changed.contains (ARDOUR::Properties::length)) {
1402 enable_display (true);
1403 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1404 what_changed.contains (ARDOUR::Properties::length)) {
1405 enable_display (true);
1410 MidiRegionView::reset_width_dependent_items (double pixel_width)
1412 RegionView::reset_width_dependent_items(pixel_width);
1414 if (_enable_display) {
1418 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1419 if ((*x)->canvas_item()->width() >= _pixel_width) {
1426 move_step_edit_cursor (_step_edit_cursor_position);
1427 set_step_edit_cursor_width (_step_edit_cursor_width);
1431 MidiRegionView::set_height (double height)
1433 double old_height = _height;
1434 RegionView::set_height(height);
1436 apply_note_range (midi_stream_view()->lowest_note(),
1437 midi_stream_view()->highest_note(),
1438 height != old_height);
1441 name_text->raise_to_top();
1444 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1445 (*x)->set_height (midi_stream_view()->contents_height());
1448 if (_step_edit_cursor) {
1449 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1454 /** Apply the current note range from the stream view
1455 * by repositioning/hiding notes as necessary
1458 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1460 if (!_enable_display) {
1464 if (!force && _current_range_min == min && _current_range_max == max) {
1468 _current_range_min = min;
1469 _current_range_max = max;
1475 MidiRegionView::add_ghost (TimeAxisView& tv)
1477 double unit_position = _region->position () / samples_per_pixel;
1478 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1479 MidiGhostRegion* ghost;
1481 if (mtv && mtv->midi_view()) {
1482 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1483 to allow having midi notes on top of note lines and waveforms.
1485 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1487 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1490 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1491 ghost->add_note(*i);
1494 ghost->set_colors ();
1495 ghost->set_height ();
1496 ghost->set_duration (_region->length() / samples_per_pixel);
1497 ghosts.push_back (ghost);
1503 /** Begin tracking note state for successive calls to add_event
1506 MidiRegionView::begin_write()
1508 if (_active_notes) {
1509 delete[] _active_notes;
1511 _active_notes = new Note*[128];
1512 for (unsigned i = 0; i < 128; ++i) {
1513 _active_notes[i] = 0;
1518 /** Destroy note state for add_event
1521 MidiRegionView::end_write()
1523 delete[] _active_notes;
1525 _marked_for_selection.clear();
1526 _marked_for_velocity.clear();
1530 /** Resolve an active MIDI note (while recording).
1533 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1535 if (midi_view()->note_mode() != Sustained) {
1539 if (_active_notes && _active_notes[note]) {
1540 /* Set note length so update_note() works. Note this is a local note
1541 for recording, not from a model, so we can safely mess with it. */
1542 _active_notes[note]->note()->set_length(
1543 end_time - _active_notes[note]->note()->time());
1545 /* End time is relative to the region being recorded. */
1546 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1548 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1549 _active_notes[note]->set_outline_all ();
1550 _active_notes[note] = 0;
1555 /** Extend active notes to rightmost edge of region (if length is changed)
1558 MidiRegionView::extend_active_notes()
1560 if (!_active_notes) {
1564 for (unsigned i = 0; i < 128; ++i) {
1565 if (_active_notes[i]) {
1566 _active_notes[i]->set_x1(
1567 trackview.editor().sample_to_pixel(_region->length()));
1573 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1575 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1579 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1581 if (!route_ui || !route_ui->midi_track()) {
1585 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1589 /* NotePlayer deletes itself */
1593 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1595 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1596 start_playing_midi_chord(notes);
1600 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1602 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1606 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1608 if (!route_ui || !route_ui->midi_track()) {
1612 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1614 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1623 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1625 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1627 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1628 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1629 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1631 visible = (note->note() >= _current_range_min) &&
1632 (note->note() <= _current_range_max);
1638 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1642 if ((sus = dynamic_cast<Note*>(note))) {
1643 update_sustained(sus, update_ghost_regions);
1644 } else if ((hit = dynamic_cast<Hit*>(note))) {
1645 update_hit(hit, update_ghost_regions);
1649 /** Update a canvas note's size from its model note.
1650 * @param ev Canvas note to update.
1651 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1654 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1656 TempoMap& map (trackview.session()->tempo_map());
1657 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1658 boost::shared_ptr<NoteType> note = ev->note();
1660 const double session_source_start = _region->quarter_note() - mr->start_beats();
1661 const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1663 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1665 const double y0 = 1 + floor(note_to_y(note->note()));
1668 /* trim note display to not overlap the end of its region */
1669 if (note->length().to_double() > 0.0) {
1670 double note_end_time = note->end_time().to_double();
1672 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1673 note_end_time = mr->start_beats() + mr->length_beats();
1676 const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
1678 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1680 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1683 y1 = y0 + std::max(1., floor(note_height()) - 1);
1685 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1687 if (!note->length()) {
1688 if (_active_notes && note->note() < 128) {
1689 Note* const old_rect = _active_notes[note->note()];
1691 /* There is an active note on this key, so we have a stuck
1692 note. Finish the old rectangle here. */
1693 old_rect->set_x1 (x1);
1694 old_rect->set_outline_all ();
1696 _active_notes[note->note()] = ev;
1698 /* outline all but right edge */
1699 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1700 ArdourCanvas::Rectangle::TOP|
1701 ArdourCanvas::Rectangle::LEFT|
1702 ArdourCanvas::Rectangle::BOTTOM));
1704 /* outline all edges */
1705 ev->set_outline_all ();
1708 // Update color in case velocity has changed
1709 const uint32_t base_col = ev->base_color();
1710 ev->set_fill_color(base_col);
1711 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1713 if (update_ghost_regions) {
1714 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1715 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1717 gr->update_note (ev);
1724 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1726 boost::shared_ptr<NoteType> note = ev->note();
1728 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1729 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1731 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1732 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1733 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1735 // see DnD note in MidiRegionView::apply_note_range() above
1736 if (y <= 0 || y >= _height) {
1742 ev->set_position (ArdourCanvas::Duple (x, y));
1743 ev->set_height (diamond_size);
1745 // Update color in case velocity has changed
1746 const uint32_t base_col = ev->base_color();
1747 ev->set_fill_color(base_col);
1748 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1750 if (update_ghost_regions) {
1751 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1752 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1754 gr->update_hit (ev);
1760 /** Add a MIDI note to the view (with length).
1762 * If in sustained mode, notes with length 0 will be considered active
1763 * notes, and resolve_note should be called when the corresponding note off
1764 * event arrives, to properly display the note.
1767 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1769 NoteBase* event = 0;
1771 if (midi_view()->note_mode() == Sustained) {
1773 Note* ev_rect = new Note (*this, _note_group, note);
1775 update_sustained (ev_rect);
1779 } else if (midi_view()->note_mode() == Percussive) {
1781 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1783 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1785 update_hit (ev_diamond);
1794 MidiGhostRegion* gr;
1796 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1797 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1798 gr->add_note(event);
1802 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1803 note_selected(event, true);
1806 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1807 event->show_velocity();
1810 event->on_channel_selection_change (get_selected_channels());
1811 _events.push_back(event);
1820 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1821 MidiStreamView* const view = mtv->midi_view();
1823 view->update_note_range (note->note());
1828 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1829 Evoral::Beats pos, Evoral::Beats len)
1831 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1833 /* potentially extend region to hold new note */
1835 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1836 framepos_t region_end = _region->last_frame();
1838 if (end_frame > region_end) {
1839 /* XX sets length in beats from audio space. make musical */
1840 _region->set_length (end_frame - _region->position(), 0);
1843 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1844 MidiStreamView* const view = mtv->midi_view();
1846 view->update_note_range(new_note->note());
1848 _marked_for_selection.clear ();
1850 start_note_diff_command (_("step add"));
1852 clear_editor_note_selection ();
1853 note_diff_add_note (new_note, true, false);
1857 // last_step_edit_note = new_note;
1861 MidiRegionView::step_sustain (Evoral::Beats beats)
1863 change_note_lengths (false, false, beats, false, true);
1866 /** Add a new patch change flag to the canvas.
1867 * @param patch the patch change to add
1868 * @param the text to display in the flag
1869 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1872 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1874 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1875 const double x = trackview.editor().sample_to_pixel (region_frames);
1877 double const height = midi_stream_view()->contents_height();
1879 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1880 // so we need to do something more sophisticated to keep its color
1881 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1884 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1885 new PatchChange(*this, group,
1892 if (patch_change->item().width() < _pixel_width) {
1893 // Show unless patch change is beyond the region bounds
1894 if (region_frames < 0 || region_frames >= _region->length()) {
1895 patch_change->hide();
1897 patch_change->show();
1900 patch_change->hide ();
1903 _patch_changes.push_back (patch_change);
1906 MIDI::Name::PatchPrimaryKey
1907 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1909 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1912 /// Return true iff @p pc applies to the given time on the given channel.
1914 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1916 return pc->time() <= time && pc->channel() == channel;
1920 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1922 // The earliest event not before time
1923 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1925 // Go backwards until we find the latest PC for this channel, or the start
1926 while (i != _model->patch_changes().begin() &&
1927 (i == _model->patch_changes().end() ||
1928 !patch_applies(*i, time, channel))) {
1932 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1933 key.set_bank((*i)->bank());
1934 key.set_program((*i)->program ());
1942 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1944 string name = _("alter patch change");
1945 trackview.editor().begin_reversible_command (name);
1946 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1948 if (pc.patch()->program() != new_patch.program()) {
1949 c->change_program (pc.patch (), new_patch.program());
1952 int const new_bank = new_patch.bank();
1953 if (pc.patch()->bank() != new_bank) {
1954 c->change_bank (pc.patch (), new_bank);
1957 _model->apply_command (*trackview.session(), c);
1958 trackview.editor().commit_reversible_command ();
1960 _patch_changes.clear ();
1961 display_patch_changes ();
1965 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
1967 string name = _("alter patch change");
1968 trackview.editor().begin_reversible_command (name);
1969 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1971 if (old_change->time() != new_change.time()) {
1972 c->change_time (old_change, new_change.time());
1975 if (old_change->channel() != new_change.channel()) {
1976 c->change_channel (old_change, new_change.channel());
1979 if (old_change->program() != new_change.program()) {
1980 c->change_program (old_change, new_change.program());
1983 if (old_change->bank() != new_change.bank()) {
1984 c->change_bank (old_change, new_change.bank());
1987 _model->apply_command (*trackview.session(), c);
1988 trackview.editor().commit_reversible_command ();
1990 _patch_changes.clear ();
1991 display_patch_changes ();
1994 /** Add a patch change to the region.
1995 * @param t Time in frames relative to region position
1996 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1997 * MidiTimeAxisView::get_channel_for_add())
2000 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2002 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2003 string name = _("add patch change");
2005 trackview.editor().begin_reversible_command (name);
2006 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2007 c->add (MidiModel::PatchChangePtr (
2008 new Evoral::PatchChange<Evoral::Beats> (
2009 absolute_frames_to_source_beats (_region->position() + t),
2010 mtv->get_channel_for_add(), patch.program(), patch.bank()
2015 _model->apply_command (*trackview.session(), c);
2016 trackview.editor().commit_reversible_command ();
2018 _patch_changes.clear ();
2019 display_patch_changes ();
2023 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2025 trackview.editor().begin_reversible_command (_("move patch change"));
2026 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2027 c->change_time (pc.patch (), t);
2028 _model->apply_command (*trackview.session(), c);
2029 trackview.editor().commit_reversible_command ();
2031 _patch_changes.clear ();
2032 display_patch_changes ();
2036 MidiRegionView::delete_patch_change (PatchChange* pc)
2038 trackview.editor().begin_reversible_command (_("delete patch change"));
2039 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2040 c->remove (pc->patch ());
2041 _model->apply_command (*trackview.session(), c);
2042 trackview.editor().commit_reversible_command ();
2044 _patch_changes.clear ();
2045 display_patch_changes ();
2049 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2051 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2053 key.set_bank(key.bank() + delta);
2055 key.set_program(key.program() + delta);
2057 change_patch_change(patch, key);
2061 MidiRegionView::note_deleted (NoteBase* cne)
2063 if (_entered_note && cne == _entered_note) {
2067 if (_selection.empty()) {
2071 _selection.erase (cne);
2075 MidiRegionView::delete_selection()
2077 if (_selection.empty()) {
2081 if (trackview.editor().drags()->active()) {
2085 start_note_diff_command (_("delete selection"));
2087 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2088 if ((*i)->selected()) {
2089 _note_diff_command->remove((*i)->note());
2097 hide_verbose_cursor ();
2101 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2103 start_note_diff_command (_("delete note"));
2104 _note_diff_command->remove (n);
2107 hide_verbose_cursor ();
2111 MidiRegionView::clear_editor_note_selection ()
2113 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2114 PublicEditor& editor(trackview.editor());
2115 editor.get_selection().clear_midi_notes();
2119 MidiRegionView::clear_selection ()
2121 clear_selection_internal();
2122 PublicEditor& editor(trackview.editor());
2123 editor.get_selection().remove(this);
2127 MidiRegionView::clear_selection_internal ()
2129 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2131 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2132 (*i)->set_selected(false);
2133 (*i)->hide_velocity();
2138 // Clearing selection entirely, ungrab keyboard
2139 Keyboard::magic_widget_drop_focus();
2140 _grabbed_keyboard = false;
2145 MidiRegionView::unique_select(NoteBase* ev)
2147 clear_editor_note_selection();
2148 add_to_selection(ev);
2152 MidiRegionView::select_all_notes ()
2154 clear_editor_note_selection ();
2156 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2157 add_to_selection (*i);
2162 MidiRegionView::select_range (framepos_t start, framepos_t end)
2164 clear_editor_note_selection ();
2166 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2167 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2168 if (t >= start && t <= end) {
2169 add_to_selection (*i);
2175 MidiRegionView::invert_selection ()
2177 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2178 if ((*i)->selected()) {
2179 remove_from_selection(*i);
2181 add_to_selection (*i);
2186 /** Used for selection undo/redo.
2187 The requested notes most likely won't exist in the view until the next model redisplay.
2190 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2193 list<Evoral::event_id_t>::iterator n;
2195 for (n = notes.begin(); n != notes.end(); ++n) {
2196 if ((cne = find_canvas_note(*n)) != 0) {
2197 add_to_selection (cne);
2199 _pending_note_selection.insert(*n);
2205 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2207 bool have_selection = !_selection.empty();
2208 uint8_t low_note = 127;
2209 uint8_t high_note = 0;
2210 MidiModel::Notes& notes (_model->notes());
2211 _optimization_iterator = _events.begin();
2213 if (extend && !have_selection) {
2217 /* scan existing selection to get note range */
2219 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2220 if ((*i)->note()->note() < low_note) {
2221 low_note = (*i)->note()->note();
2223 if ((*i)->note()->note() > high_note) {
2224 high_note = (*i)->note()->note();
2229 clear_editor_note_selection ();
2231 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2232 /* only note previously selected is the one we are
2233 * reselecting. treat this as cancelling the selection.
2240 low_note = min (low_note, notenum);
2241 high_note = max (high_note, notenum);
2244 _no_sound_notes = true;
2246 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2248 boost::shared_ptr<NoteType> note (*n);
2250 bool select = false;
2252 if (((1 << note->channel()) & channel_mask) != 0) {
2254 if ((note->note() >= low_note && note->note() <= high_note)) {
2257 } else if (note->note() == notenum) {
2263 if ((cne = find_canvas_note (note)) != 0) {
2264 // extend is false because we've taken care of it,
2265 // since it extends by time range, not pitch.
2266 note_selected (cne, add, false);
2270 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2274 _no_sound_notes = false;
2278 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2280 MidiModel::Notes& notes (_model->notes());
2281 _optimization_iterator = _events.begin();
2283 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2285 boost::shared_ptr<NoteType> note (*n);
2288 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2289 if ((cne = find_canvas_note (note)) != 0) {
2290 if (cne->selected()) {
2291 note_deselected (cne);
2293 note_selected (cne, true, false);
2301 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2304 clear_editor_note_selection();
2305 add_to_selection (ev);
2310 if (!ev->selected()) {
2311 add_to_selection (ev);
2315 /* find end of latest note selected, select all between that and the start of "ev" */
2317 Evoral::Beats earliest = Evoral::MaxBeats;
2318 Evoral::Beats latest = Evoral::Beats();
2320 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2321 if ((*i)->note()->end_time() > latest) {
2322 latest = (*i)->note()->end_time();
2324 if ((*i)->note()->time() < earliest) {
2325 earliest = (*i)->note()->time();
2329 if (ev->note()->end_time() > latest) {
2330 latest = ev->note()->end_time();
2333 if (ev->note()->time() < earliest) {
2334 earliest = ev->note()->time();
2337 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2339 /* find notes entirely within OR spanning the earliest..latest range */
2341 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2342 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2343 add_to_selection (*i);
2351 MidiRegionView::note_deselected(NoteBase* ev)
2353 remove_from_selection (ev);
2357 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2359 PublicEditor& editor = trackview.editor();
2361 // Convert to local coordinates
2362 const framepos_t p = _region->position();
2363 const double y = midi_view()->y_position();
2364 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2365 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2366 const double y0 = max(0.0, gy0 - y);
2367 const double y1 = max(0.0, gy1 - y);
2369 // TODO: Make this faster by storing the last updated selection rect, and only
2370 // adjusting things that are in the area that appears/disappeared.
2371 // We probably need a tree to be able to find events in O(log(n)) time.
2373 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2374 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2375 // Rectangles intersect
2376 if (!(*i)->selected()) {
2377 add_to_selection (*i);
2379 } else if ((*i)->selected() && !extend) {
2380 // Rectangles do not intersect
2381 remove_from_selection (*i);
2385 typedef RouteTimeAxisView::AutomationTracks ATracks;
2386 typedef std::list<Selectable*> Selectables;
2388 /* Add control points to selection. */
2389 const ATracks& atracks = midi_view()->automation_tracks();
2390 Selectables selectables;
2391 editor.get_selection().clear_points();
2392 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2393 a->second->get_selectables(start, end, gy0, gy1, selectables);
2394 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2395 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2397 editor.get_selection().add(cp);
2400 a->second->set_selected_points(editor.get_selection().points);
2405 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2411 // TODO: Make this faster by storing the last updated selection rect, and only
2412 // adjusting things that are in the area that appears/disappeared.
2413 // We probably need a tree to be able to find events in O(log(n)) time.
2415 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2416 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2417 // within y- (note-) range
2418 if (!(*i)->selected()) {
2419 add_to_selection (*i);
2421 } else if ((*i)->selected() && !extend) {
2422 remove_from_selection (*i);
2428 MidiRegionView::remove_from_selection (NoteBase* ev)
2430 Selection::iterator i = _selection.find (ev);
2432 if (i != _selection.end()) {
2433 _selection.erase (i);
2434 if (_selection.empty() && _grabbed_keyboard) {
2436 Keyboard::magic_widget_drop_focus();
2437 _grabbed_keyboard = false;
2441 ev->set_selected (false);
2442 ev->hide_velocity ();
2444 if (_selection.empty()) {
2445 PublicEditor& editor (trackview.editor());
2446 editor.get_selection().remove (this);
2451 MidiRegionView::add_to_selection (NoteBase* ev)
2453 const bool selection_was_empty = _selection.empty();
2455 if (_selection.insert (ev).second) {
2456 ev->set_selected (true);
2457 start_playing_midi_note ((ev)->note());
2458 if (selection_was_empty && _entered) {
2459 // Grab keyboard for moving notes with arrow keys
2460 Keyboard::magic_widget_grab_focus();
2461 _grabbed_keyboard = true;
2465 if (selection_was_empty) {
2466 PublicEditor& editor (trackview.editor());
2467 editor.get_selection().add (this);
2472 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2474 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2475 PossibleChord to_play;
2476 Evoral::Beats earliest = Evoral::MaxBeats;
2478 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2479 if ((*i)->note()->time() < earliest) {
2480 earliest = (*i)->note()->time();
2484 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2485 if ((*i)->note()->time() == earliest) {
2486 to_play.push_back ((*i)->note());
2488 (*i)->move_event(dx, dy);
2491 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2493 if (to_play.size() > 1) {
2495 PossibleChord shifted;
2497 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2498 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2499 moved_note->set_note (moved_note->note() + cumulative_dy);
2500 shifted.push_back (moved_note);
2503 start_playing_midi_chord (shifted);
2505 } else if (!to_play.empty()) {
2507 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2508 moved_note->set_note (moved_note->note() + cumulative_dy);
2509 start_playing_midi_note (moved_note);
2515 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2517 uint8_t lowest_note_in_selection = 127;
2518 uint8_t highest_note_in_selection = 0;
2519 uint8_t highest_note_difference = 0;
2521 // find highest and lowest notes first
2523 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2524 uint8_t pitch = (*i)->note()->note();
2525 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2526 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2530 cerr << "dnote: " << (int) dnote << endl;
2531 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2532 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2533 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2534 << int(highest_note_in_selection) << endl;
2535 cerr << "selection size: " << _selection.size() << endl;
2536 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2539 // Make sure the note pitch does not exceed the MIDI standard range
2540 if (highest_note_in_selection + dnote > 127) {
2541 highest_note_difference = highest_note_in_selection - 127;
2543 TempoMap& map (trackview.session()->tempo_map());
2545 start_note_diff_command (_("move notes"));
2547 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2549 double const start_qn = _region->quarter_note() - midi_region()->start_beats();
2550 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2551 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2556 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2558 uint8_t original_pitch = (*i)->note()->note();
2559 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2561 // keep notes in standard midi range
2562 clamp_to_0_127(new_pitch);
2564 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2565 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2567 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2572 // care about notes being moved beyond the upper/lower bounds on the canvas
2573 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2574 highest_note_in_selection > midi_stream_view()->highest_note()) {
2575 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2579 /** @param x Pixel relative to the region position.
2580 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2581 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2582 * @return Snapped frame relative to the region position.
2585 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2587 PublicEditor& editor (trackview.editor());
2588 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2591 /** @param x Pixel relative to the region position.
2592 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2593 * @return Snapped pixel relative to the region position.
2596 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2598 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2602 MidiRegionView::get_position_pixels()
2604 framepos_t region_frame = get_position();
2605 return trackview.editor().sample_to_pixel(region_frame);
2609 MidiRegionView::get_end_position_pixels()
2611 framepos_t frame = get_position() + get_duration ();
2612 return trackview.editor().sample_to_pixel(frame);
2616 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2618 /* the time converter will return the frame corresponding to `beats'
2619 relative to the start of the source. The start of the source
2620 is an implied position given by region->position - region->start
2622 const framepos_t source_start = _region->position() - _region->start();
2623 return source_start + _source_relative_time_converter.to (beats);
2627 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2629 /* the `frames' argument needs to be converted into a frame count
2630 relative to the start of the source before being passed in to the
2633 const framepos_t source_start = _region->position() - _region->start();
2634 return _source_relative_time_converter.from (frames - source_start);
2638 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2640 return _region_relative_time_converter.to(beats);
2644 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2646 return _region_relative_time_converter.from(frames);
2650 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2652 return _region_relative_time_converter_double.from(frames);
2656 MidiRegionView::begin_resizing (bool /*at_front*/)
2658 _resize_data.clear();
2660 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2661 Note *note = dynamic_cast<Note*> (*i);
2663 // only insert CanvasNotes into the map
2665 NoteResizeData *resize_data = new NoteResizeData();
2666 resize_data->note = note;
2668 // create a new SimpleRect from the note which will be the resize preview
2669 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2670 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2672 // calculate the colors: get the color settings
2673 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2674 UIConfiguration::instance().color ("midi note selected"),
2677 // make the resize preview notes more transparent and bright
2678 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2680 // calculate color based on note velocity
2681 resize_rect->set_fill_color (UINT_INTERPOLATE(
2682 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2686 resize_rect->set_outline_color (NoteBase::calculate_outline (
2687 UIConfiguration::instance().color ("midi note selected")));
2689 resize_data->resize_rect = resize_rect;
2690 _resize_data.push_back(resize_data);
2695 /** Update resizing notes while user drags.
2696 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2697 * @param at_front which end of the note (true == note on, false == note off)
2698 * @param delta_x change in mouse position since the start of the drag
2699 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2700 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2701 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2702 * as the \a primary note.
2703 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2704 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2707 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2709 TempoMap& tmap (trackview.session()->tempo_map());
2710 bool cursor_set = false;
2711 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2713 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2714 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2715 Note* canvas_note = (*i)->note;
2720 current_x = canvas_note->x0() + delta_x + snap_delta;
2722 current_x = primary->x0() + delta_x + snap_delta;
2726 current_x = canvas_note->x1() + delta_x + snap_delta;
2728 current_x = primary->x1() + delta_x + snap_delta;
2732 if (current_x < 0) {
2733 // This works even with snapping because RegionView::snap_frame_to_frame()
2734 // snaps forward if the snapped sample is before the beginning of the region
2737 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2738 current_x = trackview.editor().sample_to_pixel(_region->length());
2743 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2745 resize_rect->set_x0 (current_x - snap_delta);
2747 resize_rect->set_x1 (canvas_note->x1());
2750 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2752 resize_rect->set_x1 (current_x - snap_delta);
2754 resize_rect->set_x0 (canvas_note->x0());
2758 /* Convert snap delta from pixels to beats. */
2759 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2760 double snap_delta_beats = 0.0;
2763 /* negative beat offsets aren't allowed */
2764 if (snap_delta_samps > 0) {
2765 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2766 } else if (snap_delta_samps < 0) {
2767 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2772 int32_t divisions = 0;
2775 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2776 divisions = trackview.editor().get_grid_music_divisions (0);
2778 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2780 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2781 - midi_region()->beat()) + midi_region()->start_beats();
2783 Evoral::Beats len = Evoral::Beats();
2786 if (beats < canvas_note->note()->end_time()) {
2787 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2788 len += canvas_note->note()->length();
2791 if (beats >= canvas_note->note()->time()) {
2792 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2796 len = std::max(Evoral::Beats(1 / 512.0), len);
2799 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2800 show_verbose_cursor (buf, 0, 0);
2809 /** Finish resizing notes when the user releases the mouse button.
2810 * Parameters the same as for \a update_resizing().
2813 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2815 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2816 TempoMap& tmap (trackview.session()->tempo_map());
2818 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2819 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2821 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2822 Note* canvas_note = (*i)->note;
2823 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2825 /* Get the new x position for this resize, which is in pixels relative
2826 * to the region position.
2833 current_x = canvas_note->x0() + delta_x + snap_delta;
2835 current_x = primary->x0() + delta_x + snap_delta;
2839 current_x = canvas_note->x1() + delta_x + snap_delta;
2841 current_x = primary->x1() + delta_x + snap_delta;
2845 if (current_x < 0) {
2848 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2849 current_x = trackview.editor().sample_to_pixel(_region->length());
2852 /* Convert snap delta from pixels to beats with sign. */
2853 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2854 double snap_delta_beats = 0.0;
2857 if (snap_delta_samps > 0) {
2858 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2859 } else if (snap_delta_samps < 0) {
2860 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2864 uint32_t divisions = 0;
2865 /* Convert the new x position to a frame within the source */
2866 framepos_t current_fr;
2868 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2869 divisions = trackview.editor().get_grid_music_divisions (0);
2871 current_fr = trackview.editor().pixel_to_sample (current_x);
2874 /* and then to beats */
2875 const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
2876 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
2877 const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
2879 if (at_front && x_beats < canvas_note->note()->end_time()) {
2880 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2881 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2882 len += canvas_note->note()->length();
2885 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2890 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2891 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2892 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2899 _resize_data.clear();
2904 MidiRegionView::abort_resizing ()
2906 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2907 delete (*i)->resize_rect;
2911 _resize_data.clear ();
2915 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2917 uint8_t new_velocity;
2920 new_velocity = event->note()->velocity() + velocity;
2921 clamp_to_0_127(new_velocity);
2923 new_velocity = velocity;
2926 event->set_selected (event->selected()); // change color
2928 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2932 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2937 new_note = event->note()->note() + note;
2942 clamp_to_0_127 (new_note);
2943 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2947 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2949 bool change_start = false;
2950 bool change_length = false;
2951 Evoral::Beats new_start;
2952 Evoral::Beats new_length;
2954 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2956 front_delta: if positive - move the start of the note later in time (shortening it)
2957 if negative - move the start of the note earlier in time (lengthening it)
2959 end_delta: if positive - move the end of the note later in time (lengthening it)
2960 if negative - move the end of the note earlier in time (shortening it)
2963 if (!!front_delta) {
2964 if (front_delta < 0) {
2966 if (event->note()->time() < -front_delta) {
2967 new_start = Evoral::Beats();
2969 new_start = event->note()->time() + front_delta; // moves earlier
2972 /* start moved toward zero, so move the end point out to where it used to be.
2973 Note that front_delta is negative, so this increases the length.
2976 new_length = event->note()->length() - front_delta;
2977 change_start = true;
2978 change_length = true;
2982 Evoral::Beats new_pos = event->note()->time() + front_delta;
2984 if (new_pos < event->note()->end_time()) {
2985 new_start = event->note()->time() + front_delta;
2986 /* start moved toward the end, so move the end point back to where it used to be */
2987 new_length = event->note()->length() - front_delta;
2988 change_start = true;
2989 change_length = true;
2996 bool can_change = true;
2997 if (end_delta < 0) {
2998 if (event->note()->length() < -end_delta) {
3004 new_length = event->note()->length() + end_delta;
3005 change_length = true;
3010 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3013 if (change_length) {
3014 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3019 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3021 uint8_t new_channel;
3025 if (event->note()->channel() < -chn) {
3028 new_channel = event->note()->channel() + chn;
3031 new_channel = event->note()->channel() + chn;
3034 new_channel = (uint8_t) chn;
3037 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3041 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3043 Evoral::Beats new_time;
3047 if (event->note()->time() < -delta) {
3048 new_time = Evoral::Beats();
3050 new_time = event->note()->time() + delta;
3053 new_time = event->note()->time() + delta;
3059 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3063 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3065 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3069 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3074 if (_selection.empty()) {
3089 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3090 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3096 start_note_diff_command (_("change velocities"));
3098 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3099 Selection::iterator next = i;
3103 if (i == _selection.begin()) {
3104 change_note_velocity (*i, delta, true);
3105 value = (*i)->note()->velocity() + delta;
3107 change_note_velocity (*i, value, false);
3111 change_note_velocity (*i, delta, true);
3120 if (!_selection.empty()) {
3122 snprintf (buf, sizeof (buf), "Vel %d",
3123 (int) (*_selection.begin())->note()->velocity());
3124 show_verbose_cursor (buf, 10, 10);
3130 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3132 if (_selection.empty()) {
3149 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3151 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3155 if ((int8_t) (*i)->note()->note() + delta > 127) {
3162 start_note_diff_command (_("transpose"));
3164 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3165 Selection::iterator next = i;
3167 change_note_note (*i, delta, true);
3175 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3179 delta = Evoral::Beats(1.0/128.0);
3181 /* grab the current grid distance */
3182 delta = get_grid_beats(_region->position());
3190 start_note_diff_command (_("change note lengths"));
3192 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3193 Selection::iterator next = i;
3196 /* note the negation of the delta for start */
3199 (start ? -delta : Evoral::Beats()),
3200 (end ? delta : Evoral::Beats()));
3209 MidiRegionView::nudge_notes (bool forward, bool fine)
3211 if (_selection.empty()) {
3215 /* pick a note as the point along the timeline to get the nudge distance.
3216 its not necessarily the earliest note, so we may want to pull the notes out
3217 into a vector and sort before using the first one.
3220 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3221 Evoral::Beats delta;
3225 /* non-fine, move by 1 bar regardless of snap */
3226 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3228 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3230 /* grid is off - use nudge distance */
3233 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3234 delta = region_frames_to_region_beats (fabs ((double)distance));
3240 framepos_t next_pos = ref_point;
3243 if (max_framepos - 1 < next_pos) {
3247 if (next_pos == 0) {
3253 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3254 const framecnt_t distance = ref_point - next_pos;
3255 delta = region_frames_to_region_beats (fabs ((double)distance));
3266 start_note_diff_command (_("nudge"));
3268 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3269 Selection::iterator next = i;
3271 change_note_time (*i, delta, true);
3279 MidiRegionView::change_channel(uint8_t channel)
3281 start_note_diff_command(_("change channel"));
3282 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3283 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3291 MidiRegionView::note_entered(NoteBase* ev)
3295 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3297 if (_mouse_state == SelectTouchDragging) {
3299 note_selected (ev, true);
3301 } else if (editor->current_mouse_mode() == MouseContent) {
3303 remove_ghost_note ();
3304 show_verbose_cursor (ev->note ());
3306 } else if (editor->current_mouse_mode() == MouseDraw) {
3308 remove_ghost_note ();
3309 show_verbose_cursor (ev->note ());
3314 MidiRegionView::note_left (NoteBase*)
3318 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3319 (*i)->hide_velocity ();
3322 hide_verbose_cursor ();
3326 MidiRegionView::patch_entered (PatchChange* p)
3329 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3330 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3331 << _("Channel ") << ((int) p->patch()->channel() + 1);
3332 show_verbose_cursor (s.str(), 10, 20);
3333 p->item().grab_focus();
3337 MidiRegionView::patch_left (PatchChange *)
3339 hide_verbose_cursor ();
3340 /* focus will transfer back via the enter-notify event sent to this
3346 MidiRegionView::sysex_entered (SysEx* p)
3350 // need a way to extract text from p->_flag->_text
3352 // show_verbose_cursor (s.str(), 10, 20);
3353 p->item().grab_focus();
3357 MidiRegionView::sysex_left (SysEx *)
3359 hide_verbose_cursor ();
3360 /* focus will transfer back via the enter-notify event sent to this
3366 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3368 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3369 Editing::MouseMode mm = editor->current_mouse_mode();
3370 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3372 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3373 if (can_set_cursor && ctx) {
3374 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3375 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3376 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3377 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3379 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3385 MidiRegionView::get_fill_color() const
3387 const std::string mod_name = (_dragging ? "dragging region" :
3388 trackview.editor().internal_editing() ? "editable region" :
3391 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3392 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3393 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3394 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3396 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3400 MidiRegionView::midi_channel_mode_changed ()
3402 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3403 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3404 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3406 if (mode == ForceChannel) {
3407 mask = 0xFFFF; // Show all notes as active (below)
3410 // Update notes for selection
3411 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3412 (*i)->on_channel_selection_change (mask);
3415 _patch_changes.clear ();
3416 display_patch_changes ();
3420 MidiRegionView::instrument_settings_changed ()
3426 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3428 if (_selection.empty()) {
3432 PublicEditor& editor (trackview.editor());
3436 /* XXX what to do ? */
3440 editor.get_cut_buffer().add (selection_as_cut_buffer());
3448 start_note_diff_command();
3450 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3457 note_diff_remove_note (*i);
3467 MidiRegionView::selection_as_cut_buffer () const
3471 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3472 NoteType* n = (*i)->note().get();
3473 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3476 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3482 /** This method handles undo */
3484 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3486 bool commit = false;
3487 // Paste notes, if available
3488 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3489 if (m != selection.midi_notes.end()) {
3490 ctx.counts.increase_n_notes();
3491 if (!(*m)->empty()) {
3494 paste_internal(pos, ctx.count, ctx.times, **m);
3497 // Paste control points to automation children, if available
3498 typedef RouteTimeAxisView::AutomationTracks ATracks;
3499 const ATracks& atracks = midi_view()->automation_tracks();
3500 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3501 if (a->second->paste(pos, selection, ctx, sub_num)) {
3503 trackview.editor().begin_reversible_command (Operations::paste);
3510 trackview.editor().commit_reversible_command ();
3515 /** This method handles undo */
3517 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3523 start_note_diff_command (_("paste"));
3525 const Evoral::Beats snap_beats = get_grid_beats(pos);
3526 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3527 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3528 const Evoral::Beats duration = last_time - first_time;
3529 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3530 const Evoral::Beats paste_offset = snap_duration * paste_count;
3531 const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
3532 Evoral::Beats end_point = Evoral::Beats();
3534 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3537 duration, pos, _region->position(),
3540 clear_editor_note_selection ();
3542 for (int n = 0; n < (int) times; ++n) {
3544 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3546 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3547 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3548 copied_note->set_id (Evoral::next_event_id());
3550 /* make all newly added notes selected */
3552 note_diff_add_note (copied_note, true);
3553 end_point = copied_note->end_time();
3557 /* if we pasted past the current end of the region, extend the region */
3559 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3560 framepos_t region_end = _region->position() + _region->length() - 1;
3562 if (end_frame > region_end) {
3564 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3566 _region->clear_changes ();
3567 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3568 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3569 trackview.session()->add_command (new StatefulDiffCommand (_region));
3575 struct EventNoteTimeEarlyFirstComparator {
3576 bool operator() (NoteBase* a, NoteBase* b) {
3577 return a->note()->time() < b->note()->time();
3582 MidiRegionView::time_sort_events ()
3584 if (!_sort_needed) {
3588 EventNoteTimeEarlyFirstComparator cmp;
3591 _sort_needed = false;
3595 MidiRegionView::goto_next_note (bool add_to_selection)
3597 bool use_next = false;
3599 if (_events.back()->selected()) {
3603 time_sort_events ();
3605 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3606 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3608 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3609 if ((*i)->selected()) {
3612 } else if (use_next) {
3613 if (channel_mask & (1 << (*i)->note()->channel())) {
3614 if (!add_to_selection) {
3617 note_selected (*i, true, false);
3624 /* use the first one */
3626 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3627 unique_select (_events.front());
3632 MidiRegionView::goto_previous_note (bool add_to_selection)
3634 bool use_next = false;
3636 if (_events.front()->selected()) {
3640 time_sort_events ();
3642 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3643 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3645 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3646 if ((*i)->selected()) {
3649 } else if (use_next) {
3650 if (channel_mask & (1 << (*i)->note()->channel())) {
3651 if (!add_to_selection) {
3654 note_selected (*i, true, false);
3661 /* use the last one */
3663 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3664 unique_select (*(_events.rbegin()));
3669 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3671 bool had_selected = false;
3673 time_sort_events ();
3675 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3676 if ((*i)->selected()) {
3677 selected.insert ((*i)->note());
3678 had_selected = true;
3682 if (allow_all_if_none_selected && !had_selected) {
3683 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3684 selected.insert ((*i)->note());
3690 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3692 x = std::max(0.0, x);
3694 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3699 _note_group->canvas_to_item (x, y);
3701 PublicEditor& editor = trackview.editor ();
3703 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3705 const int32_t divisions = editor.get_grid_music_divisions (state);
3706 const bool shift_snap = midi_view()->note_mode() != Percussive;
3707 const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap);
3709 /* prevent Percussive mode from displaying a ghost hit at region end */
3710 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3711 _ghost_note->hide();
3712 hide_verbose_cursor ();
3716 /* ghost note may have been snapped before region */
3717 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3718 _ghost_note->hide();
3721 } else if (_ghost_note) {
3722 _ghost_note->show();
3725 /* calculate time in beats relative to start of source */
3726 const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3728 _ghost_note->note()->set_time (snapped_beats);
3729 _ghost_note->note()->set_length (length);
3730 _ghost_note->note()->set_note (y_to_note (y));
3731 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3732 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3733 /* the ghost note does not appear in ghost regions, so pass false in here */
3734 update_note (_ghost_note, false);
3736 show_verbose_cursor (_ghost_note->note ());
3740 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3742 remove_ghost_note ();
3744 boost::shared_ptr<NoteType> g (new NoteType);
3745 if (midi_view()->note_mode() == Sustained) {
3746 _ghost_note = new Note (*this, _note_group, g);
3748 _ghost_note = new Hit (*this, _note_group, 10, g);
3750 _ghost_note->set_ignore_events (true);
3751 _ghost_note->set_outline_color (0x000000aa);
3752 update_ghost_note (x, y, state);
3753 _ghost_note->show ();
3755 show_verbose_cursor (_ghost_note->note ());
3759 MidiRegionView::remove_ghost_note ()
3766 MidiRegionView::hide_verbose_cursor ()
3768 trackview.editor().verbose_cursor()->hide ();
3769 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3771 mtv->set_note_highlight (NO_MIDI_NOTE);
3776 MidiRegionView::snap_changed ()
3782 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3786 MidiRegionView::drop_down_keys ()
3788 _mouse_state = None;
3792 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3794 /* XXX: This is dead code. What was it for? */
3796 double note = y_to_note(y);
3798 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3800 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3802 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3803 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3804 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3805 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3810 bool add_mrv_selection = false;
3812 if (_selection.empty()) {
3813 add_mrv_selection = true;
3816 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3817 if (_selection.insert (*i).second) {
3818 (*i)->set_selected (true);
3822 if (add_mrv_selection) {
3823 PublicEditor& editor (trackview.editor());
3824 editor.get_selection().add (this);
3829 MidiRegionView::color_handler ()
3831 RegionView::color_handler ();
3833 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3834 (*i)->set_selected ((*i)->selected()); // will change color
3837 /* XXX probably more to do here */
3841 MidiRegionView::enable_display (bool yn)
3843 RegionView::enable_display (yn);
3847 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3849 if (_step_edit_cursor == 0) {
3850 ArdourCanvas::Item* const group = get_canvas_group();
3852 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3853 _step_edit_cursor->set_y0 (0);
3854 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3855 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3856 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3859 move_step_edit_cursor (pos);
3860 _step_edit_cursor->show ();
3864 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3866 _step_edit_cursor_position = pos;
3868 if (_step_edit_cursor) {
3869 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3870 _step_edit_cursor->set_x0 (pixel);
3871 set_step_edit_cursor_width (_step_edit_cursor_width);
3876 MidiRegionView::hide_step_edit_cursor ()
3878 if (_step_edit_cursor) {
3879 _step_edit_cursor->hide ();
3884 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3886 _step_edit_cursor_width = beats;
3888 if (_step_edit_cursor) {
3889 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3890 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3891 - region_beats_to_region_frames (_step_edit_cursor_position)));
3895 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3896 * @param w Source that the data will end up in.
3899 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3901 if (!_active_notes) {
3902 /* we aren't actively being recorded to */
3906 boost::shared_ptr<MidiSource> src = w.lock ();
3907 if (!src || src != midi_region()->midi_source()) {
3908 /* recorded data was not destined for our source */
3912 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3914 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3916 framepos_t back = max_framepos;
3918 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3919 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
3921 if (ev.is_channel_event()) {
3922 if (get_channel_mode() == FilterChannels) {
3923 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3929 /* convert from session frames to source beats */
3930 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3931 ev.time() - src->timeline_position() + _region->start());
3933 if (ev.type() == MIDI_CMD_NOTE_ON) {
3934 boost::shared_ptr<NoteType> note (
3935 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3937 add_note (note, true);
3939 /* fix up our note range */
3940 if (ev.note() < _current_range_min) {
3941 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3942 } else if (ev.note() > _current_range_max) {
3943 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3946 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3947 resolve_note (ev.note (), time_beats);
3953 midi_stream_view()->check_record_layers (region(), back);
3957 MidiRegionView::trim_front_starting ()
3959 /* We used to eparent the note group to the region view's parent, so that it didn't change.
3965 MidiRegionView::trim_front_ending ()
3967 if (_region->start() < 0) {
3968 /* Trim drag made start time -ve; fix this */
3969 midi_region()->fix_negative_start ();
3974 MidiRegionView::edit_patch_change (PatchChange* pc)
3976 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3978 int response = d.run();
3981 case Gtk::RESPONSE_ACCEPT:
3983 case Gtk::RESPONSE_REJECT:
3984 delete_patch_change (pc);
3990 change_patch_change (pc->patch(), d.patch ());
3994 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
3997 // sysyex object doesn't have a pointer to a sysex event
3998 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3999 // c->remove (sysex->sysex());
4000 // _model->apply_command (*trackview.session(), c);
4002 //_sys_exes.clear ();
4003 // display_sysexes();
4007 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4009 using namespace MIDI::Name;
4012 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4014 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4016 MIDI::Name::PatchPrimaryKey patch_key;
4017 get_patch_key_at(n->time(), n->channel(), patch_key);
4018 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4021 patch_key.program(),
4027 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4029 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4030 (int) n->channel() + 1,
4031 (int) n->velocity());
4037 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4038 uint8_t new_value) const
4040 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4042 mtv->set_note_highlight (new_value);
4045 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4049 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4051 show_verbose_cursor_for_new_note_value(n, n->note());
4055 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4057 trackview.editor().verbose_cursor()->set (text);
4058 trackview.editor().verbose_cursor()->show ();
4059 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4063 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4065 if (_model->notes().empty()) {
4066 return 0x40; // No notes, use default
4069 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4070 if (m == _model->notes().begin()) {
4071 // Before the start, use the velocity of the first note
4072 return (*m)->velocity();
4073 } else if (m == _model->notes().end()) {
4074 // Past the end, use the velocity of the last note
4076 return (*m)->velocity();
4079 // Interpolate velocity of surrounding notes
4080 MidiModel::Notes::const_iterator n = m;
4083 const double frac = ((time - (*n)->time()).to_double() /
4084 ((*m)->time() - (*n)->time()).to_double());
4086 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4089 /** @param p A session framepos.
4090 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4091 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4092 * @return beat duration of p snapped to the grid subdivision underneath it.
4095 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
4097 TempoMap& map (trackview.session()->tempo_map());
4098 double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4100 if (divisions != 0 && shift_snap) {
4101 const double qaf = map.quarter_note_at_frame (p + _region->position());
4102 /* Hack so that we always snap to the note that we are over, instead of snapping
4103 to the next one if we're more than halfway through the one we're over.
4105 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4106 const double rem = eqaf - qaf;
4108 eqaf -= grid_beats.to_double();
4111 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4113 return Evoral::Beats (eqaf - session_start_off);
4117 MidiRegionView::get_channel_mode () const
4119 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4120 return rtav->midi_track()->get_playback_channel_mode();
4124 MidiRegionView::get_selected_channels () const
4126 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4127 return rtav->midi_track()->get_playback_channel_mask();
4132 MidiRegionView::get_grid_beats(framepos_t pos) const
4134 PublicEditor& editor = trackview.editor();
4135 bool success = false;
4136 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4138 beats = Evoral::Beats(1);
4143 MidiRegionView::y_to_note (double y) const
4145 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4146 + _current_range_min;
4150 } else if (n > 127) {
4154 /* min due to rounding and/or off-by-one errors */
4155 return min ((uint8_t) n, _current_range_max);
4159 MidiRegionView::note_to_y(uint8_t note) const
4161 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;