2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "midi++/midnam_patch.h"
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
81 #include "patch_change.h"
83 #include "ui_config.h"
87 using namespace ARDOUR;
89 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
95 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
96 RouteTimeAxisView& tv,
97 boost::shared_ptr<MidiRegion> r,
100 : RegionView (parent, tv, r, spu, basic_color)
101 , _current_range_min(0)
102 , _current_range_max(0)
103 , _region_relative_time_converter(r->session().tempo_map(), r->position())
104 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
105 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
107 , _note_group (new ArdourCanvas::Container (group))
108 , _note_diff_command (0)
110 , _step_edit_cursor (0)
111 , _step_edit_cursor_width (1.0)
112 , _step_edit_cursor_position (0.0)
113 , _channel_selection_scoped_note (0)
116 , _sort_needed (true)
117 , _optimization_iterator (_events.end())
119 , _no_sound_notes (false)
120 , _last_display_zoom (0)
123 , _grabbed_keyboard (false)
125 , _note_entered (false)
126 , _mouse_changed_selection (false)
128 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
129 _note_group->raise_to_top();
130 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
132 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
133 connect_to_diskstream ();
136 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
137 RouteTimeAxisView& tv,
138 boost::shared_ptr<MidiRegion> r,
140 uint32_t basic_color,
142 TimeAxisViewItem::Visibility visibility)
143 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
144 , _current_range_min(0)
145 , _current_range_max(0)
146 , _region_relative_time_converter(r->session().tempo_map(), r->position())
147 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
148 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
150 , _note_group (new ArdourCanvas::Container (group))
151 , _note_diff_command (0)
153 , _step_edit_cursor (0)
154 , _step_edit_cursor_width (1.0)
155 , _step_edit_cursor_position (0.0)
156 , _channel_selection_scoped_note (0)
159 , _sort_needed (true)
160 , _optimization_iterator (_events.end())
162 , _no_sound_notes (false)
163 , _last_display_zoom (0)
166 , _grabbed_keyboard (false)
168 , _note_entered (false)
169 , _mouse_changed_selection (false)
171 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
172 _note_group->raise_to_top();
174 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
176 connect_to_diskstream ();
180 MidiRegionView::parameter_changed (std::string const & p)
182 if (p == "display-first-midi-bank-as-zero") {
183 if (_enable_display) {
189 MidiRegionView::MidiRegionView (const MidiRegionView& other)
190 : sigc::trackable(other)
192 , _current_range_min(0)
193 , _current_range_max(0)
194 , _region_relative_time_converter(other.region_relative_time_converter())
195 , _source_relative_time_converter(other.source_relative_time_converter())
196 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
198 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
199 , _note_diff_command (0)
201 , _step_edit_cursor (0)
202 , _step_edit_cursor_width (1.0)
203 , _step_edit_cursor_position (0.0)
204 , _channel_selection_scoped_note (0)
207 , _sort_needed (true)
208 , _optimization_iterator (_events.end())
210 , _no_sound_notes (false)
211 , _last_display_zoom (0)
214 , _grabbed_keyboard (false)
216 , _note_entered (false)
217 , _mouse_changed_selection (false)
222 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
223 : RegionView (other, boost::shared_ptr<Region> (region))
224 , _current_range_min(0)
225 , _current_range_max(0)
226 , _region_relative_time_converter(other.region_relative_time_converter())
227 , _source_relative_time_converter(other.source_relative_time_converter())
228 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
230 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
231 , _note_diff_command (0)
233 , _step_edit_cursor (0)
234 , _step_edit_cursor_width (1.0)
235 , _step_edit_cursor_position (0.0)
236 , _channel_selection_scoped_note (0)
239 , _sort_needed (true)
240 , _optimization_iterator (_events.end())
242 , _no_sound_notes (false)
243 , _last_display_zoom (0)
246 , _grabbed_keyboard (false)
248 , _note_entered (false)
249 , _mouse_changed_selection (false)
255 MidiRegionView::init (bool wfd)
257 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
260 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
261 midi_region()->midi_source(0)->load_model(lm);
264 _model = midi_region()->midi_source(0)->model();
265 _enable_display = false;
266 fill_color_name = "midi frame base";
268 RegionView::init (false);
270 //set_height (trackview.current_height());
273 region_sync_changed ();
274 region_resized (ARDOUR::bounds_change);
279 _enable_display = true;
282 display_model (_model);
286 reset_width_dependent_items (_pixel_width);
288 group->raise_to_top();
290 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
291 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
294 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
295 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
297 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
298 boost::bind (&MidiRegionView::snap_changed, this),
301 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
302 boost::bind (&MidiRegionView::mouse_mode_changed, this),
305 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
306 connect_to_diskstream ();
310 MidiRegionView::instrument_info () const
312 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
313 return route_ui->route()->instrument_info();
316 const boost::shared_ptr<ARDOUR::MidiRegion>
317 MidiRegionView::midi_region() const
319 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
323 MidiRegionView::connect_to_diskstream ()
325 midi_view()->midi_track()->DataRecorded.connect(
326 *this, invalidator(*this),
327 boost::bind (&MidiRegionView::data_recorded, this, _1),
332 MidiRegionView::canvas_group_event(GdkEvent* ev)
334 if (in_destructor || _recregion) {
338 if (!trackview.editor().internal_editing()) {
339 // not in internal edit mode, so just act like a normal region
340 return RegionView::canvas_group_event (ev);
346 case GDK_ENTER_NOTIFY:
347 _last_event_x = ev->crossing.x;
348 _last_event_y = ev->crossing.y;
349 enter_notify(&ev->crossing);
350 // set entered_regionview (among other things)
351 return RegionView::canvas_group_event (ev);
353 case GDK_LEAVE_NOTIFY:
354 _last_event_x = ev->crossing.x;
355 _last_event_y = ev->crossing.y;
356 leave_notify(&ev->crossing);
357 // reset entered_regionview (among other things)
358 return RegionView::canvas_group_event (ev);
361 if (scroll (&ev->scroll)) {
367 return key_press (&ev->key);
369 case GDK_KEY_RELEASE:
370 return key_release (&ev->key);
372 case GDK_BUTTON_PRESS:
373 return button_press (&ev->button);
375 case GDK_BUTTON_RELEASE:
376 r = button_release (&ev->button);
379 case GDK_MOTION_NOTIFY:
380 _last_event_x = ev->motion.x;
381 _last_event_y = ev->motion.y;
382 return motion (&ev->motion);
388 return RegionView::canvas_group_event (ev);
392 MidiRegionView::enter_notify (GdkEventCrossing* ev)
401 MidiRegionView::leave_notify (GdkEventCrossing*)
410 MidiRegionView::mouse_mode_changed ()
412 // Adjust frame colour (become more transparent for internal tools)
416 if (!trackview.editor().internal_editing()) {
417 /* Switched out of internal editing mode while entered.
418 Only necessary for leave as a mouse_mode_change over a region
419 automatically triggers an enter event. */
422 else if (trackview.editor().current_mouse_mode() == MouseContent) {
423 // hide cursor and ghost note after changing to internal edit mode
424 remove_ghost_note ();
426 /* XXX This is problematic as the function is executed for every region
427 and only for one region _note_entered can be true. Still it's
428 necessary as to hide the verbose cursor when we're changing from
429 draw mode to internal edit mode. These lines are the reason why
430 in some situations no verbose cursor is shown when we enter internal
431 edit mode over a note. */
432 if (!_note_entered) {
433 hide_verbose_cursor ();
440 MidiRegionView::enter_internal()
442 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
443 // Show ghost note under pencil
444 create_ghost_note(_last_event_x, _last_event_y);
447 if (!_selection.empty()) {
448 // Grab keyboard for moving selected notes with arrow keys
449 Keyboard::magic_widget_grab_focus();
450 _grabbed_keyboard = true;
453 // Lower frame handles below notes so they don't steal events
454 if (frame_handle_start) {
455 frame_handle_start->lower_to_bottom();
457 if (frame_handle_end) {
458 frame_handle_end->lower_to_bottom();
463 MidiRegionView::leave_internal()
465 hide_verbose_cursor ();
466 remove_ghost_note ();
467 _note_entered = false;
469 if (_grabbed_keyboard) {
470 Keyboard::magic_widget_drop_focus();
471 _grabbed_keyboard = false;
474 // Raise frame handles above notes so they catch events
475 if (frame_handle_start) {
476 frame_handle_start->raise_to_top();
478 if (frame_handle_end) {
479 frame_handle_end->raise_to_top();
484 MidiRegionView::button_press (GdkEventButton* ev)
486 if (ev->button != 1) {
490 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
491 MouseMode m = editor->current_mouse_mode();
493 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
494 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
497 if (_mouse_state != SelectTouchDragging) {
499 _pressed_button = ev->button;
500 _mouse_state = Pressed;
505 _pressed_button = ev->button;
506 _mouse_changed_selection = false;
512 MidiRegionView::button_release (GdkEventButton* ev)
514 double event_x, event_y;
516 if (ev->button != 1) {
523 group->canvas_to_item (event_x, event_y);
526 PublicEditor& editor = trackview.editor ();
528 _press_cursor_ctx.reset();
530 switch (_mouse_state) {
531 case Pressed: // Clicked
533 switch (editor.current_mouse_mode()) {
535 /* no motion occurred - simple click */
536 clear_editor_note_selection ();
537 _mouse_changed_selection = true;
543 _mouse_changed_selection = true;
545 if (Keyboard::is_insert_note_event(ev)) {
547 double event_x, event_y;
551 group->canvas_to_item (event_x, event_y);
553 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
554 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
556 clear_editor_note_selection ();
563 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
564 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
575 /* Only create a ghost note when we added a note, not when we were drag-selecting. */
576 create_ghost_note (ev->x, ev->y);
577 case SelectRectDragging:
578 editor.drags()->end_grab ((GdkEvent *) ev);
587 if (_mouse_changed_selection) {
588 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
589 trackview.editor().commit_reversible_selection_op ();
596 MidiRegionView::motion (GdkEventMotion* ev)
598 PublicEditor& editor = trackview.editor ();
600 if (!_note_entered) {
602 if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
603 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
604 _mouse_state != AddDragging) {
606 create_ghost_note (ev->x, ev->y);
608 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
609 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
611 update_ghost_note (ev->x, ev->y);
613 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
615 remove_ghost_note ();
616 hide_verbose_cursor ();
618 } else if (editor.current_mouse_mode() == MouseDraw) {
621 update_ghost_note (ev->x, ev->y);
624 create_ghost_note (ev->x, ev->y);
629 /* any motion immediately hides velocity text that may have been visible */
631 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
632 (*i)->hide_velocity ();
635 switch (_mouse_state) {
638 if (_pressed_button == 1) {
640 MouseMode m = editor.current_mouse_mode();
642 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
643 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
644 _mouse_state = AddDragging;
645 remove_ghost_note ();
646 hide_verbose_cursor ();
648 } else if (m == MouseContent) {
649 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
650 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
651 clear_editor_note_selection ();
652 _mouse_changed_selection = true;
654 _mouse_state = SelectRectDragging;
656 } else if (m == MouseRange) {
657 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
658 _mouse_state = SelectVerticalDragging;
665 case SelectRectDragging:
666 case SelectVerticalDragging:
668 editor.drags()->motion_handler ((GdkEvent *) ev, false);
671 case SelectTouchDragging:
679 /* we may be dragging some non-note object (eg. patch-change, sysex)
682 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
687 MidiRegionView::scroll (GdkEventScroll* ev)
689 if (_selection.empty()) {
693 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
694 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
695 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
696 * through so that it still works for navigation.
701 hide_verbose_cursor ();
703 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
704 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
705 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
707 if (ev->direction == GDK_SCROLL_UP) {
708 change_velocities (true, fine, false, together);
709 } else if (ev->direction == GDK_SCROLL_DOWN) {
710 change_velocities (false, fine, false, together);
712 /* left, right: we don't use them */
720 MidiRegionView::key_press (GdkEventKey* ev)
722 /* since GTK bindings are generally activated on press, and since
723 detectable auto-repeat is the name of the game and only sends
724 repeated presses, carry out key actions at key press, not release.
727 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
729 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
730 _mouse_state = SelectTouchDragging;
733 } else if (ev->keyval == GDK_Escape && unmodified) {
734 clear_editor_note_selection ();
737 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
739 bool start = (ev->keyval == GDK_comma);
740 bool end = (ev->keyval == GDK_period);
741 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
742 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
744 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
748 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
750 if (_selection.empty()) {
757 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
759 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
761 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
762 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
764 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
767 trackview.editor().commit_reversible_selection_op();
771 } else if (ev->keyval == GDK_Up) {
773 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
774 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
775 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
777 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
778 change_velocities (true, fine, allow_smush, together);
780 transpose (true, fine, allow_smush);
784 } else if (ev->keyval == GDK_Down) {
786 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
787 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
788 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
790 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
791 change_velocities (false, fine, allow_smush, together);
793 transpose (false, fine, allow_smush);
797 } else if (ev->keyval == GDK_Left) {
799 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
800 nudge_notes (false, fine);
803 } else if (ev->keyval == GDK_Right) {
805 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
806 nudge_notes (true, fine);
809 } else if (ev->keyval == GDK_c && unmodified) {
813 } else if (ev->keyval == GDK_v && unmodified) {
822 MidiRegionView::key_release (GdkEventKey* ev)
824 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
832 MidiRegionView::channel_edit ()
834 if (_selection.empty()) {
838 /* pick a note somewhat at random (since Selection is a set<>) to
839 * provide the "current" channel for the dialog.
842 uint8_t current_channel = (*_selection.begin())->note()->channel ();
843 MidiChannelDialog channel_dialog (current_channel);
844 int ret = channel_dialog.run ();
847 case Gtk::RESPONSE_OK:
853 uint8_t new_channel = channel_dialog.active_channel ();
855 start_note_diff_command (_("channel edit"));
857 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
858 Selection::iterator next = i;
860 change_note_channel (*i, new_channel);
868 MidiRegionView::velocity_edit ()
870 if (_selection.empty()) {
874 /* pick a note somewhat at random (since Selection is a set<>) to
875 * provide the "current" velocity for the dialog.
878 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
879 MidiVelocityDialog velocity_dialog (current_velocity);
880 int ret = velocity_dialog.run ();
883 case Gtk::RESPONSE_OK:
889 uint8_t new_velocity = velocity_dialog.velocity ();
891 start_note_diff_command (_("velocity edit"));
893 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
894 Selection::iterator next = i;
896 change_note_velocity (*i, new_velocity, false);
904 MidiRegionView::show_list_editor ()
907 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
909 _list_editor->present ();
912 /** Add a note to the model, and the view, at a canvas (click) coordinate.
913 * \param t time in frames relative to the position of the region
914 * \param y vertical position in pixels
915 * \param length duration of the note in beats
916 * \param snap_t true to snap t to the grid, otherwise false.
919 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bool snap_t)
921 if (length < 2 * DBL_EPSILON) {
925 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
926 MidiStreamView* const view = mtv->midi_view();
927 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
933 // Start of note in frames relative to region start
934 uint32_t divisions = 0;
937 framecnt_t grid_frames;
938 t = snap_frame_to_grid_underneath (t, grid_frames);
939 divisions = trackview.editor().get_grid_music_divisions (0);
943 const MidiModel::TimeType beat_time = Evoral::Beats (trackview.session()->tempo_map().exact_beat_at_frame (_region->position() + t, divisions)
944 - (mr->beat() - mr->start_beats().to_double()));
945 const double note = view->y_to_note(y);
946 const uint8_t chan = mtv->get_channel_for_add();
947 const uint8_t velocity = get_velocity_for_add(beat_time);
949 const boost::shared_ptr<NoteType> new_note(
950 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
952 if (_model->contains (new_note)) {
956 view->update_note_range(new_note->note());
958 start_note_diff_command(_("add note"));
960 clear_editor_note_selection ();
961 note_diff_add_note (new_note, true, false);
965 play_midi_note (new_note);
969 MidiRegionView::clear_events ()
971 // clear selection without signaling
972 clear_selection_internal ();
975 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
976 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
981 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
986 _patch_changes.clear();
988 _optimization_iterator = _events.end();
992 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
996 content_connection.disconnect ();
997 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
998 /* Don't signal as nobody else needs to know until selection has been altered. */
1001 if (_enable_display) {
1007 MidiRegionView::start_note_diff_command (string name)
1009 if (!_note_diff_command) {
1010 trackview.editor().begin_reversible_command (name);
1011 _note_diff_command = _model->new_note_diff_command (name);
1016 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1018 if (_note_diff_command) {
1019 _note_diff_command->add (note);
1022 _marked_for_selection.insert(note);
1024 if (show_velocity) {
1025 _marked_for_velocity.insert(note);
1030 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1032 if (_note_diff_command && ev->note()) {
1033 _note_diff_command->remove(ev->note());
1038 MidiRegionView::note_diff_add_change (NoteBase* ev,
1039 MidiModel::NoteDiffCommand::Property property,
1042 if (_note_diff_command) {
1043 _note_diff_command->change (ev->note(), property, val);
1048 MidiRegionView::note_diff_add_change (NoteBase* ev,
1049 MidiModel::NoteDiffCommand::Property property,
1052 if (_note_diff_command) {
1053 _note_diff_command->change (ev->note(), property, val);
1058 MidiRegionView::apply_diff (bool as_subcommand)
1061 bool commit = false;
1063 if (!_note_diff_command) {
1067 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1068 // Mark all selected notes for selection when model reloads
1069 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1070 _marked_for_selection.insert((*i)->note());
1074 midi_view()->midi_track()->midi_playlist()->region_edited(
1075 _region, _note_diff_command);
1077 if (as_subcommand) {
1078 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1080 _model->apply_command (*trackview.session(), _note_diff_command);
1084 _note_diff_command = 0;
1086 if (add_or_remove) {
1087 _marked_for_selection.clear();
1090 _marked_for_velocity.clear();
1092 trackview.editor().commit_reversible_command ();
1097 MidiRegionView::abort_command()
1099 delete _note_diff_command;
1100 _note_diff_command = 0;
1101 clear_editor_note_selection();
1105 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1107 if (_optimization_iterator != _events.end()) {
1108 ++_optimization_iterator;
1111 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1112 return *_optimization_iterator;
1115 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1116 if ((*_optimization_iterator)->note() == note) {
1117 return *_optimization_iterator;
1124 /** This version finds any canvas note matching the supplied note. */
1126 MidiRegionView::find_canvas_note (NoteType note)
1128 Events::iterator it;
1130 for (it = _events.begin(); it != _events.end(); ++it) {
1131 if (*((*it)->note()) == note) {
1140 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1142 MidiModel::Notes notes;
1143 _model->get_notes (notes, op, val, chan_mask);
1145 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1146 NoteBase* cne = find_canvas_note (*n);
1154 MidiRegionView::redisplay_model()
1156 if (_active_notes) {
1157 // Currently recording
1158 const framecnt_t zoom = trackview.editor().get_current_zoom();
1159 if (zoom != _last_display_zoom) {
1160 /* Update resolved canvas notes to reflect changes in zoom without
1161 touching model. Leave active notes (with length 0) alone since
1162 they are being extended. */
1163 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1164 if ((*i)->note()->length() > 0) {
1168 _last_display_zoom = zoom;
1177 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1178 (*i)->invalidate ();
1181 MidiModel::ReadLock lock(_model->read_lock());
1183 MidiModel::Notes& notes (_model->notes());
1184 _optimization_iterator = _events.begin();
1186 bool empty_when_starting = _events.empty();
1189 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1191 boost::shared_ptr<NoteType> note (*n);
1194 if (note_in_region_range (note, visible)) {
1196 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1208 cne = add_note (note, visible);
1211 set<boost::shared_ptr<NoteType> >::iterator it;
1212 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1213 if (*(*it) == *note) {
1214 add_to_selection (cne);
1220 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1227 /* remove note items that are no longer valid */
1229 if (!empty_when_starting) {
1230 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1231 if (!(*i)->valid ()) {
1233 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1234 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1236 gr->remove_note (*i);
1241 i = _events.erase (i);
1249 _patch_changes.clear();
1253 display_patch_changes ();
1255 _marked_for_selection.clear ();
1256 _marked_for_velocity.clear ();
1257 _pending_note_selection.clear ();
1259 /* we may have caused _events to contain things out of order (e.g. if a note
1260 moved earlier or later). we don't generally need them in time order, but
1261 make a note that a sort is required for those cases that require it.
1264 _sort_needed = true;
1268 MidiRegionView::display_patch_changes ()
1270 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1271 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1273 for (uint8_t i = 0; i < 16; ++i) {
1274 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1278 /** @param active_channel true to display patch changes fully, false to display
1279 * them `greyed-out' (as on an inactive channel)
1282 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1284 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1286 if ((*i)->channel() != channel) {
1290 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1291 add_canvas_patch_change (*i, patch_name, active_channel);
1296 MidiRegionView::display_sysexes()
1298 bool have_periodic_system_messages = false;
1299 bool display_periodic_messages = true;
1301 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1303 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1304 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1305 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1308 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1309 have_periodic_system_messages = true;
1315 if (have_periodic_system_messages) {
1316 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1318 /* get an approximate value for the number of samples per video frame */
1320 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1322 /* if we are zoomed out beyond than the cutoff (i.e. more
1323 * frames per pixel than frames per 4 video frames), don't
1324 * show periodic sysex messages.
1327 if (zoom > (video_frame*4)) {
1328 display_periodic_messages = false;
1332 display_periodic_messages = false;
1335 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1337 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1338 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1340 Evoral::Beats time = (*i)->time();
1343 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1344 if (!display_periodic_messages) {
1352 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1353 str << int((*i)->buffer()[b]);
1354 if (b != (*i)->size() -1) {
1358 string text = str.str();
1360 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1362 double height = midi_stream_view()->contents_height();
1364 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1365 // SysEx canvas object!!!
1367 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1368 new SysEx (*this, _note_group, text, height, x, 1.0));
1370 // Show unless message is beyond the region bounds
1371 if (time - _region->start() >= _region->length() || time < _region->start()) {
1377 _sys_exes.push_back(sysex);
1381 MidiRegionView::~MidiRegionView ()
1383 in_destructor = true;
1385 hide_verbose_cursor ();
1387 delete _list_editor;
1389 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1391 if (_active_notes) {
1399 delete _note_diff_command;
1400 delete _step_edit_cursor;
1404 MidiRegionView::region_resized (const PropertyChange& what_changed)
1406 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1408 if (what_changed.contains (ARDOUR::Properties::position)) {
1409 _region_relative_time_converter.set_origin_b(_region->position());
1410 _region_relative_time_converter_double.set_origin_b(_region->position());
1411 /* reset_width dependent_items() redisplays model */
1415 if (what_changed.contains (ARDOUR::Properties::start) ||
1416 what_changed.contains (ARDOUR::Properties::position)) {
1417 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1419 /* catch end and start trim so we can update the view*/
1420 if (!what_changed.contains (ARDOUR::Properties::start) &&
1421 what_changed.contains (ARDOUR::Properties::length)) {
1422 enable_display (true);
1423 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1424 what_changed.contains (ARDOUR::Properties::length)) {
1425 enable_display (true);
1430 MidiRegionView::reset_width_dependent_items (double pixel_width)
1432 RegionView::reset_width_dependent_items(pixel_width);
1434 if (_enable_display) {
1438 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1439 if ((*x)->canvas_item()->width() >= _pixel_width) {
1446 move_step_edit_cursor (_step_edit_cursor_position);
1447 set_step_edit_cursor_width (_step_edit_cursor_width);
1451 MidiRegionView::set_height (double height)
1453 double old_height = _height;
1454 RegionView::set_height(height);
1456 apply_note_range (midi_stream_view()->lowest_note(),
1457 midi_stream_view()->highest_note(),
1458 height != old_height);
1461 name_text->raise_to_top();
1464 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1465 (*x)->set_height (midi_stream_view()->contents_height());
1468 if (_step_edit_cursor) {
1469 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1474 /** Apply the current note range from the stream view
1475 * by repositioning/hiding notes as necessary
1478 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1480 if (!_enable_display) {
1484 if (!force && _current_range_min == min && _current_range_max == max) {
1488 _current_range_min = min;
1489 _current_range_max = max;
1491 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1492 NoteBase* event = *i;
1493 boost::shared_ptr<NoteType> note (event->note());
1495 if (note->note() < _current_range_min ||
1496 note->note() > _current_range_max) {
1502 if (Note* cnote = dynamic_cast<Note*>(event)) {
1504 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1505 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1507 if (y0 < 0 || y1 >= _height) {
1508 /* During DnD, the region uses the 'old/current'
1509 * midi_stream_view()'s range and its position/height calculation.
1511 * Ideally DnD would decouple the midi_stream_view() for the
1512 * region(s) being dragged and set it to the target's range
1513 * (or in case of the drop-zone, FullRange).
1514 * but I don't see how this can be done without major rework.
1516 * For now, just prevent visual bleeding of events in case
1517 * the target-track is smaller.
1525 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1532 MidiRegionView::add_ghost (TimeAxisView& tv)
1534 double unit_position = _region->position () / samples_per_pixel;
1535 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1536 MidiGhostRegion* ghost;
1538 if (mtv && mtv->midi_view()) {
1539 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1540 to allow having midi notes on top of note lines and waveforms.
1542 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1544 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1547 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1548 ghost->add_note(*i);
1551 ghost->set_height ();
1552 ghost->set_duration (_region->length() / samples_per_pixel);
1553 ghosts.push_back (ghost);
1559 /** Begin tracking note state for successive calls to add_event
1562 MidiRegionView::begin_write()
1564 if (_active_notes) {
1565 delete[] _active_notes;
1567 _active_notes = new Note*[128];
1568 for (unsigned i = 0; i < 128; ++i) {
1569 _active_notes[i] = 0;
1574 /** Destroy note state for add_event
1577 MidiRegionView::end_write()
1579 delete[] _active_notes;
1581 _marked_for_selection.clear();
1582 _marked_for_velocity.clear();
1586 /** Resolve an active MIDI note (while recording).
1589 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1591 if (midi_view()->note_mode() != Sustained) {
1595 if (_active_notes && _active_notes[note]) {
1596 /* Set note length so update_note() works. Note this is a local note
1597 for recording, not from a model, so we can safely mess with it. */
1598 _active_notes[note]->note()->set_length(
1599 end_time - _active_notes[note]->note()->time());
1601 /* End time is relative to the region being recorded. */
1602 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1604 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1605 _active_notes[note]->set_outline_all ();
1606 _active_notes[note] = 0;
1611 /** Extend active notes to rightmost edge of region (if length is changed)
1614 MidiRegionView::extend_active_notes()
1616 if (!_active_notes) {
1620 for (unsigned i = 0; i < 128; ++i) {
1621 if (_active_notes[i]) {
1622 _active_notes[i]->set_x1(
1623 trackview.editor().sample_to_pixel(_region->length()));
1629 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1631 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1635 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1637 if (!route_ui || !route_ui->midi_track()) {
1641 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1645 /* NotePlayer deletes itself */
1649 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1651 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1652 start_playing_midi_chord(notes);
1656 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1658 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1662 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1664 if (!route_ui || !route_ui->midi_track()) {
1668 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1670 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1679 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1681 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1682 const bool outside = (note->time() < midi_reg->start_beats() ||
1683 note->time() > midi_reg->start_beats() + midi_reg->length_beats());
1685 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1686 (note->note() <= midi_stream_view()->highest_note());
1692 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1696 if ((sus = dynamic_cast<Note*>(note))) {
1697 update_sustained(sus, update_ghost_regions);
1698 } else if ((hit = dynamic_cast<Hit*>(note))) {
1699 update_hit(hit, update_ghost_regions);
1703 /** Update a canvas note's size from its model note.
1704 * @param ev Canvas note to update.
1705 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1708 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1710 TempoMap& map (trackview.session()->tempo_map());
1711 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1712 boost::shared_ptr<NoteType> note = ev->note();
1713 const framepos_t note_start_frames = map.frame_at_beat (_region->beat() - mr->start_beats().to_double()
1714 + note->time().to_double()) - _region->position();
1716 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1718 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1719 double y1;/* trim note display to not overlap the end of its region */
1721 if (note->length() > 0) {
1722 Evoral::Beats note_end_time = note->end_time();
1724 if (note->end_time() > mr->start_beats() + mr->length_beats()) {
1725 note_end_time = mr->start_beats() + mr->length_beats();
1728 const framepos_t note_end_frames = map.frame_at_beat (_region->beat() - mr->start_beats().to_double()
1729 + note_end_time.to_double()) - _region->position();
1731 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1733 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1736 y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1);
1738 ArdourCanvas::Rect rect (x0, y0, x1, y1);
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 (x1);
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 //const uint32_t base_col = ev->base_color();
1764 //ev->set_fill_color(base_col);
1765 //ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1767 if (update_ghost_regions) {
1768 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1769 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1771 gr->update_note (ev);
1778 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1780 boost::shared_ptr<NoteType> note = ev->note();
1782 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_beat (_region->beat() - midi_region()->start_beats().to_double()
1783 + note->time().to_double()) - _region->position();
1784 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1785 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1786 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1788 // see DnD note in MidiRegionView::apply_note_range() above
1789 if (y <= 0 || y >= _height) {
1795 ev->set_position (ArdourCanvas::Duple (x, y));
1796 ev->set_height (diamond_size);
1798 // Update color in case velocity has changed
1799 ev->set_fill_color(ev->base_color());
1800 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1802 if (update_ghost_regions) {
1803 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1804 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1806 gr->update_note (ev);
1812 /** Add a MIDI note to the view (with length).
1814 * If in sustained mode, notes with length 0 will be considered active
1815 * notes, and resolve_note should be called when the corresponding note off
1816 * event arrives, to properly display the note.
1819 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1821 NoteBase* event = 0;
1823 if (midi_view()->note_mode() == Sustained) {
1825 Note* ev_rect = new Note (*this, _note_group, note);
1827 update_sustained (ev_rect);
1831 } else if (midi_view()->note_mode() == Percussive) {
1833 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1835 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1837 update_hit (ev_diamond);
1846 MidiGhostRegion* gr;
1848 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1849 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1850 gr->add_note(event);
1854 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1855 note_selected(event, true);
1858 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1859 event->show_velocity();
1862 event->on_channel_selection_change (get_selected_channels());
1863 _events.push_back(event);
1872 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1873 MidiStreamView* const view = mtv->midi_view();
1875 view->update_note_range (note->note());
1880 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1881 Evoral::Beats pos, Evoral::Beats len)
1883 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1885 /* potentially extend region to hold new note */
1887 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1888 framepos_t region_end = _region->last_frame();
1890 if (end_frame > region_end) {
1891 /* XX sets length in beats from audio space. make musical */
1892 _region->set_length (end_frame - _region->position(), 0);
1895 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1896 MidiStreamView* const view = mtv->midi_view();
1898 view->update_note_range(new_note->note());
1900 _marked_for_selection.clear ();
1902 start_note_diff_command (_("step add"));
1904 clear_editor_note_selection ();
1905 note_diff_add_note (new_note, true, false);
1909 // last_step_edit_note = new_note;
1913 MidiRegionView::step_sustain (Evoral::Beats beats)
1915 change_note_lengths (false, false, beats, false, true);
1918 /** Add a new patch change flag to the canvas.
1919 * @param patch the patch change to add
1920 * @param the text to display in the flag
1921 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1924 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1926 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1927 const double x = trackview.editor().sample_to_pixel (region_frames);
1929 double const height = midi_stream_view()->contents_height();
1931 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1932 // so we need to do something more sophisticated to keep its color
1933 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1936 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1937 new PatchChange(*this, group,
1944 if (patch_change->item().width() < _pixel_width) {
1945 // Show unless patch change is beyond the region bounds
1946 if (region_frames < 0 || region_frames >= _region->length()) {
1947 patch_change->hide();
1949 patch_change->show();
1952 patch_change->hide ();
1955 _patch_changes.push_back (patch_change);
1958 MIDI::Name::PatchPrimaryKey
1959 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1961 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1964 /// Return true iff @p pc applies to the given time on the given channel.
1966 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1968 return pc->time() <= time && pc->channel() == channel;
1972 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1974 // The earliest event not before time
1975 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1977 // Go backwards until we find the latest PC for this channel, or the start
1978 while (i != _model->patch_changes().begin() &&
1979 (i == _model->patch_changes().end() ||
1980 !patch_applies(*i, time, channel))) {
1984 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1985 key.set_bank((*i)->bank());
1986 key.set_program((*i)->program ());
1994 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1996 string name = _("alter patch change");
1997 trackview.editor().begin_reversible_command (name);
1998 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2000 if (pc.patch()->program() != new_patch.program()) {
2001 c->change_program (pc.patch (), new_patch.program());
2004 int const new_bank = new_patch.bank();
2005 if (pc.patch()->bank() != new_bank) {
2006 c->change_bank (pc.patch (), new_bank);
2009 _model->apply_command (*trackview.session(), c);
2010 trackview.editor().commit_reversible_command ();
2012 _patch_changes.clear ();
2013 display_patch_changes ();
2017 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2019 string name = _("alter patch change");
2020 trackview.editor().begin_reversible_command (name);
2021 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2023 if (old_change->time() != new_change.time()) {
2024 c->change_time (old_change, new_change.time());
2027 if (old_change->channel() != new_change.channel()) {
2028 c->change_channel (old_change, new_change.channel());
2031 if (old_change->program() != new_change.program()) {
2032 c->change_program (old_change, new_change.program());
2035 if (old_change->bank() != new_change.bank()) {
2036 c->change_bank (old_change, new_change.bank());
2039 _model->apply_command (*trackview.session(), c);
2040 trackview.editor().commit_reversible_command ();
2042 _patch_changes.clear ();
2043 display_patch_changes ();
2046 /** Add a patch change to the region.
2047 * @param t Time in frames relative to region position
2048 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2049 * MidiTimeAxisView::get_channel_for_add())
2052 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2054 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2055 string name = _("add patch change");
2057 trackview.editor().begin_reversible_command (name);
2058 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2059 c->add (MidiModel::PatchChangePtr (
2060 new Evoral::PatchChange<Evoral::Beats> (
2061 absolute_frames_to_source_beats (_region->position() + t),
2062 mtv->get_channel_for_add(), patch.program(), patch.bank()
2067 _model->apply_command (*trackview.session(), c);
2068 trackview.editor().commit_reversible_command ();
2070 _patch_changes.clear ();
2071 display_patch_changes ();
2075 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2077 trackview.editor().begin_reversible_command (_("move patch change"));
2078 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2079 c->change_time (pc.patch (), t);
2080 _model->apply_command (*trackview.session(), c);
2081 trackview.editor().commit_reversible_command ();
2083 _patch_changes.clear ();
2084 display_patch_changes ();
2088 MidiRegionView::delete_patch_change (PatchChange* pc)
2090 trackview.editor().begin_reversible_command (_("delete patch change"));
2091 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2092 c->remove (pc->patch ());
2093 _model->apply_command (*trackview.session(), c);
2094 trackview.editor().commit_reversible_command ();
2096 _patch_changes.clear ();
2097 display_patch_changes ();
2101 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2103 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2105 key.set_bank(key.bank() + delta);
2107 key.set_program(key.program() + delta);
2109 change_patch_change(patch, key);
2113 MidiRegionView::note_deleted (NoteBase* cne)
2115 if (_selection.empty()) {
2119 _selection.erase (cne);
2123 MidiRegionView::delete_selection()
2125 if (_selection.empty()) {
2129 start_note_diff_command (_("delete selection"));
2131 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2132 if ((*i)->selected()) {
2133 _note_diff_command->remove((*i)->note());
2140 hide_verbose_cursor ();
2144 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2146 start_note_diff_command (_("delete note"));
2147 _note_diff_command->remove (n);
2150 hide_verbose_cursor ();
2154 MidiRegionView::clear_editor_note_selection ()
2156 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2157 PublicEditor& editor(trackview.editor());
2158 editor.get_selection().clear_midi_notes();
2162 MidiRegionView::clear_selection ()
2164 clear_selection_internal();
2165 PublicEditor& editor(trackview.editor());
2166 editor.get_selection().remove(this);
2170 MidiRegionView::clear_selection_internal ()
2172 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2174 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2175 (*i)->set_selected(false);
2176 (*i)->hide_velocity();
2181 // Clearing selection entirely, ungrab keyboard
2182 Keyboard::magic_widget_drop_focus();
2183 _grabbed_keyboard = false;
2188 MidiRegionView::unique_select(NoteBase* ev)
2190 clear_editor_note_selection();
2191 add_to_selection(ev);
2195 MidiRegionView::select_all_notes ()
2197 clear_editor_note_selection ();
2199 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2200 add_to_selection (*i);
2205 MidiRegionView::select_range (framepos_t start, framepos_t end)
2207 clear_editor_note_selection ();
2209 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2210 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2211 if (t >= start && t <= end) {
2212 add_to_selection (*i);
2218 MidiRegionView::invert_selection ()
2220 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2221 if ((*i)->selected()) {
2222 remove_from_selection(*i);
2224 add_to_selection (*i);
2229 /** Used for selection undo/redo.
2230 The requested notes most likely won't exist in the view until the next model redisplay.
2233 MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
2236 list<boost::shared_ptr<NoteType> >::iterator n;
2238 for (n = notes.begin(); n != notes.end(); ++n) {
2239 if ((cne = find_canvas_note(*(*n))) != 0) {
2240 add_to_selection (cne);
2242 _pending_note_selection.insert(*n);
2248 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2250 bool have_selection = !_selection.empty();
2251 uint8_t low_note = 127;
2252 uint8_t high_note = 0;
2253 MidiModel::Notes& notes (_model->notes());
2254 _optimization_iterator = _events.begin();
2256 if (extend && !have_selection) {
2260 /* scan existing selection to get note range */
2262 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2263 if ((*i)->note()->note() < low_note) {
2264 low_note = (*i)->note()->note();
2266 if ((*i)->note()->note() > high_note) {
2267 high_note = (*i)->note()->note();
2272 clear_editor_note_selection ();
2274 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2275 /* only note previously selected is the one we are
2276 * reselecting. treat this as cancelling the selection.
2283 low_note = min (low_note, notenum);
2284 high_note = max (high_note, notenum);
2287 _no_sound_notes = true;
2289 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2291 boost::shared_ptr<NoteType> note (*n);
2293 bool select = false;
2295 if (((1 << note->channel()) & channel_mask) != 0) {
2297 if ((note->note() >= low_note && note->note() <= high_note)) {
2300 } else if (note->note() == notenum) {
2306 if ((cne = find_canvas_note (note)) != 0) {
2307 // extend is false because we've taken care of it,
2308 // since it extends by time range, not pitch.
2309 note_selected (cne, add, false);
2313 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2317 _no_sound_notes = false;
2321 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2323 MidiModel::Notes& notes (_model->notes());
2324 _optimization_iterator = _events.begin();
2326 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2328 boost::shared_ptr<NoteType> note (*n);
2331 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2332 if ((cne = find_canvas_note (note)) != 0) {
2333 if (cne->selected()) {
2334 note_deselected (cne);
2336 note_selected (cne, true, false);
2344 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2347 clear_editor_note_selection();
2348 add_to_selection (ev);
2353 if (!ev->selected()) {
2354 add_to_selection (ev);
2358 /* find end of latest note selected, select all between that and the start of "ev" */
2360 Evoral::Beats earliest = Evoral::MaxBeats;
2361 Evoral::Beats latest = Evoral::Beats();
2363 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2364 if ((*i)->note()->end_time() > latest) {
2365 latest = (*i)->note()->end_time();
2367 if ((*i)->note()->time() < earliest) {
2368 earliest = (*i)->note()->time();
2372 if (ev->note()->end_time() > latest) {
2373 latest = ev->note()->end_time();
2376 if (ev->note()->time() < earliest) {
2377 earliest = ev->note()->time();
2380 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2382 /* find notes entirely within OR spanning the earliest..latest range */
2384 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2385 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2386 add_to_selection (*i);
2394 MidiRegionView::note_deselected(NoteBase* ev)
2396 remove_from_selection (ev);
2400 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2402 PublicEditor& editor = trackview.editor();
2404 // Convert to local coordinates
2405 const framepos_t p = _region->position();
2406 const double y = midi_view()->y_position();
2407 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2408 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2409 const double y0 = max(0.0, gy0 - y);
2410 const double y1 = max(0.0, gy1 - y);
2412 // TODO: Make this faster by storing the last updated selection rect, and only
2413 // adjusting things that are in the area that appears/disappeared.
2414 // We probably need a tree to be able to find events in O(log(n)) time.
2416 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2417 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2418 // Rectangles intersect
2419 if (!(*i)->selected()) {
2420 add_to_selection (*i);
2422 } else if ((*i)->selected() && !extend) {
2423 // Rectangles do not intersect
2424 remove_from_selection (*i);
2428 typedef RouteTimeAxisView::AutomationTracks ATracks;
2429 typedef std::list<Selectable*> Selectables;
2431 /* Add control points to selection. */
2432 const ATracks& atracks = midi_view()->automation_tracks();
2433 Selectables selectables;
2434 editor.get_selection().clear_points();
2435 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2436 a->second->get_selectables(start, end, gy0, gy1, selectables);
2437 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2438 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2440 editor.get_selection().add(cp);
2443 a->second->set_selected_points(editor.get_selection().points);
2448 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2454 // TODO: Make this faster by storing the last updated selection rect, and only
2455 // adjusting things that are in the area that appears/disappeared.
2456 // We probably need a tree to be able to find events in O(log(n)) time.
2458 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2459 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2460 // within y- (note-) range
2461 if (!(*i)->selected()) {
2462 add_to_selection (*i);
2464 } else if ((*i)->selected() && !extend) {
2465 remove_from_selection (*i);
2471 MidiRegionView::remove_from_selection (NoteBase* ev)
2473 Selection::iterator i = _selection.find (ev);
2475 if (i != _selection.end()) {
2476 _selection.erase (i);
2477 if (_selection.empty() && _grabbed_keyboard) {
2479 Keyboard::magic_widget_drop_focus();
2480 _grabbed_keyboard = false;
2484 ev->set_selected (false);
2485 ev->hide_velocity ();
2487 if (_selection.empty()) {
2488 PublicEditor& editor (trackview.editor());
2489 editor.get_selection().remove (this);
2494 MidiRegionView::add_to_selection (NoteBase* ev)
2496 const bool selection_was_empty = _selection.empty();
2498 if (_selection.insert (ev).second) {
2499 ev->set_selected (true);
2500 start_playing_midi_note ((ev)->note());
2501 if (selection_was_empty && _entered) {
2502 // Grab keyboard for moving notes with arrow keys
2503 Keyboard::magic_widget_grab_focus();
2504 _grabbed_keyboard = true;
2508 if (selection_was_empty) {
2509 PublicEditor& editor (trackview.editor());
2510 editor.get_selection().add (this);
2515 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2517 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2518 PossibleChord to_play;
2519 Evoral::Beats earliest = Evoral::MaxBeats;
2521 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2522 if ((*i)->note()->time() < earliest) {
2523 earliest = (*i)->note()->time();
2527 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2528 if ((*i)->note()->time() == earliest) {
2529 to_play.push_back ((*i)->note());
2531 (*i)->move_event(dx, dy);
2534 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2536 if (to_play.size() > 1) {
2538 PossibleChord shifted;
2540 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2541 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2542 moved_note->set_note (moved_note->note() + cumulative_dy);
2543 shifted.push_back (moved_note);
2546 start_playing_midi_chord (shifted);
2548 } else if (!to_play.empty()) {
2550 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2551 moved_note->set_note (moved_note->note() + cumulative_dy);
2552 start_playing_midi_note (moved_note);
2558 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2560 uint8_t lowest_note_in_selection = 127;
2561 uint8_t highest_note_in_selection = 0;
2562 uint8_t highest_note_difference = 0;
2564 // find highest and lowest notes first
2566 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2567 uint8_t pitch = (*i)->note()->note();
2568 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2569 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2573 cerr << "dnote: " << (int) dnote << endl;
2574 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2575 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2576 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2577 << int(highest_note_in_selection) << endl;
2578 cerr << "selection size: " << _selection.size() << endl;
2579 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2582 // Make sure the note pitch does not exceed the MIDI standard range
2583 if (highest_note_in_selection + dnote > 127) {
2584 highest_note_difference = highest_note_in_selection - 127;
2587 start_note_diff_command (_("move notes"));
2589 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2591 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2592 Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
2598 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2600 uint8_t original_pitch = (*i)->note()->note();
2601 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2603 // keep notes in standard midi range
2604 clamp_to_0_127(new_pitch);
2606 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2607 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2609 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2614 // care about notes being moved beyond the upper/lower bounds on the canvas
2615 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2616 highest_note_in_selection > midi_stream_view()->highest_note()) {
2617 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2621 /** @param x Pixel relative to the region position.
2622 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2623 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2624 * @return Snapped frame relative to the region position.
2627 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2629 PublicEditor& editor (trackview.editor());
2630 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2633 /** @param x Pixel relative to the region position.
2634 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2635 * @return Snapped pixel relative to the region position.
2638 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2640 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2644 MidiRegionView::get_position_pixels()
2646 framepos_t region_frame = get_position();
2647 return trackview.editor().sample_to_pixel(region_frame);
2651 MidiRegionView::get_end_position_pixels()
2653 framepos_t frame = get_position() + get_duration ();
2654 return trackview.editor().sample_to_pixel(frame);
2658 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2660 /* the time converter will return the frame corresponding to `beats'
2661 relative to the start of the source. The start of the source
2662 is an implied position given by region->position - region->start
2664 const framepos_t source_start = _region->position() - _region->start();
2665 return source_start + _source_relative_time_converter.to (beats);
2669 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2671 /* the `frames' argument needs to be converted into a frame count
2672 relative to the start of the source before being passed in to the
2675 const framepos_t source_start = _region->position() - _region->start();
2676 return _source_relative_time_converter.from (frames - source_start);
2680 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2682 return _region_relative_time_converter.to(beats);
2686 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2688 return _region_relative_time_converter.from(frames);
2692 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2694 return _region_relative_time_converter_double.from(frames);
2698 MidiRegionView::begin_resizing (bool /*at_front*/)
2700 _resize_data.clear();
2702 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2703 Note *note = dynamic_cast<Note*> (*i);
2705 // only insert CanvasNotes into the map
2707 NoteResizeData *resize_data = new NoteResizeData();
2708 resize_data->note = note;
2710 // create a new SimpleRect from the note which will be the resize preview
2711 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2712 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2714 // calculate the colors: get the color settings
2715 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2716 UIConfiguration::instance().color ("midi note selected"),
2719 // make the resize preview notes more transparent and bright
2720 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2722 // calculate color based on note velocity
2723 resize_rect->set_fill_color (UINT_INTERPOLATE(
2724 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2728 resize_rect->set_outline_color (NoteBase::calculate_outline (
2729 UIConfiguration::instance().color ("midi note selected")));
2731 resize_data->resize_rect = resize_rect;
2732 _resize_data.push_back(resize_data);
2737 /** Update resizing notes while user drags.
2738 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2739 * @param at_front which end of the note (true == note on, false == note off)
2740 * @param delta_x change in mouse position since the start of the drag
2741 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2742 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2743 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2744 * as the \a primary note.
2745 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2746 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2749 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2751 TempoMap& tmap (trackview.session()->tempo_map());
2752 bool cursor_set = false;
2753 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2755 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2756 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2757 Note* canvas_note = (*i)->note;
2762 current_x = canvas_note->x0() + delta_x + snap_delta;
2764 current_x = primary->x0() + delta_x + snap_delta;
2768 current_x = canvas_note->x1() + delta_x + snap_delta;
2770 current_x = primary->x1() + delta_x + snap_delta;
2774 if (current_x < 0) {
2775 // This works even with snapping because RegionView::snap_frame_to_frame()
2776 // snaps forward if the snapped sample is before the beginning of the region
2779 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2780 current_x = trackview.editor().sample_to_pixel(_region->length());
2785 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2787 resize_rect->set_x0 (current_x - snap_delta);
2789 resize_rect->set_x1 (canvas_note->x1());
2792 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2794 resize_rect->set_x1 (current_x - snap_delta);
2796 resize_rect->set_x0 (canvas_note->x0());
2800 /* Convert snap delta from pixels to beats. */
2801 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2802 double snap_delta_beats = 0.0;
2805 /* negative beat offsets aren't allowed */
2806 if (snap_delta_samps > 0) {
2807 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2808 } else if (snap_delta_samps < 0) {
2809 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2814 uint32_t divisions = 0;
2817 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2818 divisions = trackview.editor().get_grid_music_divisions (0);
2820 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2822 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2823 - midi_region()->beat()) + midi_region()->start_beats();
2825 Evoral::Beats len = Evoral::Beats();
2828 if (beats < canvas_note->note()->end_time()) {
2829 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2830 len += canvas_note->note()->length();
2833 if (beats >= canvas_note->note()->time()) {
2834 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2838 len = std::max(Evoral::Beats(1 / 512.0), len);
2841 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2842 show_verbose_cursor (buf, 0, 0);
2851 /** Finish resizing notes when the user releases the mouse button.
2852 * Parameters the same as for \a update_resizing().
2855 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2857 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2858 TempoMap& tmap (trackview.session()->tempo_map());
2860 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2861 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2863 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2864 Note* canvas_note = (*i)->note;
2865 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2867 /* Get the new x position for this resize, which is in pixels relative
2868 * to the region position.
2875 current_x = canvas_note->x0() + delta_x + snap_delta;
2877 current_x = primary->x0() + delta_x + snap_delta;
2881 current_x = canvas_note->x1() + delta_x + snap_delta;
2883 current_x = primary->x1() + delta_x + snap_delta;
2887 if (current_x < 0) {
2890 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2891 current_x = trackview.editor().sample_to_pixel(_region->length());
2894 /* Convert snap delta from pixels to beats with sign. */
2895 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2896 double snap_delta_beats = 0.0;
2899 if (snap_delta_samps > 0) {
2900 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2901 } else if (snap_delta_samps < 0) {
2902 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2906 uint32_t divisions = 0;
2907 /* Convert the new x position to a frame within the source */
2908 framepos_t current_fr;
2910 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2911 divisions = trackview.editor().get_grid_music_divisions (0);
2913 current_fr = trackview.editor().pixel_to_sample (current_x);
2916 /* and then to beats */
2917 const Evoral::Beats x_beats = Evoral::Beats (tmap.exact_beat_at_frame (current_fr + midi_region()->position(), divisions)
2918 - midi_region()->beat()) + midi_region()->start_beats();
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_frame (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 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3371 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3372 << _("Channel ") << ((int) p->patch()->channel() + 1);
3373 show_verbose_cursor (s.str(), 10, 20);
3374 p->item().grab_focus();
3378 MidiRegionView::patch_left (PatchChange *)
3380 hide_verbose_cursor ();
3381 /* focus will transfer back via the enter-notify event sent to this
3387 MidiRegionView::sysex_entered (SysEx* p)
3391 // need a way to extract text from p->_flag->_text
3393 // show_verbose_cursor (s.str(), 10, 20);
3394 p->item().grab_focus();
3398 MidiRegionView::sysex_left (SysEx *)
3400 hide_verbose_cursor ();
3401 /* focus will transfer back via the enter-notify event sent to this
3407 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3409 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3410 Editing::MouseMode mm = editor->current_mouse_mode();
3411 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3413 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3414 if (can_set_cursor && ctx) {
3415 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3416 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3417 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3418 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3420 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3426 MidiRegionView::get_fill_color() const
3428 const std::string mod_name = (_dragging ? "dragging region" :
3429 trackview.editor().internal_editing() ? "editable region" :
3432 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3433 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3434 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3435 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3437 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3441 MidiRegionView::midi_channel_mode_changed ()
3443 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3444 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3445 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3447 if (mode == ForceChannel) {
3448 mask = 0xFFFF; // Show all notes as active (below)
3451 // Update notes for selection
3452 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3453 (*i)->on_channel_selection_change (mask);
3456 _patch_changes.clear ();
3457 display_patch_changes ();
3461 MidiRegionView::instrument_settings_changed ()
3467 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3469 if (_selection.empty()) {
3473 PublicEditor& editor (trackview.editor());
3477 /* XXX what to do ? */
3481 editor.get_cut_buffer().add (selection_as_cut_buffer());
3489 start_note_diff_command();
3491 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3498 note_diff_remove_note (*i);
3508 MidiRegionView::selection_as_cut_buffer () const
3512 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3513 NoteType* n = (*i)->note().get();
3514 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3517 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3523 /** This method handles undo */
3525 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3527 bool commit = false;
3528 // Paste notes, if available
3529 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3530 if (m != selection.midi_notes.end()) {
3531 ctx.counts.increase_n_notes();
3532 if (!(*m)->empty()) { commit = true; }
3533 paste_internal(pos, ctx.count, ctx.times, **m);
3536 // Paste control points to automation children, if available
3537 typedef RouteTimeAxisView::AutomationTracks ATracks;
3538 const ATracks& atracks = midi_view()->automation_tracks();
3539 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3540 if (a->second->paste(pos, selection, ctx, sub_num)) {
3546 trackview.editor().commit_reversible_command ();
3551 /** This method handles undo */
3553 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3559 start_note_diff_command (_("paste"));
3561 const Evoral::Beats snap_beats = get_grid_beats(pos);
3562 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3563 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3564 const Evoral::Beats duration = last_time - first_time;
3565 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3566 const Evoral::Beats paste_offset = snap_duration * paste_count;
3567 const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3568 Evoral::Beats end_point = Evoral::Beats();
3570 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3573 duration, pos, _region->position(),
3576 clear_editor_note_selection ();
3578 for (int n = 0; n < (int) times; ++n) {
3580 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3582 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3583 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3584 copied_note->set_id (Evoral::next_event_id());
3586 /* make all newly added notes selected */
3588 note_diff_add_note (copied_note, true);
3589 end_point = copied_note->end_time();
3593 /* if we pasted past the current end of the region, extend the region */
3595 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3596 framepos_t region_end = _region->position() + _region->length() - 1;
3598 if (end_frame > region_end) {
3600 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3602 _region->clear_changes ();
3603 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3604 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
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);
3869 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3871 if (_step_edit_cursor == 0) {
3872 ArdourCanvas::Item* const group = get_canvas_group();
3874 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3875 _step_edit_cursor->set_y0 (0);
3876 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3877 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3878 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3881 move_step_edit_cursor (pos);
3882 _step_edit_cursor->show ();
3886 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3888 _step_edit_cursor_position = pos;
3890 if (_step_edit_cursor) {
3891 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3892 _step_edit_cursor->set_x0 (pixel);
3893 set_step_edit_cursor_width (_step_edit_cursor_width);
3898 MidiRegionView::hide_step_edit_cursor ()
3900 if (_step_edit_cursor) {
3901 _step_edit_cursor->hide ();
3906 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3908 _step_edit_cursor_width = beats;
3910 if (_step_edit_cursor) {
3911 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3912 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3913 - region_beats_to_region_frames (_step_edit_cursor_position)));
3917 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3918 * @param w Source that the data will end up in.
3921 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3923 if (!_active_notes) {
3924 /* we aren't actively being recorded to */
3928 boost::shared_ptr<MidiSource> src = w.lock ();
3929 if (!src || src != midi_region()->midi_source()) {
3930 /* recorded data was not destined for our source */
3934 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3936 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3938 framepos_t back = max_framepos;
3940 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3941 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3943 if (ev.is_channel_event()) {
3944 if (get_channel_mode() == FilterChannels) {
3945 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3951 /* convert from session frames to source beats */
3952 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3953 ev.time() - src->timeline_position() + _region->start());
3955 if (ev.type() == MIDI_CMD_NOTE_ON) {
3956 boost::shared_ptr<NoteType> note (
3957 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3959 add_note (note, true);
3961 /* fix up our note range */
3962 if (ev.note() < _current_range_min) {
3963 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3964 } else if (ev.note() > _current_range_max) {
3965 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3968 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3969 resolve_note (ev.note (), time_beats);
3975 midi_stream_view()->check_record_layers (region(), back);
3979 MidiRegionView::trim_front_starting ()
3981 /* We used to eparent the note group to the region view's parent, so that it didn't change.
3987 MidiRegionView::trim_front_ending ()
3989 if (_region->start() < 0) {
3990 /* Trim drag made start time -ve; fix this */
3991 midi_region()->fix_negative_start ();
3996 MidiRegionView::edit_patch_change (PatchChange* pc)
3998 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4000 int response = d.run();
4003 case Gtk::RESPONSE_ACCEPT:
4005 case Gtk::RESPONSE_REJECT:
4006 delete_patch_change (pc);
4012 change_patch_change (pc->patch(), d.patch ());
4016 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4019 // sysyex object doesn't have a pointer to a sysex event
4020 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4021 // c->remove (sysex->sysex());
4022 // _model->apply_command (*trackview.session(), c);
4024 //_sys_exes.clear ();
4025 // display_sysexes();
4029 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4031 using namespace MIDI::Name;
4034 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4036 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4038 MIDI::Name::PatchPrimaryKey patch_key;
4039 get_patch_key_at(n->time(), n->channel(), patch_key);
4040 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4043 patch_key.program(),
4049 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4051 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4052 (int) n->channel() + 1,
4053 (int) n->velocity());
4059 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4060 uint8_t new_value) const
4062 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4064 mtv->set_note_highlight (new_value);
4067 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4071 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4073 show_verbose_cursor_for_new_note_value(n, n->note());
4077 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4079 trackview.editor().verbose_cursor()->set (text);
4080 trackview.editor().verbose_cursor()->show ();
4081 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4085 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4087 if (_model->notes().empty()) {
4088 return 0x40; // No notes, use default
4091 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4092 if (m == _model->notes().begin()) {
4093 // Before the start, use the velocity of the first note
4094 return (*m)->velocity();
4095 } else if (m == _model->notes().end()) {
4096 // Past the end, use the velocity of the last note
4098 return (*m)->velocity();
4101 // Interpolate velocity of surrounding notes
4102 MidiModel::Notes::const_iterator n = m;
4105 const double frac = ((time - (*n)->time()).to_double() /
4106 ((*m)->time() - (*n)->time()).to_double());
4108 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4111 /** @param p A session framepos.
4112 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
4113 * @return p snapped to the grid subdivision underneath it.
4116 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
4118 PublicEditor& editor = trackview.editor ();
4119 const Evoral::Beats grid_beats = get_grid_beats(p);
4120 const Evoral::Beats p_beat = max (Evoral::Beats(), region_frames_to_region_beats (p));
4122 grid_frames = region_beats_to_region_frames (p_beat + grid_beats) - region_beats_to_region_frames (p_beat);
4124 /* Hack so that we always snap to the note that we are over, instead of snapping
4125 to the next one if we're more than halfway through the one we're over.
4127 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
4128 p -= grid_frames / 2;
4131 return snap_frame_to_frame (p);
4135 MidiRegionView::get_channel_mode () const
4137 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4138 return rtav->midi_track()->get_playback_channel_mode();
4142 MidiRegionView::get_selected_channels () const
4144 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4145 return rtav->midi_track()->get_playback_channel_mask();
4150 MidiRegionView::get_grid_beats(framepos_t pos) const
4152 PublicEditor& editor = trackview.editor();
4153 bool success = false;
4154 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4156 beats = Evoral::Beats(1);