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 if (_optimization_iterator != _events.end()) {
1121 ++_optimization_iterator;
1124 if (_optimization_iterator != _events.end() && (*(*_optimization_iterator)->note()) == note) {
1125 return *_optimization_iterator;
1128 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1129 if (*((*_optimization_iterator)->note()) == note) {
1130 return *_optimization_iterator;
1138 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1140 MidiModel::Notes notes;
1141 _model->get_notes (notes, op, val, chan_mask);
1143 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1144 NoteBase* cne = find_canvas_note (*n);
1152 MidiRegionView::redisplay_model()
1154 if (_active_notes) {
1155 // Currently recording
1156 const framecnt_t zoom = trackview.editor().get_current_zoom();
1157 if (zoom != _last_display_zoom) {
1158 /* Update resolved canvas notes to reflect changes in zoom without
1159 touching model. Leave active notes (with length 0) alone since
1160 they are being extended. */
1161 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1162 if ((*i)->note()->length() > 0) {
1166 _last_display_zoom = zoom;
1175 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1176 (*i)->invalidate ();
1179 MidiModel::ReadLock lock(_model->read_lock());
1181 MidiModel::Notes& notes (_model->notes());
1182 _optimization_iterator = _events.begin();
1184 bool empty_when_starting = _events.empty();
1186 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1188 boost::shared_ptr<NoteType> note (*n);
1192 if (note_in_region_range (note, visible)) {
1194 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1207 cne = add_note (note, visible);
1210 set<boost::shared_ptr<NoteType> >::iterator it;
1211 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1212 if (*(*it) == *note) {
1213 add_to_selection (cne);
1219 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1226 /* remove note items that are no longer valid */
1228 if (!empty_when_starting) {
1229 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1230 if (!(*i)->valid ()) {
1232 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1233 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1235 gr->remove_note (*i);
1240 i = _events.erase (i);
1248 _patch_changes.clear();
1252 display_patch_changes ();
1254 _marked_for_selection.clear ();
1255 _marked_for_velocity.clear ();
1256 _pending_note_selection.clear ();
1258 /* we may have caused _events to contain things out of order (e.g. if a note
1259 moved earlier or later). we don't generally need them in time order, but
1260 make a note that a sort is required for those cases that require it.
1263 _sort_needed = true;
1267 MidiRegionView::display_patch_changes ()
1269 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1270 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1272 for (uint8_t i = 0; i < 16; ++i) {
1273 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1277 /** @param active_channel true to display patch changes fully, false to display
1278 * them `greyed-out' (as on an inactive channel)
1281 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1283 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1285 if ((*i)->channel() != channel) {
1289 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1290 add_canvas_patch_change (*i, patch_name, active_channel);
1295 MidiRegionView::display_sysexes()
1297 bool have_periodic_system_messages = false;
1298 bool display_periodic_messages = true;
1300 if (!ARDOUR_UI::config()->get_never_display_periodic_midi()) {
1302 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1303 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1304 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1307 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1308 have_periodic_system_messages = true;
1314 if (have_periodic_system_messages) {
1315 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1317 /* get an approximate value for the number of samples per video frame */
1319 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1321 /* if we are zoomed out beyond than the cutoff (i.e. more
1322 * frames per pixel than frames per 4 video frames), don't
1323 * show periodic sysex messages.
1326 if (zoom > (video_frame*4)) {
1327 display_periodic_messages = false;
1331 display_periodic_messages = false;
1334 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1336 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1337 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1339 Evoral::Beats time = (*i)->time();
1342 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1343 if (!display_periodic_messages) {
1351 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1352 str << int((*i)->buffer()[b]);
1353 if (b != (*i)->size() -1) {
1357 string text = str.str();
1359 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1361 double height = midi_stream_view()->contents_height();
1363 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1364 // SysEx canvas object!!!
1366 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1367 new SysEx (*this, _note_group, text, height, x, 1.0));
1369 // Show unless message is beyond the region bounds
1370 if (time - _region->start() >= _region->length() || time < _region->start()) {
1376 _sys_exes.push_back(sysex);
1380 MidiRegionView::~MidiRegionView ()
1382 in_destructor = true;
1384 trackview.editor().verbose_cursor()->hide ();
1386 note_delete_connection.disconnect ();
1388 delete _list_editor;
1390 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1392 if (_active_notes) {
1396 _selection_cleared_connection.disconnect ();
1399 clear_events (false);
1402 delete _note_diff_command;
1403 delete _step_edit_cursor;
1404 delete _temporary_note_group;
1408 MidiRegionView::region_resized (const PropertyChange& what_changed)
1410 RegionView::region_resized(what_changed);
1412 if (what_changed.contains (ARDOUR::Properties::position)) {
1413 _region_relative_time_converter.set_origin_b(_region->position());
1414 set_duration(_region->length(), 0);
1415 if (_enable_display) {
1420 if (what_changed.contains (ARDOUR::Properties::start) ||
1421 what_changed.contains (ARDOUR::Properties::position)) {
1422 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1427 MidiRegionView::reset_width_dependent_items (double pixel_width)
1429 RegionView::reset_width_dependent_items(pixel_width);
1431 if (_enable_display) {
1435 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1436 if ((*x)->canvas_item()->width() >= _pixel_width) {
1443 move_step_edit_cursor (_step_edit_cursor_position);
1444 set_step_edit_cursor_width (_step_edit_cursor_width);
1448 MidiRegionView::set_height (double height)
1450 double old_height = _height;
1451 RegionView::set_height(height);
1453 apply_note_range (midi_stream_view()->lowest_note(),
1454 midi_stream_view()->highest_note(),
1455 height != old_height);
1458 name_text->raise_to_top();
1461 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1462 (*x)->set_height (midi_stream_view()->contents_height());
1465 if (_step_edit_cursor) {
1466 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1471 /** Apply the current note range from the stream view
1472 * by repositioning/hiding notes as necessary
1475 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1477 if (!_enable_display) {
1481 if (!force && _current_range_min == min && _current_range_max == max) {
1485 _current_range_min = min;
1486 _current_range_max = max;
1488 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1489 NoteBase* event = *i;
1490 boost::shared_ptr<NoteType> note (event->note());
1492 if (note->note() < _current_range_min ||
1493 note->note() > _current_range_max) {
1499 if (Note* cnote = dynamic_cast<Note*>(event)) {
1501 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1502 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1507 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1514 MidiRegionView::add_ghost (TimeAxisView& tv)
1516 double unit_position = _region->position () / samples_per_pixel;
1517 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1518 MidiGhostRegion* ghost;
1520 if (mtv && mtv->midi_view()) {
1521 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1522 to allow having midi notes on top of note lines and waveforms.
1524 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1526 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1529 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1530 ghost->add_note(*i);
1533 ghost->set_height ();
1534 ghost->set_duration (_region->length() / samples_per_pixel);
1535 ghosts.push_back (ghost);
1537 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1543 /** Begin tracking note state for successive calls to add_event
1546 MidiRegionView::begin_write()
1548 if (_active_notes) {
1549 delete[] _active_notes;
1551 _active_notes = new Note*[128];
1552 for (unsigned i = 0; i < 128; ++i) {
1553 _active_notes[i] = 0;
1558 /** Destroy note state for add_event
1561 MidiRegionView::end_write()
1563 delete[] _active_notes;
1565 _marked_for_selection.clear();
1566 _marked_for_velocity.clear();
1570 /** Resolve an active MIDI note (while recording).
1573 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1575 if (midi_view()->note_mode() != Sustained) {
1579 if (_active_notes && _active_notes[note]) {
1580 /* Set note length so update_note() works. Note this is a local note
1581 for recording, not from a model, so we can safely mess with it. */
1582 _active_notes[note]->note()->set_length(
1583 end_time - _active_notes[note]->note()->time());
1585 /* End time is relative to the region being recorded. */
1586 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1588 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1589 _active_notes[note]->set_outline_all ();
1590 _active_notes[note] = 0;
1595 /** Extend active notes to rightmost edge of region (if length is changed)
1598 MidiRegionView::extend_active_notes()
1600 if (!_active_notes) {
1604 for (unsigned i = 0; i < 128; ++i) {
1605 if (_active_notes[i]) {
1606 _active_notes[i]->set_x1(
1607 trackview.editor().sample_to_pixel(_region->length()));
1613 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1615 if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1619 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1621 if (!route_ui || !route_ui->midi_track()) {
1625 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1629 /* NotePlayer deletes itself */
1633 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1635 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1636 start_playing_midi_chord(notes);
1640 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1642 if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1646 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1648 if (!route_ui || !route_ui->midi_track()) {
1652 _note_player = boost::shared_ptr<NotePlayer>(new NotePlayer(route_ui->midi_track()));
1654 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1655 _note_player->add (*n);
1658 _note_player->on ();
1663 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1665 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1666 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1668 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1669 (note->note() <= midi_stream_view()->highest_note());
1675 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1679 if ((sus = dynamic_cast<Note*>(note))) {
1680 update_sustained(sus, update_ghost_regions);
1681 } else if ((hit = dynamic_cast<Hit*>(note))) {
1682 update_hit(hit, update_ghost_regions);
1686 /** Update a canvas note's size from its model note.
1687 * @param ev Canvas note to update.
1688 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1691 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1693 boost::shared_ptr<NoteType> note = ev->note();
1694 const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1695 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1700 /* trim note display to not overlap the end of its region */
1702 if (note->length() > 0) {
1703 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1704 ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames));
1706 ev->set_x1 (trackview.editor().sample_to_pixel (_region->length()));
1709 ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
1711 if (!note->length()) {
1712 if (_active_notes && note->note() < 128) {
1713 Note* const old_rect = _active_notes[note->note()];
1715 /* There is an active note on this key, so we have a stuck
1716 note. Finish the old rectangle here. */
1717 old_rect->set_x1 (x);
1718 old_rect->set_outline_all ();
1720 _active_notes[note->note()] = ev;
1722 /* outline all but right edge */
1723 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1724 ArdourCanvas::Rectangle::TOP|
1725 ArdourCanvas::Rectangle::LEFT|
1726 ArdourCanvas::Rectangle::BOTTOM));
1728 /* outline all edges */
1729 ev->set_outline_all ();
1732 // Update color in case velocity has changed
1733 ev->set_fill_color(ev->base_color());
1734 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1736 if (update_ghost_regions) {
1737 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1738 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1740 gr->update_note (ev);
1747 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1749 boost::shared_ptr<NoteType> note = ev->note();
1751 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1752 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1753 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1754 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1756 ev->set_position (ArdourCanvas::Duple (x, y));
1757 ev->set_height (diamond_size);
1759 // Update color in case velocity has changed
1760 ev->set_fill_color(ev->base_color());
1761 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1763 if (update_ghost_regions) {
1764 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1765 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1767 gr->update_note (ev);
1773 /** Add a MIDI note to the view (with length).
1775 * If in sustained mode, notes with length 0 will be considered active
1776 * notes, and resolve_note should be called when the corresponding note off
1777 * event arrives, to properly display the note.
1780 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1782 NoteBase* event = 0;
1784 if (midi_view()->note_mode() == Sustained) {
1786 Note* ev_rect = new Note (*this, _note_group, note);
1788 update_sustained (ev_rect);
1792 } else if (midi_view()->note_mode() == Percussive) {
1794 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1796 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1798 update_hit (ev_diamond);
1807 MidiGhostRegion* gr;
1809 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1810 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1811 gr->add_note(event);
1815 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1816 note_selected(event, true);
1819 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1820 event->show_velocity();
1823 event->on_channel_selection_change (get_selected_channels());
1824 _events.push_back(event);
1833 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1834 MidiStreamView* const view = mtv->midi_view();
1836 view->update_note_range (note->note());
1841 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1842 Evoral::Beats pos, Evoral::Beats len)
1844 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1846 /* potentially extend region to hold new note */
1848 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1849 framepos_t region_end = _region->last_frame();
1851 if (end_frame > region_end) {
1852 _region->set_length (end_frame - _region->position());
1855 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1856 MidiStreamView* const view = mtv->midi_view();
1858 view->update_note_range(new_note->note());
1860 _marked_for_selection.clear ();
1863 start_note_diff_command (_("step add"));
1864 note_diff_add_note (new_note, true, false);
1867 // last_step_edit_note = new_note;
1871 MidiRegionView::step_sustain (Evoral::Beats beats)
1873 change_note_lengths (false, false, beats, false, true);
1876 /** Add a new patch change flag to the canvas.
1877 * @param patch the patch change to add
1878 * @param the text to display in the flag
1879 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1882 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1884 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1885 const double x = trackview.editor().sample_to_pixel (region_frames);
1887 double const height = midi_stream_view()->contents_height();
1889 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1890 // so we need to do something more sophisticated to keep its color
1891 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1894 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1895 new PatchChange(*this, group,
1902 if (patch_change->item().width() < _pixel_width) {
1903 // Show unless patch change is beyond the region bounds
1904 if (region_frames < 0 || region_frames >= _region->length()) {
1905 patch_change->hide();
1907 patch_change->show();
1910 patch_change->hide ();
1913 _patch_changes.push_back (patch_change);
1916 MIDI::Name::PatchPrimaryKey
1917 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1919 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1922 /// Return true iff @p pc applies to the given time on the given channel.
1924 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1926 return pc->time() <= time && pc->channel() == channel;
1930 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1932 // The earliest event not before time
1933 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1935 // Go backwards until we find the latest PC for this channel, or the start
1936 while (i != _model->patch_changes().begin() &&
1937 (i == _model->patch_changes().end() ||
1938 !patch_applies(*i, time, channel))) {
1942 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1943 key.set_bank((*i)->bank());
1944 key.set_program((*i)->program ());
1952 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1954 string name = _("alter patch change");
1955 trackview.editor().begin_reversible_command (name);
1956 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1958 if (pc.patch()->program() != new_patch.program()) {
1959 c->change_program (pc.patch (), new_patch.program());
1962 int const new_bank = new_patch.bank();
1963 if (pc.patch()->bank() != new_bank) {
1964 c->change_bank (pc.patch (), new_bank);
1967 _model->apply_command (*trackview.session(), c);
1968 trackview.editor().commit_reversible_command ();
1970 _patch_changes.clear ();
1971 display_patch_changes ();
1975 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
1977 string name = _("alter patch change");
1978 trackview.editor().begin_reversible_command (name);
1979 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1981 if (old_change->time() != new_change.time()) {
1982 c->change_time (old_change, new_change.time());
1985 if (old_change->channel() != new_change.channel()) {
1986 c->change_channel (old_change, new_change.channel());
1989 if (old_change->program() != new_change.program()) {
1990 c->change_program (old_change, new_change.program());
1993 if (old_change->bank() != new_change.bank()) {
1994 c->change_bank (old_change, new_change.bank());
1997 _model->apply_command (*trackview.session(), c);
1998 trackview.editor().commit_reversible_command ();
2000 _patch_changes.clear ();
2001 display_patch_changes ();
2004 /** Add a patch change to the region.
2005 * @param t Time in frames relative to region position
2006 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2007 * MidiTimeAxisView::get_channel_for_add())
2010 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2012 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2013 string name = _("add patch change");
2015 trackview.editor().begin_reversible_command (name);
2016 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2017 c->add (MidiModel::PatchChangePtr (
2018 new Evoral::PatchChange<Evoral::Beats> (
2019 absolute_frames_to_source_beats (_region->position() + t),
2020 mtv->get_channel_for_add(), patch.program(), patch.bank()
2025 _model->apply_command (*trackview.session(), c);
2026 trackview.editor().commit_reversible_command ();
2028 _patch_changes.clear ();
2029 display_patch_changes ();
2033 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2035 trackview.editor().begin_reversible_command (_("move patch change"));
2036 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2037 c->change_time (pc.patch (), t);
2038 _model->apply_command (*trackview.session(), c);
2039 trackview.editor().commit_reversible_command ();
2041 _patch_changes.clear ();
2042 display_patch_changes ();
2046 MidiRegionView::delete_patch_change (PatchChange* pc)
2048 trackview.editor().begin_reversible_command (_("delete patch change"));
2049 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2050 c->remove (pc->patch ());
2051 _model->apply_command (*trackview.session(), c);
2052 trackview.editor().commit_reversible_command ();
2054 _patch_changes.clear ();
2055 display_patch_changes ();
2059 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2061 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2063 key.set_bank(key.bank() + delta);
2065 key.set_program(key.program() + delta);
2067 change_patch_change(patch, key);
2071 MidiRegionView::maybe_remove_deleted_note_from_selection (NoteBase* cne)
2073 if (_selection.empty()) {
2077 _selection.erase (cne);
2081 MidiRegionView::delete_selection()
2083 if (_selection.empty()) {
2087 start_note_diff_command (_("delete selection"));
2089 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2090 if ((*i)->selected()) {
2091 _note_diff_command->remove((*i)->note());
2101 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2103 start_note_diff_command (_("delete note"));
2104 _note_diff_command->remove (n);
2107 trackview.editor().verbose_cursor()->hide ();
2111 MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
2113 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2115 Selection::iterator tmp = i;
2118 (*i)->set_selected (false);
2119 (*i)->hide_velocity ();
2120 _selection.erase (i);
2128 if (!ev && _entered) {
2129 // Clearing selection entirely, ungrab keyboard
2130 Keyboard::magic_widget_drop_focus();
2131 _grabbed_keyboard = false;
2134 /* this does not change the status of this regionview w.r.t the editor
2139 SelectionCleared (this); /* EMIT SIGNAL */
2144 MidiRegionView::unique_select(NoteBase* ev)
2146 const bool selection_was_empty = _selection.empty();
2148 clear_selection_except (ev);
2150 /* don't bother with checking to see if we should remove this
2151 regionview from the editor selection, since we're about to add
2152 another note, and thus put/keep this regionview in the editor
2156 if (!ev->selected()) {
2157 add_to_selection (ev);
2158 if (selection_was_empty && _entered) {
2159 // Grab keyboard for moving notes with arrow keys
2160 Keyboard::magic_widget_grab_focus();
2161 _grabbed_keyboard = true;
2167 MidiRegionView::select_all_notes ()
2171 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2172 add_to_selection (*i);
2177 MidiRegionView::select_range (framepos_t start, framepos_t end)
2181 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2182 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2183 if (t >= start && t <= end) {
2184 add_to_selection (*i);
2190 MidiRegionView::invert_selection ()
2192 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2193 if ((*i)->selected()) {
2194 remove_from_selection(*i);
2196 add_to_selection (*i);
2201 /** Used for selection undo/redo.
2202 The requested notes most likely won't exist in the view until the next model redisplay.
2205 MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
2208 list<boost::shared_ptr<NoteType> >::iterator n;
2210 for (n = notes.begin(); n != notes.end(); ++n) {
2211 if ((cne = find_canvas_note(*(*n))) != 0) {
2212 add_to_selection (cne);
2214 _pending_note_selection.insert(*n);
2220 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2222 bool have_selection = !_selection.empty();
2223 uint8_t low_note = 127;
2224 uint8_t high_note = 0;
2225 MidiModel::Notes& notes (_model->notes());
2226 _optimization_iterator = _events.begin();
2228 if (extend && !have_selection) {
2232 /* scan existing selection to get note range */
2234 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2235 if ((*i)->note()->note() < low_note) {
2236 low_note = (*i)->note()->note();
2238 if ((*i)->note()->note() > high_note) {
2239 high_note = (*i)->note()->note();
2246 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2247 /* only note previously selected is the one we are
2248 * reselecting. treat this as cancelling the selection.
2255 low_note = min (low_note, notenum);
2256 high_note = max (high_note, notenum);
2259 _no_sound_notes = true;
2261 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2263 boost::shared_ptr<NoteType> note (*n);
2265 bool select = false;
2267 if (((1 << note->channel()) & channel_mask) != 0) {
2269 if ((note->note() >= low_note && note->note() <= high_note)) {
2272 } else if (note->note() == notenum) {
2278 if ((cne = find_canvas_note (note)) != 0) {
2279 // extend is false because we've taken care of it,
2280 // since it extends by time range, not pitch.
2281 note_selected (cne, add, false);
2285 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2289 _no_sound_notes = false;
2293 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2295 MidiModel::Notes& notes (_model->notes());
2296 _optimization_iterator = _events.begin();
2298 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2300 boost::shared_ptr<NoteType> note (*n);
2303 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2304 if ((cne = find_canvas_note (note)) != 0) {
2305 if (cne->selected()) {
2306 note_deselected (cne);
2308 note_selected (cne, true, false);
2316 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2319 clear_selection_except (ev);
2320 if (!_selection.empty()) {
2321 PublicEditor& editor (trackview.editor());
2322 editor.get_selection().add (this);
2328 if (!ev->selected()) {
2329 add_to_selection (ev);
2333 /* find end of latest note selected, select all between that and the start of "ev" */
2335 Evoral::Beats earliest = Evoral::MaxBeats;
2336 Evoral::Beats latest = Evoral::Beats();
2338 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2339 if ((*i)->note()->end_time() > latest) {
2340 latest = (*i)->note()->end_time();
2342 if ((*i)->note()->time() < earliest) {
2343 earliest = (*i)->note()->time();
2347 if (ev->note()->end_time() > latest) {
2348 latest = ev->note()->end_time();
2351 if (ev->note()->time() < earliest) {
2352 earliest = ev->note()->time();
2355 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2357 /* find notes entirely within OR spanning the earliest..latest range */
2359 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2360 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2361 add_to_selection (*i);
2369 MidiRegionView::note_deselected(NoteBase* ev)
2371 remove_from_selection (ev);
2375 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2377 PublicEditor& editor = trackview.editor();
2379 // Convert to local coordinates
2380 const framepos_t p = _region->position();
2381 const double y = midi_view()->y_position();
2382 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2383 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2384 const double y0 = max(0.0, gy0 - y);
2385 const double y1 = max(0.0, gy1 - y);
2387 // TODO: Make this faster by storing the last updated selection rect, and only
2388 // adjusting things that are in the area that appears/disappeared.
2389 // We probably need a tree to be able to find events in O(log(n)) time.
2391 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2392 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2393 // Rectangles intersect
2394 if (!(*i)->selected()) {
2395 add_to_selection (*i);
2397 } else if ((*i)->selected() && !extend) {
2398 // Rectangles do not intersect
2399 remove_from_selection (*i);
2403 typedef RouteTimeAxisView::AutomationTracks ATracks;
2404 typedef std::list<Selectable*> Selectables;
2406 /* Add control points to selection. */
2407 const ATracks& atracks = midi_view()->automation_tracks();
2408 Selectables selectables;
2409 editor.get_selection().clear_points();
2410 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2411 a->second->get_selectables(start, end, gy0, gy1, selectables);
2412 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2413 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2415 editor.get_selection().add(cp);
2418 a->second->set_selected_points(editor.get_selection().points);
2423 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2429 // TODO: Make this faster by storing the last updated selection rect, and only
2430 // adjusting things that are in the area that appears/disappeared.
2431 // We probably need a tree to be able to find events in O(log(n)) time.
2433 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2434 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2435 // within y- (note-) range
2436 if (!(*i)->selected()) {
2437 add_to_selection (*i);
2439 } else if ((*i)->selected() && !extend) {
2440 remove_from_selection (*i);
2446 MidiRegionView::remove_from_selection (NoteBase* ev)
2448 Selection::iterator i = _selection.find (ev);
2450 if (i != _selection.end()) {
2451 _selection.erase (i);
2452 if (_selection.empty() && _grabbed_keyboard) {
2454 Keyboard::magic_widget_drop_focus();
2455 _grabbed_keyboard = false;
2459 ev->set_selected (false);
2460 ev->hide_velocity ();
2462 if (_selection.empty()) {
2463 PublicEditor& editor (trackview.editor());
2464 editor.get_selection().remove (this);
2469 MidiRegionView::add_to_selection (NoteBase* ev)
2471 const bool selection_was_empty = _selection.empty();
2473 if (_selection.insert (ev).second) {
2474 ev->set_selected (true);
2475 start_playing_midi_note ((ev)->note());
2476 if (selection_was_empty && _entered) {
2477 // Grab keyboard for moving notes with arrow keys
2478 Keyboard::magic_widget_grab_focus();
2479 _grabbed_keyboard = true;
2483 if (selection_was_empty) {
2484 PublicEditor& editor (trackview.editor());
2485 editor.get_selection().add (this);
2490 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2492 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2493 PossibleChord to_play;
2494 Evoral::Beats earliest = Evoral::MaxBeats;
2496 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2497 if ((*i)->note()->time() < earliest) {
2498 earliest = (*i)->note()->time();
2502 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2503 if ((*i)->note()->time() == earliest) {
2504 to_play.push_back ((*i)->note());
2506 (*i)->move_event(dx, dy);
2509 if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) {
2511 if (to_play.size() > 1) {
2513 PossibleChord shifted;
2515 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2516 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2517 moved_note->set_note (moved_note->note() + cumulative_dy);
2518 shifted.push_back (moved_note);
2521 start_playing_midi_chord (shifted);
2523 } else if (!to_play.empty()) {
2525 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2526 moved_note->set_note (moved_note->note() + cumulative_dy);
2527 start_playing_midi_note (moved_note);
2533 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2535 uint8_t lowest_note_in_selection = 127;
2536 uint8_t highest_note_in_selection = 0;
2537 uint8_t highest_note_difference = 0;
2539 // find highest and lowest notes first
2541 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2542 uint8_t pitch = (*i)->note()->note();
2543 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2544 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2548 cerr << "dnote: " << (int) dnote << endl;
2549 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2550 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2551 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2552 << int(highest_note_in_selection) << endl;
2553 cerr << "selection size: " << _selection.size() << endl;
2554 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2557 // Make sure the note pitch does not exceed the MIDI standard range
2558 if (highest_note_in_selection + dnote > 127) {
2559 highest_note_difference = highest_note_in_selection - 127;
2562 start_note_diff_command (_("move notes"));
2564 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2566 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2567 Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
2573 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2575 uint8_t original_pitch = (*i)->note()->note();
2576 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2578 // keep notes in standard midi range
2579 clamp_to_0_127(new_pitch);
2581 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2582 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2584 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2589 // care about notes being moved beyond the upper/lower bounds on the canvas
2590 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2591 highest_note_in_selection > midi_stream_view()->highest_note()) {
2592 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2596 /** @param x Pixel relative to the region position.
2597 * @return Snapped frame relative to the region position.
2600 MidiRegionView::snap_pixel_to_sample(double x)
2602 PublicEditor& editor (trackview.editor());
2603 return snap_frame_to_frame (editor.pixel_to_sample (x));
2606 /** @param x Pixel relative to the region position.
2607 * @return Snapped pixel relative to the region position.
2610 MidiRegionView::snap_to_pixel(double x)
2612 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x));
2616 MidiRegionView::get_position_pixels()
2618 framepos_t region_frame = get_position();
2619 return trackview.editor().sample_to_pixel(region_frame);
2623 MidiRegionView::get_end_position_pixels()
2625 framepos_t frame = get_position() + get_duration ();
2626 return trackview.editor().sample_to_pixel(frame);
2630 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2632 /* the time converter will return the frame corresponding to `beats'
2633 relative to the start of the source. The start of the source
2634 is an implied position given by region->position - region->start
2636 const framepos_t source_start = _region->position() - _region->start();
2637 return source_start + _source_relative_time_converter.to (beats);
2641 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2643 /* the `frames' argument needs to be converted into a frame count
2644 relative to the start of the source before being passed in to the
2647 const framepos_t source_start = _region->position() - _region->start();
2648 return _source_relative_time_converter.from (frames - source_start);
2652 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2654 return _region_relative_time_converter.to(beats);
2658 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2660 return _region_relative_time_converter.from(frames);
2664 MidiRegionView::begin_resizing (bool /*at_front*/)
2666 _resize_data.clear();
2668 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2669 Note *note = dynamic_cast<Note*> (*i);
2671 // only insert CanvasNotes into the map
2673 NoteResizeData *resize_data = new NoteResizeData();
2674 resize_data->note = note;
2676 // create a new SimpleRect from the note which will be the resize preview
2677 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2678 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2680 // calculate the colors: get the color settings
2681 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2682 ARDOUR_UI::config()->color ("midi note selected"),
2685 // make the resize preview notes more transparent and bright
2686 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2688 // calculate color based on note velocity
2689 resize_rect->set_fill_color (UINT_INTERPOLATE(
2690 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2694 resize_rect->set_outline_color (NoteBase::calculate_outline (
2695 ARDOUR_UI::config()->color ("midi note selected")));
2697 resize_data->resize_rect = resize_rect;
2698 _resize_data.push_back(resize_data);
2703 /** Update resizing notes while user drags.
2704 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2705 * @param at_front which end of the note (true == note on, false == note off)
2706 * @param delta_x change in mouse position since the start of the drag
2707 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2708 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2709 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2710 * as the \a primary note.
2713 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2715 bool cursor_set = false;
2717 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2718 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2719 Note* canvas_note = (*i)->note;
2724 current_x = canvas_note->x0() + delta_x;
2726 current_x = primary->x0() + delta_x;
2730 current_x = canvas_note->x1() + delta_x;
2732 current_x = primary->x1() + delta_x;
2736 if (current_x < 0) {
2737 // This works even with snapping because RegionView::snap_frame_to_frame()
2738 // snaps forward if the snapped sample is before the beginning of the region
2741 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2742 current_x = trackview.editor().sample_to_pixel(_region->length());
2746 resize_rect->set_x0 (snap_to_pixel(current_x));
2747 resize_rect->set_x1 (canvas_note->x1());
2749 resize_rect->set_x1 (snap_to_pixel(current_x));
2750 resize_rect->set_x0 (canvas_note->x0());
2754 const double snapped_x = snap_pixel_to_sample (current_x);
2755 Evoral::Beats beats = region_frames_to_region_beats (snapped_x);
2756 Evoral::Beats len = Evoral::Beats();
2759 if (beats < canvas_note->note()->end_time()) {
2760 len = canvas_note->note()->time() - beats;
2761 len += canvas_note->note()->length();
2764 if (beats >= canvas_note->note()->time()) {
2765 len = beats - canvas_note->note()->time();
2769 len = std::max(Evoral::Beats(1 / 512.0), len);
2772 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2773 show_verbose_cursor (buf, 0, 0);
2782 /** Finish resizing notes when the user releases the mouse button.
2783 * Parameters the same as for \a update_resizing().
2786 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2788 start_note_diff_command (_("resize notes"));
2790 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2791 Note* canvas_note = (*i)->note;
2792 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2794 /* Get the new x position for this resize, which is in pixels relative
2795 * to the region position.
2802 current_x = canvas_note->x0() + delta_x;
2804 current_x = primary->x0() + delta_x;
2808 current_x = canvas_note->x1() + delta_x;
2810 current_x = primary->x1() + delta_x;
2814 if (current_x < 0) {
2817 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2818 current_x = trackview.editor().sample_to_pixel(_region->length());
2821 /* Convert that to a frame within the source */
2822 current_x = snap_pixel_to_sample (current_x) + _region->start ();
2824 /* and then to beats */
2825 const Evoral::Beats x_beats = region_frames_to_region_beats (current_x);
2827 if (at_front && x_beats < canvas_note->note()->end_time()) {
2828 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats);
2830 Evoral::Beats len = canvas_note->note()->time() - x_beats;
2831 len += canvas_note->note()->length();
2834 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2839 const Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2840 x_beats - canvas_note->note()->time());
2841 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2848 _resize_data.clear();
2853 MidiRegionView::abort_resizing ()
2855 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2856 delete (*i)->resize_rect;
2860 _resize_data.clear ();
2864 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2866 uint8_t new_velocity;
2869 new_velocity = event->note()->velocity() + velocity;
2870 clamp_to_0_127(new_velocity);
2872 new_velocity = velocity;
2875 event->set_selected (event->selected()); // change color
2877 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2881 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2886 new_note = event->note()->note() + note;
2891 clamp_to_0_127 (new_note);
2892 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2896 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2898 bool change_start = false;
2899 bool change_length = false;
2900 Evoral::Beats new_start;
2901 Evoral::Beats new_length;
2903 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2905 front_delta: if positive - move the start of the note later in time (shortening it)
2906 if negative - move the start of the note earlier in time (lengthening it)
2908 end_delta: if positive - move the end of the note later in time (lengthening it)
2909 if negative - move the end of the note earlier in time (shortening it)
2912 if (!!front_delta) {
2913 if (front_delta < 0) {
2915 if (event->note()->time() < -front_delta) {
2916 new_start = Evoral::Beats();
2918 new_start = event->note()->time() + front_delta; // moves earlier
2921 /* start moved toward zero, so move the end point out to where it used to be.
2922 Note that front_delta is negative, so this increases the length.
2925 new_length = event->note()->length() - front_delta;
2926 change_start = true;
2927 change_length = true;
2931 Evoral::Beats new_pos = event->note()->time() + front_delta;
2933 if (new_pos < event->note()->end_time()) {
2934 new_start = event->note()->time() + front_delta;
2935 /* start moved toward the end, so move the end point back to where it used to be */
2936 new_length = event->note()->length() - front_delta;
2937 change_start = true;
2938 change_length = true;
2945 bool can_change = true;
2946 if (end_delta < 0) {
2947 if (event->note()->length() < -end_delta) {
2953 new_length = event->note()->length() + end_delta;
2954 change_length = true;
2959 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2962 if (change_length) {
2963 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2968 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
2970 uint8_t new_channel;
2974 if (event->note()->channel() < -chn) {
2977 new_channel = event->note()->channel() + chn;
2980 new_channel = event->note()->channel() + chn;
2983 new_channel = (uint8_t) chn;
2986 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2990 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
2992 Evoral::Beats new_time;
2996 if (event->note()->time() < -delta) {
2997 new_time = Evoral::Beats();
2999 new_time = event->note()->time() + delta;
3002 new_time = event->note()->time() + delta;
3008 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3012 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3014 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3018 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3023 if (_selection.empty()) {
3038 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3039 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3045 start_note_diff_command (_("change velocities"));
3047 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3048 Selection::iterator next = i;
3052 if (i == _selection.begin()) {
3053 change_note_velocity (*i, delta, true);
3054 value = (*i)->note()->velocity() + delta;
3056 change_note_velocity (*i, value, false);
3060 change_note_velocity (*i, delta, true);
3069 if (!_selection.empty()) {
3071 snprintf (buf, sizeof (buf), "Vel %d",
3072 (int) (*_selection.begin())->note()->velocity());
3073 show_verbose_cursor (buf, 10, 10);
3079 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3081 if (_selection.empty()) {
3098 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3100 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3104 if ((int8_t) (*i)->note()->note() + delta > 127) {
3111 start_note_diff_command (_("transpose"));
3113 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3114 Selection::iterator next = i;
3116 change_note_note (*i, delta, true);
3124 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3128 delta = Evoral::Beats(1.0/128.0);
3130 /* grab the current grid distance */
3131 delta = get_grid_beats(_region->position());
3139 start_note_diff_command (_("change note lengths"));
3141 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3142 Selection::iterator next = i;
3145 /* note the negation of the delta for start */
3148 (start ? -delta : Evoral::Beats()),
3149 (end ? delta : Evoral::Beats()));
3158 MidiRegionView::nudge_notes (bool forward, bool fine)
3160 if (_selection.empty()) {
3164 /* pick a note as the point along the timeline to get the nudge distance.
3165 its not necessarily the earliest note, so we may want to pull the notes out
3166 into a vector and sort before using the first one.
3169 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3170 Evoral::Beats delta;
3174 /* non-fine, move by 1 bar regardless of snap */
3175 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
3177 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3179 /* grid is off - use nudge distance */
3182 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3183 delta = region_frames_to_region_beats (fabs ((double)distance));
3189 framepos_t next_pos = ref_point;
3192 if (max_framepos - 1 < next_pos) {
3196 if (next_pos == 0) {
3202 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3203 const framecnt_t distance = ref_point - next_pos;
3204 delta = region_frames_to_region_beats (fabs ((double)distance));
3215 start_note_diff_command (_("nudge"));
3217 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3218 Selection::iterator next = i;
3220 change_note_time (*i, delta, true);
3228 MidiRegionView::change_channel(uint8_t channel)
3230 start_note_diff_command(_("change channel"));
3231 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3232 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3240 MidiRegionView::note_entered(NoteBase* ev)
3242 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3244 if (_mouse_state == SelectTouchDragging) {
3245 note_selected (ev, true);
3246 } else if (editor->current_mouse_mode() == MouseContent) {
3247 show_verbose_cursor (ev->note ());
3248 } else if (editor->current_mouse_mode() == MouseDraw) {
3249 show_verbose_cursor (ev->note ());
3254 MidiRegionView::note_left (NoteBase*)
3256 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3258 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3259 (*i)->hide_velocity ();
3262 editor->verbose_cursor()->hide ();
3266 MidiRegionView::patch_entered (PatchChange* p)
3269 /* XXX should get patch name if we can */
3270 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3271 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3272 << _("Channel ") << ((int) p->patch()->channel() + 1);
3273 show_verbose_cursor (s.str(), 10, 20);
3274 p->item().grab_focus();
3278 MidiRegionView::patch_left (PatchChange *)
3280 trackview.editor().verbose_cursor()->hide ();
3281 /* focus will transfer back via the enter-notify event sent to this
3287 MidiRegionView::sysex_entered (SysEx* p)
3291 // need a way to extract text from p->_flag->_text
3293 // show_verbose_cursor (s.str(), 10, 20);
3294 p->item().grab_focus();
3298 MidiRegionView::sysex_left (SysEx *)
3300 trackview.editor().verbose_cursor()->hide ();
3301 /* focus will transfer back via the enter-notify event sent to this
3307 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3309 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3310 Editing::MouseMode mm = editor->current_mouse_mode();
3311 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3313 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3314 if (can_set_cursor && ctx) {
3315 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3316 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3317 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3318 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3320 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3326 MidiRegionView::get_fill_color() const
3328 const std::string mod_name = (_dragging ? "dragging region" :
3329 trackview.editor().internal_editing() ? "editable region" :
3332 return ARDOUR_UI::config()->color_mod ("selected region base", mod_name);
3333 } else if ((!ARDOUR_UI::config()->get_show_name_highlight() || high_enough_for_name) &&
3334 !ARDOUR_UI::config()->get_color_regions_using_track_color()) {
3335 return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name);
3337 return ARDOUR_UI::config()->color_mod (fill_color, mod_name);
3341 MidiRegionView::midi_channel_mode_changed ()
3343 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3344 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3345 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3347 if (mode == ForceChannel) {
3348 mask = 0xFFFF; // Show all notes as active (below)
3351 // Update notes for selection
3352 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3353 (*i)->on_channel_selection_change (mask);
3356 _patch_changes.clear ();
3357 display_patch_changes ();
3361 MidiRegionView::instrument_settings_changed ()
3367 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3369 if (_selection.empty()) {
3373 PublicEditor& editor (trackview.editor());
3377 /* XXX what to do ? */
3381 editor.get_cut_buffer().add (selection_as_cut_buffer());
3389 start_note_diff_command();
3391 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3398 note_diff_remove_note (*i);
3408 MidiRegionView::selection_as_cut_buffer () const
3412 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3413 NoteType* n = (*i)->note().get();
3414 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3417 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3423 /** This method handles undo */
3425 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
3427 // Paste notes, if available
3428 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3429 if (m != selection.midi_notes.end()) {
3430 ctx.counts.increase_n_notes();
3431 paste_internal(pos, ctx.count, ctx.times, **m);
3434 // Paste control points to automation children, if available
3435 typedef RouteTimeAxisView::AutomationTracks ATracks;
3436 const ATracks& atracks = midi_view()->automation_tracks();
3437 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3438 a->second->paste(pos, selection, ctx);
3444 /** This method handles undo */
3446 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3452 start_note_diff_command (_("paste"));
3454 const Evoral::Beats snap_beats = get_grid_beats(pos);
3455 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3456 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3457 const Evoral::Beats duration = last_time - first_time;
3458 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3459 const Evoral::Beats paste_offset = snap_duration * paste_count;
3460 const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3461 Evoral::Beats end_point = Evoral::Beats();
3463 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3466 duration, pos, _region->position(),
3471 for (int n = 0; n < (int) times; ++n) {
3473 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3475 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3476 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3478 /* make all newly added notes selected */
3480 note_diff_add_note (copied_note, true);
3481 end_point = copied_note->end_time();
3485 /* if we pasted past the current end of the region, extend the region */
3487 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3488 framepos_t region_end = _region->position() + _region->length() - 1;
3490 if (end_frame > region_end) {
3492 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3494 _region->clear_changes ();
3495 _region->set_length (end_frame - _region->position());
3496 trackview.session()->add_command (new StatefulDiffCommand (_region));
3502 struct EventNoteTimeEarlyFirstComparator {
3503 bool operator() (NoteBase* a, NoteBase* b) {
3504 return a->note()->time() < b->note()->time();
3509 MidiRegionView::time_sort_events ()
3511 if (!_sort_needed) {
3515 EventNoteTimeEarlyFirstComparator cmp;
3518 _sort_needed = false;
3522 MidiRegionView::goto_next_note (bool add_to_selection)
3524 bool use_next = false;
3526 if (_events.back()->selected()) {
3530 time_sort_events ();
3532 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3533 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3535 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3536 if ((*i)->selected()) {
3539 } else if (use_next) {
3540 if (channel_mask & (1 << (*i)->note()->channel())) {
3541 if (!add_to_selection) {
3544 note_selected (*i, true, false);
3551 /* use the first one */
3553 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3554 unique_select (_events.front());
3559 MidiRegionView::goto_previous_note (bool add_to_selection)
3561 bool use_next = false;
3563 if (_events.front()->selected()) {
3567 time_sort_events ();
3569 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3570 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3572 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3573 if ((*i)->selected()) {
3576 } else if (use_next) {
3577 if (channel_mask & (1 << (*i)->note()->channel())) {
3578 if (!add_to_selection) {
3581 note_selected (*i, true, false);
3588 /* use the last one */
3590 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3591 unique_select (*(_events.rbegin()));
3596 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3598 bool had_selected = false;
3600 time_sort_events ();
3602 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3603 if ((*i)->selected()) {
3604 selected.insert ((*i)->note());
3605 had_selected = true;
3609 if (allow_all_if_none_selected && !had_selected) {
3610 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3611 selected.insert ((*i)->note());
3617 MidiRegionView::update_ghost_note (double x, double y)
3619 x = std::max(0.0, x);
3621 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3626 _note_group->canvas_to_item (x, y);
3628 PublicEditor& editor = trackview.editor ();
3630 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3631 framecnt_t grid_frames;
3632 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3634 /* calculate time in beats relative to start of source */
3635 const Evoral::Beats length = get_grid_beats(unsnapped_frame);
3636 const Evoral::Beats time = std::max(
3638 absolute_frames_to_source_beats (f + _region->position ()));
3640 _ghost_note->note()->set_time (time);
3641 _ghost_note->note()->set_length (length);
3642 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3643 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3644 _ghost_note->note()->set_velocity (get_velocity_for_add (time));
3646 /* the ghost note does not appear in ghost regions, so pass false in here */
3647 update_note (_ghost_note, false);
3649 show_verbose_cursor (_ghost_note->note ());
3653 MidiRegionView::create_ghost_note (double x, double y)
3655 remove_ghost_note ();
3657 boost::shared_ptr<NoteType> g (new NoteType);
3658 if (midi_view()->note_mode() == Sustained) {
3659 _ghost_note = new Note (*this, _note_group, g);
3661 _ghost_note = new Hit (*this, _note_group, 10, g);
3663 _ghost_note->set_ignore_events (true);
3664 _ghost_note->set_outline_color (0x000000aa);
3665 update_ghost_note (x, y);
3666 _ghost_note->show ();
3668 show_verbose_cursor (_ghost_note->note ());
3672 MidiRegionView::remove_ghost_note ()
3679 MidiRegionView::snap_changed ()
3685 create_ghost_note (_last_ghost_x, _last_ghost_y);
3689 MidiRegionView::drop_down_keys ()
3691 _mouse_state = None;
3695 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3697 /* XXX: This is dead code. What was it for? */
3699 double note = midi_stream_view()->y_to_note(y);
3701 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3703 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3705 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3706 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3707 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3708 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3713 bool add_mrv_selection = false;
3715 if (_selection.empty()) {
3716 add_mrv_selection = true;
3719 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3720 if (_selection.insert (*i).second) {
3721 (*i)->set_selected (true);
3725 if (add_mrv_selection) {
3726 PublicEditor& editor (trackview.editor());
3727 editor.get_selection().add (this);
3732 MidiRegionView::color_handler ()
3734 RegionView::color_handler ();
3736 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3737 (*i)->set_selected ((*i)->selected()); // will change color
3740 /* XXX probably more to do here */
3744 MidiRegionView::enable_display (bool yn)
3746 RegionView::enable_display (yn);
3753 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3755 if (_step_edit_cursor == 0) {
3756 ArdourCanvas::Item* const group = get_canvas_group();
3758 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3759 _step_edit_cursor->set_y0 (0);
3760 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3761 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3762 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3765 move_step_edit_cursor (pos);
3766 _step_edit_cursor->show ();
3770 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3772 _step_edit_cursor_position = pos;
3774 if (_step_edit_cursor) {
3775 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3776 _step_edit_cursor->set_x0 (pixel);
3777 set_step_edit_cursor_width (_step_edit_cursor_width);
3782 MidiRegionView::hide_step_edit_cursor ()
3784 if (_step_edit_cursor) {
3785 _step_edit_cursor->hide ();
3790 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3792 _step_edit_cursor_width = beats;
3794 if (_step_edit_cursor) {
3795 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
3799 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3800 * @param w Source that the data will end up in.
3803 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3805 if (!_active_notes) {
3806 /* we aren't actively being recorded to */
3810 boost::shared_ptr<MidiSource> src = w.lock ();
3811 if (!src || src != midi_region()->midi_source()) {
3812 /* recorded data was not destined for our source */
3816 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3818 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3820 framepos_t back = max_framepos;
3822 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3823 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3825 if (ev.is_channel_event()) {
3826 if (get_channel_mode() == FilterChannels) {
3827 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3833 /* convert from session frames to source beats */
3834 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3835 ev.time() - src->timeline_position() + _region->start());
3837 if (ev.type() == MIDI_CMD_NOTE_ON) {
3838 boost::shared_ptr<NoteType> note (
3839 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3841 add_note (note, true);
3843 /* fix up our note range */
3844 if (ev.note() < _current_range_min) {
3845 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3846 } else if (ev.note() > _current_range_max) {
3847 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3850 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3851 resolve_note (ev.note (), time_beats);
3857 midi_stream_view()->check_record_layers (region(), back);
3861 MidiRegionView::trim_front_starting ()
3863 /* Reparent the note group to the region view's parent, so that it doesn't change
3864 when the region view is trimmed.
3866 _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3867 _temporary_note_group->move (group->position ());
3868 _note_group->reparent (_temporary_note_group);
3872 MidiRegionView::trim_front_ending ()
3874 _note_group->reparent (group);
3875 delete _temporary_note_group;
3876 _temporary_note_group = 0;
3878 if (_region->start() < 0) {
3879 /* Trim drag made start time -ve; fix this */
3880 midi_region()->fix_negative_start ();
3885 MidiRegionView::edit_patch_change (PatchChange* pc)
3887 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3889 int response = d.run();
3892 case Gtk::RESPONSE_ACCEPT:
3894 case Gtk::RESPONSE_REJECT:
3895 delete_patch_change (pc);
3901 change_patch_change (pc->patch(), d.patch ());
3905 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
3908 // sysyex object doesn't have a pointer to a sysex event
3909 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3910 // c->remove (sysex->sysex());
3911 // _model->apply_command (*trackview.session(), c);
3913 //_sys_exes.clear ();
3914 // display_sysexes();
3918 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3920 using namespace MIDI::Name;
3924 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3926 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
3928 MIDI::Name::PatchPrimaryKey patch_key;
3929 get_patch_key_at(n->time(), n->channel(), patch_key);
3930 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
3933 patch_key.program(),
3939 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
3941 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
3942 (int) n->channel() + 1,
3943 (int) n->velocity());
3945 show_verbose_cursor(buf, 10, 20);
3949 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3951 trackview.editor().verbose_cursor()->set (text);
3952 trackview.editor().verbose_cursor()->show ();
3953 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
3957 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
3959 if (_model->notes().empty()) {
3960 return 0x40; // No notes, use default
3963 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
3964 if (m == _model->notes().begin()) {
3965 // Before the start, use the velocity of the first note
3966 return (*m)->velocity();
3967 } else if (m == _model->notes().end()) {
3968 // Past the end, use the velocity of the last note
3970 return (*m)->velocity();
3973 // Interpolate velocity of surrounding notes
3974 MidiModel::Notes::const_iterator n = m;
3977 const double frac = ((time - (*n)->time()).to_double() /
3978 ((*m)->time() - (*n)->time()).to_double());
3980 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
3983 /** @param p A session framepos.
3984 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3985 * @return p snapped to the grid subdivision underneath it.
3988 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3990 PublicEditor& editor = trackview.editor ();
3992 const Evoral::Beats grid_beats = get_grid_beats(p);
3994 grid_frames = region_beats_to_region_frames (grid_beats);
3996 /* Hack so that we always snap to the note that we are over, instead of snapping
3997 to the next one if we're more than halfway through the one we're over.
3999 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
4000 p -= grid_frames / 2;
4003 return snap_frame_to_frame (p);
4006 /** Called when the selection has been cleared in any MidiRegionView.
4007 * @param rv MidiRegionView that the selection was cleared in.
4010 MidiRegionView::selection_cleared (MidiRegionView* rv)
4016 /* Clear our selection in sympathy; but don't signal the fact */
4017 clear_selection (false);
4021 MidiRegionView::note_button_release ()
4023 _note_player.reset();
4027 MidiRegionView::get_channel_mode () const
4029 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4030 return rtav->midi_track()->get_playback_channel_mode();
4034 MidiRegionView::get_selected_channels () const
4036 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4037 return rtav->midi_track()->get_playback_channel_mask();
4042 MidiRegionView::get_grid_beats(framepos_t pos) const
4044 PublicEditor& editor = trackview.editor();
4045 bool success = false;
4046 Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos);
4048 beats = Evoral::Beats(1);