2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "midi++/midnam_patch.h"
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
81 #include "patch_change.h"
83 #include "ui_config.h"
87 using namespace ARDOUR;
89 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
95 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
97 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
98 RouteTimeAxisView& tv,
99 boost::shared_ptr<MidiRegion> r,
101 uint32_t basic_color)
102 : RegionView (parent, tv, r, spu, basic_color)
103 , _current_range_min(0)
104 , _current_range_max(0)
105 , _region_relative_time_converter(r->session().tempo_map(), r->position())
106 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
107 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
109 , _note_group (new ArdourCanvas::Container (group))
110 , _note_diff_command (0)
112 , _step_edit_cursor (0)
113 , _step_edit_cursor_width (1.0)
114 , _step_edit_cursor_position (0.0)
115 , _channel_selection_scoped_note (0)
116 , _temporary_note_group (0)
119 , _sort_needed (true)
120 , _optimization_iterator (_events.end())
122 , _no_sound_notes (false)
123 , _last_display_zoom (0)
126 , _grabbed_keyboard (false)
128 , _note_entered (false)
129 , _mouse_changed_selection (false)
131 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
132 _note_group->raise_to_top();
133 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
135 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
136 connect_to_diskstream ();
138 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
140 PublicEditor& editor (trackview.editor());
141 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
144 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
145 RouteTimeAxisView& tv,
146 boost::shared_ptr<MidiRegion> r,
148 uint32_t basic_color,
150 TimeAxisViewItem::Visibility visibility)
151 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
152 , _current_range_min(0)
153 , _current_range_max(0)
154 , _region_relative_time_converter(r->session().tempo_map(), r->position())
155 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
156 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
158 , _note_group (new ArdourCanvas::Container (group))
159 , _note_diff_command (0)
161 , _step_edit_cursor (0)
162 , _step_edit_cursor_width (1.0)
163 , _step_edit_cursor_position (0.0)
164 , _channel_selection_scoped_note (0)
165 , _temporary_note_group (0)
168 , _sort_needed (true)
169 , _optimization_iterator (_events.end())
171 , _no_sound_notes (false)
172 , _last_display_zoom (0)
175 , _grabbed_keyboard (false)
177 , _note_entered (false)
178 , _mouse_changed_selection (false)
180 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
181 _note_group->raise_to_top();
183 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
185 connect_to_diskstream ();
187 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
189 PublicEditor& editor (trackview.editor());
190 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
194 MidiRegionView::parameter_changed (std::string const & p)
196 if (p == "display-first-midi-bank-as-zero") {
197 if (_enable_display) {
203 MidiRegionView::MidiRegionView (const MidiRegionView& other)
204 : sigc::trackable(other)
206 , _current_range_min(0)
207 , _current_range_max(0)
208 , _region_relative_time_converter(other.region_relative_time_converter())
209 , _source_relative_time_converter(other.source_relative_time_converter())
210 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
212 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
213 , _note_diff_command (0)
215 , _step_edit_cursor (0)
216 , _step_edit_cursor_width (1.0)
217 , _step_edit_cursor_position (0.0)
218 , _channel_selection_scoped_note (0)
219 , _temporary_note_group (0)
222 , _sort_needed (true)
223 , _optimization_iterator (_events.end())
225 , _no_sound_notes (false)
226 , _last_display_zoom (0)
229 , _grabbed_keyboard (false)
231 , _note_entered (false)
232 , _mouse_changed_selection (false)
237 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
238 : RegionView (other, boost::shared_ptr<Region> (region))
239 , _current_range_min(0)
240 , _current_range_max(0)
241 , _region_relative_time_converter(other.region_relative_time_converter())
242 , _source_relative_time_converter(other.source_relative_time_converter())
243 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
245 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
246 , _note_diff_command (0)
248 , _step_edit_cursor (0)
249 , _step_edit_cursor_width (1.0)
250 , _step_edit_cursor_position (0.0)
251 , _channel_selection_scoped_note (0)
252 , _temporary_note_group (0)
255 , _sort_needed (true)
256 , _optimization_iterator (_events.end())
258 , _no_sound_notes (false)
259 , _last_display_zoom (0)
262 , _grabbed_keyboard (false)
264 , _note_entered (false)
265 , _mouse_changed_selection (false)
271 MidiRegionView::init (bool wfd)
273 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
276 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
277 midi_region()->midi_source(0)->load_model(lm);
280 _model = midi_region()->midi_source(0)->model();
281 _enable_display = false;
282 fill_color_name = "midi frame base";
284 RegionView::init (false);
286 set_height (trackview.current_height());
289 region_sync_changed ();
290 region_resized (ARDOUR::bounds_change);
295 _enable_display = true;
298 display_model (_model);
302 reset_width_dependent_items (_pixel_width);
304 group->raise_to_top();
306 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
307 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
310 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
311 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
313 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
314 boost::bind (&MidiRegionView::snap_changed, this),
317 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
318 boost::bind (&MidiRegionView::mouse_mode_changed, this),
321 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
322 connect_to_diskstream ();
324 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
326 PublicEditor& editor (trackview.editor());
327 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
331 MidiRegionView::instrument_info () const
333 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
334 return route_ui->route()->instrument_info();
337 const boost::shared_ptr<ARDOUR::MidiRegion>
338 MidiRegionView::midi_region() const
340 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
344 MidiRegionView::connect_to_diskstream ()
346 midi_view()->midi_track()->DataRecorded.connect(
347 *this, invalidator(*this),
348 boost::bind (&MidiRegionView::data_recorded, this, _1),
353 MidiRegionView::canvas_group_event(GdkEvent* ev)
355 if (in_destructor || _recregion) {
359 if (!trackview.editor().internal_editing()) {
360 // not in internal edit mode, so just act like a normal region
361 return RegionView::canvas_group_event (ev);
367 case GDK_ENTER_NOTIFY:
368 _last_event_x = ev->crossing.x;
369 _last_event_y = ev->crossing.y;
370 enter_notify(&ev->crossing);
371 // set entered_regionview (among other things)
372 return RegionView::canvas_group_event (ev);
374 case GDK_LEAVE_NOTIFY:
375 _last_event_x = ev->crossing.x;
376 _last_event_y = ev->crossing.y;
377 leave_notify(&ev->crossing);
378 // reset entered_regionview (among other things)
379 return RegionView::canvas_group_event (ev);
382 if (scroll (&ev->scroll)) {
388 return key_press (&ev->key);
390 case GDK_KEY_RELEASE:
391 return key_release (&ev->key);
393 case GDK_BUTTON_PRESS:
394 return button_press (&ev->button);
396 case GDK_BUTTON_RELEASE:
397 r = button_release (&ev->button);
400 case GDK_MOTION_NOTIFY:
401 _last_event_x = ev->motion.x;
402 _last_event_y = ev->motion.y;
403 return motion (&ev->motion);
409 return RegionView::canvas_group_event (ev);
413 MidiRegionView::enter_notify (GdkEventCrossing* ev)
422 MidiRegionView::leave_notify (GdkEventCrossing*)
431 MidiRegionView::mouse_mode_changed ()
433 // Adjust frame colour (become more transparent for internal tools)
437 if (!trackview.editor().internal_editing()) {
438 /* Switched out of internal editing mode while entered.
439 Only necessary for leave as a mouse_mode_change over a region
440 automatically triggers an enter event. */
443 else if (trackview.editor().current_mouse_mode() == MouseContent) {
444 // hide cursor and ghost note after changing to internal edit mode
445 remove_ghost_note ();
447 /* XXX This is problematic as the function is executed for every region
448 and only for one region _note_entered can be true. Still it's
449 necessary as to hide the verbose cursor when we're changing from
450 draw mode to internal edit mode. These lines are the reason why
451 in some situations no verbose cursor is shown when we enter internal
452 edit mode over a note. */
453 if (!_note_entered) {
454 hide_verbose_cursor ();
461 MidiRegionView::enter_internal()
463 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
464 // Show ghost note under pencil
465 create_ghost_note(_last_event_x, _last_event_y);
468 if (!_selection.empty()) {
469 // Grab keyboard for moving selected notes with arrow keys
470 Keyboard::magic_widget_grab_focus();
471 _grabbed_keyboard = true;
474 // Lower frame handles below notes so they don't steal events
475 if (frame_handle_start) {
476 frame_handle_start->lower_to_bottom();
478 if (frame_handle_end) {
479 frame_handle_end->lower_to_bottom();
484 MidiRegionView::leave_internal()
486 hide_verbose_cursor ();
487 remove_ghost_note ();
488 _note_entered = false;
490 if (_grabbed_keyboard) {
491 Keyboard::magic_widget_drop_focus();
492 _grabbed_keyboard = false;
495 // Raise frame handles above notes so they catch events
496 if (frame_handle_start) {
497 frame_handle_start->raise_to_top();
499 if (frame_handle_end) {
500 frame_handle_end->raise_to_top();
505 MidiRegionView::button_press (GdkEventButton* ev)
507 if (ev->button != 1) {
511 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
512 MouseMode m = editor->current_mouse_mode();
514 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
515 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
518 if (_mouse_state != SelectTouchDragging) {
520 _pressed_button = ev->button;
521 _mouse_state = Pressed;
526 _pressed_button = ev->button;
527 _mouse_changed_selection = false;
533 MidiRegionView::button_release (GdkEventButton* ev)
535 double event_x, event_y;
537 if (ev->button != 1) {
544 group->canvas_to_item (event_x, event_y);
547 PublicEditor& editor = trackview.editor ();
549 _press_cursor_ctx.reset();
551 switch (_mouse_state) {
552 case Pressed: // Clicked
554 switch (editor.current_mouse_mode()) {
556 /* no motion occured - simple click */
558 _mouse_changed_selection = true;
564 _mouse_changed_selection = true;
566 if (Keyboard::is_insert_note_event(ev)) {
568 double event_x, event_y;
572 group->canvas_to_item (event_x, event_y);
574 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
575 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
584 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
585 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
596 /* Only create a ghost note when we added a note, not when we were drag-selecting. */
597 create_ghost_note (ev->x, ev->y);
598 case SelectRectDragging:
599 editor.drags()->end_grab ((GdkEvent *) ev);
608 if (_mouse_changed_selection) {
609 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
610 trackview.editor().commit_reversible_selection_op ();
617 MidiRegionView::motion (GdkEventMotion* ev)
619 PublicEditor& editor = trackview.editor ();
621 if (!_note_entered) {
623 if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
624 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
625 _mouse_state != AddDragging) {
627 create_ghost_note (ev->x, ev->y);
629 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
630 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
632 update_ghost_note (ev->x, ev->y);
634 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
636 remove_ghost_note ();
637 hide_verbose_cursor ();
639 } else if (editor.current_mouse_mode() == MouseDraw) {
642 update_ghost_note (ev->x, ev->y);
645 create_ghost_note (ev->x, ev->y);
650 /* any motion immediately hides velocity text that may have been visible */
652 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
653 (*i)->hide_velocity ();
656 switch (_mouse_state) {
659 if (_pressed_button == 1) {
661 MouseMode m = editor.current_mouse_mode();
663 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
664 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
665 _mouse_state = AddDragging;
666 remove_ghost_note ();
667 hide_verbose_cursor ();
669 } else if (m == MouseContent) {
670 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
671 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
673 _mouse_changed_selection = true;
675 _mouse_state = SelectRectDragging;
677 } else if (m == MouseRange) {
678 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
679 _mouse_state = SelectVerticalDragging;
686 case SelectRectDragging:
687 case SelectVerticalDragging:
689 editor.drags()->motion_handler ((GdkEvent *) ev, false);
692 case SelectTouchDragging:
700 /* we may be dragging some non-note object (eg. patch-change, sysex)
703 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
708 MidiRegionView::scroll (GdkEventScroll* ev)
710 if (_selection.empty()) {
714 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
715 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
716 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
717 * through so that it still works for navigation.
722 hide_verbose_cursor ();
724 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
725 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
726 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
728 if (ev->direction == GDK_SCROLL_UP) {
729 change_velocities (true, fine, false, together);
730 } else if (ev->direction == GDK_SCROLL_DOWN) {
731 change_velocities (false, fine, false, together);
733 /* left, right: we don't use them */
741 MidiRegionView::key_press (GdkEventKey* ev)
743 /* since GTK bindings are generally activated on press, and since
744 detectable auto-repeat is the name of the game and only sends
745 repeated presses, carry out key actions at key press, not release.
748 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
750 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
751 _mouse_state = SelectTouchDragging;
754 } else if (ev->keyval == GDK_Escape && unmodified) {
758 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
760 bool start = (ev->keyval == GDK_comma);
761 bool end = (ev->keyval == GDK_period);
762 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
763 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
765 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
769 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
771 if (_selection.empty()) {
778 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
780 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
782 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
783 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
785 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
788 trackview.editor().commit_reversible_selection_op();
792 } else if (ev->keyval == GDK_Up) {
794 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
795 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
796 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
798 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
799 change_velocities (true, fine, allow_smush, together);
801 transpose (true, fine, allow_smush);
805 } else if (ev->keyval == GDK_Down) {
807 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
808 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
809 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
811 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
812 change_velocities (false, fine, allow_smush, together);
814 transpose (false, fine, allow_smush);
818 } else if (ev->keyval == GDK_Left) {
820 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
821 nudge_notes (false, fine);
824 } else if (ev->keyval == GDK_Right) {
826 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
827 nudge_notes (true, fine);
830 } else if (ev->keyval == GDK_c && unmodified) {
834 } else if (ev->keyval == GDK_v && unmodified) {
843 MidiRegionView::key_release (GdkEventKey* ev)
845 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
853 MidiRegionView::channel_edit ()
855 if (_selection.empty()) {
859 /* pick a note somewhat at random (since Selection is a set<>) to
860 * provide the "current" channel for the dialog.
863 uint8_t current_channel = (*_selection.begin())->note()->channel ();
864 MidiChannelDialog channel_dialog (current_channel);
865 int ret = channel_dialog.run ();
868 case Gtk::RESPONSE_OK:
874 uint8_t new_channel = channel_dialog.active_channel ();
876 start_note_diff_command (_("channel edit"));
878 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
879 Selection::iterator next = i;
881 change_note_channel (*i, new_channel);
889 MidiRegionView::velocity_edit ()
891 if (_selection.empty()) {
895 /* pick a note somewhat at random (since Selection is a set<>) to
896 * provide the "current" velocity for the dialog.
899 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
900 MidiVelocityDialog velocity_dialog (current_velocity);
901 int ret = velocity_dialog.run ();
904 case Gtk::RESPONSE_OK:
910 uint8_t new_velocity = velocity_dialog.velocity ();
912 start_note_diff_command (_("velocity edit"));
914 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
915 Selection::iterator next = i;
917 change_note_velocity (*i, new_velocity, false);
925 MidiRegionView::show_list_editor ()
928 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
930 _list_editor->present ();
933 /** Add a note to the model, and the view, at a canvas (click) coordinate.
934 * \param t time in frames relative to the position of the region
935 * \param y vertical position in pixels
936 * \param length duration of the note in beats
937 * \param snap_t true to snap t to the grid, otherwise false.
940 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bool snap_t)
942 if (length < 2 * DBL_EPSILON) {
946 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
947 MidiStreamView* const view = mtv->midi_view();
949 // Start of note in frames relative to region start
951 framecnt_t grid_frames;
952 t = snap_frame_to_grid_underneath (t, grid_frames);
955 const MidiModel::TimeType beat_time = region_frames_to_region_beats(
956 t + _region->start());
958 const double note = view->y_to_note(y);
959 const uint8_t chan = mtv->get_channel_for_add();
960 const uint8_t velocity = get_velocity_for_add(beat_time);
962 const boost::shared_ptr<NoteType> new_note(
963 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
965 if (_model->contains (new_note)) {
969 view->update_note_range(new_note->note());
971 start_note_diff_command(_("add note"));
974 note_diff_add_note (new_note, true, false);
978 play_midi_note (new_note);
982 MidiRegionView::clear_events (bool with_selection_signal)
984 clear_selection (with_selection_signal);
987 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
988 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
993 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
998 _patch_changes.clear();
1000 _optimization_iterator = _events.end();
1004 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
1008 content_connection.disconnect ();
1009 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
1010 /* Don't signal as nobody else needs to know until selection has been altered. */
1011 clear_events (false);
1013 if (_enable_display) {
1019 MidiRegionView::start_note_diff_command (string name)
1021 if (!_note_diff_command) {
1022 trackview.editor().begin_reversible_command (name);
1023 _note_diff_command = _model->new_note_diff_command (name);
1028 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1030 if (_note_diff_command) {
1031 _note_diff_command->add (note);
1034 _marked_for_selection.insert(note);
1036 if (show_velocity) {
1037 _marked_for_velocity.insert(note);
1042 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1044 if (_note_diff_command && ev->note()) {
1045 _note_diff_command->remove(ev->note());
1050 MidiRegionView::note_diff_add_change (NoteBase* ev,
1051 MidiModel::NoteDiffCommand::Property property,
1054 if (_note_diff_command) {
1055 _note_diff_command->change (ev->note(), property, val);
1060 MidiRegionView::note_diff_add_change (NoteBase* ev,
1061 MidiModel::NoteDiffCommand::Property property,
1064 if (_note_diff_command) {
1065 _note_diff_command->change (ev->note(), property, val);
1070 MidiRegionView::apply_diff (bool as_subcommand)
1073 bool commit = false;
1075 if (!_note_diff_command) {
1079 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1080 // Mark all selected notes for selection when model reloads
1081 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1082 _marked_for_selection.insert((*i)->note());
1086 midi_view()->midi_track()->midi_playlist()->region_edited(
1087 _region, _note_diff_command);
1089 if (as_subcommand) {
1090 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1092 _model->apply_command (*trackview.session(), _note_diff_command);
1096 _note_diff_command = 0;
1098 if (add_or_remove) {
1099 _marked_for_selection.clear();
1102 _marked_for_velocity.clear();
1104 trackview.editor().commit_reversible_command ();
1109 MidiRegionView::abort_command()
1111 delete _note_diff_command;
1112 _note_diff_command = 0;
1117 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1119 if (_optimization_iterator != _events.end()) {
1120 ++_optimization_iterator;
1123 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1124 return *_optimization_iterator;
1127 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1128 if ((*_optimization_iterator)->note() == note) {
1129 return *_optimization_iterator;
1136 /** This version finds any canvas note matching the supplied note. */
1138 MidiRegionView::find_canvas_note (NoteType note)
1140 Events::iterator it;
1142 for (it = _events.begin(); it != _events.end(); ++it) {
1143 if (*((*it)->note()) == note) {
1152 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1154 MidiModel::Notes notes;
1155 _model->get_notes (notes, op, val, chan_mask);
1157 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1158 NoteBase* cne = find_canvas_note (*n);
1166 MidiRegionView::redisplay_model()
1168 if (_active_notes) {
1169 // Currently recording
1170 const framecnt_t zoom = trackview.editor().get_current_zoom();
1171 if (zoom != _last_display_zoom) {
1172 /* Update resolved canvas notes to reflect changes in zoom without
1173 touching model. Leave active notes (with length 0) alone since
1174 they are being extended. */
1175 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1176 if ((*i)->note()->length() > 0) {
1180 _last_display_zoom = zoom;
1189 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1190 (*i)->invalidate ();
1193 MidiModel::ReadLock lock(_model->read_lock());
1195 MidiModel::Notes& notes (_model->notes());
1196 _optimization_iterator = _events.begin();
1198 bool empty_when_starting = _events.empty();
1200 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1202 boost::shared_ptr<NoteType> note (*n);
1206 if (note_in_region_range (note, visible)) {
1208 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1221 cne = add_note (note, visible);
1224 set<boost::shared_ptr<NoteType> >::iterator it;
1225 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1226 if (*(*it) == *note) {
1227 add_to_selection (cne);
1233 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1240 /* remove note items that are no longer valid */
1242 if (!empty_when_starting) {
1243 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1244 if (!(*i)->valid ()) {
1246 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1247 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1249 gr->remove_note (*i);
1254 i = _events.erase (i);
1262 _patch_changes.clear();
1266 display_patch_changes ();
1268 _marked_for_selection.clear ();
1269 _marked_for_velocity.clear ();
1270 _pending_note_selection.clear ();
1272 /* we may have caused _events to contain things out of order (e.g. if a note
1273 moved earlier or later). we don't generally need them in time order, but
1274 make a note that a sort is required for those cases that require it.
1277 _sort_needed = true;
1281 MidiRegionView::display_patch_changes ()
1283 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1284 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1286 for (uint8_t i = 0; i < 16; ++i) {
1287 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1291 /** @param active_channel true to display patch changes fully, false to display
1292 * them `greyed-out' (as on an inactive channel)
1295 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1297 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1299 if ((*i)->channel() != channel) {
1303 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1304 add_canvas_patch_change (*i, patch_name, active_channel);
1309 MidiRegionView::display_sysexes()
1311 bool have_periodic_system_messages = false;
1312 bool display_periodic_messages = true;
1314 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1316 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1317 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1318 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1321 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1322 have_periodic_system_messages = true;
1328 if (have_periodic_system_messages) {
1329 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1331 /* get an approximate value for the number of samples per video frame */
1333 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1335 /* if we are zoomed out beyond than the cutoff (i.e. more
1336 * frames per pixel than frames per 4 video frames), don't
1337 * show periodic sysex messages.
1340 if (zoom > (video_frame*4)) {
1341 display_periodic_messages = false;
1345 display_periodic_messages = false;
1348 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1350 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1351 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1353 Evoral::Beats time = (*i)->time();
1356 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1357 if (!display_periodic_messages) {
1365 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1366 str << int((*i)->buffer()[b]);
1367 if (b != (*i)->size() -1) {
1371 string text = str.str();
1373 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1375 double height = midi_stream_view()->contents_height();
1377 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1378 // SysEx canvas object!!!
1380 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1381 new SysEx (*this, _note_group, text, height, x, 1.0));
1383 // Show unless message is beyond the region bounds
1384 if (time - _region->start() >= _region->length() || time < _region->start()) {
1390 _sys_exes.push_back(sysex);
1394 MidiRegionView::~MidiRegionView ()
1396 in_destructor = true;
1398 hide_verbose_cursor ();
1400 delete _list_editor;
1402 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1404 if (_active_notes) {
1408 _selection_cleared_connection.disconnect ();
1411 clear_events (false);
1414 delete _note_diff_command;
1415 delete _step_edit_cursor;
1416 delete _temporary_note_group;
1420 MidiRegionView::region_resized (const PropertyChange& what_changed)
1422 RegionView::region_resized(what_changed);
1424 if (what_changed.contains (ARDOUR::Properties::position)) {
1425 _region_relative_time_converter.set_origin_b(_region->position());
1426 _region_relative_time_converter_double.set_origin_b(_region->position());
1427 set_duration(_region->length(), 0);
1428 if (_enable_display) {
1433 if (what_changed.contains (ARDOUR::Properties::start) ||
1434 what_changed.contains (ARDOUR::Properties::position)) {
1435 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1440 MidiRegionView::reset_width_dependent_items (double pixel_width)
1442 RegionView::reset_width_dependent_items(pixel_width);
1444 if (_enable_display) {
1448 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1449 if ((*x)->canvas_item()->width() >= _pixel_width) {
1456 move_step_edit_cursor (_step_edit_cursor_position);
1457 set_step_edit_cursor_width (_step_edit_cursor_width);
1461 MidiRegionView::set_height (double height)
1463 double old_height = _height;
1464 RegionView::set_height(height);
1466 apply_note_range (midi_stream_view()->lowest_note(),
1467 midi_stream_view()->highest_note(),
1468 height != old_height);
1471 name_text->raise_to_top();
1474 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1475 (*x)->set_height (midi_stream_view()->contents_height());
1478 if (_step_edit_cursor) {
1479 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1484 /** Apply the current note range from the stream view
1485 * by repositioning/hiding notes as necessary
1488 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1490 if (!_enable_display) {
1494 if (!force && _current_range_min == min && _current_range_max == max) {
1498 _current_range_min = min;
1499 _current_range_max = max;
1501 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1502 NoteBase* event = *i;
1503 boost::shared_ptr<NoteType> note (event->note());
1505 if (note->note() < _current_range_min ||
1506 note->note() > _current_range_max) {
1512 if (Note* cnote = dynamic_cast<Note*>(event)) {
1514 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1515 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1517 if (y0 < 0 || y1 >= _height) {
1518 /* During DnD, the region uses the 'old/current'
1519 * midi_stream_view()'s range and its position/height calculation.
1521 * Ideally DnD would decouple the midi_stream_view() for the
1522 * region(s) being dragged and set it to the target's range
1523 * (or in case of the drop-zone, FullRange).
1524 * but I don't see how this can be done without major rework.
1526 * For now, just prevent visual bleeding of events in case
1527 * the target-track is smaller.
1535 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1542 MidiRegionView::add_ghost (TimeAxisView& tv)
1544 double unit_position = _region->position () / samples_per_pixel;
1545 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1546 MidiGhostRegion* ghost;
1548 if (mtv && mtv->midi_view()) {
1549 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1550 to allow having midi notes on top of note lines and waveforms.
1552 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1554 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1557 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1558 ghost->add_note(*i);
1561 ghost->set_height ();
1562 ghost->set_duration (_region->length() / samples_per_pixel);
1563 ghosts.push_back (ghost);
1569 /** Begin tracking note state for successive calls to add_event
1572 MidiRegionView::begin_write()
1574 if (_active_notes) {
1575 delete[] _active_notes;
1577 _active_notes = new Note*[128];
1578 for (unsigned i = 0; i < 128; ++i) {
1579 _active_notes[i] = 0;
1584 /** Destroy note state for add_event
1587 MidiRegionView::end_write()
1589 delete[] _active_notes;
1591 _marked_for_selection.clear();
1592 _marked_for_velocity.clear();
1596 /** Resolve an active MIDI note (while recording).
1599 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1601 if (midi_view()->note_mode() != Sustained) {
1605 if (_active_notes && _active_notes[note]) {
1606 /* Set note length so update_note() works. Note this is a local note
1607 for recording, not from a model, so we can safely mess with it. */
1608 _active_notes[note]->note()->set_length(
1609 end_time - _active_notes[note]->note()->time());
1611 /* End time is relative to the region being recorded. */
1612 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1614 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1615 _active_notes[note]->set_outline_all ();
1616 _active_notes[note] = 0;
1621 /** Extend active notes to rightmost edge of region (if length is changed)
1624 MidiRegionView::extend_active_notes()
1626 if (!_active_notes) {
1630 for (unsigned i = 0; i < 128; ++i) {
1631 if (_active_notes[i]) {
1632 _active_notes[i]->set_x1(
1633 trackview.editor().sample_to_pixel(_region->length()));
1639 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1641 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1645 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1647 if (!route_ui || !route_ui->midi_track()) {
1651 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1655 /* NotePlayer deletes itself */
1659 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1661 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1662 start_playing_midi_chord(notes);
1666 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1668 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1672 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1674 if (!route_ui || !route_ui->midi_track()) {
1678 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1680 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1689 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1691 /* This is imprecise due to all the conversion conversion involved, so only
1692 hide notes if they seem to start more than one tick before the start. */
1693 const framecnt_t tick_frames = Evoral::Beats::tick().to_ticks(trackview.session()->frame_rate());
1694 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1695 const bool outside = ((note_start_frames <= -tick_frames) ||
1696 (note_start_frames >= _region->length()));
1698 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1699 (note->note() <= midi_stream_view()->highest_note());
1705 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1709 if ((sus = dynamic_cast<Note*>(note))) {
1710 update_sustained(sus, update_ghost_regions);
1711 } else if ((hit = dynamic_cast<Hit*>(note))) {
1712 update_hit(hit, update_ghost_regions);
1716 /** Update a canvas note's size from its model note.
1717 * @param ev Canvas note to update.
1718 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1721 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1723 boost::shared_ptr<NoteType> note = ev->note();
1724 const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1725 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1730 /* trim note display to not overlap the end of its region */
1732 if (note->length() > 0) {
1733 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1734 ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1);
1736 ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1);
1739 ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
1741 if (!note->length()) {
1742 if (_active_notes && note->note() < 128) {
1743 Note* const old_rect = _active_notes[note->note()];
1745 /* There is an active note on this key, so we have a stuck
1746 note. Finish the old rectangle here. */
1747 old_rect->set_x1 (x);
1748 old_rect->set_outline_all ();
1750 _active_notes[note->note()] = ev;
1752 /* outline all but right edge */
1753 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1754 ArdourCanvas::Rectangle::TOP|
1755 ArdourCanvas::Rectangle::LEFT|
1756 ArdourCanvas::Rectangle::BOTTOM));
1758 /* outline all edges */
1759 ev->set_outline_all ();
1762 // Update color in case velocity has changed
1763 ev->set_fill_color(ev->base_color());
1764 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1766 if (update_ghost_regions) {
1767 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1768 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1770 gr->update_note (ev);
1777 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1779 boost::shared_ptr<NoteType> note = ev->note();
1781 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1782 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1783 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1784 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1786 // see DnD note in MidiRegionView::apply_note_range() above
1787 if (y <= 0 || y >= _height) {
1793 ev->set_position (ArdourCanvas::Duple (x, y));
1794 ev->set_height (diamond_size);
1796 // Update color in case velocity has changed
1797 ev->set_fill_color(ev->base_color());
1798 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1800 if (update_ghost_regions) {
1801 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1802 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1804 gr->update_note (ev);
1810 /** Add a MIDI note to the view (with length).
1812 * If in sustained mode, notes with length 0 will be considered active
1813 * notes, and resolve_note should be called when the corresponding note off
1814 * event arrives, to properly display the note.
1817 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1819 NoteBase* event = 0;
1821 if (midi_view()->note_mode() == Sustained) {
1823 Note* ev_rect = new Note (*this, _note_group, note);
1825 update_sustained (ev_rect);
1829 } else if (midi_view()->note_mode() == Percussive) {
1831 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1833 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1835 update_hit (ev_diamond);
1844 MidiGhostRegion* gr;
1846 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1847 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1848 gr->add_note(event);
1852 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1853 note_selected(event, true);
1856 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1857 event->show_velocity();
1860 event->on_channel_selection_change (get_selected_channels());
1861 _events.push_back(event);
1870 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1871 MidiStreamView* const view = mtv->midi_view();
1873 view->update_note_range (note->note());
1878 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1879 Evoral::Beats pos, Evoral::Beats len)
1881 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1883 /* potentially extend region to hold new note */
1885 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1886 framepos_t region_end = _region->last_frame();
1888 if (end_frame > region_end) {
1889 _region->set_length (end_frame - _region->position());
1892 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1893 MidiStreamView* const view = mtv->midi_view();
1895 view->update_note_range(new_note->note());
1897 _marked_for_selection.clear ();
1899 start_note_diff_command (_("step add"));
1902 note_diff_add_note (new_note, true, false);
1906 // last_step_edit_note = new_note;
1910 MidiRegionView::step_sustain (Evoral::Beats beats)
1912 change_note_lengths (false, false, beats, false, true);
1915 /** Add a new patch change flag to the canvas.
1916 * @param patch the patch change to add
1917 * @param the text to display in the flag
1918 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1921 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1923 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1924 const double x = trackview.editor().sample_to_pixel (region_frames);
1926 double const height = midi_stream_view()->contents_height();
1928 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1929 // so we need to do something more sophisticated to keep its color
1930 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1933 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1934 new PatchChange(*this, group,
1941 if (patch_change->item().width() < _pixel_width) {
1942 // Show unless patch change is beyond the region bounds
1943 if (region_frames < 0 || region_frames >= _region->length()) {
1944 patch_change->hide();
1946 patch_change->show();
1949 patch_change->hide ();
1952 _patch_changes.push_back (patch_change);
1955 MIDI::Name::PatchPrimaryKey
1956 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1958 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1961 /// Return true iff @p pc applies to the given time on the given channel.
1963 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1965 return pc->time() <= time && pc->channel() == channel;
1969 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1971 // The earliest event not before time
1972 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1974 // Go backwards until we find the latest PC for this channel, or the start
1975 while (i != _model->patch_changes().begin() &&
1976 (i == _model->patch_changes().end() ||
1977 !patch_applies(*i, time, channel))) {
1981 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1982 key.set_bank((*i)->bank());
1983 key.set_program((*i)->program ());
1991 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1993 string name = _("alter patch change");
1994 trackview.editor().begin_reversible_command (name);
1995 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1997 if (pc.patch()->program() != new_patch.program()) {
1998 c->change_program (pc.patch (), new_patch.program());
2001 int const new_bank = new_patch.bank();
2002 if (pc.patch()->bank() != new_bank) {
2003 c->change_bank (pc.patch (), new_bank);
2006 _model->apply_command (*trackview.session(), c);
2007 trackview.editor().commit_reversible_command ();
2009 _patch_changes.clear ();
2010 display_patch_changes ();
2014 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2016 string name = _("alter patch change");
2017 trackview.editor().begin_reversible_command (name);
2018 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2020 if (old_change->time() != new_change.time()) {
2021 c->change_time (old_change, new_change.time());
2024 if (old_change->channel() != new_change.channel()) {
2025 c->change_channel (old_change, new_change.channel());
2028 if (old_change->program() != new_change.program()) {
2029 c->change_program (old_change, new_change.program());
2032 if (old_change->bank() != new_change.bank()) {
2033 c->change_bank (old_change, new_change.bank());
2036 _model->apply_command (*trackview.session(), c);
2037 trackview.editor().commit_reversible_command ();
2039 _patch_changes.clear ();
2040 display_patch_changes ();
2043 /** Add a patch change to the region.
2044 * @param t Time in frames relative to region position
2045 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2046 * MidiTimeAxisView::get_channel_for_add())
2049 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2051 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2052 string name = _("add patch change");
2054 trackview.editor().begin_reversible_command (name);
2055 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2056 c->add (MidiModel::PatchChangePtr (
2057 new Evoral::PatchChange<Evoral::Beats> (
2058 absolute_frames_to_source_beats (_region->position() + t),
2059 mtv->get_channel_for_add(), patch.program(), patch.bank()
2064 _model->apply_command (*trackview.session(), c);
2065 trackview.editor().commit_reversible_command ();
2067 _patch_changes.clear ();
2068 display_patch_changes ();
2072 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2074 trackview.editor().begin_reversible_command (_("move patch change"));
2075 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2076 c->change_time (pc.patch (), t);
2077 _model->apply_command (*trackview.session(), c);
2078 trackview.editor().commit_reversible_command ();
2080 _patch_changes.clear ();
2081 display_patch_changes ();
2085 MidiRegionView::delete_patch_change (PatchChange* pc)
2087 trackview.editor().begin_reversible_command (_("delete patch change"));
2088 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2089 c->remove (pc->patch ());
2090 _model->apply_command (*trackview.session(), c);
2091 trackview.editor().commit_reversible_command ();
2093 _patch_changes.clear ();
2094 display_patch_changes ();
2098 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2100 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2102 key.set_bank(key.bank() + delta);
2104 key.set_program(key.program() + delta);
2106 change_patch_change(patch, key);
2110 MidiRegionView::note_deleted (NoteBase* cne)
2112 if (_selection.empty()) {
2116 _selection.erase (cne);
2120 MidiRegionView::delete_selection()
2122 if (_selection.empty()) {
2126 start_note_diff_command (_("delete selection"));
2128 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2129 if ((*i)->selected()) {
2130 _note_diff_command->remove((*i)->note());
2137 hide_verbose_cursor ();
2141 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2143 start_note_diff_command (_("delete note"));
2144 _note_diff_command->remove (n);
2147 hide_verbose_cursor ();
2151 MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
2153 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2155 Selection::iterator tmp = i;
2158 (*i)->set_selected (false);
2159 (*i)->hide_velocity ();
2160 _selection.erase (i);
2168 if (!ev && _entered) {
2169 // Clearing selection entirely, ungrab keyboard
2170 Keyboard::magic_widget_drop_focus();
2171 _grabbed_keyboard = false;
2174 /* this does not change the status of this regionview w.r.t the editor
2179 SelectionCleared (this); /* EMIT SIGNAL */
2184 MidiRegionView::unique_select(NoteBase* ev)
2186 const bool selection_was_empty = _selection.empty();
2188 clear_selection_except (ev);
2190 /* don't bother with checking to see if we should remove this
2191 regionview from the editor selection, since we're about to add
2192 another note, and thus put/keep this regionview in the editor
2196 if (!ev->selected()) {
2197 add_to_selection (ev);
2198 if (selection_was_empty && _entered) {
2199 // Grab keyboard for moving notes with arrow keys
2200 Keyboard::magic_widget_grab_focus();
2201 _grabbed_keyboard = true;
2207 MidiRegionView::select_all_notes ()
2211 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2212 add_to_selection (*i);
2217 MidiRegionView::select_range (framepos_t start, framepos_t end)
2221 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2222 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2223 if (t >= start && t <= end) {
2224 add_to_selection (*i);
2230 MidiRegionView::invert_selection ()
2232 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2233 if ((*i)->selected()) {
2234 remove_from_selection(*i);
2236 add_to_selection (*i);
2241 /** Used for selection undo/redo.
2242 The requested notes most likely won't exist in the view until the next model redisplay.
2245 MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
2248 list<boost::shared_ptr<NoteType> >::iterator n;
2250 for (n = notes.begin(); n != notes.end(); ++n) {
2251 if ((cne = find_canvas_note(*(*n))) != 0) {
2252 add_to_selection (cne);
2254 _pending_note_selection.insert(*n);
2260 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2262 bool have_selection = !_selection.empty();
2263 uint8_t low_note = 127;
2264 uint8_t high_note = 0;
2265 MidiModel::Notes& notes (_model->notes());
2266 _optimization_iterator = _events.begin();
2268 if (extend && !have_selection) {
2272 /* scan existing selection to get note range */
2274 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2275 if ((*i)->note()->note() < low_note) {
2276 low_note = (*i)->note()->note();
2278 if ((*i)->note()->note() > high_note) {
2279 high_note = (*i)->note()->note();
2286 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2287 /* only note previously selected is the one we are
2288 * reselecting. treat this as cancelling the selection.
2295 low_note = min (low_note, notenum);
2296 high_note = max (high_note, notenum);
2299 _no_sound_notes = true;
2301 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2303 boost::shared_ptr<NoteType> note (*n);
2305 bool select = false;
2307 if (((1 << note->channel()) & channel_mask) != 0) {
2309 if ((note->note() >= low_note && note->note() <= high_note)) {
2312 } else if (note->note() == notenum) {
2318 if ((cne = find_canvas_note (note)) != 0) {
2319 // extend is false because we've taken care of it,
2320 // since it extends by time range, not pitch.
2321 note_selected (cne, add, false);
2325 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2329 _no_sound_notes = false;
2333 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2335 MidiModel::Notes& notes (_model->notes());
2336 _optimization_iterator = _events.begin();
2338 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2340 boost::shared_ptr<NoteType> note (*n);
2343 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2344 if ((cne = find_canvas_note (note)) != 0) {
2345 if (cne->selected()) {
2346 note_deselected (cne);
2348 note_selected (cne, true, false);
2356 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2359 clear_selection_except (ev);
2360 if (!_selection.empty()) {
2361 PublicEditor& editor (trackview.editor());
2362 editor.get_selection().add (this);
2368 if (!ev->selected()) {
2369 add_to_selection (ev);
2373 /* find end of latest note selected, select all between that and the start of "ev" */
2375 Evoral::Beats earliest = Evoral::MaxBeats;
2376 Evoral::Beats latest = Evoral::Beats();
2378 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2379 if ((*i)->note()->end_time() > latest) {
2380 latest = (*i)->note()->end_time();
2382 if ((*i)->note()->time() < earliest) {
2383 earliest = (*i)->note()->time();
2387 if (ev->note()->end_time() > latest) {
2388 latest = ev->note()->end_time();
2391 if (ev->note()->time() < earliest) {
2392 earliest = ev->note()->time();
2395 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2397 /* find notes entirely within OR spanning the earliest..latest range */
2399 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2400 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2401 add_to_selection (*i);
2409 MidiRegionView::note_deselected(NoteBase* ev)
2411 remove_from_selection (ev);
2415 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2417 PublicEditor& editor = trackview.editor();
2419 // Convert to local coordinates
2420 const framepos_t p = _region->position();
2421 const double y = midi_view()->y_position();
2422 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2423 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2424 const double y0 = max(0.0, gy0 - y);
2425 const double y1 = max(0.0, gy1 - y);
2427 // TODO: Make this faster by storing the last updated selection rect, and only
2428 // adjusting things that are in the area that appears/disappeared.
2429 // We probably need a tree to be able to find events in O(log(n)) time.
2431 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2432 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2433 // Rectangles intersect
2434 if (!(*i)->selected()) {
2435 add_to_selection (*i);
2437 } else if ((*i)->selected() && !extend) {
2438 // Rectangles do not intersect
2439 remove_from_selection (*i);
2443 typedef RouteTimeAxisView::AutomationTracks ATracks;
2444 typedef std::list<Selectable*> Selectables;
2446 /* Add control points to selection. */
2447 const ATracks& atracks = midi_view()->automation_tracks();
2448 Selectables selectables;
2449 editor.get_selection().clear_points();
2450 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2451 a->second->get_selectables(start, end, gy0, gy1, selectables);
2452 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2453 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2455 editor.get_selection().add(cp);
2458 a->second->set_selected_points(editor.get_selection().points);
2463 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2469 // TODO: Make this faster by storing the last updated selection rect, and only
2470 // adjusting things that are in the area that appears/disappeared.
2471 // We probably need a tree to be able to find events in O(log(n)) time.
2473 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2474 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2475 // within y- (note-) range
2476 if (!(*i)->selected()) {
2477 add_to_selection (*i);
2479 } else if ((*i)->selected() && !extend) {
2480 remove_from_selection (*i);
2486 MidiRegionView::remove_from_selection (NoteBase* ev)
2488 Selection::iterator i = _selection.find (ev);
2490 if (i != _selection.end()) {
2491 _selection.erase (i);
2492 if (_selection.empty() && _grabbed_keyboard) {
2494 Keyboard::magic_widget_drop_focus();
2495 _grabbed_keyboard = false;
2499 ev->set_selected (false);
2500 ev->hide_velocity ();
2502 if (_selection.empty()) {
2503 PublicEditor& editor (trackview.editor());
2504 editor.get_selection().remove (this);
2509 MidiRegionView::add_to_selection (NoteBase* ev)
2511 const bool selection_was_empty = _selection.empty();
2513 if (_selection.insert (ev).second) {
2514 ev->set_selected (true);
2515 start_playing_midi_note ((ev)->note());
2516 if (selection_was_empty && _entered) {
2517 // Grab keyboard for moving notes with arrow keys
2518 Keyboard::magic_widget_grab_focus();
2519 _grabbed_keyboard = true;
2523 if (selection_was_empty) {
2524 PublicEditor& editor (trackview.editor());
2525 editor.get_selection().add (this);
2530 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2532 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2533 PossibleChord to_play;
2534 Evoral::Beats earliest = Evoral::MaxBeats;
2536 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2537 if ((*i)->note()->time() < earliest) {
2538 earliest = (*i)->note()->time();
2542 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2543 if ((*i)->note()->time() == earliest) {
2544 to_play.push_back ((*i)->note());
2546 (*i)->move_event(dx, dy);
2549 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2551 if (to_play.size() > 1) {
2553 PossibleChord shifted;
2555 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2556 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2557 moved_note->set_note (moved_note->note() + cumulative_dy);
2558 shifted.push_back (moved_note);
2561 start_playing_midi_chord (shifted);
2563 } else if (!to_play.empty()) {
2565 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2566 moved_note->set_note (moved_note->note() + cumulative_dy);
2567 start_playing_midi_note (moved_note);
2573 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2575 uint8_t lowest_note_in_selection = 127;
2576 uint8_t highest_note_in_selection = 0;
2577 uint8_t highest_note_difference = 0;
2579 // find highest and lowest notes first
2581 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2582 uint8_t pitch = (*i)->note()->note();
2583 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2584 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2588 cerr << "dnote: " << (int) dnote << endl;
2589 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2590 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2591 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2592 << int(highest_note_in_selection) << endl;
2593 cerr << "selection size: " << _selection.size() << endl;
2594 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2597 // Make sure the note pitch does not exceed the MIDI standard range
2598 if (highest_note_in_selection + dnote > 127) {
2599 highest_note_difference = highest_note_in_selection - 127;
2602 start_note_diff_command (_("move notes"));
2604 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2606 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2607 Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
2613 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2615 uint8_t original_pitch = (*i)->note()->note();
2616 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2618 // keep notes in standard midi range
2619 clamp_to_0_127(new_pitch);
2621 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2622 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2624 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2629 // care about notes being moved beyond the upper/lower bounds on the canvas
2630 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2631 highest_note_in_selection > midi_stream_view()->highest_note()) {
2632 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2636 /** @param x Pixel relative to the region position.
2637 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2638 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2639 * @return Snapped frame relative to the region position.
2642 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2644 PublicEditor& editor (trackview.editor());
2645 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2648 /** @param x Pixel relative to the region position.
2649 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2650 * @return Snapped pixel relative to the region position.
2653 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2655 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2659 MidiRegionView::get_position_pixels()
2661 framepos_t region_frame = get_position();
2662 return trackview.editor().sample_to_pixel(region_frame);
2666 MidiRegionView::get_end_position_pixels()
2668 framepos_t frame = get_position() + get_duration ();
2669 return trackview.editor().sample_to_pixel(frame);
2673 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2675 /* the time converter will return the frame corresponding to `beats'
2676 relative to the start of the source. The start of the source
2677 is an implied position given by region->position - region->start
2679 const framepos_t source_start = _region->position() - _region->start();
2680 return source_start + _source_relative_time_converter.to (beats);
2684 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2686 /* the `frames' argument needs to be converted into a frame count
2687 relative to the start of the source before being passed in to the
2690 const framepos_t source_start = _region->position() - _region->start();
2691 return _source_relative_time_converter.from (frames - source_start);
2695 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2697 return _region_relative_time_converter.to(beats);
2701 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2703 return _region_relative_time_converter.from(frames);
2707 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2709 return _region_relative_time_converter_double.from(frames);
2713 MidiRegionView::begin_resizing (bool /*at_front*/)
2715 _resize_data.clear();
2717 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2718 Note *note = dynamic_cast<Note*> (*i);
2720 // only insert CanvasNotes into the map
2722 NoteResizeData *resize_data = new NoteResizeData();
2723 resize_data->note = note;
2725 // create a new SimpleRect from the note which will be the resize preview
2726 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2727 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2729 // calculate the colors: get the color settings
2730 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2731 UIConfiguration::instance().color ("midi note selected"),
2734 // make the resize preview notes more transparent and bright
2735 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2737 // calculate color based on note velocity
2738 resize_rect->set_fill_color (UINT_INTERPOLATE(
2739 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2743 resize_rect->set_outline_color (NoteBase::calculate_outline (
2744 UIConfiguration::instance().color ("midi note selected")));
2746 resize_data->resize_rect = resize_rect;
2747 _resize_data.push_back(resize_data);
2752 /** Update resizing notes while user drags.
2753 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2754 * @param at_front which end of the note (true == note on, false == note off)
2755 * @param delta_x change in mouse position since the start of the drag
2756 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2757 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2758 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2759 * as the \a primary note.
2760 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2761 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2764 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2766 bool cursor_set = false;
2767 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2769 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2770 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2771 Note* canvas_note = (*i)->note;
2776 current_x = canvas_note->x0() + delta_x + snap_delta;
2778 current_x = primary->x0() + delta_x + snap_delta;
2782 current_x = canvas_note->x1() + delta_x + snap_delta;
2784 current_x = primary->x1() + delta_x + snap_delta;
2788 if (current_x < 0) {
2789 // This works even with snapping because RegionView::snap_frame_to_frame()
2790 // snaps forward if the snapped sample is before the beginning of the region
2793 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2794 current_x = trackview.editor().sample_to_pixel(_region->length());
2799 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2801 resize_rect->set_x0 (current_x - snap_delta);
2803 resize_rect->set_x1 (canvas_note->x1());
2806 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2808 resize_rect->set_x1 (current_x - snap_delta);
2810 resize_rect->set_x0 (canvas_note->x0());
2814 /* Convert snap delta from pixels to beats. */
2815 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2816 double snap_delta_beats = 0.0;
2819 /* negative beat offsets aren't allowed */
2820 if (snap_delta_samps > 0) {
2821 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2822 } else if (snap_delta_samps < 0) {
2823 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2827 const double snapped_x = (with_snap ? snap_pixel_to_sample (current_x, ensure_snap) : trackview.editor ().pixel_to_sample (current_x));
2828 Evoral::Beats beats = region_frames_to_region_beats (snapped_x);
2829 Evoral::Beats len = Evoral::Beats();
2832 if (beats < canvas_note->note()->end_time()) {
2833 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2834 len += canvas_note->note()->length();
2837 if (beats >= canvas_note->note()->time()) {
2838 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2842 len = std::max(Evoral::Beats(1 / 512.0), len);
2845 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2846 show_verbose_cursor (buf, 0, 0);
2855 /** Finish resizing notes when the user releases the mouse button.
2856 * Parameters the same as for \a update_resizing().
2859 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2861 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2863 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2864 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2866 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2867 Note* canvas_note = (*i)->note;
2868 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2870 /* Get the new x position for this resize, which is in pixels relative
2871 * to the region position.
2878 current_x = canvas_note->x0() + delta_x + snap_delta;
2880 current_x = primary->x0() + delta_x + snap_delta;
2884 current_x = canvas_note->x1() + delta_x + snap_delta;
2886 current_x = primary->x1() + delta_x + snap_delta;
2890 if (current_x < 0) {
2893 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2894 current_x = trackview.editor().sample_to_pixel(_region->length());
2897 /* Convert snap delta from pixels to beats with sign. */
2898 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2899 double snap_delta_beats = 0.0;
2902 if (snap_delta_samps > 0) {
2903 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2904 } else if (snap_delta_samps < 0) {
2905 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2909 /* Convert the new x position to a frame within the source */
2910 framepos_t current_fr;
2912 current_fr = snap_pixel_to_sample (current_x, ensure_snap) + _region->start ();
2914 current_fr = trackview.editor().pixel_to_sample (current_x) + _region->start ();
2917 /* and then to beats */
2918 const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr);
2920 if (at_front && x_beats < canvas_note->note()->end_time()) {
2921 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2922 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2923 len += canvas_note->note()->length();
2926 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2931 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2932 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2933 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2940 _resize_data.clear();
2945 MidiRegionView::abort_resizing ()
2947 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2948 delete (*i)->resize_rect;
2952 _resize_data.clear ();
2956 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2958 uint8_t new_velocity;
2961 new_velocity = event->note()->velocity() + velocity;
2962 clamp_to_0_127(new_velocity);
2964 new_velocity = velocity;
2967 event->set_selected (event->selected()); // change color
2969 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2973 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2978 new_note = event->note()->note() + note;
2983 clamp_to_0_127 (new_note);
2984 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2988 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2990 bool change_start = false;
2991 bool change_length = false;
2992 Evoral::Beats new_start;
2993 Evoral::Beats new_length;
2995 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2997 front_delta: if positive - move the start of the note later in time (shortening it)
2998 if negative - move the start of the note earlier in time (lengthening it)
3000 end_delta: if positive - move the end of the note later in time (lengthening it)
3001 if negative - move the end of the note earlier in time (shortening it)
3004 if (!!front_delta) {
3005 if (front_delta < 0) {
3007 if (event->note()->time() < -front_delta) {
3008 new_start = Evoral::Beats();
3010 new_start = event->note()->time() + front_delta; // moves earlier
3013 /* start moved toward zero, so move the end point out to where it used to be.
3014 Note that front_delta is negative, so this increases the length.
3017 new_length = event->note()->length() - front_delta;
3018 change_start = true;
3019 change_length = true;
3023 Evoral::Beats new_pos = event->note()->time() + front_delta;
3025 if (new_pos < event->note()->end_time()) {
3026 new_start = event->note()->time() + front_delta;
3027 /* start moved toward the end, so move the end point back to where it used to be */
3028 new_length = event->note()->length() - front_delta;
3029 change_start = true;
3030 change_length = true;
3037 bool can_change = true;
3038 if (end_delta < 0) {
3039 if (event->note()->length() < -end_delta) {
3045 new_length = event->note()->length() + end_delta;
3046 change_length = true;
3051 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3054 if (change_length) {
3055 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3060 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3062 uint8_t new_channel;
3066 if (event->note()->channel() < -chn) {
3069 new_channel = event->note()->channel() + chn;
3072 new_channel = event->note()->channel() + chn;
3075 new_channel = (uint8_t) chn;
3078 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3082 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3084 Evoral::Beats new_time;
3088 if (event->note()->time() < -delta) {
3089 new_time = Evoral::Beats();
3091 new_time = event->note()->time() + delta;
3094 new_time = event->note()->time() + delta;
3100 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3104 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3106 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3110 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3115 if (_selection.empty()) {
3130 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3131 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3137 start_note_diff_command (_("change velocities"));
3139 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3140 Selection::iterator next = i;
3144 if (i == _selection.begin()) {
3145 change_note_velocity (*i, delta, true);
3146 value = (*i)->note()->velocity() + delta;
3148 change_note_velocity (*i, value, false);
3152 change_note_velocity (*i, delta, true);
3161 if (!_selection.empty()) {
3163 snprintf (buf, sizeof (buf), "Vel %d",
3164 (int) (*_selection.begin())->note()->velocity());
3165 show_verbose_cursor (buf, 10, 10);
3171 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3173 if (_selection.empty()) {
3190 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3192 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3196 if ((int8_t) (*i)->note()->note() + delta > 127) {
3203 start_note_diff_command (_("transpose"));
3205 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3206 Selection::iterator next = i;
3208 change_note_note (*i, delta, true);
3216 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3220 delta = Evoral::Beats(1.0/128.0);
3222 /* grab the current grid distance */
3223 delta = get_grid_beats(_region->position());
3231 start_note_diff_command (_("change note lengths"));
3233 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3234 Selection::iterator next = i;
3237 /* note the negation of the delta for start */
3240 (start ? -delta : Evoral::Beats()),
3241 (end ? delta : Evoral::Beats()));
3250 MidiRegionView::nudge_notes (bool forward, bool fine)
3252 if (_selection.empty()) {
3256 /* pick a note as the point along the timeline to get the nudge distance.
3257 its not necessarily the earliest note, so we may want to pull the notes out
3258 into a vector and sort before using the first one.
3261 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3262 Evoral::Beats delta;
3266 /* non-fine, move by 1 bar regardless of snap */
3267 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
3269 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3271 /* grid is off - use nudge distance */
3274 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3275 delta = region_frames_to_region_beats (fabs ((double)distance));
3281 framepos_t next_pos = ref_point;
3284 if (max_framepos - 1 < next_pos) {
3288 if (next_pos == 0) {
3294 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3295 const framecnt_t distance = ref_point - next_pos;
3296 delta = region_frames_to_region_beats (fabs ((double)distance));
3307 start_note_diff_command (_("nudge"));
3309 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3310 Selection::iterator next = i;
3312 change_note_time (*i, delta, true);
3320 MidiRegionView::change_channel(uint8_t channel)
3322 start_note_diff_command(_("change channel"));
3323 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3324 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3332 MidiRegionView::note_entered(NoteBase* ev)
3334 _note_entered = true;
3336 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3338 if (_mouse_state == SelectTouchDragging) {
3340 note_selected (ev, true);
3342 } else if (editor->current_mouse_mode() == MouseContent) {
3344 remove_ghost_note ();
3345 show_verbose_cursor (ev->note ());
3347 } else if (editor->current_mouse_mode() == MouseDraw) {
3349 remove_ghost_note ();
3350 show_verbose_cursor (ev->note ());
3355 MidiRegionView::note_left (NoteBase*)
3357 _note_entered = false;
3359 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3360 (*i)->hide_velocity ();
3363 hide_verbose_cursor ();
3367 MidiRegionView::patch_entered (PatchChange* p)
3370 /* XXX should get patch name if we can */
3371 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3372 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3373 << _("Channel ") << ((int) p->patch()->channel() + 1);
3374 show_verbose_cursor (s.str(), 10, 20);
3375 p->item().grab_focus();
3379 MidiRegionView::patch_left (PatchChange *)
3381 hide_verbose_cursor ();
3382 /* focus will transfer back via the enter-notify event sent to this
3388 MidiRegionView::sysex_entered (SysEx* p)
3392 // need a way to extract text from p->_flag->_text
3394 // show_verbose_cursor (s.str(), 10, 20);
3395 p->item().grab_focus();
3399 MidiRegionView::sysex_left (SysEx *)
3401 hide_verbose_cursor ();
3402 /* focus will transfer back via the enter-notify event sent to this
3408 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3410 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3411 Editing::MouseMode mm = editor->current_mouse_mode();
3412 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3414 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3415 if (can_set_cursor && ctx) {
3416 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3417 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3418 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3419 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3421 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3427 MidiRegionView::get_fill_color() const
3429 const std::string mod_name = (_dragging ? "dragging region" :
3430 trackview.editor().internal_editing() ? "editable region" :
3433 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3434 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3435 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3436 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3438 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3442 MidiRegionView::midi_channel_mode_changed ()
3444 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3445 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3446 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3448 if (mode == ForceChannel) {
3449 mask = 0xFFFF; // Show all notes as active (below)
3452 // Update notes for selection
3453 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3454 (*i)->on_channel_selection_change (mask);
3457 _patch_changes.clear ();
3458 display_patch_changes ();
3462 MidiRegionView::instrument_settings_changed ()
3468 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3470 if (_selection.empty()) {
3474 PublicEditor& editor (trackview.editor());
3478 /* XXX what to do ? */
3482 editor.get_cut_buffer().add (selection_as_cut_buffer());
3490 start_note_diff_command();
3492 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3499 note_diff_remove_note (*i);
3509 MidiRegionView::selection_as_cut_buffer () const
3513 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3514 NoteType* n = (*i)->note().get();
3515 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3518 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3524 /** This method handles undo */
3526 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
3528 bool commit = false;
3529 // Paste notes, if available
3530 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3531 if (m != selection.midi_notes.end()) {
3532 ctx.counts.increase_n_notes();
3533 if (!(*m)->empty()) { commit = true; }
3534 paste_internal(pos, ctx.count, ctx.times, **m);
3537 // Paste control points to automation children, if available
3538 typedef RouteTimeAxisView::AutomationTracks ATracks;
3539 const ATracks& atracks = midi_view()->automation_tracks();
3540 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3541 if (a->second->paste(pos, selection, ctx)) {
3547 trackview.editor().commit_reversible_command ();
3552 /** This method handles undo */
3554 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3560 start_note_diff_command (_("paste"));
3562 const Evoral::Beats snap_beats = get_grid_beats(pos);
3563 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3564 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3565 const Evoral::Beats duration = last_time - first_time;
3566 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3567 const Evoral::Beats paste_offset = snap_duration * paste_count;
3568 const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3569 Evoral::Beats end_point = Evoral::Beats();
3571 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3574 duration, pos, _region->position(),
3579 for (int n = 0; n < (int) times; ++n) {
3581 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3583 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3584 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3585 copied_note->set_id (Evoral::next_event_id());
3587 /* make all newly added notes selected */
3589 note_diff_add_note (copied_note, true);
3590 end_point = copied_note->end_time();
3594 /* if we pasted past the current end of the region, extend the region */
3596 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3597 framepos_t region_end = _region->position() + _region->length() - 1;
3599 if (end_frame > region_end) {
3601 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3603 _region->clear_changes ();
3604 _region->set_length (end_frame - _region->position());
3605 trackview.session()->add_command (new StatefulDiffCommand (_region));
3611 struct EventNoteTimeEarlyFirstComparator {
3612 bool operator() (NoteBase* a, NoteBase* b) {
3613 return a->note()->time() < b->note()->time();
3618 MidiRegionView::time_sort_events ()
3620 if (!_sort_needed) {
3624 EventNoteTimeEarlyFirstComparator cmp;
3627 _sort_needed = false;
3631 MidiRegionView::goto_next_note (bool add_to_selection)
3633 bool use_next = false;
3635 if (_events.back()->selected()) {
3639 time_sort_events ();
3641 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3642 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3644 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3645 if ((*i)->selected()) {
3648 } else if (use_next) {
3649 if (channel_mask & (1 << (*i)->note()->channel())) {
3650 if (!add_to_selection) {
3653 note_selected (*i, true, false);
3660 /* use the first one */
3662 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3663 unique_select (_events.front());
3668 MidiRegionView::goto_previous_note (bool add_to_selection)
3670 bool use_next = false;
3672 if (_events.front()->selected()) {
3676 time_sort_events ();
3678 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3679 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3681 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3682 if ((*i)->selected()) {
3685 } else if (use_next) {
3686 if (channel_mask & (1 << (*i)->note()->channel())) {
3687 if (!add_to_selection) {
3690 note_selected (*i, true, false);
3697 /* use the last one */
3699 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3700 unique_select (*(_events.rbegin()));
3705 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3707 bool had_selected = false;
3709 time_sort_events ();
3711 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3712 if ((*i)->selected()) {
3713 selected.insert ((*i)->note());
3714 had_selected = true;
3718 if (allow_all_if_none_selected && !had_selected) {
3719 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3720 selected.insert ((*i)->note());
3726 MidiRegionView::update_ghost_note (double x, double y)
3728 x = std::max(0.0, x);
3730 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3735 _note_group->canvas_to_item (x, y);
3737 PublicEditor& editor = trackview.editor ();
3739 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3740 framecnt_t grid_frames;
3741 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3743 /* calculate time in beats relative to start of source */
3744 const Evoral::Beats length = get_grid_beats(unsnapped_frame);
3745 const Evoral::Beats time = std::max(
3747 absolute_frames_to_source_beats (f + _region->position ()));
3749 _ghost_note->note()->set_time (time);
3750 _ghost_note->note()->set_length (length);
3751 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3752 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3753 _ghost_note->note()->set_velocity (get_velocity_for_add (time));
3755 /* the ghost note does not appear in ghost regions, so pass false in here */
3756 update_note (_ghost_note, false);
3758 show_verbose_cursor (_ghost_note->note ());
3762 MidiRegionView::create_ghost_note (double x, double y)
3764 remove_ghost_note ();
3766 boost::shared_ptr<NoteType> g (new NoteType);
3767 if (midi_view()->note_mode() == Sustained) {
3768 _ghost_note = new Note (*this, _note_group, g);
3770 _ghost_note = new Hit (*this, _note_group, 10, g);
3772 _ghost_note->set_ignore_events (true);
3773 _ghost_note->set_outline_color (0x000000aa);
3774 update_ghost_note (x, y);
3775 _ghost_note->show ();
3777 show_verbose_cursor (_ghost_note->note ());
3781 MidiRegionView::remove_ghost_note ()
3788 MidiRegionView::hide_verbose_cursor ()
3790 trackview.editor().verbose_cursor()->hide ();
3791 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3793 mtv->set_note_highlight (NO_MIDI_NOTE);
3798 MidiRegionView::snap_changed ()
3804 create_ghost_note (_last_ghost_x, _last_ghost_y);
3808 MidiRegionView::drop_down_keys ()
3810 _mouse_state = None;
3814 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3816 /* XXX: This is dead code. What was it for? */
3818 double note = midi_stream_view()->y_to_note(y);
3820 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3822 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3824 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3825 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3826 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3827 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3832 bool add_mrv_selection = false;
3834 if (_selection.empty()) {
3835 add_mrv_selection = true;
3838 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3839 if (_selection.insert (*i).second) {
3840 (*i)->set_selected (true);
3844 if (add_mrv_selection) {
3845 PublicEditor& editor (trackview.editor());
3846 editor.get_selection().add (this);
3851 MidiRegionView::color_handler ()
3853 RegionView::color_handler ();
3855 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3856 (*i)->set_selected ((*i)->selected()); // will change color
3859 /* XXX probably more to do here */
3863 MidiRegionView::enable_display (bool yn)
3865 RegionView::enable_display (yn);
3872 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3874 if (_step_edit_cursor == 0) {
3875 ArdourCanvas::Item* const group = get_canvas_group();
3877 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3878 _step_edit_cursor->set_y0 (0);
3879 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3880 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3881 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3884 move_step_edit_cursor (pos);
3885 _step_edit_cursor->show ();
3889 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3891 _step_edit_cursor_position = pos;
3893 if (_step_edit_cursor) {
3894 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3895 _step_edit_cursor->set_x0 (pixel);
3896 set_step_edit_cursor_width (_step_edit_cursor_width);
3901 MidiRegionView::hide_step_edit_cursor ()
3903 if (_step_edit_cursor) {
3904 _step_edit_cursor->hide ();
3909 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3911 _step_edit_cursor_width = beats;
3913 if (_step_edit_cursor) {
3914 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
3918 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3919 * @param w Source that the data will end up in.
3922 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3924 if (!_active_notes) {
3925 /* we aren't actively being recorded to */
3929 boost::shared_ptr<MidiSource> src = w.lock ();
3930 if (!src || src != midi_region()->midi_source()) {
3931 /* recorded data was not destined for our source */
3935 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3937 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3939 framepos_t back = max_framepos;
3941 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3942 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3944 if (ev.is_channel_event()) {
3945 if (get_channel_mode() == FilterChannels) {
3946 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3952 /* convert from session frames to source beats */
3953 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3954 ev.time() - src->timeline_position() + _region->start());
3956 if (ev.type() == MIDI_CMD_NOTE_ON) {
3957 boost::shared_ptr<NoteType> note (
3958 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3960 add_note (note, true);
3962 /* fix up our note range */
3963 if (ev.note() < _current_range_min) {
3964 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3965 } else if (ev.note() > _current_range_max) {
3966 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3969 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3970 resolve_note (ev.note (), time_beats);
3976 midi_stream_view()->check_record_layers (region(), back);
3980 MidiRegionView::trim_front_starting ()
3982 /* Reparent the note group to the region view's parent, so that it doesn't change
3983 when the region view is trimmed.
3985 _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3986 _temporary_note_group->move (group->position ());
3987 _note_group->reparent (_temporary_note_group);
3991 MidiRegionView::trim_front_ending ()
3993 _note_group->reparent (group);
3994 delete _temporary_note_group;
3995 _temporary_note_group = 0;
3997 if (_region->start() < 0) {
3998 /* Trim drag made start time -ve; fix this */
3999 midi_region()->fix_negative_start ();
4004 MidiRegionView::edit_patch_change (PatchChange* pc)
4006 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4008 int response = d.run();
4011 case Gtk::RESPONSE_ACCEPT:
4013 case Gtk::RESPONSE_REJECT:
4014 delete_patch_change (pc);
4020 change_patch_change (pc->patch(), d.patch ());
4024 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4027 // sysyex object doesn't have a pointer to a sysex event
4028 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4029 // c->remove (sysex->sysex());
4030 // _model->apply_command (*trackview.session(), c);
4032 //_sys_exes.clear ();
4033 // display_sysexes();
4037 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4039 using namespace MIDI::Name;
4043 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4045 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4047 MIDI::Name::PatchPrimaryKey patch_key;
4048 get_patch_key_at(n->time(), n->channel(), patch_key);
4049 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4052 patch_key.program(),
4055 mtv->set_note_highlight (n->note());
4059 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4061 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
4062 (int) n->channel() + 1,
4063 (int) n->velocity());
4065 show_verbose_cursor(buf, 10, 20);
4069 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4071 trackview.editor().verbose_cursor()->set (text);
4072 trackview.editor().verbose_cursor()->show ();
4073 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4077 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4079 if (_model->notes().empty()) {
4080 return 0x40; // No notes, use default
4083 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4084 if (m == _model->notes().begin()) {
4085 // Before the start, use the velocity of the first note
4086 return (*m)->velocity();
4087 } else if (m == _model->notes().end()) {
4088 // Past the end, use the velocity of the last note
4090 return (*m)->velocity();
4093 // Interpolate velocity of surrounding notes
4094 MidiModel::Notes::const_iterator n = m;
4097 const double frac = ((time - (*n)->time()).to_double() /
4098 ((*m)->time() - (*n)->time()).to_double());
4100 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4103 /** @param p A session framepos.
4104 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
4105 * @return p snapped to the grid subdivision underneath it.
4108 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
4110 PublicEditor& editor = trackview.editor ();
4112 const Evoral::Beats grid_beats = get_grid_beats(p);
4114 grid_frames = region_beats_to_region_frames (grid_beats);
4116 /* Hack so that we always snap to the note that we are over, instead of snapping
4117 to the next one if we're more than halfway through the one we're over.
4119 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
4120 p -= grid_frames / 2;
4123 return snap_frame_to_frame (p);
4126 /** Called when the selection has been cleared in any MidiRegionView.
4127 * @param rv MidiRegionView that the selection was cleared in.
4130 MidiRegionView::selection_cleared (MidiRegionView* rv)
4136 /* Clear our selection in sympathy; but don't signal the fact */
4137 clear_selection (false);
4141 MidiRegionView::get_channel_mode () const
4143 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4144 return rtav->midi_track()->get_playback_channel_mode();
4148 MidiRegionView::get_selected_channels () const
4150 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4151 return rtav->midi_track()->get_playback_channel_mask();
4156 MidiRegionView::get_grid_beats(framepos_t pos) const
4158 PublicEditor& editor = trackview.editor();
4159 bool success = false;
4160 Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos);
4162 beats = Evoral::Beats(1);