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)
394 enter_internal (ev->state);
401 MidiRegionView::leave_notify (GdkEventCrossing*)
410 MidiRegionView::mouse_mode_changed ()
412 // Adjust frame colour (become more transparent for internal tools)
416 if (!trackview.editor().internal_editing()) {
417 /* Switched out of internal editing mode while entered.
418 Only necessary for leave as a mouse_mode_change over a region
419 automatically triggers an enter event. */
422 else if (trackview.editor().current_mouse_mode() == MouseContent) {
423 // hide cursor and ghost note after changing to internal edit mode
424 remove_ghost_note ();
426 /* XXX This is problematic as the function is executed for every region
427 and only for one region _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 (uint32_t state)
442 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
443 // Show ghost note under pencil
444 create_ghost_note(_last_event_x, _last_event_y, state);
447 if (!_selection.empty()) {
448 // Grab keyboard for moving selected notes with arrow keys
449 Keyboard::magic_widget_grab_focus();
450 _grabbed_keyboard = true;
453 // Lower frame handles below notes so they don't steal events
454 if (frame_handle_start) {
455 frame_handle_start->lower_to_bottom();
457 if (frame_handle_end) {
458 frame_handle_end->lower_to_bottom();
463 MidiRegionView::leave_internal()
465 hide_verbose_cursor ();
466 remove_ghost_note ();
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) + _region->position());
554 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, ev->state);
556 clear_editor_note_selection ();
563 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x) + _region->position());
564 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, ev->state);
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, ev->state);
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, ev->state);
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, ev->state);
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, ev->state);
624 create_ghost_note (ev->x, ev->y, ev->state);
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, uint32_t state)
921 if (length < 2 * DBL_EPSILON) {
925 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
926 MidiStreamView* const view = mtv->midi_view();
927 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
933 // Start of note in frames relative to region start
934 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
935 const double snapped_qn = snap_frame_to_grid_underneath (t, divisions).to_double();
936 Evoral::Beats beat_time = Evoral::Beats (snapped_qn);
938 const double note = view->y_to_note(y);
939 const uint8_t chan = mtv->get_channel_for_add();
940 const uint8_t velocity = get_velocity_for_add(beat_time);
942 const boost::shared_ptr<NoteType> new_note(
943 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
945 if (_model->contains (new_note)) {
949 view->update_note_range(new_note->note());
951 start_note_diff_command(_("add note"));
953 clear_editor_note_selection ();
954 note_diff_add_note (new_note, true, false);
958 play_midi_note (new_note);
962 MidiRegionView::clear_events ()
964 // clear selection without signaling
965 clear_selection_internal ();
968 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
969 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
974 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
979 _patch_changes.clear();
981 _optimization_iterator = _events.end();
985 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
989 content_connection.disconnect ();
990 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
991 /* Don't signal as nobody else needs to know until selection has been altered. */
994 if (_enable_display) {
1000 MidiRegionView::start_note_diff_command (string name)
1002 if (!_note_diff_command) {
1003 trackview.editor().begin_reversible_command (name);
1004 _note_diff_command = _model->new_note_diff_command (name);
1009 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1011 if (_note_diff_command) {
1012 _note_diff_command->add (note);
1015 _marked_for_selection.insert(note);
1017 if (show_velocity) {
1018 _marked_for_velocity.insert(note);
1023 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1025 if (_note_diff_command && ev->note()) {
1026 _note_diff_command->remove(ev->note());
1031 MidiRegionView::note_diff_add_change (NoteBase* ev,
1032 MidiModel::NoteDiffCommand::Property property,
1035 if (_note_diff_command) {
1036 _note_diff_command->change (ev->note(), property, val);
1041 MidiRegionView::note_diff_add_change (NoteBase* ev,
1042 MidiModel::NoteDiffCommand::Property property,
1045 if (_note_diff_command) {
1046 _note_diff_command->change (ev->note(), property, val);
1051 MidiRegionView::apply_diff (bool as_subcommand)
1054 bool commit = false;
1056 if (!_note_diff_command) {
1060 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1061 // Mark all selected notes for selection when model reloads
1062 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1063 _marked_for_selection.insert((*i)->note());
1067 midi_view()->midi_track()->midi_playlist()->region_edited(
1068 _region, _note_diff_command);
1070 if (as_subcommand) {
1071 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1073 _model->apply_command (*trackview.session(), _note_diff_command);
1077 _note_diff_command = 0;
1079 if (add_or_remove) {
1080 _marked_for_selection.clear();
1083 _marked_for_velocity.clear();
1085 trackview.editor().commit_reversible_command ();
1090 MidiRegionView::abort_command()
1092 delete _note_diff_command;
1093 _note_diff_command = 0;
1094 clear_editor_note_selection();
1098 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1100 if (_optimization_iterator != _events.end()) {
1101 ++_optimization_iterator;
1104 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1105 return *_optimization_iterator;
1108 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1109 if ((*_optimization_iterator)->note() == note) {
1110 return *_optimization_iterator;
1117 /** This version finds any canvas note matching the supplied note. */
1119 MidiRegionView::find_canvas_note (NoteType note)
1121 Events::iterator it;
1123 for (it = _events.begin(); it != _events.end(); ++it) {
1124 if (*((*it)->note()) == note) {
1133 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1135 MidiModel::Notes notes;
1136 _model->get_notes (notes, op, val, chan_mask);
1138 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1139 NoteBase* cne = find_canvas_note (*n);
1147 MidiRegionView::redisplay_model()
1149 if (_active_notes) {
1150 // Currently recording
1151 const framecnt_t zoom = trackview.editor().get_current_zoom();
1152 if (zoom != _last_display_zoom) {
1153 /* Update resolved canvas notes to reflect changes in zoom without
1154 touching model. Leave active notes (with length 0) alone since
1155 they are being extended. */
1156 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1157 if ((*i)->note()->length() > 0) {
1161 _last_display_zoom = zoom;
1170 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1171 (*i)->invalidate ();
1174 MidiModel::ReadLock lock(_model->read_lock());
1176 MidiModel::Notes& notes (_model->notes());
1177 _optimization_iterator = _events.begin();
1179 bool empty_when_starting = _events.empty();
1182 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1184 boost::shared_ptr<NoteType> note (*n);
1187 if (note_in_region_range (note, visible)) {
1189 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1201 cne = add_note (note, visible);
1204 set<boost::shared_ptr<NoteType> >::iterator it;
1205 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1206 if (*(*it) == *note) {
1207 add_to_selection (cne);
1213 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1220 /* remove note items that are no longer valid */
1222 if (!empty_when_starting) {
1223 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1224 if (!(*i)->valid ()) {
1226 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1227 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1229 gr->remove_note (*i);
1234 i = _events.erase (i);
1242 _patch_changes.clear();
1246 display_patch_changes ();
1248 _marked_for_selection.clear ();
1249 _marked_for_velocity.clear ();
1250 _pending_note_selection.clear ();
1252 /* we may have caused _events to contain things out of order (e.g. if a note
1253 moved earlier or later). we don't generally need them in time order, but
1254 make a note that a sort is required for those cases that require it.
1257 _sort_needed = true;
1261 MidiRegionView::display_patch_changes ()
1263 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1264 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1266 for (uint8_t i = 0; i < 16; ++i) {
1267 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1271 /** @param active_channel true to display patch changes fully, false to display
1272 * them `greyed-out' (as on an inactive channel)
1275 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1277 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1279 if ((*i)->channel() != channel) {
1283 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1284 add_canvas_patch_change (*i, patch_name, active_channel);
1289 MidiRegionView::display_sysexes()
1291 bool have_periodic_system_messages = false;
1292 bool display_periodic_messages = true;
1294 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1296 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1297 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1298 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1301 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1302 have_periodic_system_messages = true;
1308 if (have_periodic_system_messages) {
1309 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1311 /* get an approximate value for the number of samples per video frame */
1313 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1315 /* if we are zoomed out beyond than the cutoff (i.e. more
1316 * frames per pixel than frames per 4 video frames), don't
1317 * show periodic sysex messages.
1320 if (zoom > (video_frame*4)) {
1321 display_periodic_messages = false;
1325 display_periodic_messages = false;
1328 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1330 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1331 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1333 Evoral::Beats time = (*i)->time();
1336 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1337 if (!display_periodic_messages) {
1345 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1346 str << int((*i)->buffer()[b]);
1347 if (b != (*i)->size() -1) {
1351 string text = str.str();
1353 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1355 double height = midi_stream_view()->contents_height();
1357 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1358 // SysEx canvas object!!!
1360 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1361 new SysEx (*this, _note_group, text, height, x, 1.0));
1363 // Show unless message is beyond the region bounds
1364 if (time - _region->start() >= _region->length() || time < _region->start()) {
1370 _sys_exes.push_back(sysex);
1374 MidiRegionView::~MidiRegionView ()
1376 in_destructor = true;
1378 hide_verbose_cursor ();
1380 delete _list_editor;
1382 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1384 if (_active_notes) {
1392 delete _note_diff_command;
1393 delete _step_edit_cursor;
1397 MidiRegionView::region_resized (const PropertyChange& what_changed)
1399 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1401 if (what_changed.contains (ARDOUR::Properties::position)) {
1402 _region_relative_time_converter.set_origin_b(_region->position());
1403 _region_relative_time_converter_double.set_origin_b(_region->position());
1404 /* reset_width dependent_items() redisplays model */
1408 if (what_changed.contains (ARDOUR::Properties::start) ||
1409 what_changed.contains (ARDOUR::Properties::position)) {
1410 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1412 /* catch end and start trim so we can update the view*/
1413 if (!what_changed.contains (ARDOUR::Properties::start) &&
1414 what_changed.contains (ARDOUR::Properties::length)) {
1415 enable_display (true);
1416 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1417 what_changed.contains (ARDOUR::Properties::length)) {
1418 enable_display (true);
1423 MidiRegionView::reset_width_dependent_items (double pixel_width)
1425 RegionView::reset_width_dependent_items(pixel_width);
1427 if (_enable_display) {
1431 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1432 if ((*x)->canvas_item()->width() >= _pixel_width) {
1439 move_step_edit_cursor (_step_edit_cursor_position);
1440 set_step_edit_cursor_width (_step_edit_cursor_width);
1444 MidiRegionView::set_height (double height)
1446 double old_height = _height;
1447 RegionView::set_height(height);
1449 apply_note_range (midi_stream_view()->lowest_note(),
1450 midi_stream_view()->highest_note(),
1451 height != old_height);
1454 name_text->raise_to_top();
1457 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1458 (*x)->set_height (midi_stream_view()->contents_height());
1461 if (_step_edit_cursor) {
1462 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1467 /** Apply the current note range from the stream view
1468 * by repositioning/hiding notes as necessary
1471 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1473 if (!_enable_display) {
1477 if (!force && _current_range_min == min && _current_range_max == max) {
1481 _current_range_min = min;
1482 _current_range_max = max;
1484 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1485 NoteBase* event = *i;
1486 boost::shared_ptr<NoteType> note (event->note());
1488 if (note->note() < _current_range_min ||
1489 note->note() > _current_range_max) {
1495 if (Note* cnote = dynamic_cast<Note*>(event)) {
1497 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1498 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1500 if (y0 < 0 || y1 >= _height) {
1501 /* During DnD, the region uses the 'old/current'
1502 * midi_stream_view()'s range and its position/height calculation.
1504 * Ideally DnD would decouple the midi_stream_view() for the
1505 * region(s) being dragged and set it to the target's range
1506 * (or in case of the drop-zone, FullRange).
1507 * but I don't see how this can be done without major rework.
1509 * For now, just prevent visual bleeding of events in case
1510 * the target-track is smaller.
1518 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1525 MidiRegionView::add_ghost (TimeAxisView& tv)
1527 double unit_position = _region->position () / samples_per_pixel;
1528 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1529 MidiGhostRegion* ghost;
1531 if (mtv && mtv->midi_view()) {
1532 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1533 to allow having midi notes on top of note lines and waveforms.
1535 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1537 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1540 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1541 ghost->add_note(*i);
1544 ghost->set_height ();
1545 ghost->set_duration (_region->length() / samples_per_pixel);
1546 ghosts.push_back (ghost);
1552 /** Begin tracking note state for successive calls to add_event
1555 MidiRegionView::begin_write()
1557 if (_active_notes) {
1558 delete[] _active_notes;
1560 _active_notes = new Note*[128];
1561 for (unsigned i = 0; i < 128; ++i) {
1562 _active_notes[i] = 0;
1567 /** Destroy note state for add_event
1570 MidiRegionView::end_write()
1572 delete[] _active_notes;
1574 _marked_for_selection.clear();
1575 _marked_for_velocity.clear();
1579 /** Resolve an active MIDI note (while recording).
1582 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1584 if (midi_view()->note_mode() != Sustained) {
1588 if (_active_notes && _active_notes[note]) {
1589 /* Set note length so update_note() works. Note this is a local note
1590 for recording, not from a model, so we can safely mess with it. */
1591 _active_notes[note]->note()->set_length(
1592 end_time - _active_notes[note]->note()->time());
1594 /* End time is relative to the region being recorded. */
1595 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1597 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1598 _active_notes[note]->set_outline_all ();
1599 _active_notes[note] = 0;
1604 /** Extend active notes to rightmost edge of region (if length is changed)
1607 MidiRegionView::extend_active_notes()
1609 if (!_active_notes) {
1613 for (unsigned i = 0; i < 128; ++i) {
1614 if (_active_notes[i]) {
1615 _active_notes[i]->set_x1(
1616 trackview.editor().sample_to_pixel(_region->length()));
1622 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1624 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1628 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1630 if (!route_ui || !route_ui->midi_track()) {
1634 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1638 /* NotePlayer deletes itself */
1642 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1644 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1645 start_playing_midi_chord(notes);
1649 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1651 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1655 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1657 if (!route_ui || !route_ui->midi_track()) {
1661 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1663 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1672 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1674 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1675 const bool outside = (note->time() < midi_reg->start_pulse() * 4.0 ||
1676 note->time() > (midi_reg->start_pulse() + midi_reg->length_pulse()) * 4.0);
1678 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1679 (note->note() <= midi_stream_view()->highest_note());
1685 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1689 if ((sus = dynamic_cast<Note*>(note))) {
1690 update_sustained(sus, update_ghost_regions);
1691 } else if ((hit = dynamic_cast<Hit*>(note))) {
1692 update_hit(hit, update_ghost_regions);
1696 /** Update a canvas note's size from its model note.
1697 * @param ev Canvas note to update.
1698 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1701 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1703 TempoMap& map (trackview.session()->tempo_map());
1704 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1705 boost::shared_ptr<NoteType> note = ev->note();
1706 const double qn_note_time = note->time().to_double() + ((_region->pulse() - mr->start_pulse()) * 4.0);
1707 const framepos_t note_start_frames = map.frame_at_quarter_note (qn_note_time) - _region->position();
1708 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1710 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1713 /* trim note display to not overlap the end of its region */
1714 if (note->length() > 0) {
1715 Evoral::Beats note_end_time = note->end_time();
1717 if (note->end_time().to_double() > (mr->start_pulse() + mr->length_pulse()) * 4.0) {
1718 note_end_time = Evoral::Beats ((mr->start_pulse() + mr->length_pulse()) * 4.0);
1720 const double session_qn_start = (_region->pulse() - mr->start_pulse()) * 4.0;
1721 const double quarter_note_end_time = session_qn_start + note_end_time.to_double();
1723 const framepos_t note_end_frames = map.frame_at_quarter_note (quarter_note_end_time) - _region->position();
1724 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1726 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1729 y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1);
1731 ArdourCanvas::Rect rect (x0, y0, x1, y1);
1734 if (!note->length()) {
1735 if (_active_notes && note->note() < 128) {
1736 Note* const old_rect = _active_notes[note->note()];
1738 /* There is an active note on this key, so we have a stuck
1739 note. Finish the old rectangle here. */
1740 old_rect->set_x1 (x1);
1741 old_rect->set_outline_all ();
1743 _active_notes[note->note()] = ev;
1745 /* outline all but right edge */
1746 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1747 ArdourCanvas::Rectangle::TOP|
1748 ArdourCanvas::Rectangle::LEFT|
1749 ArdourCanvas::Rectangle::BOTTOM));
1751 /* outline all edges */
1752 ev->set_outline_all ();
1755 // Update color in case velocity has changed
1756 //const uint32_t base_col = ev->base_color();
1757 //ev->set_fill_color(base_col);
1758 //ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1760 if (update_ghost_regions) {
1761 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1762 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1764 gr->update_note (ev);
1771 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1773 boost::shared_ptr<NoteType> note = ev->note();
1775 const double note_time_qn = note->time().to_double() + ((_region->pulse() - midi_region()->start_pulse()) * 4.0);
1776 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1778 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1779 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1780 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1782 // see DnD note in MidiRegionView::apply_note_range() above
1783 if (y <= 0 || y >= _height) {
1789 ev->set_position (ArdourCanvas::Duple (x, y));
1790 ev->set_height (diamond_size);
1792 // Update color in case velocity has changed
1793 ev->set_fill_color(ev->base_color());
1794 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1796 if (update_ghost_regions) {
1797 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1798 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1800 gr->update_note (ev);
1806 /** Add a MIDI note to the view (with length).
1808 * If in sustained mode, notes with length 0 will be considered active
1809 * notes, and resolve_note should be called when the corresponding note off
1810 * event arrives, to properly display the note.
1813 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1815 NoteBase* event = 0;
1817 if (midi_view()->note_mode() == Sustained) {
1819 Note* ev_rect = new Note (*this, _note_group, note);
1821 update_sustained (ev_rect);
1825 } else if (midi_view()->note_mode() == Percussive) {
1827 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1829 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1831 update_hit (ev_diamond);
1840 MidiGhostRegion* gr;
1842 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1843 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1844 gr->add_note(event);
1848 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1849 note_selected(event, true);
1852 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1853 event->show_velocity();
1856 event->on_channel_selection_change (get_selected_channels());
1857 _events.push_back(event);
1866 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1867 MidiStreamView* const view = mtv->midi_view();
1869 view->update_note_range (note->note());
1874 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1875 Evoral::Beats pos, Evoral::Beats len)
1877 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1879 /* potentially extend region to hold new note */
1881 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1882 framepos_t region_end = _region->last_frame();
1884 if (end_frame > region_end) {
1885 /* XX sets length in beats from audio space. make musical */
1886 _region->set_length (end_frame - _region->position(), 0);
1889 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1890 MidiStreamView* const view = mtv->midi_view();
1892 view->update_note_range(new_note->note());
1894 _marked_for_selection.clear ();
1896 start_note_diff_command (_("step add"));
1898 clear_editor_note_selection ();
1899 note_diff_add_note (new_note, true, false);
1903 // last_step_edit_note = new_note;
1907 MidiRegionView::step_sustain (Evoral::Beats beats)
1909 change_note_lengths (false, false, beats, false, true);
1912 /** Add a new patch change flag to the canvas.
1913 * @param patch the patch change to add
1914 * @param the text to display in the flag
1915 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1918 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1920 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1921 const double x = trackview.editor().sample_to_pixel (region_frames);
1923 double const height = midi_stream_view()->contents_height();
1925 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1926 // so we need to do something more sophisticated to keep its color
1927 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1930 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1931 new PatchChange(*this, group,
1938 if (patch_change->item().width() < _pixel_width) {
1939 // Show unless patch change is beyond the region bounds
1940 if (region_frames < 0 || region_frames >= _region->length()) {
1941 patch_change->hide();
1943 patch_change->show();
1946 patch_change->hide ();
1949 _patch_changes.push_back (patch_change);
1952 MIDI::Name::PatchPrimaryKey
1953 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1955 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1958 /// Return true iff @p pc applies to the given time on the given channel.
1960 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1962 return pc->time() <= time && pc->channel() == channel;
1966 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1968 // The earliest event not before time
1969 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1971 // Go backwards until we find the latest PC for this channel, or the start
1972 while (i != _model->patch_changes().begin() &&
1973 (i == _model->patch_changes().end() ||
1974 !patch_applies(*i, time, channel))) {
1978 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1979 key.set_bank((*i)->bank());
1980 key.set_program((*i)->program ());
1988 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1990 string name = _("alter patch change");
1991 trackview.editor().begin_reversible_command (name);
1992 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1994 if (pc.patch()->program() != new_patch.program()) {
1995 c->change_program (pc.patch (), new_patch.program());
1998 int const new_bank = new_patch.bank();
1999 if (pc.patch()->bank() != new_bank) {
2000 c->change_bank (pc.patch (), new_bank);
2003 _model->apply_command (*trackview.session(), c);
2004 trackview.editor().commit_reversible_command ();
2006 _patch_changes.clear ();
2007 display_patch_changes ();
2011 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2013 string name = _("alter patch change");
2014 trackview.editor().begin_reversible_command (name);
2015 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2017 if (old_change->time() != new_change.time()) {
2018 c->change_time (old_change, new_change.time());
2021 if (old_change->channel() != new_change.channel()) {
2022 c->change_channel (old_change, new_change.channel());
2025 if (old_change->program() != new_change.program()) {
2026 c->change_program (old_change, new_change.program());
2029 if (old_change->bank() != new_change.bank()) {
2030 c->change_bank (old_change, new_change.bank());
2033 _model->apply_command (*trackview.session(), c);
2034 trackview.editor().commit_reversible_command ();
2036 _patch_changes.clear ();
2037 display_patch_changes ();
2040 /** Add a patch change to the region.
2041 * @param t Time in frames relative to region position
2042 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2043 * MidiTimeAxisView::get_channel_for_add())
2046 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2048 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2049 string name = _("add patch change");
2051 trackview.editor().begin_reversible_command (name);
2052 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2053 c->add (MidiModel::PatchChangePtr (
2054 new Evoral::PatchChange<Evoral::Beats> (
2055 absolute_frames_to_source_beats (_region->position() + t),
2056 mtv->get_channel_for_add(), patch.program(), patch.bank()
2061 _model->apply_command (*trackview.session(), c);
2062 trackview.editor().commit_reversible_command ();
2064 _patch_changes.clear ();
2065 display_patch_changes ();
2069 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2071 trackview.editor().begin_reversible_command (_("move patch change"));
2072 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2073 c->change_time (pc.patch (), t);
2074 _model->apply_command (*trackview.session(), c);
2075 trackview.editor().commit_reversible_command ();
2077 _patch_changes.clear ();
2078 display_patch_changes ();
2082 MidiRegionView::delete_patch_change (PatchChange* pc)
2084 trackview.editor().begin_reversible_command (_("delete patch change"));
2085 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2086 c->remove (pc->patch ());
2087 _model->apply_command (*trackview.session(), c);
2088 trackview.editor().commit_reversible_command ();
2090 _patch_changes.clear ();
2091 display_patch_changes ();
2095 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2097 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2099 key.set_bank(key.bank() + delta);
2101 key.set_program(key.program() + delta);
2103 change_patch_change(patch, key);
2107 MidiRegionView::note_deleted (NoteBase* cne)
2109 if (_selection.empty()) {
2113 _selection.erase (cne);
2117 MidiRegionView::delete_selection()
2119 if (_selection.empty()) {
2123 start_note_diff_command (_("delete selection"));
2125 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2126 if ((*i)->selected()) {
2127 _note_diff_command->remove((*i)->note());
2134 hide_verbose_cursor ();
2138 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2140 start_note_diff_command (_("delete note"));
2141 _note_diff_command->remove (n);
2144 hide_verbose_cursor ();
2148 MidiRegionView::clear_editor_note_selection ()
2150 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2151 PublicEditor& editor(trackview.editor());
2152 editor.get_selection().clear_midi_notes();
2156 MidiRegionView::clear_selection ()
2158 clear_selection_internal();
2159 PublicEditor& editor(trackview.editor());
2160 editor.get_selection().remove(this);
2164 MidiRegionView::clear_selection_internal ()
2166 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2168 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2169 (*i)->set_selected(false);
2170 (*i)->hide_velocity();
2175 // Clearing selection entirely, ungrab keyboard
2176 Keyboard::magic_widget_drop_focus();
2177 _grabbed_keyboard = false;
2182 MidiRegionView::unique_select(NoteBase* ev)
2184 clear_editor_note_selection();
2185 add_to_selection(ev);
2189 MidiRegionView::select_all_notes ()
2191 clear_editor_note_selection ();
2193 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2194 add_to_selection (*i);
2199 MidiRegionView::select_range (framepos_t start, framepos_t end)
2201 clear_editor_note_selection ();
2203 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2204 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2205 if (t >= start && t <= end) {
2206 add_to_selection (*i);
2212 MidiRegionView::invert_selection ()
2214 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2215 if ((*i)->selected()) {
2216 remove_from_selection(*i);
2218 add_to_selection (*i);
2223 /** Used for selection undo/redo.
2224 The requested notes most likely won't exist in the view until the next model redisplay.
2227 MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
2230 list<boost::shared_ptr<NoteType> >::iterator n;
2232 for (n = notes.begin(); n != notes.end(); ++n) {
2233 if ((cne = find_canvas_note(*(*n))) != 0) {
2234 add_to_selection (cne);
2236 _pending_note_selection.insert(*n);
2242 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2244 bool have_selection = !_selection.empty();
2245 uint8_t low_note = 127;
2246 uint8_t high_note = 0;
2247 MidiModel::Notes& notes (_model->notes());
2248 _optimization_iterator = _events.begin();
2250 if (extend && !have_selection) {
2254 /* scan existing selection to get note range */
2256 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2257 if ((*i)->note()->note() < low_note) {
2258 low_note = (*i)->note()->note();
2260 if ((*i)->note()->note() > high_note) {
2261 high_note = (*i)->note()->note();
2266 clear_editor_note_selection ();
2268 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2269 /* only note previously selected is the one we are
2270 * reselecting. treat this as cancelling the selection.
2277 low_note = min (low_note, notenum);
2278 high_note = max (high_note, notenum);
2281 _no_sound_notes = true;
2283 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2285 boost::shared_ptr<NoteType> note (*n);
2287 bool select = false;
2289 if (((1 << note->channel()) & channel_mask) != 0) {
2291 if ((note->note() >= low_note && note->note() <= high_note)) {
2294 } else if (note->note() == notenum) {
2300 if ((cne = find_canvas_note (note)) != 0) {
2301 // extend is false because we've taken care of it,
2302 // since it extends by time range, not pitch.
2303 note_selected (cne, add, false);
2307 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2311 _no_sound_notes = false;
2315 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2317 MidiModel::Notes& notes (_model->notes());
2318 _optimization_iterator = _events.begin();
2320 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2322 boost::shared_ptr<NoteType> note (*n);
2325 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2326 if ((cne = find_canvas_note (note)) != 0) {
2327 if (cne->selected()) {
2328 note_deselected (cne);
2330 note_selected (cne, true, false);
2338 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2341 clear_editor_note_selection();
2342 add_to_selection (ev);
2347 if (!ev->selected()) {
2348 add_to_selection (ev);
2352 /* find end of latest note selected, select all between that and the start of "ev" */
2354 Evoral::Beats earliest = Evoral::MaxBeats;
2355 Evoral::Beats latest = Evoral::Beats();
2357 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2358 if ((*i)->note()->end_time() > latest) {
2359 latest = (*i)->note()->end_time();
2361 if ((*i)->note()->time() < earliest) {
2362 earliest = (*i)->note()->time();
2366 if (ev->note()->end_time() > latest) {
2367 latest = ev->note()->end_time();
2370 if (ev->note()->time() < earliest) {
2371 earliest = ev->note()->time();
2374 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2376 /* find notes entirely within OR spanning the earliest..latest range */
2378 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2379 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2380 add_to_selection (*i);
2388 MidiRegionView::note_deselected(NoteBase* ev)
2390 remove_from_selection (ev);
2394 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2396 PublicEditor& editor = trackview.editor();
2398 // Convert to local coordinates
2399 const framepos_t p = _region->position();
2400 const double y = midi_view()->y_position();
2401 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2402 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2403 const double y0 = max(0.0, gy0 - y);
2404 const double y1 = max(0.0, gy1 - y);
2406 // TODO: Make this faster by storing the last updated selection rect, and only
2407 // adjusting things that are in the area that appears/disappeared.
2408 // We probably need a tree to be able to find events in O(log(n)) time.
2410 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2411 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2412 // Rectangles intersect
2413 if (!(*i)->selected()) {
2414 add_to_selection (*i);
2416 } else if ((*i)->selected() && !extend) {
2417 // Rectangles do not intersect
2418 remove_from_selection (*i);
2422 typedef RouteTimeAxisView::AutomationTracks ATracks;
2423 typedef std::list<Selectable*> Selectables;
2425 /* Add control points to selection. */
2426 const ATracks& atracks = midi_view()->automation_tracks();
2427 Selectables selectables;
2428 editor.get_selection().clear_points();
2429 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2430 a->second->get_selectables(start, end, gy0, gy1, selectables);
2431 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2432 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2434 editor.get_selection().add(cp);
2437 a->second->set_selected_points(editor.get_selection().points);
2442 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2448 // TODO: Make this faster by storing the last updated selection rect, and only
2449 // adjusting things that are in the area that appears/disappeared.
2450 // We probably need a tree to be able to find events in O(log(n)) time.
2452 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2453 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2454 // within y- (note-) range
2455 if (!(*i)->selected()) {
2456 add_to_selection (*i);
2458 } else if ((*i)->selected() && !extend) {
2459 remove_from_selection (*i);
2465 MidiRegionView::remove_from_selection (NoteBase* ev)
2467 Selection::iterator i = _selection.find (ev);
2469 if (i != _selection.end()) {
2470 _selection.erase (i);
2471 if (_selection.empty() && _grabbed_keyboard) {
2473 Keyboard::magic_widget_drop_focus();
2474 _grabbed_keyboard = false;
2478 ev->set_selected (false);
2479 ev->hide_velocity ();
2481 if (_selection.empty()) {
2482 PublicEditor& editor (trackview.editor());
2483 editor.get_selection().remove (this);
2488 MidiRegionView::add_to_selection (NoteBase* ev)
2490 const bool selection_was_empty = _selection.empty();
2492 if (_selection.insert (ev).second) {
2493 ev->set_selected (true);
2494 start_playing_midi_note ((ev)->note());
2495 if (selection_was_empty && _entered) {
2496 // Grab keyboard for moving notes with arrow keys
2497 Keyboard::magic_widget_grab_focus();
2498 _grabbed_keyboard = true;
2502 if (selection_was_empty) {
2503 PublicEditor& editor (trackview.editor());
2504 editor.get_selection().add (this);
2509 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2511 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2512 PossibleChord to_play;
2513 Evoral::Beats earliest = Evoral::MaxBeats;
2515 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2516 if ((*i)->note()->time() < earliest) {
2517 earliest = (*i)->note()->time();
2521 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2522 if ((*i)->note()->time() == earliest) {
2523 to_play.push_back ((*i)->note());
2525 (*i)->move_event(dx, dy);
2528 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2530 if (to_play.size() > 1) {
2532 PossibleChord shifted;
2534 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2535 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2536 moved_note->set_note (moved_note->note() + cumulative_dy);
2537 shifted.push_back (moved_note);
2540 start_playing_midi_chord (shifted);
2542 } else if (!to_play.empty()) {
2544 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2545 moved_note->set_note (moved_note->note() + cumulative_dy);
2546 start_playing_midi_note (moved_note);
2552 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2554 uint8_t lowest_note_in_selection = 127;
2555 uint8_t highest_note_in_selection = 0;
2556 uint8_t highest_note_difference = 0;
2558 // find highest and lowest notes first
2560 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2561 uint8_t pitch = (*i)->note()->note();
2562 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2563 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2567 cerr << "dnote: " << (int) dnote << endl;
2568 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2569 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2570 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2571 << int(highest_note_in_selection) << endl;
2572 cerr << "selection size: " << _selection.size() << endl;
2573 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2576 // Make sure the note pitch does not exceed the MIDI standard range
2577 if (highest_note_in_selection + dnote > 127) {
2578 highest_note_difference = highest_note_in_selection - 127;
2580 TempoMap& map (trackview.session()->tempo_map());
2582 start_note_diff_command (_("move notes"));
2584 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2586 double const start_qn = (_region->pulse() - midi_region()->start_pulse()) * 4.0;
2587 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2588 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2593 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2595 uint8_t original_pitch = (*i)->note()->note();
2596 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2598 // keep notes in standard midi range
2599 clamp_to_0_127(new_pitch);
2601 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2602 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2604 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2609 // care about notes being moved beyond the upper/lower bounds on the canvas
2610 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2611 highest_note_in_selection > midi_stream_view()->highest_note()) {
2612 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2616 /** @param x Pixel relative to the region position.
2617 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2618 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2619 * @return Snapped frame relative to the region position.
2622 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2624 PublicEditor& editor (trackview.editor());
2625 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2628 /** @param x Pixel relative to the region position.
2629 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2630 * @return Snapped pixel relative to the region position.
2633 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2635 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2639 MidiRegionView::get_position_pixels()
2641 framepos_t region_frame = get_position();
2642 return trackview.editor().sample_to_pixel(region_frame);
2646 MidiRegionView::get_end_position_pixels()
2648 framepos_t frame = get_position() + get_duration ();
2649 return trackview.editor().sample_to_pixel(frame);
2653 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2655 /* the time converter will return the frame corresponding to `beats'
2656 relative to the start of the source. The start of the source
2657 is an implied position given by region->position - region->start
2659 const framepos_t source_start = _region->position() - _region->start();
2660 return source_start + _source_relative_time_converter.to (beats);
2664 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2666 /* the `frames' argument needs to be converted into a frame count
2667 relative to the start of the source before being passed in to the
2670 const framepos_t source_start = _region->position() - _region->start();
2671 return _source_relative_time_converter.from (frames - source_start);
2675 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2677 return _region_relative_time_converter.to(beats);
2681 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2683 return _region_relative_time_converter.from(frames);
2687 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2689 return _region_relative_time_converter_double.from(frames);
2693 MidiRegionView::begin_resizing (bool /*at_front*/)
2695 _resize_data.clear();
2697 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2698 Note *note = dynamic_cast<Note*> (*i);
2700 // only insert CanvasNotes into the map
2702 NoteResizeData *resize_data = new NoteResizeData();
2703 resize_data->note = note;
2705 // create a new SimpleRect from the note which will be the resize preview
2706 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2707 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2709 // calculate the colors: get the color settings
2710 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2711 UIConfiguration::instance().color ("midi note selected"),
2714 // make the resize preview notes more transparent and bright
2715 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2717 // calculate color based on note velocity
2718 resize_rect->set_fill_color (UINT_INTERPOLATE(
2719 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2723 resize_rect->set_outline_color (NoteBase::calculate_outline (
2724 UIConfiguration::instance().color ("midi note selected")));
2726 resize_data->resize_rect = resize_rect;
2727 _resize_data.push_back(resize_data);
2732 /** Update resizing notes while user drags.
2733 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2734 * @param at_front which end of the note (true == note on, false == note off)
2735 * @param delta_x change in mouse position since the start of the drag
2736 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2737 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2738 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2739 * as the \a primary note.
2740 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2741 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2744 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2746 TempoMap& tmap (trackview.session()->tempo_map());
2747 bool cursor_set = false;
2748 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2750 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2751 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2752 Note* canvas_note = (*i)->note;
2757 current_x = canvas_note->x0() + delta_x + snap_delta;
2759 current_x = primary->x0() + delta_x + snap_delta;
2763 current_x = canvas_note->x1() + delta_x + snap_delta;
2765 current_x = primary->x1() + delta_x + snap_delta;
2769 if (current_x < 0) {
2770 // This works even with snapping because RegionView::snap_frame_to_frame()
2771 // snaps forward if the snapped sample is before the beginning of the region
2774 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2775 current_x = trackview.editor().sample_to_pixel(_region->length());
2780 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2782 resize_rect->set_x0 (current_x - snap_delta);
2784 resize_rect->set_x1 (canvas_note->x1());
2787 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2789 resize_rect->set_x1 (current_x - snap_delta);
2791 resize_rect->set_x0 (canvas_note->x0());
2795 /* Convert snap delta from pixels to beats. */
2796 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2797 double snap_delta_beats = 0.0;
2800 /* negative beat offsets aren't allowed */
2801 if (snap_delta_samps > 0) {
2802 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2803 } else if (snap_delta_samps < 0) {
2804 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2809 int32_t divisions = 0;
2812 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2813 divisions = trackview.editor().get_grid_music_divisions (0);
2815 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2817 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2818 - midi_region()->beat()) + midi_region()->start_beats();
2820 Evoral::Beats len = Evoral::Beats();
2823 if (beats < canvas_note->note()->end_time()) {
2824 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2825 len += canvas_note->note()->length();
2828 if (beats >= canvas_note->note()->time()) {
2829 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2833 len = std::max(Evoral::Beats(1 / 512.0), len);
2836 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2837 show_verbose_cursor (buf, 0, 0);
2846 /** Finish resizing notes when the user releases the mouse button.
2847 * Parameters the same as for \a update_resizing().
2850 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2852 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2853 TempoMap& tmap (trackview.session()->tempo_map());
2855 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2856 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2858 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2859 Note* canvas_note = (*i)->note;
2860 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2862 /* Get the new x position for this resize, which is in pixels relative
2863 * to the region position.
2870 current_x = canvas_note->x0() + delta_x + snap_delta;
2872 current_x = primary->x0() + delta_x + snap_delta;
2876 current_x = canvas_note->x1() + delta_x + snap_delta;
2878 current_x = primary->x1() + delta_x + snap_delta;
2882 if (current_x < 0) {
2885 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2886 current_x = trackview.editor().sample_to_pixel(_region->length());
2889 /* Convert snap delta from pixels to beats with sign. */
2890 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2891 double snap_delta_beats = 0.0;
2894 if (snap_delta_samps > 0) {
2895 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2896 } else if (snap_delta_samps < 0) {
2897 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2901 uint32_t divisions = 0;
2902 /* Convert the new x position to a frame within the source */
2903 framepos_t current_fr;
2905 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2906 divisions = trackview.editor().get_grid_music_divisions (0);
2908 current_fr = trackview.editor().pixel_to_sample (current_x);
2911 /* and then to beats */
2912 const double e_baf = tmap.exact_beat_at_frame (current_fr + midi_region()->position(), divisions);
2913 const double quarter_note_start_beat = tmap.quarter_note_at_beat (_region->beat() - midi_region()->start_beats().to_double());
2914 const Evoral::Beats x_beats = Evoral::Beats (tmap.quarter_note_at_beat (e_baf) - quarter_note_start_beat);
2916 if (at_front && x_beats < canvas_note->note()->end_time()) {
2917 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2918 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2919 len += canvas_note->note()->length();
2922 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2927 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2928 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2929 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2936 _resize_data.clear();
2941 MidiRegionView::abort_resizing ()
2943 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2944 delete (*i)->resize_rect;
2948 _resize_data.clear ();
2952 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2954 uint8_t new_velocity;
2957 new_velocity = event->note()->velocity() + velocity;
2958 clamp_to_0_127(new_velocity);
2960 new_velocity = velocity;
2963 event->set_selected (event->selected()); // change color
2965 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2969 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2974 new_note = event->note()->note() + note;
2979 clamp_to_0_127 (new_note);
2980 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2984 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2986 bool change_start = false;
2987 bool change_length = false;
2988 Evoral::Beats new_start;
2989 Evoral::Beats new_length;
2991 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2993 front_delta: if positive - move the start of the note later in time (shortening it)
2994 if negative - move the start of the note earlier in time (lengthening it)
2996 end_delta: if positive - move the end of the note later in time (lengthening it)
2997 if negative - move the end of the note earlier in time (shortening it)
3000 if (!!front_delta) {
3001 if (front_delta < 0) {
3003 if (event->note()->time() < -front_delta) {
3004 new_start = Evoral::Beats();
3006 new_start = event->note()->time() + front_delta; // moves earlier
3009 /* start moved toward zero, so move the end point out to where it used to be.
3010 Note that front_delta is negative, so this increases the length.
3013 new_length = event->note()->length() - front_delta;
3014 change_start = true;
3015 change_length = true;
3019 Evoral::Beats new_pos = event->note()->time() + front_delta;
3021 if (new_pos < event->note()->end_time()) {
3022 new_start = event->note()->time() + front_delta;
3023 /* start moved toward the end, so move the end point back to where it used to be */
3024 new_length = event->note()->length() - front_delta;
3025 change_start = true;
3026 change_length = true;
3033 bool can_change = true;
3034 if (end_delta < 0) {
3035 if (event->note()->length() < -end_delta) {
3041 new_length = event->note()->length() + end_delta;
3042 change_length = true;
3047 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3050 if (change_length) {
3051 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3056 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3058 uint8_t new_channel;
3062 if (event->note()->channel() < -chn) {
3065 new_channel = event->note()->channel() + chn;
3068 new_channel = event->note()->channel() + chn;
3071 new_channel = (uint8_t) chn;
3074 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3078 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3080 Evoral::Beats new_time;
3084 if (event->note()->time() < -delta) {
3085 new_time = Evoral::Beats();
3087 new_time = event->note()->time() + delta;
3090 new_time = event->note()->time() + delta;
3096 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3100 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3102 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3106 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3111 if (_selection.empty()) {
3126 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3127 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3133 start_note_diff_command (_("change velocities"));
3135 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3136 Selection::iterator next = i;
3140 if (i == _selection.begin()) {
3141 change_note_velocity (*i, delta, true);
3142 value = (*i)->note()->velocity() + delta;
3144 change_note_velocity (*i, value, false);
3148 change_note_velocity (*i, delta, true);
3157 if (!_selection.empty()) {
3159 snprintf (buf, sizeof (buf), "Vel %d",
3160 (int) (*_selection.begin())->note()->velocity());
3161 show_verbose_cursor (buf, 10, 10);
3167 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3169 if (_selection.empty()) {
3186 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3188 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3192 if ((int8_t) (*i)->note()->note() + delta > 127) {
3199 start_note_diff_command (_("transpose"));
3201 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3202 Selection::iterator next = i;
3204 change_note_note (*i, delta, true);
3212 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3216 delta = Evoral::Beats(1.0/128.0);
3218 /* grab the current grid distance */
3219 delta = get_grid_beats(_region->position());
3227 start_note_diff_command (_("change note lengths"));
3229 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3230 Selection::iterator next = i;
3233 /* note the negation of the delta for start */
3236 (start ? -delta : Evoral::Beats()),
3237 (end ? delta : Evoral::Beats()));
3246 MidiRegionView::nudge_notes (bool forward, bool fine)
3248 if (_selection.empty()) {
3252 /* pick a note as the point along the timeline to get the nudge distance.
3253 its not necessarily the earliest note, so we may want to pull the notes out
3254 into a vector and sort before using the first one.
3257 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3258 Evoral::Beats delta;
3262 /* non-fine, move by 1 bar regardless of snap */
3263 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3265 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3267 /* grid is off - use nudge distance */
3270 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3271 delta = region_frames_to_region_beats (fabs ((double)distance));
3277 framepos_t next_pos = ref_point;
3280 if (max_framepos - 1 < next_pos) {
3284 if (next_pos == 0) {
3290 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3291 const framecnt_t distance = ref_point - next_pos;
3292 delta = region_frames_to_region_beats (fabs ((double)distance));
3303 start_note_diff_command (_("nudge"));
3305 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3306 Selection::iterator next = i;
3308 change_note_time (*i, delta, true);
3316 MidiRegionView::change_channel(uint8_t channel)
3318 start_note_diff_command(_("change channel"));
3319 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3320 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3328 MidiRegionView::note_entered(NoteBase* ev)
3330 _note_entered = true;
3332 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3334 if (_mouse_state == SelectTouchDragging) {
3336 note_selected (ev, true);
3338 } else if (editor->current_mouse_mode() == MouseContent) {
3340 remove_ghost_note ();
3341 show_verbose_cursor (ev->note ());
3343 } else if (editor->current_mouse_mode() == MouseDraw) {
3345 remove_ghost_note ();
3346 show_verbose_cursor (ev->note ());
3351 MidiRegionView::note_left (NoteBase*)
3353 _note_entered = false;
3355 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3356 (*i)->hide_velocity ();
3359 hide_verbose_cursor ();
3363 MidiRegionView::patch_entered (PatchChange* p)
3366 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3367 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3368 << _("Channel ") << ((int) p->patch()->channel() + 1);
3369 show_verbose_cursor (s.str(), 10, 20);
3370 p->item().grab_focus();
3374 MidiRegionView::patch_left (PatchChange *)
3376 hide_verbose_cursor ();
3377 /* focus will transfer back via the enter-notify event sent to this
3383 MidiRegionView::sysex_entered (SysEx* p)
3387 // need a way to extract text from p->_flag->_text
3389 // show_verbose_cursor (s.str(), 10, 20);
3390 p->item().grab_focus();
3394 MidiRegionView::sysex_left (SysEx *)
3396 hide_verbose_cursor ();
3397 /* focus will transfer back via the enter-notify event sent to this
3403 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3405 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3406 Editing::MouseMode mm = editor->current_mouse_mode();
3407 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3409 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3410 if (can_set_cursor && ctx) {
3411 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3412 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3413 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3414 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3416 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3422 MidiRegionView::get_fill_color() const
3424 const std::string mod_name = (_dragging ? "dragging region" :
3425 trackview.editor().internal_editing() ? "editable region" :
3428 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3429 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3430 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3431 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3433 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3437 MidiRegionView::midi_channel_mode_changed ()
3439 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3440 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3441 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3443 if (mode == ForceChannel) {
3444 mask = 0xFFFF; // Show all notes as active (below)
3447 // Update notes for selection
3448 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3449 (*i)->on_channel_selection_change (mask);
3452 _patch_changes.clear ();
3453 display_patch_changes ();
3457 MidiRegionView::instrument_settings_changed ()
3463 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3465 if (_selection.empty()) {
3469 PublicEditor& editor (trackview.editor());
3473 /* XXX what to do ? */
3477 editor.get_cut_buffer().add (selection_as_cut_buffer());
3485 start_note_diff_command();
3487 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3494 note_diff_remove_note (*i);
3504 MidiRegionView::selection_as_cut_buffer () const
3508 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3509 NoteType* n = (*i)->note().get();
3510 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3513 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3519 /** This method handles undo */
3521 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3523 bool commit = false;
3524 // Paste notes, if available
3525 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3526 if (m != selection.midi_notes.end()) {
3527 ctx.counts.increase_n_notes();
3528 if (!(*m)->empty()) {
3531 paste_internal(pos, ctx.count, ctx.times, **m);
3534 // Paste control points to automation children, if available
3535 typedef RouteTimeAxisView::AutomationTracks ATracks;
3536 const ATracks& atracks = midi_view()->automation_tracks();
3537 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3538 if (a->second->paste(pos, selection, ctx, sub_num)) {
3540 trackview.editor().begin_reversible_command (Operations::paste);
3547 trackview.editor().commit_reversible_command ();
3552 /** This method handles undo */
3554 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3560 start_note_diff_command (_("paste"));
3562 const Evoral::Beats snap_beats = get_grid_beats(pos);
3563 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3564 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3565 const Evoral::Beats duration = last_time - first_time;
3566 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3567 const Evoral::Beats paste_offset = snap_duration * paste_count;
3568 const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3569 Evoral::Beats end_point = Evoral::Beats();
3571 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3574 duration, pos, _region->position(),
3577 clear_editor_note_selection ();
3579 for (int n = 0; n < (int) times; ++n) {
3581 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3583 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3584 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3585 copied_note->set_id (Evoral::next_event_id());
3587 /* make all newly added notes selected */
3589 note_diff_add_note (copied_note, true);
3590 end_point = copied_note->end_time();
3594 /* if we pasted past the current end of the region, extend the region */
3596 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3597 framepos_t region_end = _region->position() + _region->length() - 1;
3599 if (end_frame > region_end) {
3601 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3603 _region->clear_changes ();
3604 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3605 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3606 trackview.session()->add_command (new StatefulDiffCommand (_region));
3612 struct EventNoteTimeEarlyFirstComparator {
3613 bool operator() (NoteBase* a, NoteBase* b) {
3614 return a->note()->time() < b->note()->time();
3619 MidiRegionView::time_sort_events ()
3621 if (!_sort_needed) {
3625 EventNoteTimeEarlyFirstComparator cmp;
3628 _sort_needed = false;
3632 MidiRegionView::goto_next_note (bool add_to_selection)
3634 bool use_next = false;
3636 if (_events.back()->selected()) {
3640 time_sort_events ();
3642 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3643 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3645 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3646 if ((*i)->selected()) {
3649 } else if (use_next) {
3650 if (channel_mask & (1 << (*i)->note()->channel())) {
3651 if (!add_to_selection) {
3654 note_selected (*i, true, false);
3661 /* use the first one */
3663 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3664 unique_select (_events.front());
3669 MidiRegionView::goto_previous_note (bool add_to_selection)
3671 bool use_next = false;
3673 if (_events.front()->selected()) {
3677 time_sort_events ();
3679 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3680 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3682 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3683 if ((*i)->selected()) {
3686 } else if (use_next) {
3687 if (channel_mask & (1 << (*i)->note()->channel())) {
3688 if (!add_to_selection) {
3691 note_selected (*i, true, false);
3698 /* use the last one */
3700 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3701 unique_select (*(_events.rbegin()));
3706 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3708 bool had_selected = false;
3710 time_sort_events ();
3712 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3713 if ((*i)->selected()) {
3714 selected.insert ((*i)->note());
3715 had_selected = true;
3719 if (allow_all_if_none_selected && !had_selected) {
3720 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3721 selected.insert ((*i)->note());
3727 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3729 x = std::max(0.0, x);
3731 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3736 _note_group->canvas_to_item (x, y);
3738 PublicEditor& editor = trackview.editor ();
3740 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3742 const int32_t divisions = editor.get_grid_music_divisions (state);
3743 const double snapped_region_qn = snap_frame_to_grid_underneath (unsnapped_frame, divisions).to_double();
3745 Evoral::Beats snapped_beats = Evoral::Beats (snapped_region_qn);
3746 /* calculate time in beats relative to start of source */
3747 const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3749 _ghost_note->note()->set_time (snapped_beats);
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 (snapped_beats));
3754 /* the ghost note does not appear in ghost regions, so pass false in here */
3755 update_note (_ghost_note, false);
3757 show_verbose_cursor (_ghost_note->note ());
3761 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3763 remove_ghost_note ();
3765 boost::shared_ptr<NoteType> g (new NoteType);
3766 if (midi_view()->note_mode() == Sustained) {
3767 _ghost_note = new Note (*this, _note_group, g);
3769 _ghost_note = new Hit (*this, _note_group, 10, g);
3771 _ghost_note->set_ignore_events (true);
3772 _ghost_note->set_outline_color (0x000000aa);
3773 update_ghost_note (x, y, state);
3774 _ghost_note->show ();
3776 show_verbose_cursor (_ghost_note->note ());
3780 MidiRegionView::remove_ghost_note ()
3787 MidiRegionView::hide_verbose_cursor ()
3789 trackview.editor().verbose_cursor()->hide ();
3790 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3792 mtv->set_note_highlight (NO_MIDI_NOTE);
3797 MidiRegionView::snap_changed ()
3803 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3807 MidiRegionView::drop_down_keys ()
3809 _mouse_state = None;
3813 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3815 /* XXX: This is dead code. What was it for? */
3817 double note = midi_stream_view()->y_to_note(y);
3819 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3821 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3823 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3824 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3825 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3826 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3831 bool add_mrv_selection = false;
3833 if (_selection.empty()) {
3834 add_mrv_selection = true;
3837 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3838 if (_selection.insert (*i).second) {
3839 (*i)->set_selected (true);
3843 if (add_mrv_selection) {
3844 PublicEditor& editor (trackview.editor());
3845 editor.get_selection().add (this);
3850 MidiRegionView::color_handler ()
3852 RegionView::color_handler ();
3854 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3855 (*i)->set_selected ((*i)->selected()); // will change color
3858 /* XXX probably more to do here */
3862 MidiRegionView::enable_display (bool yn)
3864 RegionView::enable_display (yn);
3868 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3870 if (_step_edit_cursor == 0) {
3871 ArdourCanvas::Item* const group = get_canvas_group();
3873 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3874 _step_edit_cursor->set_y0 (0);
3875 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3876 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3877 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3880 move_step_edit_cursor (pos);
3881 _step_edit_cursor->show ();
3885 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3887 _step_edit_cursor_position = pos;
3889 if (_step_edit_cursor) {
3890 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3891 _step_edit_cursor->set_x0 (pixel);
3892 set_step_edit_cursor_width (_step_edit_cursor_width);
3897 MidiRegionView::hide_step_edit_cursor ()
3899 if (_step_edit_cursor) {
3900 _step_edit_cursor->hide ();
3905 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3907 _step_edit_cursor_width = beats;
3909 if (_step_edit_cursor) {
3910 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3911 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3912 - region_beats_to_region_frames (_step_edit_cursor_position)));
3916 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3917 * @param w Source that the data will end up in.
3920 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3922 if (!_active_notes) {
3923 /* we aren't actively being recorded to */
3927 boost::shared_ptr<MidiSource> src = w.lock ();
3928 if (!src || src != midi_region()->midi_source()) {
3929 /* recorded data was not destined for our source */
3933 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3935 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3937 framepos_t back = max_framepos;
3939 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3940 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3942 if (ev.is_channel_event()) {
3943 if (get_channel_mode() == FilterChannels) {
3944 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3950 /* convert from session frames to source beats */
3951 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3952 ev.time() - src->timeline_position() + _region->start());
3954 if (ev.type() == MIDI_CMD_NOTE_ON) {
3955 boost::shared_ptr<NoteType> note (
3956 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3958 add_note (note, true);
3960 /* fix up our note range */
3961 if (ev.note() < _current_range_min) {
3962 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3963 } else if (ev.note() > _current_range_max) {
3964 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3967 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3968 resolve_note (ev.note (), time_beats);
3974 midi_stream_view()->check_record_layers (region(), back);
3978 MidiRegionView::trim_front_starting ()
3980 /* We used to eparent the note group to the region view's parent, so that it didn't change.
3986 MidiRegionView::trim_front_ending ()
3988 if (_region->start() < 0) {
3989 /* Trim drag made start time -ve; fix this */
3990 midi_region()->fix_negative_start ();
3995 MidiRegionView::edit_patch_change (PatchChange* pc)
3997 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3999 int response = d.run();
4002 case Gtk::RESPONSE_ACCEPT:
4004 case Gtk::RESPONSE_REJECT:
4005 delete_patch_change (pc);
4011 change_patch_change (pc->patch(), d.patch ());
4015 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4018 // sysyex object doesn't have a pointer to a sysex event
4019 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4020 // c->remove (sysex->sysex());
4021 // _model->apply_command (*trackview.session(), c);
4023 //_sys_exes.clear ();
4024 // display_sysexes();
4028 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4030 using namespace MIDI::Name;
4033 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4035 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4037 MIDI::Name::PatchPrimaryKey patch_key;
4038 get_patch_key_at(n->time(), n->channel(), patch_key);
4039 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4042 patch_key.program(),
4048 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4050 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4051 (int) n->channel() + 1,
4052 (int) n->velocity());
4058 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4059 uint8_t new_value) const
4061 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4063 mtv->set_note_highlight (new_value);
4066 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4070 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4072 show_verbose_cursor_for_new_note_value(n, n->note());
4076 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4078 trackview.editor().verbose_cursor()->set (text);
4079 trackview.editor().verbose_cursor()->show ();
4080 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4084 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4086 if (_model->notes().empty()) {
4087 return 0x40; // No notes, use default
4090 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4091 if (m == _model->notes().begin()) {
4092 // Before the start, use the velocity of the first note
4093 return (*m)->velocity();
4094 } else if (m == _model->notes().end()) {
4095 // Past the end, use the velocity of the last note
4097 return (*m)->velocity();
4100 // Interpolate velocity of surrounding notes
4101 MidiModel::Notes::const_iterator n = m;
4104 const double frac = ((time - (*n)->time()).to_double() /
4105 ((*m)->time() - (*n)->time()).to_double());
4107 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4110 /** @param p A session framepos.
4111 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4112 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4113 * @return beat duration of p snapped to the grid subdivision underneath it.
4116 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions) const
4118 TempoMap& map (trackview.session()->tempo_map());
4120 const double qaf = map.quarter_note_at_frame (p + _region->position());
4122 if (divisions != 0) {
4123 eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
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 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4128 const double rem = fmod (qaf, grid_beats.to_double());
4129 if (rem >= grid_beats.to_double() / 2.0) {
4130 eqaf -= grid_beats.to_double();
4136 return Evoral::Beats (eqaf - ((_region->pulse() - midi_region()->start_pulse()) * 4.0));
4140 MidiRegionView::get_channel_mode () const
4142 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4143 return rtav->midi_track()->get_playback_channel_mode();
4147 MidiRegionView::get_selected_channels () const
4149 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4150 return rtav->midi_track()->get_playback_channel_mask();
4155 MidiRegionView::get_grid_beats(framepos_t pos) const
4157 PublicEditor& editor = trackview.editor();
4158 bool success = false;
4159 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4161 beats = Evoral::Beats(1);