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_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_track.h"
39 #include "ardour/operations.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIEvent.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
47 #include "canvas/debug.h"
48 #include "canvas/text.h"
50 #include "automation_region_view.h"
51 #include "automation_time_axis.h"
52 #include "control_point.h"
55 #include "editor_drag.h"
56 #include "ghostregion.h"
57 #include "gui_thread.h"
58 #include "item_counts.h"
60 #include "midi_channel_dialog.h"
61 #include "midi_cut_buffer.h"
62 #include "midi_list_editor.h"
63 #include "midi_region_view.h"
64 #include "midi_streamview.h"
65 #include "midi_time_axis.h"
66 #include "midi_util.h"
67 #include "midi_velocity_dialog.h"
68 #include "mouse_cursors.h"
69 #include "note_player.h"
70 #include "paste_context.h"
71 #include "public_editor.h"
72 #include "route_time_axis.h"
73 #include "rgb_macros.h"
74 #include "selection.h"
75 #include "streamview.h"
76 #include "patch_change_dialog.h"
77 #include "verbose_cursor.h"
78 #include "ardour_ui.h"
81 #include "patch_change.h"
86 using namespace ARDOUR;
88 using namespace Editing;
90 using Gtkmm2ext::Keyboard;
92 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
94 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
96 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
97 RouteTimeAxisView& tv,
98 boost::shared_ptr<MidiRegion> r,
100 uint32_t basic_color)
101 : RegionView (parent, tv, r, spu, basic_color)
102 , _current_range_min(0)
103 , _current_range_max(0)
104 , _region_relative_time_converter(r->session().tempo_map(), r->position())
105 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
107 , _note_group (new ArdourCanvas::Container (group))
108 , _note_diff_command (0)
110 , _step_edit_cursor (0)
111 , _step_edit_cursor_width (1.0)
112 , _step_edit_cursor_position (0.0)
113 , _channel_selection_scoped_note (0)
114 , _temporary_note_group (0)
117 , _sort_needed (true)
118 , _optimization_iterator (_events.end())
120 , _no_sound_notes (false)
121 , _last_display_zoom (0)
124 , _grabbed_keyboard (false)
126 , _mouse_changed_selection (false)
128 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
129 _note_group->raise_to_top();
130 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
132 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
133 connect_to_diskstream ();
135 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
137 PublicEditor& editor (trackview.editor());
138 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
141 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
142 RouteTimeAxisView& tv,
143 boost::shared_ptr<MidiRegion> r,
145 uint32_t basic_color,
147 TimeAxisViewItem::Visibility visibility)
148 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
149 , _current_range_min(0)
150 , _current_range_max(0)
151 , _region_relative_time_converter(r->session().tempo_map(), r->position())
152 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
154 , _note_group (new ArdourCanvas::Container (group))
155 , _note_diff_command (0)
157 , _step_edit_cursor (0)
158 , _step_edit_cursor_width (1.0)
159 , _step_edit_cursor_position (0.0)
160 , _channel_selection_scoped_note (0)
161 , _temporary_note_group (0)
164 , _sort_needed (true)
165 , _optimization_iterator (_events.end())
167 , _no_sound_notes (false)
168 , _last_display_zoom (0)
171 , _grabbed_keyboard (false)
173 , _mouse_changed_selection (false)
175 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
176 _note_group->raise_to_top();
178 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
180 connect_to_diskstream ();
182 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
184 PublicEditor& editor (trackview.editor());
185 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
189 MidiRegionView::parameter_changed (std::string const & p)
191 if (p == "display-first-midi-bank-as-zero") {
192 if (_enable_display) {
198 MidiRegionView::MidiRegionView (const MidiRegionView& other)
199 : sigc::trackable(other)
201 , _current_range_min(0)
202 , _current_range_max(0)
203 , _region_relative_time_converter(other.region_relative_time_converter())
204 , _source_relative_time_converter(other.source_relative_time_converter())
206 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
207 , _note_diff_command (0)
209 , _step_edit_cursor (0)
210 , _step_edit_cursor_width (1.0)
211 , _step_edit_cursor_position (0.0)
212 , _channel_selection_scoped_note (0)
213 , _temporary_note_group (0)
216 , _sort_needed (true)
217 , _optimization_iterator (_events.end())
219 , _no_sound_notes (false)
220 , _last_display_zoom (0)
223 , _grabbed_keyboard (false)
225 , _mouse_changed_selection (false)
230 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
231 : RegionView (other, boost::shared_ptr<Region> (region))
232 , _current_range_min(0)
233 , _current_range_max(0)
234 , _region_relative_time_converter(other.region_relative_time_converter())
235 , _source_relative_time_converter(other.source_relative_time_converter())
237 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
238 , _note_diff_command (0)
240 , _step_edit_cursor (0)
241 , _step_edit_cursor_width (1.0)
242 , _step_edit_cursor_position (0.0)
243 , _channel_selection_scoped_note (0)
244 , _temporary_note_group (0)
247 , _sort_needed (true)
248 , _optimization_iterator (_events.end())
250 , _no_sound_notes (false)
251 , _last_display_zoom (0)
254 , _grabbed_keyboard (false)
256 , _mouse_changed_selection (false)
262 MidiRegionView::init (bool wfd)
264 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
266 NoteBase::NoteBaseDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
267 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
271 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
272 midi_region()->midi_source(0)->load_model(lm);
275 _model = midi_region()->midi_source(0)->model();
276 _enable_display = false;
277 fill_color_name = "midi frame base";
279 RegionView::init (false);
281 set_height (trackview.current_height());
284 region_sync_changed ();
285 region_resized (ARDOUR::bounds_change);
290 _enable_display = true;
293 display_model (_model);
297 reset_width_dependent_items (_pixel_width);
299 group->raise_to_top();
301 midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
302 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
305 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
306 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
308 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
309 boost::bind (&MidiRegionView::snap_changed, this),
312 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
313 boost::bind (&MidiRegionView::mouse_mode_changed, this),
316 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
317 connect_to_diskstream ();
319 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
321 PublicEditor& editor (trackview.editor());
322 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
326 MidiRegionView::instrument_info () const
328 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
329 return route_ui->route()->instrument_info();
332 const boost::shared_ptr<ARDOUR::MidiRegion>
333 MidiRegionView::midi_region() const
335 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
339 MidiRegionView::connect_to_diskstream ()
341 midi_view()->midi_track()->DataRecorded.connect(
342 *this, invalidator(*this),
343 boost::bind (&MidiRegionView::data_recorded, this, _1),
348 MidiRegionView::canvas_group_event(GdkEvent* ev)
350 if (in_destructor || _recregion) {
354 if (!trackview.editor().internal_editing()) {
355 // not in internal edit mode, so just act like a normal region
356 return RegionView::canvas_group_event (ev);
362 case GDK_ENTER_NOTIFY:
363 _last_event_x = ev->crossing.x;
364 _last_event_y = ev->crossing.y;
365 enter_notify(&ev->crossing);
366 // set entered_regionview (among other things)
367 return RegionView::canvas_group_event (ev);
369 case GDK_LEAVE_NOTIFY:
370 _last_event_x = ev->crossing.x;
371 _last_event_y = ev->crossing.y;
372 leave_notify(&ev->crossing);
373 // reset entered_regionview (among other things)
374 return RegionView::canvas_group_event (ev);
377 if (scroll (&ev->scroll)) {
383 return key_press (&ev->key);
385 case GDK_KEY_RELEASE:
386 return key_release (&ev->key);
388 case GDK_BUTTON_PRESS:
389 return button_press (&ev->button);
391 case GDK_BUTTON_RELEASE:
392 r = button_release (&ev->button);
393 _note_player.reset();
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 trackview.editor().verbose_cursor()->hide ();
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 (_("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 editor.verbose_cursor()->hide ();
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 editor.verbose_cursor()->hide ();
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 trackview.editor().verbose_cursor()->hide ();
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 (_("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 trackview.editor().begin_reversible_command(_("add note"));
955 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
957 _model->apply_command(*trackview.session(), cmd);
958 trackview.editor().commit_reversible_command();
960 play_midi_note (new_note);
964 MidiRegionView::clear_events (bool with_selection_signal)
966 clear_selection (with_selection_signal);
969 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
970 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
975 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
980 _patch_changes.clear();
982 _optimization_iterator = _events.end();
986 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
990 content_connection.disconnect ();
991 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
992 /* Don't signal as nobody else needs to know until selection has been altered. */
993 clear_events (false);
995 if (_enable_display) {
1001 MidiRegionView::start_note_diff_command (string name)
1003 if (!_note_diff_command) {
1004 trackview.editor().begin_reversible_command (name);
1005 _note_diff_command = _model->new_note_diff_command (name);
1010 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1012 if (_note_diff_command) {
1013 _note_diff_command->add (note);
1016 _marked_for_selection.insert(note);
1018 if (show_velocity) {
1019 _marked_for_velocity.insert(note);
1024 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1026 if (_note_diff_command && ev->note()) {
1027 _note_diff_command->remove(ev->note());
1032 MidiRegionView::note_diff_add_change (NoteBase* ev,
1033 MidiModel::NoteDiffCommand::Property property,
1036 if (_note_diff_command) {
1037 _note_diff_command->change (ev->note(), property, val);
1042 MidiRegionView::note_diff_add_change (NoteBase* ev,
1043 MidiModel::NoteDiffCommand::Property property,
1046 if (_note_diff_command) {
1047 _note_diff_command->change (ev->note(), property, val);
1052 MidiRegionView::apply_diff (bool as_subcommand)
1055 bool commit = false;
1057 if (!_note_diff_command) {
1061 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1062 // Mark all selected notes for selection when model reloads
1063 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1064 _marked_for_selection.insert((*i)->note());
1068 if (as_subcommand) {
1069 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1071 _model->apply_command (*trackview.session(), _note_diff_command);
1075 _note_diff_command = 0;
1076 midi_view()->midi_track()->playlist_modified();
1078 if (add_or_remove) {
1079 _marked_for_selection.clear();
1082 _marked_for_velocity.clear();
1084 trackview.editor().commit_reversible_command ();
1089 MidiRegionView::abort_command()
1091 delete _note_diff_command;
1092 _note_diff_command = 0;
1097 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1099 if (_optimization_iterator != _events.end()) {
1100 ++_optimization_iterator;
1103 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1104 return *_optimization_iterator;
1107 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1108 if ((*_optimization_iterator)->note() == note) {
1109 return *_optimization_iterator;
1116 /** This version finds any canvas note matching the supplied note. */
1118 MidiRegionView::find_canvas_note (NoteType note)
1120 Events::iterator it;
1122 for (it = _events.begin(); it != _events.end(); ++it) {
1123 if (*((*it)->note()) == note) {
1132 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1134 MidiModel::Notes notes;
1135 _model->get_notes (notes, op, val, chan_mask);
1137 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1138 NoteBase* cne = find_canvas_note (*n);
1146 MidiRegionView::redisplay_model()
1148 if (_active_notes) {
1149 // Currently recording
1150 const framecnt_t zoom = trackview.editor().get_current_zoom();
1151 if (zoom != _last_display_zoom) {
1152 /* Update resolved canvas notes to reflect changes in zoom without
1153 touching model. Leave active notes (with length 0) alone since
1154 they are being extended. */
1155 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1156 if ((*i)->note()->length() > 0) {
1160 _last_display_zoom = zoom;
1169 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1170 (*i)->invalidate ();
1173 MidiModel::ReadLock lock(_model->read_lock());
1175 MidiModel::Notes& notes (_model->notes());
1176 _optimization_iterator = _events.begin();
1178 bool empty_when_starting = _events.empty();
1180 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1182 boost::shared_ptr<NoteType> note (*n);
1186 if (note_in_region_range (note, visible)) {
1188 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1201 cne = add_note (note, visible);
1204 set<boost::shared_ptr<NoteType> >::iterator it;
1205 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1206 if (*(*it) == *note) {
1207 add_to_selection (cne);
1213 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1220 /* remove note items that are no longer valid */
1222 if (!empty_when_starting) {
1223 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1224 if (!(*i)->valid ()) {
1226 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1227 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1229 gr->remove_note (*i);
1234 i = _events.erase (i);
1242 _patch_changes.clear();
1246 display_patch_changes ();
1248 _marked_for_selection.clear ();
1249 _marked_for_velocity.clear ();
1250 _pending_note_selection.clear ();
1252 /* we may have caused _events to contain things out of order (e.g. if a note
1253 moved earlier or later). we don't generally need them in time order, but
1254 make a note that a sort is required for those cases that require it.
1257 _sort_needed = true;
1261 MidiRegionView::display_patch_changes ()
1263 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1264 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1266 for (uint8_t i = 0; i < 16; ++i) {
1267 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1271 /** @param active_channel true to display patch changes fully, false to display
1272 * them `greyed-out' (as on an inactive channel)
1275 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1277 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1279 if ((*i)->channel() != channel) {
1283 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1284 add_canvas_patch_change (*i, patch_name, active_channel);
1289 MidiRegionView::display_sysexes()
1291 bool have_periodic_system_messages = false;
1292 bool display_periodic_messages = true;
1294 if (!ARDOUR_UI::config()->get_never_display_periodic_midi()) {
1296 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1297 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1298 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1301 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1302 have_periodic_system_messages = true;
1308 if (have_periodic_system_messages) {
1309 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1311 /* get an approximate value for the number of samples per video frame */
1313 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1315 /* if we are zoomed out beyond than the cutoff (i.e. more
1316 * frames per pixel than frames per 4 video frames), don't
1317 * show periodic sysex messages.
1320 if (zoom > (video_frame*4)) {
1321 display_periodic_messages = false;
1325 display_periodic_messages = false;
1328 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1330 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1331 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1333 Evoral::Beats time = (*i)->time();
1336 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1337 if (!display_periodic_messages) {
1345 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1346 str << int((*i)->buffer()[b]);
1347 if (b != (*i)->size() -1) {
1351 string text = str.str();
1353 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1355 double height = midi_stream_view()->contents_height();
1357 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1358 // SysEx canvas object!!!
1360 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1361 new SysEx (*this, _note_group, text, height, x, 1.0));
1363 // Show unless message is beyond the region bounds
1364 if (time - _region->start() >= _region->length() || time < _region->start()) {
1370 _sys_exes.push_back(sysex);
1374 MidiRegionView::~MidiRegionView ()
1376 in_destructor = true;
1378 trackview.editor().verbose_cursor()->hide ();
1380 note_delete_connection.disconnect ();
1382 delete _list_editor;
1384 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1386 if (_active_notes) {
1390 _selection_cleared_connection.disconnect ();
1393 clear_events (false);
1396 delete _note_diff_command;
1397 delete _step_edit_cursor;
1398 delete _temporary_note_group;
1402 MidiRegionView::region_resized (const PropertyChange& what_changed)
1404 RegionView::region_resized(what_changed);
1406 if (what_changed.contains (ARDOUR::Properties::position)) {
1407 _region_relative_time_converter.set_origin_b(_region->position());
1408 set_duration(_region->length(), 0);
1409 if (_enable_display) {
1414 if (what_changed.contains (ARDOUR::Properties::start) ||
1415 what_changed.contains (ARDOUR::Properties::position)) {
1416 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1421 MidiRegionView::reset_width_dependent_items (double pixel_width)
1423 RegionView::reset_width_dependent_items(pixel_width);
1425 if (_enable_display) {
1429 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1430 if ((*x)->canvas_item()->width() >= _pixel_width) {
1437 move_step_edit_cursor (_step_edit_cursor_position);
1438 set_step_edit_cursor_width (_step_edit_cursor_width);
1442 MidiRegionView::set_height (double height)
1444 double old_height = _height;
1445 RegionView::set_height(height);
1447 apply_note_range (midi_stream_view()->lowest_note(),
1448 midi_stream_view()->highest_note(),
1449 height != old_height);
1452 name_text->raise_to_top();
1455 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1456 (*x)->set_height (midi_stream_view()->contents_height());
1459 if (_step_edit_cursor) {
1460 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1465 /** Apply the current note range from the stream view
1466 * by repositioning/hiding notes as necessary
1469 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1471 if (!_enable_display) {
1475 if (!force && _current_range_min == min && _current_range_max == max) {
1479 _current_range_min = min;
1480 _current_range_max = max;
1482 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1483 NoteBase* event = *i;
1484 boost::shared_ptr<NoteType> note (event->note());
1486 if (note->note() < _current_range_min ||
1487 note->note() > _current_range_max) {
1493 if (Note* cnote = dynamic_cast<Note*>(event)) {
1495 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1496 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1501 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1508 MidiRegionView::add_ghost (TimeAxisView& tv)
1510 double unit_position = _region->position () / samples_per_pixel;
1511 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1512 MidiGhostRegion* ghost;
1514 if (mtv && mtv->midi_view()) {
1515 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1516 to allow having midi notes on top of note lines and waveforms.
1518 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1520 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1523 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1524 ghost->add_note(*i);
1527 ghost->set_height ();
1528 ghost->set_duration (_region->length() / samples_per_pixel);
1529 ghosts.push_back (ghost);
1531 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1537 /** Begin tracking note state for successive calls to add_event
1540 MidiRegionView::begin_write()
1542 if (_active_notes) {
1543 delete[] _active_notes;
1545 _active_notes = new Note*[128];
1546 for (unsigned i = 0; i < 128; ++i) {
1547 _active_notes[i] = 0;
1552 /** Destroy note state for add_event
1555 MidiRegionView::end_write()
1557 delete[] _active_notes;
1559 _marked_for_selection.clear();
1560 _marked_for_velocity.clear();
1564 /** Resolve an active MIDI note (while recording).
1567 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1569 if (midi_view()->note_mode() != Sustained) {
1573 if (_active_notes && _active_notes[note]) {
1574 /* Set note length so update_note() works. Note this is a local note
1575 for recording, not from a model, so we can safely mess with it. */
1576 _active_notes[note]->note()->set_length(
1577 end_time - _active_notes[note]->note()->time());
1579 /* End time is relative to the region being recorded. */
1580 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1582 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1583 _active_notes[note]->set_outline_all ();
1584 _active_notes[note] = 0;
1589 /** Extend active notes to rightmost edge of region (if length is changed)
1592 MidiRegionView::extend_active_notes()
1594 if (!_active_notes) {
1598 for (unsigned i = 0; i < 128; ++i) {
1599 if (_active_notes[i]) {
1600 _active_notes[i]->set_x1(
1601 trackview.editor().sample_to_pixel(_region->length()));
1607 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1609 if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1613 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1615 if (!route_ui || !route_ui->midi_track()) {
1619 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1623 /* NotePlayer deletes itself */
1627 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1629 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1630 start_playing_midi_chord(notes);
1634 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1636 if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1640 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1642 if (!route_ui || !route_ui->midi_track()) {
1646 _note_player = boost::shared_ptr<NotePlayer>(new NotePlayer(route_ui->midi_track()));
1648 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1649 _note_player->add (*n);
1652 _note_player->on ();
1657 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1659 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1660 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1662 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1663 (note->note() <= midi_stream_view()->highest_note());
1669 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1673 if ((sus = dynamic_cast<Note*>(note))) {
1674 update_sustained(sus, update_ghost_regions);
1675 } else if ((hit = dynamic_cast<Hit*>(note))) {
1676 update_hit(hit, update_ghost_regions);
1680 /** Update a canvas note's size from its model note.
1681 * @param ev Canvas note to update.
1682 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1685 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1687 boost::shared_ptr<NoteType> note = ev->note();
1688 const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1689 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1694 /* trim note display to not overlap the end of its region */
1696 if (note->length() > 0) {
1697 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1698 ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames));
1700 ev->set_x1 (trackview.editor().sample_to_pixel (_region->length()));
1703 ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
1705 if (!note->length()) {
1706 if (_active_notes && note->note() < 128) {
1707 Note* const old_rect = _active_notes[note->note()];
1709 /* There is an active note on this key, so we have a stuck
1710 note. Finish the old rectangle here. */
1711 old_rect->set_x1 (x);
1712 old_rect->set_outline_all ();
1714 _active_notes[note->note()] = ev;
1716 /* outline all but right edge */
1717 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1718 ArdourCanvas::Rectangle::TOP|
1719 ArdourCanvas::Rectangle::LEFT|
1720 ArdourCanvas::Rectangle::BOTTOM));
1722 /* outline all edges */
1723 ev->set_outline_all ();
1726 // Update color in case velocity has changed
1727 ev->set_fill_color(ev->base_color());
1728 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1730 if (update_ghost_regions) {
1731 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1732 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1734 gr->update_note (ev);
1741 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1743 boost::shared_ptr<NoteType> note = ev->note();
1745 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1746 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1747 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1748 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1750 ev->set_position (ArdourCanvas::Duple (x, y));
1751 ev->set_height (diamond_size);
1753 // Update color in case velocity has changed
1754 ev->set_fill_color(ev->base_color());
1755 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1757 if (update_ghost_regions) {
1758 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1759 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1761 gr->update_note (ev);
1767 /** Add a MIDI note to the view (with length).
1769 * If in sustained mode, notes with length 0 will be considered active
1770 * notes, and resolve_note should be called when the corresponding note off
1771 * event arrives, to properly display the note.
1774 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1776 NoteBase* event = 0;
1778 if (midi_view()->note_mode() == Sustained) {
1780 Note* ev_rect = new Note (*this, _note_group, note);
1782 update_sustained (ev_rect);
1786 } else if (midi_view()->note_mode() == Percussive) {
1788 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1790 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1792 update_hit (ev_diamond);
1801 MidiGhostRegion* gr;
1803 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1804 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1805 gr->add_note(event);
1809 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1810 note_selected(event, true);
1813 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1814 event->show_velocity();
1817 event->on_channel_selection_change (get_selected_channels());
1818 _events.push_back(event);
1827 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1828 MidiStreamView* const view = mtv->midi_view();
1830 view->update_note_range (note->note());
1835 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1836 Evoral::Beats pos, Evoral::Beats len)
1838 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1840 /* potentially extend region to hold new note */
1842 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1843 framepos_t region_end = _region->last_frame();
1845 if (end_frame > region_end) {
1846 _region->set_length (end_frame - _region->position());
1849 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1850 MidiStreamView* const view = mtv->midi_view();
1852 view->update_note_range(new_note->note());
1854 _marked_for_selection.clear ();
1857 start_note_diff_command (_("step add"));
1858 note_diff_add_note (new_note, true, false);
1861 // last_step_edit_note = new_note;
1865 MidiRegionView::step_sustain (Evoral::Beats beats)
1867 change_note_lengths (false, false, beats, false, true);
1870 /** Add a new patch change flag to the canvas.
1871 * @param patch the patch change to add
1872 * @param the text to display in the flag
1873 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1876 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1878 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1879 const double x = trackview.editor().sample_to_pixel (region_frames);
1881 double const height = midi_stream_view()->contents_height();
1883 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1884 // so we need to do something more sophisticated to keep its color
1885 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1888 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1889 new PatchChange(*this, group,
1896 if (patch_change->item().width() < _pixel_width) {
1897 // Show unless patch change is beyond the region bounds
1898 if (region_frames < 0 || region_frames >= _region->length()) {
1899 patch_change->hide();
1901 patch_change->show();
1904 patch_change->hide ();
1907 _patch_changes.push_back (patch_change);
1910 MIDI::Name::PatchPrimaryKey
1911 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1913 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1916 /// Return true iff @p pc applies to the given time on the given channel.
1918 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1920 return pc->time() <= time && pc->channel() == channel;
1924 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1926 // The earliest event not before time
1927 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1929 // Go backwards until we find the latest PC for this channel, or the start
1930 while (i != _model->patch_changes().begin() &&
1931 (i == _model->patch_changes().end() ||
1932 !patch_applies(*i, time, channel))) {
1936 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1937 key.set_bank((*i)->bank());
1938 key.set_program((*i)->program ());
1946 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1948 string name = _("alter patch change");
1949 trackview.editor().begin_reversible_command (name);
1950 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1952 if (pc.patch()->program() != new_patch.program()) {
1953 c->change_program (pc.patch (), new_patch.program());
1956 int const new_bank = new_patch.bank();
1957 if (pc.patch()->bank() != new_bank) {
1958 c->change_bank (pc.patch (), new_bank);
1961 _model->apply_command (*trackview.session(), c);
1962 trackview.editor().commit_reversible_command ();
1964 _patch_changes.clear ();
1965 display_patch_changes ();
1969 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
1971 string name = _("alter patch change");
1972 trackview.editor().begin_reversible_command (name);
1973 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1975 if (old_change->time() != new_change.time()) {
1976 c->change_time (old_change, new_change.time());
1979 if (old_change->channel() != new_change.channel()) {
1980 c->change_channel (old_change, new_change.channel());
1983 if (old_change->program() != new_change.program()) {
1984 c->change_program (old_change, new_change.program());
1987 if (old_change->bank() != new_change.bank()) {
1988 c->change_bank (old_change, new_change.bank());
1991 _model->apply_command (*trackview.session(), c);
1992 trackview.editor().commit_reversible_command ();
1994 _patch_changes.clear ();
1995 display_patch_changes ();
1998 /** Add a patch change to the region.
1999 * @param t Time in frames relative to region position
2000 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2001 * MidiTimeAxisView::get_channel_for_add())
2004 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2006 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2007 string name = _("add patch change");
2009 trackview.editor().begin_reversible_command (name);
2010 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2011 c->add (MidiModel::PatchChangePtr (
2012 new Evoral::PatchChange<Evoral::Beats> (
2013 absolute_frames_to_source_beats (_region->position() + t),
2014 mtv->get_channel_for_add(), patch.program(), patch.bank()
2019 _model->apply_command (*trackview.session(), c);
2020 trackview.editor().commit_reversible_command ();
2022 _patch_changes.clear ();
2023 display_patch_changes ();
2027 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2029 trackview.editor().begin_reversible_command (_("move patch change"));
2030 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2031 c->change_time (pc.patch (), t);
2032 _model->apply_command (*trackview.session(), c);
2033 trackview.editor().commit_reversible_command ();
2035 _patch_changes.clear ();
2036 display_patch_changes ();
2040 MidiRegionView::delete_patch_change (PatchChange* pc)
2042 trackview.editor().begin_reversible_command (_("delete patch change"));
2043 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2044 c->remove (pc->patch ());
2045 _model->apply_command (*trackview.session(), c);
2046 trackview.editor().commit_reversible_command ();
2048 _patch_changes.clear ();
2049 display_patch_changes ();
2053 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2055 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2057 key.set_bank(key.bank() + delta);
2059 key.set_program(key.program() + delta);
2061 change_patch_change(patch, key);
2065 MidiRegionView::maybe_remove_deleted_note_from_selection (NoteBase* cne)
2067 if (_selection.empty()) {
2071 _selection.erase (cne);
2075 MidiRegionView::delete_selection()
2077 if (_selection.empty()) {
2081 start_note_diff_command (_("delete selection"));
2083 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2084 if ((*i)->selected()) {
2085 _note_diff_command->remove((*i)->note());
2095 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2097 start_note_diff_command (_("delete note"));
2098 _note_diff_command->remove (n);
2101 trackview.editor().verbose_cursor()->hide ();
2105 MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
2107 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2109 Selection::iterator tmp = i;
2112 (*i)->set_selected (false);
2113 (*i)->hide_velocity ();
2114 _selection.erase (i);
2122 if (!ev && _entered) {
2123 // Clearing selection entirely, ungrab keyboard
2124 Keyboard::magic_widget_drop_focus();
2125 _grabbed_keyboard = false;
2128 /* this does not change the status of this regionview w.r.t the editor
2133 SelectionCleared (this); /* EMIT SIGNAL */
2138 MidiRegionView::unique_select(NoteBase* ev)
2140 const bool selection_was_empty = _selection.empty();
2142 clear_selection_except (ev);
2144 /* don't bother with checking to see if we should remove this
2145 regionview from the editor selection, since we're about to add
2146 another note, and thus put/keep this regionview in the editor
2150 if (!ev->selected()) {
2151 add_to_selection (ev);
2152 if (selection_was_empty && _entered) {
2153 // Grab keyboard for moving notes with arrow keys
2154 Keyboard::magic_widget_grab_focus();
2155 _grabbed_keyboard = true;
2161 MidiRegionView::select_all_notes ()
2165 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2166 add_to_selection (*i);
2171 MidiRegionView::select_range (framepos_t start, framepos_t end)
2175 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2176 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2177 if (t >= start && t <= end) {
2178 add_to_selection (*i);
2184 MidiRegionView::invert_selection ()
2186 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2187 if ((*i)->selected()) {
2188 remove_from_selection(*i);
2190 add_to_selection (*i);
2195 /** Used for selection undo/redo.
2196 The requested notes most likely won't exist in the view until the next model redisplay.
2199 MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
2202 list<boost::shared_ptr<NoteType> >::iterator n;
2204 for (n = notes.begin(); n != notes.end(); ++n) {
2205 if ((cne = find_canvas_note(*(*n))) != 0) {
2206 add_to_selection (cne);
2208 _pending_note_selection.insert(*n);
2214 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2216 bool have_selection = !_selection.empty();
2217 uint8_t low_note = 127;
2218 uint8_t high_note = 0;
2219 MidiModel::Notes& notes (_model->notes());
2220 _optimization_iterator = _events.begin();
2222 if (extend && !have_selection) {
2226 /* scan existing selection to get note range */
2228 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2229 if ((*i)->note()->note() < low_note) {
2230 low_note = (*i)->note()->note();
2232 if ((*i)->note()->note() > high_note) {
2233 high_note = (*i)->note()->note();
2240 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2241 /* only note previously selected is the one we are
2242 * reselecting. treat this as cancelling the selection.
2249 low_note = min (low_note, notenum);
2250 high_note = max (high_note, notenum);
2253 _no_sound_notes = true;
2255 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2257 boost::shared_ptr<NoteType> note (*n);
2259 bool select = false;
2261 if (((1 << note->channel()) & channel_mask) != 0) {
2263 if ((note->note() >= low_note && note->note() <= high_note)) {
2266 } else if (note->note() == notenum) {
2272 if ((cne = find_canvas_note (note)) != 0) {
2273 // extend is false because we've taken care of it,
2274 // since it extends by time range, not pitch.
2275 note_selected (cne, add, false);
2279 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2283 _no_sound_notes = false;
2287 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2289 MidiModel::Notes& notes (_model->notes());
2290 _optimization_iterator = _events.begin();
2292 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2294 boost::shared_ptr<NoteType> note (*n);
2297 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2298 if ((cne = find_canvas_note (note)) != 0) {
2299 if (cne->selected()) {
2300 note_deselected (cne);
2302 note_selected (cne, true, false);
2310 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2313 clear_selection_except (ev);
2314 if (!_selection.empty()) {
2315 PublicEditor& editor (trackview.editor());
2316 editor.get_selection().add (this);
2322 if (!ev->selected()) {
2323 add_to_selection (ev);
2327 /* find end of latest note selected, select all between that and the start of "ev" */
2329 Evoral::Beats earliest = Evoral::MaxBeats;
2330 Evoral::Beats latest = Evoral::Beats();
2332 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2333 if ((*i)->note()->end_time() > latest) {
2334 latest = (*i)->note()->end_time();
2336 if ((*i)->note()->time() < earliest) {
2337 earliest = (*i)->note()->time();
2341 if (ev->note()->end_time() > latest) {
2342 latest = ev->note()->end_time();
2345 if (ev->note()->time() < earliest) {
2346 earliest = ev->note()->time();
2349 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2351 /* find notes entirely within OR spanning the earliest..latest range */
2353 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2354 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2355 add_to_selection (*i);
2363 MidiRegionView::note_deselected(NoteBase* ev)
2365 remove_from_selection (ev);
2369 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2371 PublicEditor& editor = trackview.editor();
2373 // Convert to local coordinates
2374 const framepos_t p = _region->position();
2375 const double y = midi_view()->y_position();
2376 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2377 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2378 const double y0 = max(0.0, gy0 - y);
2379 const double y1 = max(0.0, gy1 - y);
2381 // TODO: Make this faster by storing the last updated selection rect, and only
2382 // adjusting things that are in the area that appears/disappeared.
2383 // We probably need a tree to be able to find events in O(log(n)) time.
2385 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2386 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2387 // Rectangles intersect
2388 if (!(*i)->selected()) {
2389 add_to_selection (*i);
2391 } else if ((*i)->selected() && !extend) {
2392 // Rectangles do not intersect
2393 remove_from_selection (*i);
2397 typedef RouteTimeAxisView::AutomationTracks ATracks;
2398 typedef std::list<Selectable*> Selectables;
2400 /* Add control points to selection. */
2401 const ATracks& atracks = midi_view()->automation_tracks();
2402 Selectables selectables;
2403 editor.get_selection().clear_points();
2404 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2405 a->second->get_selectables(start, end, gy0, gy1, selectables);
2406 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2407 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2409 editor.get_selection().add(cp);
2412 a->second->set_selected_points(editor.get_selection().points);
2417 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2423 // TODO: Make this faster by storing the last updated selection rect, and only
2424 // adjusting things that are in the area that appears/disappeared.
2425 // We probably need a tree to be able to find events in O(log(n)) time.
2427 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2428 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2429 // within y- (note-) range
2430 if (!(*i)->selected()) {
2431 add_to_selection (*i);
2433 } else if ((*i)->selected() && !extend) {
2434 remove_from_selection (*i);
2440 MidiRegionView::remove_from_selection (NoteBase* ev)
2442 Selection::iterator i = _selection.find (ev);
2444 if (i != _selection.end()) {
2445 _selection.erase (i);
2446 if (_selection.empty() && _grabbed_keyboard) {
2448 Keyboard::magic_widget_drop_focus();
2449 _grabbed_keyboard = false;
2453 ev->set_selected (false);
2454 ev->hide_velocity ();
2456 if (_selection.empty()) {
2457 PublicEditor& editor (trackview.editor());
2458 editor.get_selection().remove (this);
2463 MidiRegionView::add_to_selection (NoteBase* ev)
2465 const bool selection_was_empty = _selection.empty();
2467 if (_selection.insert (ev).second) {
2468 ev->set_selected (true);
2469 start_playing_midi_note ((ev)->note());
2470 if (selection_was_empty && _entered) {
2471 // Grab keyboard for moving notes with arrow keys
2472 Keyboard::magic_widget_grab_focus();
2473 _grabbed_keyboard = true;
2477 if (selection_was_empty) {
2478 PublicEditor& editor (trackview.editor());
2479 editor.get_selection().add (this);
2484 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2486 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2487 PossibleChord to_play;
2488 Evoral::Beats earliest = Evoral::MaxBeats;
2490 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2491 if ((*i)->note()->time() < earliest) {
2492 earliest = (*i)->note()->time();
2496 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2497 if ((*i)->note()->time() == earliest) {
2498 to_play.push_back ((*i)->note());
2500 (*i)->move_event(dx, dy);
2503 if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) {
2505 if (to_play.size() > 1) {
2507 PossibleChord shifted;
2509 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2510 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2511 moved_note->set_note (moved_note->note() + cumulative_dy);
2512 shifted.push_back (moved_note);
2515 start_playing_midi_chord (shifted);
2517 } else if (!to_play.empty()) {
2519 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2520 moved_note->set_note (moved_note->note() + cumulative_dy);
2521 start_playing_midi_note (moved_note);
2527 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2529 uint8_t lowest_note_in_selection = 127;
2530 uint8_t highest_note_in_selection = 0;
2531 uint8_t highest_note_difference = 0;
2533 // find highest and lowest notes first
2535 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2536 uint8_t pitch = (*i)->note()->note();
2537 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2538 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2542 cerr << "dnote: " << (int) dnote << endl;
2543 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2544 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2545 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2546 << int(highest_note_in_selection) << endl;
2547 cerr << "selection size: " << _selection.size() << endl;
2548 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2551 // Make sure the note pitch does not exceed the MIDI standard range
2552 if (highest_note_in_selection + dnote > 127) {
2553 highest_note_difference = highest_note_in_selection - 127;
2556 start_note_diff_command (_("move notes"));
2558 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2560 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2561 Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
2567 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2569 uint8_t original_pitch = (*i)->note()->note();
2570 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2572 // keep notes in standard midi range
2573 clamp_to_0_127(new_pitch);
2575 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2576 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2578 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2583 // care about notes being moved beyond the upper/lower bounds on the canvas
2584 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2585 highest_note_in_selection > midi_stream_view()->highest_note()) {
2586 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2590 /** @param x Pixel relative to the region position.
2591 * @return Snapped frame relative to the region position.
2594 MidiRegionView::snap_pixel_to_sample(double x)
2596 PublicEditor& editor (trackview.editor());
2597 return snap_frame_to_frame (editor.pixel_to_sample (x));
2600 /** @param x Pixel relative to the region position.
2601 * @return Snapped pixel relative to the region position.
2604 MidiRegionView::snap_to_pixel(double x)
2606 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x));
2610 MidiRegionView::get_position_pixels()
2612 framepos_t region_frame = get_position();
2613 return trackview.editor().sample_to_pixel(region_frame);
2617 MidiRegionView::get_end_position_pixels()
2619 framepos_t frame = get_position() + get_duration ();
2620 return trackview.editor().sample_to_pixel(frame);
2624 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2626 /* the time converter will return the frame corresponding to `beats'
2627 relative to the start of the source. The start of the source
2628 is an implied position given by region->position - region->start
2630 const framepos_t source_start = _region->position() - _region->start();
2631 return source_start + _source_relative_time_converter.to (beats);
2635 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2637 /* the `frames' argument needs to be converted into a frame count
2638 relative to the start of the source before being passed in to the
2641 const framepos_t source_start = _region->position() - _region->start();
2642 return _source_relative_time_converter.from (frames - source_start);
2646 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2648 return _region_relative_time_converter.to(beats);
2652 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2654 return _region_relative_time_converter.from(frames);
2658 MidiRegionView::begin_resizing (bool /*at_front*/)
2660 _resize_data.clear();
2662 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2663 Note *note = dynamic_cast<Note*> (*i);
2665 // only insert CanvasNotes into the map
2667 NoteResizeData *resize_data = new NoteResizeData();
2668 resize_data->note = note;
2670 // create a new SimpleRect from the note which will be the resize preview
2671 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2672 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2674 // calculate the colors: get the color settings
2675 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2676 ARDOUR_UI::config()->color ("midi note selected"),
2679 // make the resize preview notes more transparent and bright
2680 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2682 // calculate color based on note velocity
2683 resize_rect->set_fill_color (UINT_INTERPOLATE(
2684 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2688 resize_rect->set_outline_color (NoteBase::calculate_outline (
2689 ARDOUR_UI::config()->color ("midi note selected")));
2691 resize_data->resize_rect = resize_rect;
2692 _resize_data.push_back(resize_data);
2697 /** Update resizing notes while user drags.
2698 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2699 * @param at_front which end of the note (true == note on, false == note off)
2700 * @param delta_x change in mouse position since the start of the drag
2701 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2702 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2703 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2704 * as the \a primary note.
2707 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2709 bool cursor_set = false;
2711 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2712 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2713 Note* canvas_note = (*i)->note;
2718 current_x = canvas_note->x0() + delta_x;
2720 current_x = primary->x0() + delta_x;
2724 current_x = canvas_note->x1() + delta_x;
2726 current_x = primary->x1() + delta_x;
2730 if (current_x < 0) {
2731 // This works even with snapping because RegionView::snap_frame_to_frame()
2732 // snaps forward if the snapped sample is before the beginning of the region
2735 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2736 current_x = trackview.editor().sample_to_pixel(_region->length());
2740 resize_rect->set_x0 (snap_to_pixel(current_x));
2741 resize_rect->set_x1 (canvas_note->x1());
2743 resize_rect->set_x1 (snap_to_pixel(current_x));
2744 resize_rect->set_x0 (canvas_note->x0());
2748 const double snapped_x = snap_pixel_to_sample (current_x);
2749 Evoral::Beats beats = region_frames_to_region_beats (snapped_x);
2750 Evoral::Beats len = Evoral::Beats();
2753 if (beats < canvas_note->note()->end_time()) {
2754 len = canvas_note->note()->time() - beats;
2755 len += canvas_note->note()->length();
2758 if (beats >= canvas_note->note()->time()) {
2759 len = beats - canvas_note->note()->time();
2763 len = std::max(Evoral::Beats(1 / 512.0), len);
2766 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2767 show_verbose_cursor (buf, 0, 0);
2776 /** Finish resizing notes when the user releases the mouse button.
2777 * Parameters the same as for \a update_resizing().
2780 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2782 start_note_diff_command (_("resize notes"));
2784 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2785 Note* canvas_note = (*i)->note;
2786 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2788 /* Get the new x position for this resize, which is in pixels relative
2789 * to the region position.
2796 current_x = canvas_note->x0() + delta_x;
2798 current_x = primary->x0() + delta_x;
2802 current_x = canvas_note->x1() + delta_x;
2804 current_x = primary->x1() + delta_x;
2808 if (current_x < 0) {
2811 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2812 current_x = trackview.editor().sample_to_pixel(_region->length());
2815 /* Convert that to a frame within the source */
2816 current_x = snap_pixel_to_sample (current_x) + _region->start ();
2818 /* and then to beats */
2819 const Evoral::Beats x_beats = region_frames_to_region_beats (current_x);
2821 if (at_front && x_beats < canvas_note->note()->end_time()) {
2822 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats);
2824 Evoral::Beats len = canvas_note->note()->time() - x_beats;
2825 len += canvas_note->note()->length();
2828 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2833 const Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2834 x_beats - canvas_note->note()->time());
2835 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2842 _resize_data.clear();
2847 MidiRegionView::abort_resizing ()
2849 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2850 delete (*i)->resize_rect;
2854 _resize_data.clear ();
2858 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2860 uint8_t new_velocity;
2863 new_velocity = event->note()->velocity() + velocity;
2864 clamp_to_0_127(new_velocity);
2866 new_velocity = velocity;
2869 event->set_selected (event->selected()); // change color
2871 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2875 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2880 new_note = event->note()->note() + note;
2885 clamp_to_0_127 (new_note);
2886 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2890 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2892 bool change_start = false;
2893 bool change_length = false;
2894 Evoral::Beats new_start;
2895 Evoral::Beats new_length;
2897 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2899 front_delta: if positive - move the start of the note later in time (shortening it)
2900 if negative - move the start of the note earlier in time (lengthening it)
2902 end_delta: if positive - move the end of the note later in time (lengthening it)
2903 if negative - move the end of the note earlier in time (shortening it)
2906 if (!!front_delta) {
2907 if (front_delta < 0) {
2909 if (event->note()->time() < -front_delta) {
2910 new_start = Evoral::Beats();
2912 new_start = event->note()->time() + front_delta; // moves earlier
2915 /* start moved toward zero, so move the end point out to where it used to be.
2916 Note that front_delta is negative, so this increases the length.
2919 new_length = event->note()->length() - front_delta;
2920 change_start = true;
2921 change_length = true;
2925 Evoral::Beats new_pos = event->note()->time() + front_delta;
2927 if (new_pos < event->note()->end_time()) {
2928 new_start = event->note()->time() + front_delta;
2929 /* start moved toward the end, so move the end point back to where it used to be */
2930 new_length = event->note()->length() - front_delta;
2931 change_start = true;
2932 change_length = true;
2939 bool can_change = true;
2940 if (end_delta < 0) {
2941 if (event->note()->length() < -end_delta) {
2947 new_length = event->note()->length() + end_delta;
2948 change_length = true;
2953 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2956 if (change_length) {
2957 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2962 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
2964 uint8_t new_channel;
2968 if (event->note()->channel() < -chn) {
2971 new_channel = event->note()->channel() + chn;
2974 new_channel = event->note()->channel() + chn;
2977 new_channel = (uint8_t) chn;
2980 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2984 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
2986 Evoral::Beats new_time;
2990 if (event->note()->time() < -delta) {
2991 new_time = Evoral::Beats();
2993 new_time = event->note()->time() + delta;
2996 new_time = event->note()->time() + delta;
3002 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3006 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3008 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3012 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3017 if (_selection.empty()) {
3032 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3033 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3039 start_note_diff_command (_("change velocities"));
3041 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3042 Selection::iterator next = i;
3046 if (i == _selection.begin()) {
3047 change_note_velocity (*i, delta, true);
3048 value = (*i)->note()->velocity() + delta;
3050 change_note_velocity (*i, value, false);
3054 change_note_velocity (*i, delta, true);
3063 if (!_selection.empty()) {
3065 snprintf (buf, sizeof (buf), "Vel %d",
3066 (int) (*_selection.begin())->note()->velocity());
3067 show_verbose_cursor (buf, 10, 10);
3073 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3075 if (_selection.empty()) {
3092 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3094 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3098 if ((int8_t) (*i)->note()->note() + delta > 127) {
3105 start_note_diff_command (_("transpose"));
3107 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3108 Selection::iterator next = i;
3110 change_note_note (*i, delta, true);
3118 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3122 delta = Evoral::Beats(1.0/128.0);
3124 /* grab the current grid distance */
3125 delta = get_grid_beats(_region->position());
3133 start_note_diff_command (_("change note lengths"));
3135 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3136 Selection::iterator next = i;
3139 /* note the negation of the delta for start */
3142 (start ? -delta : Evoral::Beats()),
3143 (end ? delta : Evoral::Beats()));
3152 MidiRegionView::nudge_notes (bool forward, bool fine)
3154 if (_selection.empty()) {
3158 /* pick a note as the point along the timeline to get the nudge distance.
3159 its not necessarily the earliest note, so we may want to pull the notes out
3160 into a vector and sort before using the first one.
3163 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3164 Evoral::Beats delta;
3168 /* non-fine, move by 1 bar regardless of snap */
3169 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
3171 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3173 /* grid is off - use nudge distance */
3176 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3177 delta = region_frames_to_region_beats (fabs ((double)distance));
3183 framepos_t next_pos = ref_point;
3186 if (max_framepos - 1 < next_pos) {
3190 if (next_pos == 0) {
3196 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3197 const framecnt_t distance = ref_point - next_pos;
3198 delta = region_frames_to_region_beats (fabs ((double)distance));
3209 start_note_diff_command (_("nudge"));
3211 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3212 Selection::iterator next = i;
3214 change_note_time (*i, delta, true);
3222 MidiRegionView::change_channel(uint8_t channel)
3224 start_note_diff_command(_("change channel"));
3225 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3226 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3234 MidiRegionView::note_entered(NoteBase* ev)
3236 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3238 if (_mouse_state == SelectTouchDragging) {
3239 note_selected (ev, true);
3240 } else if (editor->current_mouse_mode() == MouseContent) {
3241 show_verbose_cursor (ev->note ());
3242 } else if (editor->current_mouse_mode() == MouseDraw) {
3243 show_verbose_cursor (ev->note ());
3248 MidiRegionView::note_left (NoteBase*)
3250 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3252 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3253 (*i)->hide_velocity ();
3256 editor->verbose_cursor()->hide ();
3260 MidiRegionView::patch_entered (PatchChange* p)
3263 /* XXX should get patch name if we can */
3264 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3265 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3266 << _("Channel ") << ((int) p->patch()->channel() + 1);
3267 show_verbose_cursor (s.str(), 10, 20);
3268 p->item().grab_focus();
3272 MidiRegionView::patch_left (PatchChange *)
3274 trackview.editor().verbose_cursor()->hide ();
3275 /* focus will transfer back via the enter-notify event sent to this
3281 MidiRegionView::sysex_entered (SysEx* p)
3285 // need a way to extract text from p->_flag->_text
3287 // show_verbose_cursor (s.str(), 10, 20);
3288 p->item().grab_focus();
3292 MidiRegionView::sysex_left (SysEx *)
3294 trackview.editor().verbose_cursor()->hide ();
3295 /* focus will transfer back via the enter-notify event sent to this
3301 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3303 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3304 Editing::MouseMode mm = editor->current_mouse_mode();
3305 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3307 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3308 if (can_set_cursor && ctx) {
3309 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3310 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3311 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3312 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3314 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3320 MidiRegionView::get_fill_color() const
3322 const std::string mod_name = (_dragging ? "dragging region" :
3323 trackview.editor().internal_editing() ? "editable region" :
3326 return ARDOUR_UI::config()->color_mod ("selected region base", mod_name);
3327 } else if ((!ARDOUR_UI::config()->get_show_name_highlight() || high_enough_for_name) &&
3328 !ARDOUR_UI::config()->get_color_regions_using_track_color()) {
3329 return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name);
3331 return ARDOUR_UI::config()->color_mod (fill_color, mod_name);
3335 MidiRegionView::midi_channel_mode_changed ()
3337 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3338 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3339 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3341 if (mode == ForceChannel) {
3342 mask = 0xFFFF; // Show all notes as active (below)
3345 // Update notes for selection
3346 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3347 (*i)->on_channel_selection_change (mask);
3350 _patch_changes.clear ();
3351 display_patch_changes ();
3355 MidiRegionView::instrument_settings_changed ()
3361 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3363 if (_selection.empty()) {
3367 PublicEditor& editor (trackview.editor());
3371 /* XXX what to do ? */
3375 editor.get_cut_buffer().add (selection_as_cut_buffer());
3383 start_note_diff_command();
3385 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3392 note_diff_remove_note (*i);
3402 MidiRegionView::selection_as_cut_buffer () const
3406 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3407 NoteType* n = (*i)->note().get();
3408 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3411 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3417 /** This method handles undo */
3419 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
3421 // Paste notes, if available
3422 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3423 if (m != selection.midi_notes.end()) {
3424 ctx.counts.increase_n_notes();
3425 paste_internal(pos, ctx.count, ctx.times, **m);
3428 // Paste control points to automation children, if available
3429 typedef RouteTimeAxisView::AutomationTracks ATracks;
3430 const ATracks& atracks = midi_view()->automation_tracks();
3431 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3432 a->second->paste(pos, selection, ctx);
3438 /** This method handles undo */
3440 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3446 start_note_diff_command (_("paste"));
3448 const Evoral::Beats snap_beats = get_grid_beats(pos);
3449 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3450 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3451 const Evoral::Beats duration = last_time - first_time;
3452 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3453 const Evoral::Beats paste_offset = snap_duration * paste_count;
3454 const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3455 Evoral::Beats end_point = Evoral::Beats();
3457 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3460 duration, pos, _region->position(),
3465 for (int n = 0; n < (int) times; ++n) {
3467 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3469 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3470 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3472 /* make all newly added notes selected */
3474 note_diff_add_note (copied_note, true);
3475 end_point = copied_note->end_time();
3479 /* if we pasted past the current end of the region, extend the region */
3481 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3482 framepos_t region_end = _region->position() + _region->length() - 1;
3484 if (end_frame > region_end) {
3486 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3488 _region->clear_changes ();
3489 _region->set_length (end_frame - _region->position());
3490 trackview.session()->add_command (new StatefulDiffCommand (_region));
3496 struct EventNoteTimeEarlyFirstComparator {
3497 bool operator() (NoteBase* a, NoteBase* b) {
3498 return a->note()->time() < b->note()->time();
3503 MidiRegionView::time_sort_events ()
3505 if (!_sort_needed) {
3509 EventNoteTimeEarlyFirstComparator cmp;
3512 _sort_needed = false;
3516 MidiRegionView::goto_next_note (bool add_to_selection)
3518 bool use_next = false;
3520 if (_events.back()->selected()) {
3524 time_sort_events ();
3526 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3527 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3529 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3530 if ((*i)->selected()) {
3533 } else if (use_next) {
3534 if (channel_mask & (1 << (*i)->note()->channel())) {
3535 if (!add_to_selection) {
3538 note_selected (*i, true, false);
3545 /* use the first one */
3547 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3548 unique_select (_events.front());
3553 MidiRegionView::goto_previous_note (bool add_to_selection)
3555 bool use_next = false;
3557 if (_events.front()->selected()) {
3561 time_sort_events ();
3563 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3564 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3566 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3567 if ((*i)->selected()) {
3570 } else if (use_next) {
3571 if (channel_mask & (1 << (*i)->note()->channel())) {
3572 if (!add_to_selection) {
3575 note_selected (*i, true, false);
3582 /* use the last one */
3584 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3585 unique_select (*(_events.rbegin()));
3590 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3592 bool had_selected = false;
3594 time_sort_events ();
3596 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3597 if ((*i)->selected()) {
3598 selected.insert ((*i)->note());
3599 had_selected = true;
3603 if (allow_all_if_none_selected && !had_selected) {
3604 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3605 selected.insert ((*i)->note());
3611 MidiRegionView::update_ghost_note (double x, double y)
3613 x = std::max(0.0, x);
3615 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3620 _note_group->canvas_to_item (x, y);
3622 PublicEditor& editor = trackview.editor ();
3624 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3625 framecnt_t grid_frames;
3626 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3628 /* calculate time in beats relative to start of source */
3629 const Evoral::Beats length = get_grid_beats(unsnapped_frame);
3630 const Evoral::Beats time = std::max(
3632 absolute_frames_to_source_beats (f + _region->position ()));
3634 _ghost_note->note()->set_time (time);
3635 _ghost_note->note()->set_length (length);
3636 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3637 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3638 _ghost_note->note()->set_velocity (get_velocity_for_add (time));
3640 /* the ghost note does not appear in ghost regions, so pass false in here */
3641 update_note (_ghost_note, false);
3643 show_verbose_cursor (_ghost_note->note ());
3647 MidiRegionView::create_ghost_note (double x, double y)
3649 remove_ghost_note ();
3651 boost::shared_ptr<NoteType> g (new NoteType);
3652 if (midi_view()->note_mode() == Sustained) {
3653 _ghost_note = new Note (*this, _note_group, g);
3655 _ghost_note = new Hit (*this, _note_group, 10, g);
3657 _ghost_note->set_ignore_events (true);
3658 _ghost_note->set_outline_color (0x000000aa);
3659 update_ghost_note (x, y);
3660 _ghost_note->show ();
3662 show_verbose_cursor (_ghost_note->note ());
3666 MidiRegionView::remove_ghost_note ()
3673 MidiRegionView::snap_changed ()
3679 create_ghost_note (_last_ghost_x, _last_ghost_y);
3683 MidiRegionView::drop_down_keys ()
3685 _mouse_state = None;
3689 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3691 /* XXX: This is dead code. What was it for? */
3693 double note = midi_stream_view()->y_to_note(y);
3695 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3697 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3699 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3700 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3701 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3702 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3707 bool add_mrv_selection = false;
3709 if (_selection.empty()) {
3710 add_mrv_selection = true;
3713 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3714 if (_selection.insert (*i).second) {
3715 (*i)->set_selected (true);
3719 if (add_mrv_selection) {
3720 PublicEditor& editor (trackview.editor());
3721 editor.get_selection().add (this);
3726 MidiRegionView::color_handler ()
3728 RegionView::color_handler ();
3730 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3731 (*i)->set_selected ((*i)->selected()); // will change color
3734 /* XXX probably more to do here */
3738 MidiRegionView::enable_display (bool yn)
3740 RegionView::enable_display (yn);
3747 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3749 if (_step_edit_cursor == 0) {
3750 ArdourCanvas::Item* const group = get_canvas_group();
3752 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3753 _step_edit_cursor->set_y0 (0);
3754 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3755 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3756 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3759 move_step_edit_cursor (pos);
3760 _step_edit_cursor->show ();
3764 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3766 _step_edit_cursor_position = pos;
3768 if (_step_edit_cursor) {
3769 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3770 _step_edit_cursor->set_x0 (pixel);
3771 set_step_edit_cursor_width (_step_edit_cursor_width);
3776 MidiRegionView::hide_step_edit_cursor ()
3778 if (_step_edit_cursor) {
3779 _step_edit_cursor->hide ();
3784 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3786 _step_edit_cursor_width = beats;
3788 if (_step_edit_cursor) {
3789 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
3793 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3794 * @param w Source that the data will end up in.
3797 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3799 if (!_active_notes) {
3800 /* we aren't actively being recorded to */
3804 boost::shared_ptr<MidiSource> src = w.lock ();
3805 if (!src || src != midi_region()->midi_source()) {
3806 /* recorded data was not destined for our source */
3810 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3812 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3814 framepos_t back = max_framepos;
3816 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3817 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3819 if (ev.is_channel_event()) {
3820 if (get_channel_mode() == FilterChannels) {
3821 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3827 /* convert from session frames to source beats */
3828 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3829 ev.time() - src->timeline_position() + _region->start());
3831 if (ev.type() == MIDI_CMD_NOTE_ON) {
3832 boost::shared_ptr<NoteType> note (
3833 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3835 add_note (note, true);
3837 /* fix up our note range */
3838 if (ev.note() < _current_range_min) {
3839 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3840 } else if (ev.note() > _current_range_max) {
3841 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3844 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3845 resolve_note (ev.note (), time_beats);
3851 midi_stream_view()->check_record_layers (region(), back);
3855 MidiRegionView::trim_front_starting ()
3857 /* Reparent the note group to the region view's parent, so that it doesn't change
3858 when the region view is trimmed.
3860 _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3861 _temporary_note_group->move (group->position ());
3862 _note_group->reparent (_temporary_note_group);
3866 MidiRegionView::trim_front_ending ()
3868 _note_group->reparent (group);
3869 delete _temporary_note_group;
3870 _temporary_note_group = 0;
3872 if (_region->start() < 0) {
3873 /* Trim drag made start time -ve; fix this */
3874 midi_region()->fix_negative_start ();
3879 MidiRegionView::edit_patch_change (PatchChange* pc)
3881 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3883 int response = d.run();
3886 case Gtk::RESPONSE_ACCEPT:
3888 case Gtk::RESPONSE_REJECT:
3889 delete_patch_change (pc);
3895 change_patch_change (pc->patch(), d.patch ());
3899 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
3902 // sysyex object doesn't have a pointer to a sysex event
3903 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3904 // c->remove (sysex->sysex());
3905 // _model->apply_command (*trackview.session(), c);
3907 //_sys_exes.clear ();
3908 // display_sysexes();
3912 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3914 using namespace MIDI::Name;
3918 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3920 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
3922 MIDI::Name::PatchPrimaryKey patch_key;
3923 get_patch_key_at(n->time(), n->channel(), patch_key);
3924 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
3927 patch_key.program(),
3933 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
3935 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
3936 (int) n->channel() + 1,
3937 (int) n->velocity());
3939 show_verbose_cursor(buf, 10, 20);
3943 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3945 trackview.editor().verbose_cursor()->set (text);
3946 trackview.editor().verbose_cursor()->show ();
3947 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
3951 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
3953 if (_model->notes().empty()) {
3954 return 0x40; // No notes, use default
3957 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
3958 if (m == _model->notes().begin()) {
3959 // Before the start, use the velocity of the first note
3960 return (*m)->velocity();
3961 } else if (m == _model->notes().end()) {
3962 // Past the end, use the velocity of the last note
3964 return (*m)->velocity();
3967 // Interpolate velocity of surrounding notes
3968 MidiModel::Notes::const_iterator n = m;
3971 const double frac = ((time - (*n)->time()).to_double() /
3972 ((*m)->time() - (*n)->time()).to_double());
3974 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
3977 /** @param p A session framepos.
3978 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3979 * @return p snapped to the grid subdivision underneath it.
3982 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3984 PublicEditor& editor = trackview.editor ();
3986 const Evoral::Beats grid_beats = get_grid_beats(p);
3988 grid_frames = region_beats_to_region_frames (grid_beats);
3990 /* Hack so that we always snap to the note that we are over, instead of snapping
3991 to the next one if we're more than halfway through the one we're over.
3993 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3994 p -= grid_frames / 2;
3997 return snap_frame_to_frame (p);
4000 /** Called when the selection has been cleared in any MidiRegionView.
4001 * @param rv MidiRegionView that the selection was cleared in.
4004 MidiRegionView::selection_cleared (MidiRegionView* rv)
4010 /* Clear our selection in sympathy; but don't signal the fact */
4011 clear_selection (false);
4015 MidiRegionView::note_button_release ()
4017 _note_player.reset();
4021 MidiRegionView::get_channel_mode () const
4023 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4024 return rtav->midi_track()->get_playback_channel_mode();
4028 MidiRegionView::get_selected_channels () const
4030 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4031 return rtav->midi_track()->get_playback_channel_mask();
4036 MidiRegionView::get_grid_beats(framepos_t pos) const
4038 PublicEditor& editor = trackview.editor();
4039 bool success = false;
4040 Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos);
4042 beats = Evoral::Beats(1);