2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "midi++/midnam_patch.h"
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
81 #include "patch_change.h"
83 #include "ui_config.h"
87 using namespace ARDOUR;
89 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
95 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
97 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
98 RouteTimeAxisView& tv,
99 boost::shared_ptr<MidiRegion> r,
101 uint32_t basic_color)
102 : RegionView (parent, tv, r, spu, basic_color)
103 , _current_range_min(0)
104 , _current_range_max(0)
105 , _region_relative_time_converter(r->session().tempo_map(), r->position())
106 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
107 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
109 , _note_group (new ArdourCanvas::Container (group))
110 , _note_diff_command (0)
112 , _step_edit_cursor (0)
113 , _step_edit_cursor_width (1.0)
114 , _step_edit_cursor_position (0.0)
115 , _channel_selection_scoped_note (0)
116 , _temporary_note_group (0)
119 , _sort_needed (true)
120 , _optimization_iterator (_events.end())
122 , _no_sound_notes (false)
123 , _last_display_zoom (0)
126 , _grabbed_keyboard (false)
128 , _mouse_changed_selection (false)
130 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
131 _note_group->raise_to_top();
132 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
134 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
135 connect_to_diskstream ();
137 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
139 PublicEditor& editor (trackview.editor());
140 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
143 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
144 RouteTimeAxisView& tv,
145 boost::shared_ptr<MidiRegion> r,
147 uint32_t basic_color,
149 TimeAxisViewItem::Visibility visibility)
150 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
151 , _current_range_min(0)
152 , _current_range_max(0)
153 , _region_relative_time_converter(r->session().tempo_map(), r->position())
154 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
155 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
157 , _note_group (new ArdourCanvas::Container (group))
158 , _note_diff_command (0)
160 , _step_edit_cursor (0)
161 , _step_edit_cursor_width (1.0)
162 , _step_edit_cursor_position (0.0)
163 , _channel_selection_scoped_note (0)
164 , _temporary_note_group (0)
167 , _sort_needed (true)
168 , _optimization_iterator (_events.end())
170 , _no_sound_notes (false)
171 , _last_display_zoom (0)
174 , _grabbed_keyboard (false)
176 , _mouse_changed_selection (false)
178 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
179 _note_group->raise_to_top();
181 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
183 connect_to_diskstream ();
185 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
187 PublicEditor& editor (trackview.editor());
188 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
192 MidiRegionView::parameter_changed (std::string const & p)
194 if (p == "display-first-midi-bank-as-zero") {
195 if (_enable_display) {
201 MidiRegionView::MidiRegionView (const MidiRegionView& other)
202 : sigc::trackable(other)
204 , _current_range_min(0)
205 , _current_range_max(0)
206 , _region_relative_time_converter(other.region_relative_time_converter())
207 , _source_relative_time_converter(other.source_relative_time_converter())
208 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
210 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
211 , _note_diff_command (0)
213 , _step_edit_cursor (0)
214 , _step_edit_cursor_width (1.0)
215 , _step_edit_cursor_position (0.0)
216 , _channel_selection_scoped_note (0)
217 , _temporary_note_group (0)
220 , _sort_needed (true)
221 , _optimization_iterator (_events.end())
223 , _no_sound_notes (false)
224 , _last_display_zoom (0)
227 , _grabbed_keyboard (false)
229 , _mouse_changed_selection (false)
234 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
235 : RegionView (other, boost::shared_ptr<Region> (region))
236 , _current_range_min(0)
237 , _current_range_max(0)
238 , _region_relative_time_converter(other.region_relative_time_converter())
239 , _source_relative_time_converter(other.source_relative_time_converter())
240 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
242 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
243 , _note_diff_command (0)
245 , _step_edit_cursor (0)
246 , _step_edit_cursor_width (1.0)
247 , _step_edit_cursor_position (0.0)
248 , _channel_selection_scoped_note (0)
249 , _temporary_note_group (0)
252 , _sort_needed (true)
253 , _optimization_iterator (_events.end())
255 , _no_sound_notes (false)
256 , _last_display_zoom (0)
259 , _grabbed_keyboard (false)
261 , _mouse_changed_selection (false)
267 MidiRegionView::init (bool wfd)
269 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
272 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
273 midi_region()->midi_source(0)->load_model(lm);
276 _model = midi_region()->midi_source(0)->model();
277 _enable_display = false;
278 fill_color_name = "midi frame base";
280 RegionView::init (false);
282 set_height (trackview.current_height());
285 region_sync_changed ();
286 region_resized (ARDOUR::bounds_change);
291 _enable_display = true;
294 display_model (_model);
298 reset_width_dependent_items (_pixel_width);
300 group->raise_to_top();
302 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
303 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
306 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
307 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
309 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
310 boost::bind (&MidiRegionView::snap_changed, this),
313 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
314 boost::bind (&MidiRegionView::mouse_mode_changed, this),
317 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
318 connect_to_diskstream ();
320 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
322 PublicEditor& editor (trackview.editor());
323 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
327 MidiRegionView::instrument_info () const
329 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
330 return route_ui->route()->instrument_info();
333 const boost::shared_ptr<ARDOUR::MidiRegion>
334 MidiRegionView::midi_region() const
336 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
340 MidiRegionView::connect_to_diskstream ()
342 midi_view()->midi_track()->DataRecorded.connect(
343 *this, invalidator(*this),
344 boost::bind (&MidiRegionView::data_recorded, this, _1),
349 MidiRegionView::canvas_group_event(GdkEvent* ev)
351 if (in_destructor || _recregion) {
355 if (!trackview.editor().internal_editing()) {
356 // not in internal edit mode, so just act like a normal region
357 return RegionView::canvas_group_event (ev);
363 case GDK_ENTER_NOTIFY:
364 _last_event_x = ev->crossing.x;
365 _last_event_y = ev->crossing.y;
366 enter_notify(&ev->crossing);
367 // set entered_regionview (among other things)
368 return RegionView::canvas_group_event (ev);
370 case GDK_LEAVE_NOTIFY:
371 _last_event_x = ev->crossing.x;
372 _last_event_y = ev->crossing.y;
373 leave_notify(&ev->crossing);
374 // reset entered_regionview (among other things)
375 return RegionView::canvas_group_event (ev);
378 if (scroll (&ev->scroll)) {
384 return key_press (&ev->key);
386 case GDK_KEY_RELEASE:
387 return key_release (&ev->key);
389 case GDK_BUTTON_PRESS:
390 return button_press (&ev->button);
392 case GDK_BUTTON_RELEASE:
393 r = button_release (&ev->button);
396 case GDK_MOTION_NOTIFY:
397 _last_event_x = ev->motion.x;
398 _last_event_y = ev->motion.y;
399 return motion (&ev->motion);
405 return RegionView::canvas_group_event (ev);
409 MidiRegionView::enter_notify (GdkEventCrossing* ev)
418 MidiRegionView::leave_notify (GdkEventCrossing*)
427 MidiRegionView::mouse_mode_changed ()
429 // Adjust frame colour (become more transparent for internal tools)
433 if (trackview.editor().internal_editing()) {
434 // Switched in to internal editing mode while entered
437 // Switched out of internal editing mode while entered
444 MidiRegionView::enter_internal()
446 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
447 // Show ghost note under pencil
448 create_ghost_note(_last_event_x, _last_event_y);
451 if (!_selection.empty()) {
452 // Grab keyboard for moving selected notes with arrow keys
453 Keyboard::magic_widget_grab_focus();
454 _grabbed_keyboard = true;
457 // Lower frame handles below notes so they don't steal events
458 if (frame_handle_start) {
459 frame_handle_start->lower_to_bottom();
461 if (frame_handle_end) {
462 frame_handle_end->lower_to_bottom();
467 MidiRegionView::leave_internal()
469 hide_verbose_cursor ();
470 remove_ghost_note ();
472 if (_grabbed_keyboard) {
473 Keyboard::magic_widget_drop_focus();
474 _grabbed_keyboard = false;
477 // Raise frame handles above notes so they catch events
478 if (frame_handle_start) {
479 frame_handle_start->raise_to_top();
481 if (frame_handle_end) {
482 frame_handle_end->raise_to_top();
487 MidiRegionView::button_press (GdkEventButton* ev)
489 if (ev->button != 1) {
493 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
494 MouseMode m = editor->current_mouse_mode();
496 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
497 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
500 if (_mouse_state != SelectTouchDragging) {
502 _pressed_button = ev->button;
503 _mouse_state = Pressed;
508 _pressed_button = ev->button;
509 _mouse_changed_selection = false;
515 MidiRegionView::button_release (GdkEventButton* ev)
517 double event_x, event_y;
519 if (ev->button != 1) {
526 group->canvas_to_item (event_x, event_y);
529 PublicEditor& editor = trackview.editor ();
531 _press_cursor_ctx.reset();
533 switch (_mouse_state) {
534 case Pressed: // Clicked
536 switch (editor.current_mouse_mode()) {
538 /* no motion occured - simple click */
540 _mouse_changed_selection = true;
547 _mouse_changed_selection = true;
549 if (Keyboard::is_insert_note_event(ev)) {
551 double event_x, event_y;
555 group->canvas_to_item (event_x, event_y);
557 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
559 /* Shorten the length by 1 tick so that we can add a new note at the next
560 grid snap without it overlapping this one.
562 beats -= Evoral::Beats::tick();
564 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
571 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
573 /* Shorten the length by 1 tick so that we can add a new note at the next
574 grid snap without it overlapping this one.
576 beats -= Evoral::Beats::tick();
578 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
589 case SelectRectDragging:
591 editor.drags()->end_grab ((GdkEvent *) ev);
593 create_ghost_note (ev->x, ev->y);
601 if (_mouse_changed_selection) {
602 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
603 trackview.editor().commit_reversible_selection_op ();
610 MidiRegionView::motion (GdkEventMotion* ev)
612 PublicEditor& editor = trackview.editor ();
614 if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
615 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
616 _mouse_state != AddDragging) {
618 create_ghost_note (ev->x, ev->y);
620 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
621 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
623 update_ghost_note (ev->x, ev->y);
625 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
627 remove_ghost_note ();
628 hide_verbose_cursor ();
630 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
632 update_ghost_note (ev->x, ev->y);
635 /* any motion immediately hides velocity text that may have been visible */
637 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
638 (*i)->hide_velocity ();
641 switch (_mouse_state) {
644 if (_pressed_button == 1) {
646 MouseMode m = editor.current_mouse_mode();
648 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
649 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
650 _mouse_state = AddDragging;
651 remove_ghost_note ();
652 hide_verbose_cursor ();
654 } else if (m == MouseContent) {
655 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
656 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
658 _mouse_changed_selection = true;
660 _mouse_state = SelectRectDragging;
662 } else if (m == MouseRange) {
663 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
664 _mouse_state = SelectVerticalDragging;
671 case SelectRectDragging:
672 case SelectVerticalDragging:
674 editor.drags()->motion_handler ((GdkEvent *) ev, false);
677 case SelectTouchDragging:
685 /* we may be dragging some non-note object (eg. patch-change, sysex)
688 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
693 MidiRegionView::scroll (GdkEventScroll* ev)
695 if (_selection.empty()) {
699 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
700 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
701 it still works for zoom.
706 hide_verbose_cursor ();
708 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
709 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
711 if (ev->direction == GDK_SCROLL_UP) {
712 change_velocities (true, fine, false, together);
713 } else if (ev->direction == GDK_SCROLL_DOWN) {
714 change_velocities (false, fine, false, together);
716 /* left, right: we don't use them */
724 MidiRegionView::key_press (GdkEventKey* ev)
726 /* since GTK bindings are generally activated on press, and since
727 detectable auto-repeat is the name of the game and only sends
728 repeated presses, carry out key actions at key press, not release.
731 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
733 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
734 _mouse_state = SelectTouchDragging;
737 } else if (ev->keyval == GDK_Escape && unmodified) {
741 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
743 bool start = (ev->keyval == GDK_comma);
744 bool end = (ev->keyval == GDK_period);
745 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
746 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
748 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
752 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
754 if (_selection.empty()) {
761 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
763 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
765 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
766 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
768 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
771 trackview.editor().commit_reversible_selection_op();
775 } else if (ev->keyval == GDK_Up) {
777 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
778 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
779 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
781 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
782 change_velocities (true, fine, allow_smush, together);
784 transpose (true, fine, allow_smush);
788 } else if (ev->keyval == GDK_Down) {
790 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
791 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
792 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
794 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
795 change_velocities (false, fine, allow_smush, together);
797 transpose (false, fine, allow_smush);
801 } else if (ev->keyval == GDK_Left) {
803 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
804 nudge_notes (false, fine);
807 } else if (ev->keyval == GDK_Right) {
809 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
810 nudge_notes (true, fine);
813 } else if (ev->keyval == GDK_c && unmodified) {
817 } else if (ev->keyval == GDK_v && unmodified) {
826 MidiRegionView::key_release (GdkEventKey* ev)
828 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
836 MidiRegionView::channel_edit ()
838 if (_selection.empty()) {
842 /* pick a note somewhat at random (since Selection is a set<>) to
843 * provide the "current" channel for the dialog.
846 uint8_t current_channel = (*_selection.begin())->note()->channel ();
847 MidiChannelDialog channel_dialog (current_channel);
848 int ret = channel_dialog.run ();
851 case Gtk::RESPONSE_OK:
857 uint8_t new_channel = channel_dialog.active_channel ();
859 start_note_diff_command (_("channel edit"));
861 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
862 Selection::iterator next = i;
864 change_note_channel (*i, new_channel);
872 MidiRegionView::velocity_edit ()
874 if (_selection.empty()) {
878 /* pick a note somewhat at random (since Selection is a set<>) to
879 * provide the "current" velocity for the dialog.
882 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
883 MidiVelocityDialog velocity_dialog (current_velocity);
884 int ret = velocity_dialog.run ();
887 case Gtk::RESPONSE_OK:
893 uint8_t new_velocity = velocity_dialog.velocity ();
895 start_note_diff_command (_("velocity edit"));
897 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
898 Selection::iterator next = i;
900 change_note_velocity (*i, new_velocity, false);
908 MidiRegionView::show_list_editor ()
911 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
913 _list_editor->present ();
916 /** Add a note to the model, and the view, at a canvas (click) coordinate.
917 * \param t time in frames relative to the position of the region
918 * \param y vertical position in pixels
919 * \param length duration of the note in beats
920 * \param snap_t true to snap t to the grid, otherwise false.
923 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bool snap_t)
925 if (length < 2 * DBL_EPSILON) {
929 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
930 MidiStreamView* const view = mtv->midi_view();
932 // Start of note in frames relative to region start
934 framecnt_t grid_frames;
935 t = snap_frame_to_grid_underneath (t, grid_frames);
938 const MidiModel::TimeType beat_time = region_frames_to_region_beats(
939 t + _region->start());
941 const double note = view->y_to_note(y);
942 const uint8_t chan = mtv->get_channel_for_add();
943 const uint8_t velocity = get_velocity_for_add(beat_time);
945 const boost::shared_ptr<NoteType> new_note(
946 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
948 if (_model->contains (new_note)) {
952 view->update_note_range(new_note->note());
954 start_note_diff_command(_("add note"));
957 note_diff_add_note (new_note, true, false);
961 play_midi_note (new_note);
965 MidiRegionView::clear_events (bool with_selection_signal)
967 clear_selection (with_selection_signal);
970 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
971 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
976 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
981 _patch_changes.clear();
983 _optimization_iterator = _events.end();
987 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
991 content_connection.disconnect ();
992 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
993 /* Don't signal as nobody else needs to know until selection has been altered. */
994 clear_events (false);
996 if (_enable_display) {
1002 MidiRegionView::start_note_diff_command (string name)
1004 if (!_note_diff_command) {
1005 trackview.editor().begin_reversible_command (name);
1006 _note_diff_command = _model->new_note_diff_command (name);
1011 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1013 if (_note_diff_command) {
1014 _note_diff_command->add (note);
1017 _marked_for_selection.insert(note);
1019 if (show_velocity) {
1020 _marked_for_velocity.insert(note);
1025 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1027 if (_note_diff_command && ev->note()) {
1028 _note_diff_command->remove(ev->note());
1033 MidiRegionView::note_diff_add_change (NoteBase* ev,
1034 MidiModel::NoteDiffCommand::Property property,
1037 if (_note_diff_command) {
1038 _note_diff_command->change (ev->note(), property, val);
1043 MidiRegionView::note_diff_add_change (NoteBase* ev,
1044 MidiModel::NoteDiffCommand::Property property,
1047 if (_note_diff_command) {
1048 _note_diff_command->change (ev->note(), property, val);
1053 MidiRegionView::apply_diff (bool as_subcommand)
1056 bool commit = false;
1058 if (!_note_diff_command) {
1062 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1063 // Mark all selected notes for selection when model reloads
1064 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1065 _marked_for_selection.insert((*i)->note());
1069 midi_view()->midi_track()->midi_playlist()->region_edited(
1070 _region, _note_diff_command);
1072 if (as_subcommand) {
1073 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1075 _model->apply_command (*trackview.session(), _note_diff_command);
1079 _note_diff_command = 0;
1081 if (add_or_remove) {
1082 _marked_for_selection.clear();
1085 _marked_for_velocity.clear();
1087 trackview.editor().commit_reversible_command ();
1092 MidiRegionView::abort_command()
1094 delete _note_diff_command;
1095 _note_diff_command = 0;
1100 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1102 if (_optimization_iterator != _events.end()) {
1103 ++_optimization_iterator;
1106 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1107 return *_optimization_iterator;
1110 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1111 if ((*_optimization_iterator)->note() == note) {
1112 return *_optimization_iterator;
1119 /** This version finds any canvas note matching the supplied note. */
1121 MidiRegionView::find_canvas_note (NoteType note)
1123 Events::iterator it;
1125 for (it = _events.begin(); it != _events.end(); ++it) {
1126 if (*((*it)->note()) == note) {
1135 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1137 MidiModel::Notes notes;
1138 _model->get_notes (notes, op, val, chan_mask);
1140 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1141 NoteBase* cne = find_canvas_note (*n);
1149 MidiRegionView::redisplay_model()
1151 if (_active_notes) {
1152 // Currently recording
1153 const framecnt_t zoom = trackview.editor().get_current_zoom();
1154 if (zoom != _last_display_zoom) {
1155 /* Update resolved canvas notes to reflect changes in zoom without
1156 touching model. Leave active notes (with length 0) alone since
1157 they are being extended. */
1158 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1159 if ((*i)->note()->length() > 0) {
1163 _last_display_zoom = zoom;
1172 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1173 (*i)->invalidate ();
1176 MidiModel::ReadLock lock(_model->read_lock());
1178 MidiModel::Notes& notes (_model->notes());
1179 _optimization_iterator = _events.begin();
1181 bool empty_when_starting = _events.empty();
1183 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1185 boost::shared_ptr<NoteType> note (*n);
1189 if (note_in_region_range (note, visible)) {
1191 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1204 cne = add_note (note, visible);
1207 set<boost::shared_ptr<NoteType> >::iterator it;
1208 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1209 if (*(*it) == *note) {
1210 add_to_selection (cne);
1216 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1223 /* remove note items that are no longer valid */
1225 if (!empty_when_starting) {
1226 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1227 if (!(*i)->valid ()) {
1229 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1230 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1232 gr->remove_note (*i);
1237 i = _events.erase (i);
1245 _patch_changes.clear();
1249 display_patch_changes ();
1251 _marked_for_selection.clear ();
1252 _marked_for_velocity.clear ();
1253 _pending_note_selection.clear ();
1255 /* we may have caused _events to contain things out of order (e.g. if a note
1256 moved earlier or later). we don't generally need them in time order, but
1257 make a note that a sort is required for those cases that require it.
1260 _sort_needed = true;
1264 MidiRegionView::display_patch_changes ()
1266 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1267 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1269 for (uint8_t i = 0; i < 16; ++i) {
1270 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1274 /** @param active_channel true to display patch changes fully, false to display
1275 * them `greyed-out' (as on an inactive channel)
1278 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1280 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1282 if ((*i)->channel() != channel) {
1286 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1287 add_canvas_patch_change (*i, patch_name, active_channel);
1292 MidiRegionView::display_sysexes()
1294 bool have_periodic_system_messages = false;
1295 bool display_periodic_messages = true;
1297 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1299 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1300 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1301 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1304 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1305 have_periodic_system_messages = true;
1311 if (have_periodic_system_messages) {
1312 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1314 /* get an approximate value for the number of samples per video frame */
1316 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1318 /* if we are zoomed out beyond than the cutoff (i.e. more
1319 * frames per pixel than frames per 4 video frames), don't
1320 * show periodic sysex messages.
1323 if (zoom > (video_frame*4)) {
1324 display_periodic_messages = false;
1328 display_periodic_messages = false;
1331 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1333 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1334 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1336 Evoral::Beats time = (*i)->time();
1339 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1340 if (!display_periodic_messages) {
1348 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1349 str << int((*i)->buffer()[b]);
1350 if (b != (*i)->size() -1) {
1354 string text = str.str();
1356 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1358 double height = midi_stream_view()->contents_height();
1360 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1361 // SysEx canvas object!!!
1363 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1364 new SysEx (*this, _note_group, text, height, x, 1.0));
1366 // Show unless message is beyond the region bounds
1367 if (time - _region->start() >= _region->length() || time < _region->start()) {
1373 _sys_exes.push_back(sysex);
1377 MidiRegionView::~MidiRegionView ()
1379 in_destructor = true;
1381 hide_verbose_cursor ();
1383 delete _list_editor;
1385 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1387 if (_active_notes) {
1391 _selection_cleared_connection.disconnect ();
1394 clear_events (false);
1397 delete _note_diff_command;
1398 delete _step_edit_cursor;
1399 delete _temporary_note_group;
1403 MidiRegionView::region_resized (const PropertyChange& what_changed)
1405 RegionView::region_resized(what_changed);
1407 if (what_changed.contains (ARDOUR::Properties::position)) {
1408 _region_relative_time_converter.set_origin_b(_region->position());
1409 _region_relative_time_converter_double.set_origin_b(_region->position());
1410 set_duration(_region->length(), 0);
1411 if (_enable_display) {
1416 if (what_changed.contains (ARDOUR::Properties::start) ||
1417 what_changed.contains (ARDOUR::Properties::position)) {
1418 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
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 /* This is imprecise due to all the conversion conversion involved, so only
1675 hide notes if they seem to start more than one tick before the start. */
1676 const framecnt_t tick_frames = Evoral::Beats::tick().to_ticks(trackview.session()->frame_rate());
1677 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1678 const bool outside = ((note_start_frames <= -tick_frames) ||
1679 (note_start_frames >= _region->length()));
1681 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1682 (note->note() <= midi_stream_view()->highest_note());
1688 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1692 if ((sus = dynamic_cast<Note*>(note))) {
1693 update_sustained(sus, update_ghost_regions);
1694 } else if ((hit = dynamic_cast<Hit*>(note))) {
1695 update_hit(hit, update_ghost_regions);
1699 /** Update a canvas note's size from its model note.
1700 * @param ev Canvas note to update.
1701 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1704 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1706 boost::shared_ptr<NoteType> note = ev->note();
1707 const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1708 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 */
1715 if (note->length() > 0) {
1716 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1717 ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames));
1719 ev->set_x1 (trackview.editor().sample_to_pixel (_region->length()));
1722 ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
1724 if (!note->length()) {
1725 if (_active_notes && note->note() < 128) {
1726 Note* const old_rect = _active_notes[note->note()];
1728 /* There is an active note on this key, so we have a stuck
1729 note. Finish the old rectangle here. */
1730 old_rect->set_x1 (x);
1731 old_rect->set_outline_all ();
1733 _active_notes[note->note()] = ev;
1735 /* outline all but right edge */
1736 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1737 ArdourCanvas::Rectangle::TOP|
1738 ArdourCanvas::Rectangle::LEFT|
1739 ArdourCanvas::Rectangle::BOTTOM));
1741 /* outline all edges */
1742 ev->set_outline_all ();
1745 // Update color in case velocity has changed
1746 ev->set_fill_color(ev->base_color());
1747 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1749 if (update_ghost_regions) {
1750 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1751 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1753 gr->update_note (ev);
1760 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1762 boost::shared_ptr<NoteType> note = ev->note();
1764 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1765 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1766 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1767 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1769 // see DnD note in MidiRegionView::apply_note_range() above
1770 if (y <= 0 || y >= _height) {
1776 ev->set_position (ArdourCanvas::Duple (x, y));
1777 ev->set_height (diamond_size);
1779 // Update color in case velocity has changed
1780 ev->set_fill_color(ev->base_color());
1781 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1783 if (update_ghost_regions) {
1784 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1785 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1787 gr->update_note (ev);
1793 /** Add a MIDI note to the view (with length).
1795 * If in sustained mode, notes with length 0 will be considered active
1796 * notes, and resolve_note should be called when the corresponding note off
1797 * event arrives, to properly display the note.
1800 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1802 NoteBase* event = 0;
1804 if (midi_view()->note_mode() == Sustained) {
1806 Note* ev_rect = new Note (*this, _note_group, note);
1808 update_sustained (ev_rect);
1812 } else if (midi_view()->note_mode() == Percussive) {
1814 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1816 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1818 update_hit (ev_diamond);
1827 MidiGhostRegion* gr;
1829 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1830 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1831 gr->add_note(event);
1835 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1836 note_selected(event, true);
1839 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1840 event->show_velocity();
1843 event->on_channel_selection_change (get_selected_channels());
1844 _events.push_back(event);
1853 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1854 MidiStreamView* const view = mtv->midi_view();
1856 view->update_note_range (note->note());
1861 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1862 Evoral::Beats pos, Evoral::Beats len)
1864 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1866 /* potentially extend region to hold new note */
1868 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1869 framepos_t region_end = _region->last_frame();
1871 if (end_frame > region_end) {
1872 _region->set_length (end_frame - _region->position());
1875 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1876 MidiStreamView* const view = mtv->midi_view();
1878 view->update_note_range(new_note->note());
1880 _marked_for_selection.clear ();
1882 start_note_diff_command (_("step add"));
1885 note_diff_add_note (new_note, true, false);
1889 // last_step_edit_note = new_note;
1893 MidiRegionView::step_sustain (Evoral::Beats beats)
1895 change_note_lengths (false, false, beats, false, true);
1898 /** Add a new patch change flag to the canvas.
1899 * @param patch the patch change to add
1900 * @param the text to display in the flag
1901 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1904 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1906 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1907 const double x = trackview.editor().sample_to_pixel (region_frames);
1909 double const height = midi_stream_view()->contents_height();
1911 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1912 // so we need to do something more sophisticated to keep its color
1913 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1916 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1917 new PatchChange(*this, group,
1924 if (patch_change->item().width() < _pixel_width) {
1925 // Show unless patch change is beyond the region bounds
1926 if (region_frames < 0 || region_frames >= _region->length()) {
1927 patch_change->hide();
1929 patch_change->show();
1932 patch_change->hide ();
1935 _patch_changes.push_back (patch_change);
1938 MIDI::Name::PatchPrimaryKey
1939 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1941 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1944 /// Return true iff @p pc applies to the given time on the given channel.
1946 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1948 return pc->time() <= time && pc->channel() == channel;
1952 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1954 // The earliest event not before time
1955 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1957 // Go backwards until we find the latest PC for this channel, or the start
1958 while (i != _model->patch_changes().begin() &&
1959 (i == _model->patch_changes().end() ||
1960 !patch_applies(*i, time, channel))) {
1964 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1965 key.set_bank((*i)->bank());
1966 key.set_program((*i)->program ());
1974 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1976 string name = _("alter patch change");
1977 trackview.editor().begin_reversible_command (name);
1978 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1980 if (pc.patch()->program() != new_patch.program()) {
1981 c->change_program (pc.patch (), new_patch.program());
1984 int const new_bank = new_patch.bank();
1985 if (pc.patch()->bank() != new_bank) {
1986 c->change_bank (pc.patch (), new_bank);
1989 _model->apply_command (*trackview.session(), c);
1990 trackview.editor().commit_reversible_command ();
1992 _patch_changes.clear ();
1993 display_patch_changes ();
1997 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
1999 string name = _("alter patch change");
2000 trackview.editor().begin_reversible_command (name);
2001 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2003 if (old_change->time() != new_change.time()) {
2004 c->change_time (old_change, new_change.time());
2007 if (old_change->channel() != new_change.channel()) {
2008 c->change_channel (old_change, new_change.channel());
2011 if (old_change->program() != new_change.program()) {
2012 c->change_program (old_change, new_change.program());
2015 if (old_change->bank() != new_change.bank()) {
2016 c->change_bank (old_change, new_change.bank());
2019 _model->apply_command (*trackview.session(), c);
2020 trackview.editor().commit_reversible_command ();
2022 _patch_changes.clear ();
2023 display_patch_changes ();
2026 /** Add a patch change to the region.
2027 * @param t Time in frames relative to region position
2028 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2029 * MidiTimeAxisView::get_channel_for_add())
2032 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2034 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2035 string name = _("add patch change");
2037 trackview.editor().begin_reversible_command (name);
2038 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2039 c->add (MidiModel::PatchChangePtr (
2040 new Evoral::PatchChange<Evoral::Beats> (
2041 absolute_frames_to_source_beats (_region->position() + t),
2042 mtv->get_channel_for_add(), patch.program(), patch.bank()
2047 _model->apply_command (*trackview.session(), c);
2048 trackview.editor().commit_reversible_command ();
2050 _patch_changes.clear ();
2051 display_patch_changes ();
2055 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2057 trackview.editor().begin_reversible_command (_("move patch change"));
2058 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2059 c->change_time (pc.patch (), t);
2060 _model->apply_command (*trackview.session(), c);
2061 trackview.editor().commit_reversible_command ();
2063 _patch_changes.clear ();
2064 display_patch_changes ();
2068 MidiRegionView::delete_patch_change (PatchChange* pc)
2070 trackview.editor().begin_reversible_command (_("delete patch change"));
2071 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2072 c->remove (pc->patch ());
2073 _model->apply_command (*trackview.session(), c);
2074 trackview.editor().commit_reversible_command ();
2076 _patch_changes.clear ();
2077 display_patch_changes ();
2081 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2083 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2085 key.set_bank(key.bank() + delta);
2087 key.set_program(key.program() + delta);
2089 change_patch_change(patch, key);
2093 MidiRegionView::note_deleted (NoteBase* cne)
2095 if (_selection.empty()) {
2099 _selection.erase (cne);
2103 MidiRegionView::delete_selection()
2105 if (_selection.empty()) {
2109 start_note_diff_command (_("delete selection"));
2111 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2112 if ((*i)->selected()) {
2113 _note_diff_command->remove((*i)->note());
2120 hide_verbose_cursor ();
2124 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2126 start_note_diff_command (_("delete note"));
2127 _note_diff_command->remove (n);
2130 hide_verbose_cursor ();
2134 MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
2136 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2138 Selection::iterator tmp = i;
2141 (*i)->set_selected (false);
2142 (*i)->hide_velocity ();
2143 _selection.erase (i);
2151 if (!ev && _entered) {
2152 // Clearing selection entirely, ungrab keyboard
2153 Keyboard::magic_widget_drop_focus();
2154 _grabbed_keyboard = false;
2157 /* this does not change the status of this regionview w.r.t the editor
2162 SelectionCleared (this); /* EMIT SIGNAL */
2167 MidiRegionView::unique_select(NoteBase* ev)
2169 const bool selection_was_empty = _selection.empty();
2171 clear_selection_except (ev);
2173 /* don't bother with checking to see if we should remove this
2174 regionview from the editor selection, since we're about to add
2175 another note, and thus put/keep this regionview in the editor
2179 if (!ev->selected()) {
2180 add_to_selection (ev);
2181 if (selection_was_empty && _entered) {
2182 // Grab keyboard for moving notes with arrow keys
2183 Keyboard::magic_widget_grab_focus();
2184 _grabbed_keyboard = true;
2190 MidiRegionView::select_all_notes ()
2194 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2195 add_to_selection (*i);
2200 MidiRegionView::select_range (framepos_t start, framepos_t end)
2204 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2205 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2206 if (t >= start && t <= end) {
2207 add_to_selection (*i);
2213 MidiRegionView::invert_selection ()
2215 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2216 if ((*i)->selected()) {
2217 remove_from_selection(*i);
2219 add_to_selection (*i);
2224 /** Used for selection undo/redo.
2225 The requested notes most likely won't exist in the view until the next model redisplay.
2228 MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
2231 list<boost::shared_ptr<NoteType> >::iterator n;
2233 for (n = notes.begin(); n != notes.end(); ++n) {
2234 if ((cne = find_canvas_note(*(*n))) != 0) {
2235 add_to_selection (cne);
2237 _pending_note_selection.insert(*n);
2243 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2245 bool have_selection = !_selection.empty();
2246 uint8_t low_note = 127;
2247 uint8_t high_note = 0;
2248 MidiModel::Notes& notes (_model->notes());
2249 _optimization_iterator = _events.begin();
2251 if (extend && !have_selection) {
2255 /* scan existing selection to get note range */
2257 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2258 if ((*i)->note()->note() < low_note) {
2259 low_note = (*i)->note()->note();
2261 if ((*i)->note()->note() > high_note) {
2262 high_note = (*i)->note()->note();
2269 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2270 /* only note previously selected is the one we are
2271 * reselecting. treat this as cancelling the selection.
2278 low_note = min (low_note, notenum);
2279 high_note = max (high_note, notenum);
2282 _no_sound_notes = true;
2284 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2286 boost::shared_ptr<NoteType> note (*n);
2288 bool select = false;
2290 if (((1 << note->channel()) & channel_mask) != 0) {
2292 if ((note->note() >= low_note && note->note() <= high_note)) {
2295 } else if (note->note() == notenum) {
2301 if ((cne = find_canvas_note (note)) != 0) {
2302 // extend is false because we've taken care of it,
2303 // since it extends by time range, not pitch.
2304 note_selected (cne, add, false);
2308 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2312 _no_sound_notes = false;
2316 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2318 MidiModel::Notes& notes (_model->notes());
2319 _optimization_iterator = _events.begin();
2321 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2323 boost::shared_ptr<NoteType> note (*n);
2326 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2327 if ((cne = find_canvas_note (note)) != 0) {
2328 if (cne->selected()) {
2329 note_deselected (cne);
2331 note_selected (cne, true, false);
2339 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2342 clear_selection_except (ev);
2343 if (!_selection.empty()) {
2344 PublicEditor& editor (trackview.editor());
2345 editor.get_selection().add (this);
2351 if (!ev->selected()) {
2352 add_to_selection (ev);
2356 /* find end of latest note selected, select all between that and the start of "ev" */
2358 Evoral::Beats earliest = Evoral::MaxBeats;
2359 Evoral::Beats latest = Evoral::Beats();
2361 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2362 if ((*i)->note()->end_time() > latest) {
2363 latest = (*i)->note()->end_time();
2365 if ((*i)->note()->time() < earliest) {
2366 earliest = (*i)->note()->time();
2370 if (ev->note()->end_time() > latest) {
2371 latest = ev->note()->end_time();
2374 if (ev->note()->time() < earliest) {
2375 earliest = ev->note()->time();
2378 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2380 /* find notes entirely within OR spanning the earliest..latest range */
2382 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2383 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2384 add_to_selection (*i);
2392 MidiRegionView::note_deselected(NoteBase* ev)
2394 remove_from_selection (ev);
2398 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2400 PublicEditor& editor = trackview.editor();
2402 // Convert to local coordinates
2403 const framepos_t p = _region->position();
2404 const double y = midi_view()->y_position();
2405 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2406 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2407 const double y0 = max(0.0, gy0 - y);
2408 const double y1 = max(0.0, gy1 - y);
2410 // TODO: Make this faster by storing the last updated selection rect, and only
2411 // adjusting things that are in the area that appears/disappeared.
2412 // We probably need a tree to be able to find events in O(log(n)) time.
2414 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2415 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2416 // Rectangles intersect
2417 if (!(*i)->selected()) {
2418 add_to_selection (*i);
2420 } else if ((*i)->selected() && !extend) {
2421 // Rectangles do not intersect
2422 remove_from_selection (*i);
2426 typedef RouteTimeAxisView::AutomationTracks ATracks;
2427 typedef std::list<Selectable*> Selectables;
2429 /* Add control points to selection. */
2430 const ATracks& atracks = midi_view()->automation_tracks();
2431 Selectables selectables;
2432 editor.get_selection().clear_points();
2433 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2434 a->second->get_selectables(start, end, gy0, gy1, selectables);
2435 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2436 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2438 editor.get_selection().add(cp);
2441 a->second->set_selected_points(editor.get_selection().points);
2446 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2452 // TODO: Make this faster by storing the last updated selection rect, and only
2453 // adjusting things that are in the area that appears/disappeared.
2454 // We probably need a tree to be able to find events in O(log(n)) time.
2456 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2457 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2458 // within y- (note-) range
2459 if (!(*i)->selected()) {
2460 add_to_selection (*i);
2462 } else if ((*i)->selected() && !extend) {
2463 remove_from_selection (*i);
2469 MidiRegionView::remove_from_selection (NoteBase* ev)
2471 Selection::iterator i = _selection.find (ev);
2473 if (i != _selection.end()) {
2474 _selection.erase (i);
2475 if (_selection.empty() && _grabbed_keyboard) {
2477 Keyboard::magic_widget_drop_focus();
2478 _grabbed_keyboard = false;
2482 ev->set_selected (false);
2483 ev->hide_velocity ();
2485 if (_selection.empty()) {
2486 PublicEditor& editor (trackview.editor());
2487 editor.get_selection().remove (this);
2492 MidiRegionView::add_to_selection (NoteBase* ev)
2494 const bool selection_was_empty = _selection.empty();
2496 if (_selection.insert (ev).second) {
2497 ev->set_selected (true);
2498 start_playing_midi_note ((ev)->note());
2499 if (selection_was_empty && _entered) {
2500 // Grab keyboard for moving notes with arrow keys
2501 Keyboard::magic_widget_grab_focus();
2502 _grabbed_keyboard = true;
2506 if (selection_was_empty) {
2507 PublicEditor& editor (trackview.editor());
2508 editor.get_selection().add (this);
2513 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2515 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2516 PossibleChord to_play;
2517 Evoral::Beats earliest = Evoral::MaxBeats;
2519 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2520 if ((*i)->note()->time() < earliest) {
2521 earliest = (*i)->note()->time();
2525 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2526 if ((*i)->note()->time() == earliest) {
2527 to_play.push_back ((*i)->note());
2529 (*i)->move_event(dx, dy);
2532 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2534 if (to_play.size() > 1) {
2536 PossibleChord shifted;
2538 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2539 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2540 moved_note->set_note (moved_note->note() + cumulative_dy);
2541 shifted.push_back (moved_note);
2544 start_playing_midi_chord (shifted);
2546 } else if (!to_play.empty()) {
2548 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2549 moved_note->set_note (moved_note->note() + cumulative_dy);
2550 start_playing_midi_note (moved_note);
2556 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2558 uint8_t lowest_note_in_selection = 127;
2559 uint8_t highest_note_in_selection = 0;
2560 uint8_t highest_note_difference = 0;
2562 // find highest and lowest notes first
2564 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2565 uint8_t pitch = (*i)->note()->note();
2566 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2567 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2571 cerr << "dnote: " << (int) dnote << endl;
2572 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2573 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2574 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2575 << int(highest_note_in_selection) << endl;
2576 cerr << "selection size: " << _selection.size() << endl;
2577 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2580 // Make sure the note pitch does not exceed the MIDI standard range
2581 if (highest_note_in_selection + dnote > 127) {
2582 highest_note_difference = highest_note_in_selection - 127;
2585 start_note_diff_command (_("move notes"));
2587 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2589 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2590 Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
2596 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2598 uint8_t original_pitch = (*i)->note()->note();
2599 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2601 // keep notes in standard midi range
2602 clamp_to_0_127(new_pitch);
2604 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2605 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2607 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2612 // care about notes being moved beyond the upper/lower bounds on the canvas
2613 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2614 highest_note_in_selection > midi_stream_view()->highest_note()) {
2615 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2619 /** @param x Pixel relative to the region position.
2620 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2621 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2622 * @return Snapped frame relative to the region position.
2625 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2627 PublicEditor& editor (trackview.editor());
2628 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2631 /** @param x Pixel relative to the region position.
2632 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2633 * @return Snapped pixel relative to the region position.
2636 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2638 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2642 MidiRegionView::get_position_pixels()
2644 framepos_t region_frame = get_position();
2645 return trackview.editor().sample_to_pixel(region_frame);
2649 MidiRegionView::get_end_position_pixels()
2651 framepos_t frame = get_position() + get_duration ();
2652 return trackview.editor().sample_to_pixel(frame);
2656 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2658 /* the time converter will return the frame corresponding to `beats'
2659 relative to the start of the source. The start of the source
2660 is an implied position given by region->position - region->start
2662 const framepos_t source_start = _region->position() - _region->start();
2663 return source_start + _source_relative_time_converter.to (beats);
2667 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2669 /* the `frames' argument needs to be converted into a frame count
2670 relative to the start of the source before being passed in to the
2673 const framepos_t source_start = _region->position() - _region->start();
2674 return _source_relative_time_converter.from (frames - source_start);
2678 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2680 return _region_relative_time_converter.to(beats);
2684 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2686 return _region_relative_time_converter.from(frames);
2690 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2692 return _region_relative_time_converter_double.from(frames);
2696 MidiRegionView::begin_resizing (bool /*at_front*/)
2698 _resize_data.clear();
2700 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2701 Note *note = dynamic_cast<Note*> (*i);
2703 // only insert CanvasNotes into the map
2705 NoteResizeData *resize_data = new NoteResizeData();
2706 resize_data->note = note;
2708 // create a new SimpleRect from the note which will be the resize preview
2709 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2710 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2712 // calculate the colors: get the color settings
2713 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2714 UIConfiguration::instance().color ("midi note selected"),
2717 // make the resize preview notes more transparent and bright
2718 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2720 // calculate color based on note velocity
2721 resize_rect->set_fill_color (UINT_INTERPOLATE(
2722 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2726 resize_rect->set_outline_color (NoteBase::calculate_outline (
2727 UIConfiguration::instance().color ("midi note selected")));
2729 resize_data->resize_rect = resize_rect;
2730 _resize_data.push_back(resize_data);
2735 /** Update resizing notes while user drags.
2736 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2737 * @param at_front which end of the note (true == note on, false == note off)
2738 * @param delta_x change in mouse position since the start of the drag
2739 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2740 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2741 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2742 * as the \a primary note.
2743 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2744 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2747 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2749 bool cursor_set = false;
2751 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2752 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2753 Note* canvas_note = (*i)->note;
2758 current_x = canvas_note->x0() + delta_x + snap_delta;
2760 current_x = primary->x0() + delta_x + snap_delta;
2764 current_x = canvas_note->x1() + delta_x + snap_delta;
2766 current_x = primary->x1() + delta_x + snap_delta;
2770 if (current_x < 0) {
2771 // This works even with snapping because RegionView::snap_frame_to_frame()
2772 // snaps forward if the snapped sample is before the beginning of the region
2775 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2776 current_x = trackview.editor().sample_to_pixel(_region->length());
2781 resize_rect->set_x0 (snap_to_pixel(current_x) - snap_delta);
2783 resize_rect->set_x0 (current_x - snap_delta);
2785 resize_rect->set_x1 (canvas_note->x1());
2788 resize_rect->set_x1 (snap_to_pixel(current_x) - snap_delta);
2790 resize_rect->set_x1 (current_x - snap_delta);
2792 resize_rect->set_x0 (canvas_note->x0());
2796 /* Convert snap delta from pixels to beats. */
2797 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2798 double snap_delta_beats = 0.0;
2801 /* negative beat offsets aren't allowed */
2802 if (snap_delta_samps > 0) {
2803 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2804 } else if (snap_delta_samps < 0) {
2805 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2809 const double snapped_x = (with_snap ? snap_pixel_to_sample (current_x) : trackview.editor ().pixel_to_sample (current_x));
2810 Evoral::Beats beats = region_frames_to_region_beats (snapped_x);
2811 Evoral::Beats len = Evoral::Beats();
2814 if (beats < canvas_note->note()->end_time()) {
2815 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2816 len += canvas_note->note()->length();
2819 if (beats >= canvas_note->note()->time()) {
2820 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2824 len = std::max(Evoral::Beats(1 / 512.0), len);
2827 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2828 show_verbose_cursor (buf, 0, 0);
2837 /** Finish resizing notes when the user releases the mouse button.
2838 * Parameters the same as for \a update_resizing().
2841 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2843 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2845 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2846 Note* canvas_note = (*i)->note;
2847 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2849 /* Get the new x position for this resize, which is in pixels relative
2850 * to the region position.
2857 current_x = canvas_note->x0() + delta_x + snap_delta;
2859 current_x = primary->x0() + delta_x + snap_delta;
2863 current_x = canvas_note->x1() + delta_x + snap_delta;
2865 current_x = primary->x1() + delta_x + snap_delta;
2869 if (current_x < 0) {
2872 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2873 current_x = trackview.editor().sample_to_pixel(_region->length());
2876 /* Convert snap delta from pixels to beats with sign. */
2877 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2878 double snap_delta_beats = 0.0;
2881 if (snap_delta_samps > 0) {
2882 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2883 } else if (snap_delta_samps < 0) {
2884 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2888 /* Convert the new x position to a frame within the source */
2889 framepos_t current_fr;
2891 current_fr = snap_pixel_to_sample (current_x) + _region->start ();
2893 current_fr = trackview.editor().pixel_to_sample (current_x) + _region->start ();
2896 /* and then to beats */
2897 const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr);
2899 if (at_front && x_beats < canvas_note->note()->end_time()) {
2900 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2901 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2902 len += canvas_note->note()->length();
2905 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2910 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2911 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2912 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2919 _resize_data.clear();
2924 MidiRegionView::abort_resizing ()
2926 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2927 delete (*i)->resize_rect;
2931 _resize_data.clear ();
2935 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2937 uint8_t new_velocity;
2940 new_velocity = event->note()->velocity() + velocity;
2941 clamp_to_0_127(new_velocity);
2943 new_velocity = velocity;
2946 event->set_selected (event->selected()); // change color
2948 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2952 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2957 new_note = event->note()->note() + note;
2962 clamp_to_0_127 (new_note);
2963 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2967 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2969 bool change_start = false;
2970 bool change_length = false;
2971 Evoral::Beats new_start;
2972 Evoral::Beats new_length;
2974 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2976 front_delta: if positive - move the start of the note later in time (shortening it)
2977 if negative - move the start of the note earlier in time (lengthening it)
2979 end_delta: if positive - move the end of the note later in time (lengthening it)
2980 if negative - move the end of the note earlier in time (shortening it)
2983 if (!!front_delta) {
2984 if (front_delta < 0) {
2986 if (event->note()->time() < -front_delta) {
2987 new_start = Evoral::Beats();
2989 new_start = event->note()->time() + front_delta; // moves earlier
2992 /* start moved toward zero, so move the end point out to where it used to be.
2993 Note that front_delta is negative, so this increases the length.
2996 new_length = event->note()->length() - front_delta;
2997 change_start = true;
2998 change_length = true;
3002 Evoral::Beats new_pos = event->note()->time() + front_delta;
3004 if (new_pos < event->note()->end_time()) {
3005 new_start = event->note()->time() + front_delta;
3006 /* start moved toward the end, so move the end point back to where it used to be */
3007 new_length = event->note()->length() - front_delta;
3008 change_start = true;
3009 change_length = true;
3016 bool can_change = true;
3017 if (end_delta < 0) {
3018 if (event->note()->length() < -end_delta) {
3024 new_length = event->note()->length() + end_delta;
3025 change_length = true;
3030 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3033 if (change_length) {
3034 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3039 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3041 uint8_t new_channel;
3045 if (event->note()->channel() < -chn) {
3048 new_channel = event->note()->channel() + chn;
3051 new_channel = event->note()->channel() + chn;
3054 new_channel = (uint8_t) chn;
3057 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3061 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3063 Evoral::Beats new_time;
3067 if (event->note()->time() < -delta) {
3068 new_time = Evoral::Beats();
3070 new_time = event->note()->time() + delta;
3073 new_time = event->note()->time() + delta;
3079 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3083 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3085 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3089 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3094 if (_selection.empty()) {
3109 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3110 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3116 start_note_diff_command (_("change velocities"));
3118 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3119 Selection::iterator next = i;
3123 if (i == _selection.begin()) {
3124 change_note_velocity (*i, delta, true);
3125 value = (*i)->note()->velocity() + delta;
3127 change_note_velocity (*i, value, false);
3131 change_note_velocity (*i, delta, true);
3140 if (!_selection.empty()) {
3142 snprintf (buf, sizeof (buf), "Vel %d",
3143 (int) (*_selection.begin())->note()->velocity());
3144 show_verbose_cursor (buf, 10, 10);
3150 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3152 if (_selection.empty()) {
3169 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3171 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3175 if ((int8_t) (*i)->note()->note() + delta > 127) {
3182 start_note_diff_command (_("transpose"));
3184 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3185 Selection::iterator next = i;
3187 change_note_note (*i, delta, true);
3195 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3199 delta = Evoral::Beats(1.0/128.0);
3201 /* grab the current grid distance */
3202 delta = get_grid_beats(_region->position());
3210 start_note_diff_command (_("change note lengths"));
3212 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3213 Selection::iterator next = i;
3216 /* note the negation of the delta for start */
3219 (start ? -delta : Evoral::Beats()),
3220 (end ? delta : Evoral::Beats()));
3229 MidiRegionView::nudge_notes (bool forward, bool fine)
3231 if (_selection.empty()) {
3235 /* pick a note as the point along the timeline to get the nudge distance.
3236 its not necessarily the earliest note, so we may want to pull the notes out
3237 into a vector and sort before using the first one.
3240 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3241 Evoral::Beats delta;
3245 /* non-fine, move by 1 bar regardless of snap */
3246 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
3248 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3250 /* grid is off - use nudge distance */
3253 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3254 delta = region_frames_to_region_beats (fabs ((double)distance));
3260 framepos_t next_pos = ref_point;
3263 if (max_framepos - 1 < next_pos) {
3267 if (next_pos == 0) {
3273 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3274 const framecnt_t distance = ref_point - next_pos;
3275 delta = region_frames_to_region_beats (fabs ((double)distance));
3286 start_note_diff_command (_("nudge"));
3288 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3289 Selection::iterator next = i;
3291 change_note_time (*i, delta, true);
3299 MidiRegionView::change_channel(uint8_t channel)
3301 start_note_diff_command(_("change channel"));
3302 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3303 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3311 MidiRegionView::note_entered(NoteBase* ev)
3313 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3315 if (_mouse_state == SelectTouchDragging) {
3316 note_selected (ev, true);
3317 } else if (editor->current_mouse_mode() == MouseContent) {
3318 show_verbose_cursor (ev->note ());
3319 } else if (editor->current_mouse_mode() == MouseDraw) {
3320 show_verbose_cursor (ev->note ());
3325 MidiRegionView::note_left (NoteBase*)
3327 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3328 (*i)->hide_velocity ();
3331 hide_verbose_cursor ();
3335 MidiRegionView::patch_entered (PatchChange* p)
3338 /* XXX should get patch name if we can */
3339 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3340 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3341 << _("Channel ") << ((int) p->patch()->channel() + 1);
3342 show_verbose_cursor (s.str(), 10, 20);
3343 p->item().grab_focus();
3347 MidiRegionView::patch_left (PatchChange *)
3349 hide_verbose_cursor ();
3350 /* focus will transfer back via the enter-notify event sent to this
3356 MidiRegionView::sysex_entered (SysEx* p)
3360 // need a way to extract text from p->_flag->_text
3362 // show_verbose_cursor (s.str(), 10, 20);
3363 p->item().grab_focus();
3367 MidiRegionView::sysex_left (SysEx *)
3369 hide_verbose_cursor ();
3370 /* focus will transfer back via the enter-notify event sent to this
3376 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3378 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3379 Editing::MouseMode mm = editor->current_mouse_mode();
3380 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3382 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3383 if (can_set_cursor && ctx) {
3384 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3385 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3386 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3387 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3389 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3395 MidiRegionView::get_fill_color() const
3397 const std::string mod_name = (_dragging ? "dragging region" :
3398 trackview.editor().internal_editing() ? "editable region" :
3401 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3402 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3403 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3404 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3406 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3410 MidiRegionView::midi_channel_mode_changed ()
3412 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3413 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3414 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3416 if (mode == ForceChannel) {
3417 mask = 0xFFFF; // Show all notes as active (below)
3420 // Update notes for selection
3421 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3422 (*i)->on_channel_selection_change (mask);
3425 _patch_changes.clear ();
3426 display_patch_changes ();
3430 MidiRegionView::instrument_settings_changed ()
3436 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3438 if (_selection.empty()) {
3442 PublicEditor& editor (trackview.editor());
3446 /* XXX what to do ? */
3450 editor.get_cut_buffer().add (selection_as_cut_buffer());
3458 start_note_diff_command();
3460 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3467 note_diff_remove_note (*i);
3477 MidiRegionView::selection_as_cut_buffer () const
3481 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3482 NoteType* n = (*i)->note().get();
3483 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3486 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3492 /** This method handles undo */
3494 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
3496 bool commit = false;
3497 // Paste notes, if available
3498 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3499 if (m != selection.midi_notes.end()) {
3500 ctx.counts.increase_n_notes();
3501 if (!(*m)->empty()) { commit = true; }
3502 paste_internal(pos, ctx.count, ctx.times, **m);
3505 // Paste control points to automation children, if available
3506 typedef RouteTimeAxisView::AutomationTracks ATracks;
3507 const ATracks& atracks = midi_view()->automation_tracks();
3508 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3509 if (a->second->paste(pos, selection, ctx)) {
3515 trackview.editor().commit_reversible_command ();
3520 /** This method handles undo */
3522 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3528 start_note_diff_command (_("paste"));
3530 const Evoral::Beats snap_beats = get_grid_beats(pos);
3531 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3532 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3533 const Evoral::Beats duration = last_time - first_time;
3534 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3535 const Evoral::Beats paste_offset = snap_duration * paste_count;
3536 const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3537 Evoral::Beats end_point = Evoral::Beats();
3539 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3542 duration, pos, _region->position(),
3547 for (int n = 0; n < (int) times; ++n) {
3549 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3551 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3552 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3553 copied_note->set_id (Evoral::next_event_id());
3555 /* make all newly added notes selected */
3557 note_diff_add_note (copied_note, true);
3558 end_point = copied_note->end_time();
3562 /* if we pasted past the current end of the region, extend the region */
3564 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3565 framepos_t region_end = _region->position() + _region->length() - 1;
3567 if (end_frame > region_end) {
3569 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3571 _region->clear_changes ();
3572 _region->set_length (end_frame - _region->position());
3573 trackview.session()->add_command (new StatefulDiffCommand (_region));
3579 struct EventNoteTimeEarlyFirstComparator {
3580 bool operator() (NoteBase* a, NoteBase* b) {
3581 return a->note()->time() < b->note()->time();
3586 MidiRegionView::time_sort_events ()
3588 if (!_sort_needed) {
3592 EventNoteTimeEarlyFirstComparator cmp;
3595 _sort_needed = false;
3599 MidiRegionView::goto_next_note (bool add_to_selection)
3601 bool use_next = false;
3603 if (_events.back()->selected()) {
3607 time_sort_events ();
3609 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3610 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3612 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3613 if ((*i)->selected()) {
3616 } else if (use_next) {
3617 if (channel_mask & (1 << (*i)->note()->channel())) {
3618 if (!add_to_selection) {
3621 note_selected (*i, true, false);
3628 /* use the first one */
3630 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3631 unique_select (_events.front());
3636 MidiRegionView::goto_previous_note (bool add_to_selection)
3638 bool use_next = false;
3640 if (_events.front()->selected()) {
3644 time_sort_events ();
3646 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3647 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3649 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3650 if ((*i)->selected()) {
3653 } else if (use_next) {
3654 if (channel_mask & (1 << (*i)->note()->channel())) {
3655 if (!add_to_selection) {
3658 note_selected (*i, true, false);
3665 /* use the last one */
3667 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3668 unique_select (*(_events.rbegin()));
3673 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3675 bool had_selected = false;
3677 time_sort_events ();
3679 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3680 if ((*i)->selected()) {
3681 selected.insert ((*i)->note());
3682 had_selected = true;
3686 if (allow_all_if_none_selected && !had_selected) {
3687 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3688 selected.insert ((*i)->note());
3694 MidiRegionView::update_ghost_note (double x, double y)
3696 x = std::max(0.0, x);
3698 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3703 _note_group->canvas_to_item (x, y);
3705 PublicEditor& editor = trackview.editor ();
3707 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3708 framecnt_t grid_frames;
3709 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3711 /* calculate time in beats relative to start of source */
3712 const Evoral::Beats length = get_grid_beats(unsnapped_frame);
3713 const Evoral::Beats time = std::max(
3715 absolute_frames_to_source_beats (f + _region->position ()));
3717 _ghost_note->note()->set_time (time);
3718 _ghost_note->note()->set_length (length);
3719 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3720 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3721 _ghost_note->note()->set_velocity (get_velocity_for_add (time));
3723 /* the ghost note does not appear in ghost regions, so pass false in here */
3724 update_note (_ghost_note, false);
3726 show_verbose_cursor (_ghost_note->note ());
3730 MidiRegionView::create_ghost_note (double x, double y)
3732 remove_ghost_note ();
3734 boost::shared_ptr<NoteType> g (new NoteType);
3735 if (midi_view()->note_mode() == Sustained) {
3736 _ghost_note = new Note (*this, _note_group, g);
3738 _ghost_note = new Hit (*this, _note_group, 10, g);
3740 _ghost_note->set_ignore_events (true);
3741 _ghost_note->set_outline_color (0x000000aa);
3742 update_ghost_note (x, y);
3743 _ghost_note->show ();
3745 show_verbose_cursor (_ghost_note->note ());
3749 MidiRegionView::remove_ghost_note ()
3756 MidiRegionView::hide_verbose_cursor ()
3758 trackview.editor().verbose_cursor()->hide ();
3759 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3761 mtv->set_note_highlight (NO_MIDI_NOTE);
3766 MidiRegionView::snap_changed ()
3772 create_ghost_note (_last_ghost_x, _last_ghost_y);
3776 MidiRegionView::drop_down_keys ()
3778 _mouse_state = None;
3782 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3784 /* XXX: This is dead code. What was it for? */
3786 double note = midi_stream_view()->y_to_note(y);
3788 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3790 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3792 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3793 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3794 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3795 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3800 bool add_mrv_selection = false;
3802 if (_selection.empty()) {
3803 add_mrv_selection = true;
3806 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3807 if (_selection.insert (*i).second) {
3808 (*i)->set_selected (true);
3812 if (add_mrv_selection) {
3813 PublicEditor& editor (trackview.editor());
3814 editor.get_selection().add (this);
3819 MidiRegionView::color_handler ()
3821 RegionView::color_handler ();
3823 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3824 (*i)->set_selected ((*i)->selected()); // will change color
3827 /* XXX probably more to do here */
3831 MidiRegionView::enable_display (bool yn)
3833 RegionView::enable_display (yn);
3840 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3842 if (_step_edit_cursor == 0) {
3843 ArdourCanvas::Item* const group = get_canvas_group();
3845 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3846 _step_edit_cursor->set_y0 (0);
3847 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3848 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3849 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3852 move_step_edit_cursor (pos);
3853 _step_edit_cursor->show ();
3857 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3859 _step_edit_cursor_position = pos;
3861 if (_step_edit_cursor) {
3862 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3863 _step_edit_cursor->set_x0 (pixel);
3864 set_step_edit_cursor_width (_step_edit_cursor_width);
3869 MidiRegionView::hide_step_edit_cursor ()
3871 if (_step_edit_cursor) {
3872 _step_edit_cursor->hide ();
3877 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3879 _step_edit_cursor_width = beats;
3881 if (_step_edit_cursor) {
3882 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
3886 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3887 * @param w Source that the data will end up in.
3890 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3892 if (!_active_notes) {
3893 /* we aren't actively being recorded to */
3897 boost::shared_ptr<MidiSource> src = w.lock ();
3898 if (!src || src != midi_region()->midi_source()) {
3899 /* recorded data was not destined for our source */
3903 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3905 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3907 framepos_t back = max_framepos;
3909 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3910 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3912 if (ev.is_channel_event()) {
3913 if (get_channel_mode() == FilterChannels) {
3914 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3920 /* convert from session frames to source beats */
3921 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3922 ev.time() - src->timeline_position() + _region->start());
3924 if (ev.type() == MIDI_CMD_NOTE_ON) {
3925 boost::shared_ptr<NoteType> note (
3926 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3928 add_note (note, true);
3930 /* fix up our note range */
3931 if (ev.note() < _current_range_min) {
3932 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3933 } else if (ev.note() > _current_range_max) {
3934 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3937 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3938 resolve_note (ev.note (), time_beats);
3944 midi_stream_view()->check_record_layers (region(), back);
3948 MidiRegionView::trim_front_starting ()
3950 /* Reparent the note group to the region view's parent, so that it doesn't change
3951 when the region view is trimmed.
3953 _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3954 _temporary_note_group->move (group->position ());
3955 _note_group->reparent (_temporary_note_group);
3959 MidiRegionView::trim_front_ending ()
3961 _note_group->reparent (group);
3962 delete _temporary_note_group;
3963 _temporary_note_group = 0;
3965 if (_region->start() < 0) {
3966 /* Trim drag made start time -ve; fix this */
3967 midi_region()->fix_negative_start ();
3972 MidiRegionView::edit_patch_change (PatchChange* pc)
3974 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3976 int response = d.run();
3979 case Gtk::RESPONSE_ACCEPT:
3981 case Gtk::RESPONSE_REJECT:
3982 delete_patch_change (pc);
3988 change_patch_change (pc->patch(), d.patch ());
3992 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
3995 // sysyex object doesn't have a pointer to a sysex event
3996 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3997 // c->remove (sysex->sysex());
3998 // _model->apply_command (*trackview.session(), c);
4000 //_sys_exes.clear ();
4001 // display_sysexes();
4005 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4007 using namespace MIDI::Name;
4011 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4013 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4015 MIDI::Name::PatchPrimaryKey patch_key;
4016 get_patch_key_at(n->time(), n->channel(), patch_key);
4017 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4020 patch_key.program(),
4023 mtv->set_note_highlight (n->note());
4027 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4029 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
4030 (int) n->channel() + 1,
4031 (int) n->velocity());
4033 show_verbose_cursor(buf, 10, 20);
4037 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4039 trackview.editor().verbose_cursor()->set (text);
4040 trackview.editor().verbose_cursor()->show ();
4041 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4045 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4047 if (_model->notes().empty()) {
4048 return 0x40; // No notes, use default
4051 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4052 if (m == _model->notes().begin()) {
4053 // Before the start, use the velocity of the first note
4054 return (*m)->velocity();
4055 } else if (m == _model->notes().end()) {
4056 // Past the end, use the velocity of the last note
4058 return (*m)->velocity();
4061 // Interpolate velocity of surrounding notes
4062 MidiModel::Notes::const_iterator n = m;
4065 const double frac = ((time - (*n)->time()).to_double() /
4066 ((*m)->time() - (*n)->time()).to_double());
4068 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4071 /** @param p A session framepos.
4072 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
4073 * @return p snapped to the grid subdivision underneath it.
4076 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
4078 PublicEditor& editor = trackview.editor ();
4080 const Evoral::Beats grid_beats = get_grid_beats(p);
4082 grid_frames = region_beats_to_region_frames (grid_beats);
4084 /* Hack so that we always snap to the note that we are over, instead of snapping
4085 to the next one if we're more than halfway through the one we're over.
4087 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
4088 p -= grid_frames / 2;
4091 return snap_frame_to_frame (p);
4094 /** Called when the selection has been cleared in any MidiRegionView.
4095 * @param rv MidiRegionView that the selection was cleared in.
4098 MidiRegionView::selection_cleared (MidiRegionView* rv)
4104 /* Clear our selection in sympathy; but don't signal the fact */
4105 clear_selection (false);
4109 MidiRegionView::get_channel_mode () const
4111 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4112 return rtav->midi_track()->get_playback_channel_mode();
4116 MidiRegionView::get_selected_channels () const
4118 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4119 return rtav->midi_track()->get_playback_channel_mask();
4124 MidiRegionView::get_grid_beats(framepos_t pos) const
4126 PublicEditor& editor = trackview.editor();
4127 bool success = false;
4128 Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos);
4130 beats = Evoral::Beats(1);