2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "midi++/midnam_patch.h"
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/Event.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
81 #include "patch_change.h"
83 #include "ui_config.h"
87 using namespace ARDOUR;
89 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
95 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
96 RouteTimeAxisView& tv,
97 boost::shared_ptr<MidiRegion> r,
100 : RegionView (parent, tv, r, spu, basic_color)
101 , _current_range_min(0)
102 , _current_range_max(0)
103 , _region_relative_time_converter(r->session().tempo_map(), r->position())
104 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
105 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
107 , _note_group (new ArdourCanvas::Container (group))
108 , _note_diff_command (0)
110 , _step_edit_cursor (0)
111 , _step_edit_cursor_width (1.0)
112 , _step_edit_cursor_position (0.0)
113 , _channel_selection_scoped_note (0)
116 , _sort_needed (true)
117 , _optimization_iterator (_events.end())
119 , _no_sound_notes (false)
120 , _last_display_zoom (0)
123 , _grabbed_keyboard (false)
126 , _mouse_changed_selection (false)
128 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
130 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
131 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
133 _note_group->raise_to_top();
134 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
136 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
137 connect_to_diskstream ();
140 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
141 RouteTimeAxisView& tv,
142 boost::shared_ptr<MidiRegion> r,
144 uint32_t basic_color,
146 TimeAxisViewItem::Visibility visibility)
147 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
148 , _current_range_min(0)
149 , _current_range_max(0)
150 , _region_relative_time_converter(r->session().tempo_map(), r->position())
151 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
152 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
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)
163 , _sort_needed (true)
164 , _optimization_iterator (_events.end())
166 , _no_sound_notes (false)
167 , _last_display_zoom (0)
170 , _grabbed_keyboard (false)
173 , _mouse_changed_selection (false)
175 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
177 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
178 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
180 _note_group->raise_to_top();
182 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
184 connect_to_diskstream ();
188 MidiRegionView::parameter_changed (std::string const & p)
190 if (p == "display-first-midi-bank-as-zero") {
191 if (_enable_display) {
197 MidiRegionView::MidiRegionView (const MidiRegionView& other)
198 : sigc::trackable(other)
200 , _current_range_min(0)
201 , _current_range_max(0)
202 , _region_relative_time_converter(other.region_relative_time_converter())
203 , _source_relative_time_converter(other.source_relative_time_converter())
204 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
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)
215 , _sort_needed (true)
216 , _optimization_iterator (_events.end())
218 , _no_sound_notes (false)
219 , _last_display_zoom (0)
222 , _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())
236 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
238 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
239 , _note_diff_command (0)
241 , _step_edit_cursor (0)
242 , _step_edit_cursor_width (1.0)
243 , _step_edit_cursor_position (0.0)
244 , _channel_selection_scoped_note (0)
247 , _sort_needed (true)
248 , _optimization_iterator (_events.end())
250 , _no_sound_notes (false)
251 , _last_display_zoom (0)
254 , _grabbed_keyboard (false)
257 , _mouse_changed_selection (false)
263 MidiRegionView::init (bool wfd)
265 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
268 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
269 midi_region()->midi_source(0)->load_model(lm);
272 _model = midi_region()->midi_source(0)->model();
273 _enable_display = false;
274 fill_color_name = "midi frame base";
276 RegionView::init (false);
278 //set_height (trackview.current_height());
281 region_sync_changed ();
282 region_resized (ARDOUR::bounds_change);
287 _enable_display = true;
290 display_model (_model);
294 reset_width_dependent_items (_pixel_width);
296 group->raise_to_top();
298 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
299 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
302 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
303 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
305 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
306 boost::bind (&MidiRegionView::snap_changed, this),
309 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
310 boost::bind (&MidiRegionView::mouse_mode_changed, this),
313 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
314 connect_to_diskstream ();
318 MidiRegionView::instrument_info () const
320 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
321 return route_ui->route()->instrument_info();
324 const boost::shared_ptr<ARDOUR::MidiRegion>
325 MidiRegionView::midi_region() const
327 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
331 MidiRegionView::connect_to_diskstream ()
333 midi_view()->midi_track()->DataRecorded.connect(
334 *this, invalidator(*this),
335 boost::bind (&MidiRegionView::data_recorded, this, _1),
340 MidiRegionView::canvas_group_event(GdkEvent* ev)
342 if (in_destructor || _recregion) {
346 if (!trackview.editor().internal_editing()) {
347 // not in internal edit mode, so just act like a normal region
348 return RegionView::canvas_group_event (ev);
354 case GDK_ENTER_NOTIFY:
355 _last_event_x = ev->crossing.x;
356 _last_event_y = ev->crossing.y;
357 enter_notify(&ev->crossing);
358 // set entered_regionview (among other things)
359 return RegionView::canvas_group_event (ev);
361 case GDK_LEAVE_NOTIFY:
362 _last_event_x = ev->crossing.x;
363 _last_event_y = ev->crossing.y;
364 leave_notify(&ev->crossing);
365 // reset entered_regionview (among other things)
366 return RegionView::canvas_group_event (ev);
369 if (scroll (&ev->scroll)) {
375 return key_press (&ev->key);
377 case GDK_KEY_RELEASE:
378 return key_release (&ev->key);
380 case GDK_BUTTON_PRESS:
381 return button_press (&ev->button);
383 case GDK_BUTTON_RELEASE:
384 r = button_release (&ev->button);
387 case GDK_MOTION_NOTIFY:
388 _last_event_x = ev->motion.x;
389 _last_event_y = ev->motion.y;
390 return motion (&ev->motion);
396 return RegionView::canvas_group_event (ev);
400 MidiRegionView::enter_notify (GdkEventCrossing* ev)
402 enter_internal (ev->state);
409 MidiRegionView::leave_notify (GdkEventCrossing*)
418 MidiRegionView::mouse_mode_changed ()
420 // Adjust frame colour (become more transparent for internal tools)
424 if (!trackview.editor().internal_editing()) {
425 /* Switched out of internal editing mode while entered.
426 Only necessary for leave as a mouse_mode_change over a region
427 automatically triggers an enter event. */
430 else if (trackview.editor().current_mouse_mode() == MouseContent) {
431 // hide cursor and ghost note after changing to internal edit mode
432 remove_ghost_note ();
434 /* XXX This is problematic as the function is executed for every region
435 and only for one region _entered_note can be true. Still it's
436 necessary as to hide the verbose cursor when we're changing from
437 draw mode to internal edit mode. These lines are the reason why
438 in some situations no verbose cursor is shown when we enter internal
439 edit mode over a note. */
440 if (!_entered_note) {
441 hide_verbose_cursor ();
448 MidiRegionView::enter_internal (uint32_t state)
450 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
451 // Show ghost note under pencil
452 create_ghost_note(_last_event_x, _last_event_y, state);
455 if (!_selection.empty()) {
456 // Grab keyboard for moving selected notes with arrow keys
457 Keyboard::magic_widget_grab_focus();
458 _grabbed_keyboard = true;
461 // Lower frame handles below notes so they don't steal events
462 if (frame_handle_start) {
463 frame_handle_start->lower_to_bottom();
465 if (frame_handle_end) {
466 frame_handle_end->lower_to_bottom();
471 MidiRegionView::leave_internal()
473 hide_verbose_cursor ();
474 remove_ghost_note ();
477 if (_grabbed_keyboard) {
478 Keyboard::magic_widget_drop_focus();
479 _grabbed_keyboard = false;
482 // Raise frame handles above notes so they catch events
483 if (frame_handle_start) {
484 frame_handle_start->raise_to_top();
486 if (frame_handle_end) {
487 frame_handle_end->raise_to_top();
492 MidiRegionView::button_press (GdkEventButton* ev)
494 if (ev->button != 1) {
498 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
499 MouseMode m = editor->current_mouse_mode();
501 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
502 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
505 if (_mouse_state != SelectTouchDragging) {
507 _pressed_button = ev->button;
509 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
511 if (midi_view()->note_mode() == Percussive) {
512 editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
514 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
517 _mouse_state = AddDragging;
518 remove_ghost_note ();
519 hide_verbose_cursor ();
521 _mouse_state = Pressed;
527 _pressed_button = ev->button;
528 _mouse_changed_selection = false;
534 MidiRegionView::button_release (GdkEventButton* ev)
536 double event_x, event_y;
538 if (ev->button != 1) {
545 group->canvas_to_item (event_x, event_y);
548 PublicEditor& editor = trackview.editor ();
550 _press_cursor_ctx.reset();
552 switch (_mouse_state) {
553 case Pressed: // Clicked
555 switch (editor.current_mouse_mode()) {
557 /* no motion occurred - simple click */
558 clear_editor_note_selection ();
559 _mouse_changed_selection = true;
565 _mouse_changed_selection = true;
566 clear_editor_note_selection ();
581 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
582 we don't want one when we were drag-selecting either. */
583 case SelectRectDragging:
584 editor.drags()->end_grab ((GdkEvent *) ev);
593 if (_mouse_changed_selection) {
594 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
595 trackview.editor().commit_reversible_selection_op ();
602 MidiRegionView::motion (GdkEventMotion* ev)
604 PublicEditor& editor = trackview.editor ();
606 if (!_entered_note) {
608 if (_mouse_state == AddDragging) {
610 remove_ghost_note ();
613 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
614 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
615 _mouse_state != AddDragging) {
617 create_ghost_note (ev->x, ev->y, ev->state);
619 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
620 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
622 update_ghost_note (ev->x, ev->y, ev->state);
624 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
626 remove_ghost_note ();
627 hide_verbose_cursor ();
629 } else if (editor.current_mouse_mode() == MouseDraw) {
632 update_ghost_note (ev->x, ev->y, ev->state);
635 create_ghost_note (ev->x, ev->y, ev->state);
640 /* any motion immediately hides velocity text that may have been visible */
642 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
643 (*i)->hide_velocity ();
646 switch (_mouse_state) {
649 if (_pressed_button == 1) {
651 MouseMode m = editor.current_mouse_mode();
653 if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
654 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
655 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
656 clear_editor_note_selection ();
657 _mouse_changed_selection = true;
659 _mouse_state = SelectRectDragging;
661 } else if (m == MouseRange) {
662 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
663 _mouse_state = SelectVerticalDragging;
670 case SelectRectDragging:
671 case SelectVerticalDragging:
673 editor.drags()->motion_handler ((GdkEvent *) ev, false);
676 case SelectTouchDragging:
684 /* we may be dragging some non-note object (eg. patch-change, sysex)
687 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
692 MidiRegionView::scroll (GdkEventScroll* ev)
694 if (_selection.empty()) {
698 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
699 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
700 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
701 * through so that it still works for navigation.
706 hide_verbose_cursor ();
708 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
709 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
710 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
712 if (ev->direction == GDK_SCROLL_UP) {
713 change_velocities (true, fine, false, together);
714 } else if (ev->direction == GDK_SCROLL_DOWN) {
715 change_velocities (false, fine, false, together);
717 /* left, right: we don't use them */
725 MidiRegionView::key_press (GdkEventKey* ev)
727 /* since GTK bindings are generally activated on press, and since
728 detectable auto-repeat is the name of the game and only sends
729 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)) {
735 if (_mouse_state != AddDragging) {
736 _mouse_state = SelectTouchDragging;
741 } else if (ev->keyval == GDK_Escape && unmodified) {
742 clear_editor_note_selection ();
745 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
747 bool start = (ev->keyval == GDK_comma);
748 bool end = (ev->keyval == GDK_period);
749 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
750 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
752 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
756 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
758 if (_selection.empty()) {
765 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
767 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
769 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
770 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
772 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
775 trackview.editor().commit_reversible_selection_op();
779 } else if (ev->keyval == GDK_Up) {
781 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
782 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
783 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
785 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
786 change_velocities (true, fine, allow_smush, together);
788 transpose (true, fine, allow_smush);
792 } else if (ev->keyval == GDK_Down) {
794 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
795 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
796 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
798 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
799 change_velocities (false, fine, allow_smush, together);
801 transpose (false, fine, allow_smush);
805 } else if (ev->keyval == GDK_Left) {
807 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
808 nudge_notes (false, fine);
811 } else if (ev->keyval == GDK_Right) {
813 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
814 nudge_notes (true, fine);
817 } else if (ev->keyval == GDK_c && unmodified) {
821 } else if (ev->keyval == GDK_v && unmodified) {
830 MidiRegionView::key_release (GdkEventKey* ev)
832 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
840 MidiRegionView::channel_edit ()
842 if (_selection.empty()) {
846 /* pick a note somewhat at random (since Selection is a set<>) to
847 * provide the "current" channel for the dialog.
850 uint8_t current_channel = (*_selection.begin())->note()->channel ();
851 MidiChannelDialog channel_dialog (current_channel);
852 int ret = channel_dialog.run ();
855 case Gtk::RESPONSE_OK:
861 uint8_t new_channel = channel_dialog.active_channel ();
863 start_note_diff_command (_("channel edit"));
865 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
866 Selection::iterator next = i;
868 change_note_channel (*i, new_channel);
876 MidiRegionView::velocity_edit ()
878 if (_selection.empty()) {
882 /* pick a note somewhat at random (since Selection is a set<>) to
883 * provide the "current" velocity for the dialog.
886 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
887 MidiVelocityDialog velocity_dialog (current_velocity);
888 int ret = velocity_dialog.run ();
891 case Gtk::RESPONSE_OK:
897 uint8_t new_velocity = velocity_dialog.velocity ();
899 start_note_diff_command (_("velocity edit"));
901 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
902 Selection::iterator next = i;
904 change_note_velocity (*i, new_velocity, false);
912 MidiRegionView::show_list_editor ()
915 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
917 _list_editor->present ();
920 /** Add a note to the model, and the view, at a canvas (click) coordinate.
921 * \param t time in frames relative to the position of the region
922 * \param y vertical position in pixels
923 * \param length duration of the note in beats
924 * \param snap_t true to snap t to the grid, otherwise false.
927 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
929 if (length < 2 * DBL_EPSILON) {
933 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
934 MidiStreamView* const view = mtv->midi_view();
935 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
941 // Start of note in frames relative to region start
942 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
943 Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
945 const double note = view->y_to_note(y);
946 const uint8_t chan = mtv->get_channel_for_add();
947 const uint8_t velocity = get_velocity_for_add(beat_time);
949 const boost::shared_ptr<NoteType> new_note(
950 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
952 if (_model->contains (new_note)) {
956 view->update_note_range(new_note->note());
958 start_note_diff_command(_("add note"));
960 clear_editor_note_selection ();
961 note_diff_add_note (new_note, true, false);
965 play_midi_note (new_note);
969 MidiRegionView::clear_events ()
971 // clear selection without signaling
972 clear_selection_internal ();
975 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
976 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
981 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
986 _patch_changes.clear();
988 _optimization_iterator = _events.end();
992 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
996 content_connection.disconnect ();
997 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
998 /* Don't signal as nobody else needs to know until selection has been altered. */
1001 if (_enable_display) {
1007 MidiRegionView::start_note_diff_command (string name)
1009 if (!_note_diff_command) {
1010 trackview.editor().begin_reversible_command (name);
1011 _note_diff_command = _model->new_note_diff_command (name);
1016 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1018 if (_note_diff_command) {
1019 _note_diff_command->add (note);
1022 _marked_for_selection.insert(note);
1024 if (show_velocity) {
1025 _marked_for_velocity.insert(note);
1030 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1032 if (_note_diff_command && ev->note()) {
1033 _note_diff_command->remove(ev->note());
1038 MidiRegionView::note_diff_add_change (NoteBase* ev,
1039 MidiModel::NoteDiffCommand::Property property,
1042 if (_note_diff_command) {
1043 _note_diff_command->change (ev->note(), property, val);
1048 MidiRegionView::note_diff_add_change (NoteBase* ev,
1049 MidiModel::NoteDiffCommand::Property property,
1052 if (_note_diff_command) {
1053 _note_diff_command->change (ev->note(), property, val);
1058 MidiRegionView::apply_diff (bool as_subcommand)
1061 bool commit = false;
1063 if (!_note_diff_command) {
1067 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1068 // Mark all selected notes for selection when model reloads
1069 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1070 _marked_for_selection.insert((*i)->note());
1074 midi_view()->midi_track()->midi_playlist()->region_edited(
1075 _region, _note_diff_command);
1077 if (as_subcommand) {
1078 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1080 _model->apply_command (*trackview.session(), _note_diff_command);
1084 _note_diff_command = 0;
1086 if (add_or_remove) {
1087 _marked_for_selection.clear();
1090 _marked_for_velocity.clear();
1092 trackview.editor().commit_reversible_command ();
1097 MidiRegionView::abort_command()
1099 delete _note_diff_command;
1100 _note_diff_command = 0;
1101 clear_editor_note_selection();
1105 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1107 if (_optimization_iterator != _events.end()) {
1108 ++_optimization_iterator;
1111 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1112 return *_optimization_iterator;
1115 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1116 if ((*_optimization_iterator)->note() == note) {
1117 return *_optimization_iterator;
1124 /** This version finds any canvas note matching the supplied note. */
1126 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1128 Events::iterator it;
1130 for (it = _events.begin(); it != _events.end(); ++it) {
1131 if ((*it)->note()->id() == id) {
1139 boost::shared_ptr<PatchChange>
1140 MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
1142 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1143 if ((*x)->patch() == p) {
1148 return boost::shared_ptr<PatchChange>();
1152 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1154 MidiModel::Notes notes;
1155 _model->get_notes (notes, op, val, chan_mask);
1157 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1158 NoteBase* cne = find_canvas_note (*n);
1166 MidiRegionView::redisplay_model()
1168 if (_active_notes) {
1169 // Currently recording
1170 const framecnt_t zoom = trackview.editor().get_current_zoom();
1171 if (zoom != _last_display_zoom) {
1172 /* Update resolved canvas notes to reflect changes in zoom without
1173 touching model. Leave active notes (with length 0) alone since
1174 they are being extended. */
1175 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1176 if ((*i)->note()->length() > 0) {
1180 _last_display_zoom = zoom;
1189 bool empty_when_starting = _events.empty();
1190 MidiModel::ReadLock lock(_model->read_lock());
1191 MidiModel::Notes missing_notes = _model->notes(); // copy
1193 if (!empty_when_starting) {
1194 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1195 boost::shared_ptr<NoteType> note ((*i)->note());
1196 /* remove note items that are no longer valid */
1197 if (!(*i)->valid () || !_model->find_note (note)) {
1199 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1200 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1202 gr->remove_note (*i);
1207 i = _events.erase (i);
1210 MidiModel::Notes::iterator f;
1211 NoteBase* cne = (*i);
1214 if (note_in_region_range (note, visible)) {
1225 if ((f = missing_notes.find (note)) != missing_notes.end()) {
1226 missing_notes.erase (f);
1237 for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
1238 boost::shared_ptr<NoteType> note (*n);
1241 if (note_in_region_range (note, visible)) {
1243 cne = add_note (note, true);
1244 set<Evoral::event_id_t>::iterator it;
1246 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1247 if ((*it) == note->id()) {
1248 add_to_selection (cne);
1258 display_patch_changes ();
1260 _marked_for_selection.clear ();
1261 _marked_for_velocity.clear ();
1262 _pending_note_selection.clear ();
1264 /* we may have caused _events to contain things out of order (e.g. if a note
1265 moved earlier or later). we don't generally need them in time order, but
1266 make a note that a sort is required for those cases that require it.
1269 _sort_needed = true;
1273 MidiRegionView::display_patch_changes ()
1275 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1276 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1278 for (uint8_t i = 0; i < 16; ++i) {
1279 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1283 /** @param active_channel true to display patch changes fully, false to display
1284 * them `greyed-out' (as on an inactive channel)
1287 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1289 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1290 boost::shared_ptr<PatchChange> p;
1292 if ((*i)->channel() != channel) {
1296 if ((p = find_canvas_patch_change (*i)) != 0) {
1297 framecnt_t region_frames = source_beats_to_region_frames ((*i)->time());
1298 const double x = trackview.editor().sample_to_pixel (region_frames);
1299 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1300 p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
1301 p->flag()->set_text (patch_name);
1303 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1304 add_canvas_patch_change (*i, patch_name, active_channel);
1310 MidiRegionView::display_sysexes()
1312 bool have_periodic_system_messages = false;
1313 bool display_periodic_messages = true;
1315 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1317 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1318 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1319 have_periodic_system_messages = true;
1324 if (have_periodic_system_messages) {
1325 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1327 /* get an approximate value for the number of samples per video frame */
1329 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1331 /* if we are zoomed out beyond than the cutoff (i.e. more
1332 * frames per pixel than frames per 4 video frames), don't
1333 * show periodic sysex messages.
1336 if (zoom > (video_frame*4)) {
1337 display_periodic_messages = false;
1341 display_periodic_messages = false;
1344 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1345 Evoral::Beats time = (*i)->time();
1347 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1348 if (!display_periodic_messages) {
1355 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1356 str << int((*i)->buffer()[b]);
1357 if (b != (*i)->size() -1) {
1361 string text = str.str();
1363 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1365 double height = midi_stream_view()->contents_height();
1367 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1368 // SysEx canvas object!!!
1370 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1371 new SysEx (*this, _note_group, text, height, x, 1.0));
1373 // Show unless message is beyond the region bounds
1374 if (time - _region->start() >= _region->length() || time < _region->start()) {
1380 _sys_exes.push_back(sysex);
1384 MidiRegionView::~MidiRegionView ()
1386 in_destructor = true;
1388 hide_verbose_cursor ();
1390 delete _list_editor;
1392 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1394 if (_active_notes) {
1401 delete _note_diff_command;
1402 delete _step_edit_cursor;
1406 MidiRegionView::region_resized (const PropertyChange& what_changed)
1408 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1410 if (what_changed.contains (ARDOUR::Properties::position)) {
1411 _region_relative_time_converter.set_origin_b(_region->position());
1412 _region_relative_time_converter_double.set_origin_b(_region->position());
1413 /* reset_width dependent_items() redisplays model */
1417 if (what_changed.contains (ARDOUR::Properties::start) ||
1418 what_changed.contains (ARDOUR::Properties::position)) {
1419 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1421 /* catch end and start trim so we can update the view*/
1422 if (!what_changed.contains (ARDOUR::Properties::start) &&
1423 what_changed.contains (ARDOUR::Properties::length)) {
1424 enable_display (true);
1425 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1426 what_changed.contains (ARDOUR::Properties::length)) {
1427 enable_display (true);
1432 MidiRegionView::reset_width_dependent_items (double pixel_width)
1434 RegionView::reset_width_dependent_items(pixel_width);
1436 if (_enable_display) {
1440 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1441 if ((*x)->canvas_item()->width() >= _pixel_width) {
1448 move_step_edit_cursor (_step_edit_cursor_position);
1449 set_step_edit_cursor_width (_step_edit_cursor_width);
1453 MidiRegionView::set_height (double height)
1455 double old_height = _height;
1456 RegionView::set_height(height);
1458 apply_note_range (midi_stream_view()->lowest_note(),
1459 midi_stream_view()->highest_note(),
1460 height != old_height);
1463 name_text->raise_to_top();
1466 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1467 (*x)->set_height (midi_stream_view()->contents_height());
1470 if (_step_edit_cursor) {
1471 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1476 /** Apply the current note range from the stream view
1477 * by repositioning/hiding notes as necessary
1480 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1482 if (!_enable_display) {
1486 if (!force && _current_range_min == min && _current_range_max == max) {
1490 _current_range_min = min;
1491 _current_range_max = max;
1497 MidiRegionView::add_ghost (TimeAxisView& tv)
1499 double unit_position = _region->position () / samples_per_pixel;
1500 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1501 MidiGhostRegion* ghost;
1503 if (mtv && mtv->midi_view()) {
1504 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1505 to allow having midi notes on top of note lines and waveforms.
1507 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1509 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1512 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1513 ghost->add_note(*i);
1516 ghost->set_colors ();
1517 ghost->set_height ();
1518 ghost->set_duration (_region->length() / samples_per_pixel);
1519 ghosts.push_back (ghost);
1525 /** Begin tracking note state for successive calls to add_event
1528 MidiRegionView::begin_write()
1530 if (_active_notes) {
1531 delete[] _active_notes;
1533 _active_notes = new Note*[128];
1534 for (unsigned i = 0; i < 128; ++i) {
1535 _active_notes[i] = 0;
1540 /** Destroy note state for add_event
1543 MidiRegionView::end_write()
1545 delete[] _active_notes;
1547 _marked_for_selection.clear();
1548 _marked_for_velocity.clear();
1552 /** Resolve an active MIDI note (while recording).
1555 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1557 if (midi_view()->note_mode() != Sustained) {
1561 if (_active_notes && _active_notes[note]) {
1562 /* Set note length so update_note() works. Note this is a local note
1563 for recording, not from a model, so we can safely mess with it. */
1564 _active_notes[note]->note()->set_length(
1565 end_time - _active_notes[note]->note()->time());
1567 /* End time is relative to the region being recorded. */
1568 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1570 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1571 _active_notes[note]->set_outline_all ();
1572 _active_notes[note] = 0;
1577 /** Extend active notes to rightmost edge of region (if length is changed)
1580 MidiRegionView::extend_active_notes()
1582 if (!_active_notes) {
1586 for (unsigned i = 0; i < 128; ++i) {
1587 if (_active_notes[i]) {
1588 _active_notes[i]->set_x1(
1589 trackview.editor().sample_to_pixel(_region->length()));
1595 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1597 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1601 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1603 if (!route_ui || !route_ui->midi_track()) {
1607 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1611 /* NotePlayer deletes itself */
1615 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1617 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1618 start_playing_midi_chord(notes);
1622 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1624 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1628 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1630 if (!route_ui || !route_ui->midi_track()) {
1634 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1636 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1645 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1647 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1649 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1650 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1651 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1653 visible = (note->note() >= _current_range_min) &&
1654 (note->note() <= _current_range_max);
1660 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1664 if ((sus = dynamic_cast<Note*>(note))) {
1665 update_sustained(sus, update_ghost_regions);
1666 } else if ((hit = dynamic_cast<Hit*>(note))) {
1667 update_hit(hit, update_ghost_regions);
1671 /** Update a canvas note's size from its model note.
1672 * @param ev Canvas note to update.
1673 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1676 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1678 TempoMap& map (trackview.session()->tempo_map());
1679 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1680 boost::shared_ptr<NoteType> note = ev->note();
1682 const double session_source_start = _region->quarter_note() - mr->start_beats();
1683 const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1685 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1687 const double y0 = 1 + floor(note_to_y(note->note()));
1690 /* trim note display to not overlap the end of its region */
1691 if (note->length().to_double() > 0.0) {
1692 double note_end_time = note->end_time().to_double();
1694 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1695 note_end_time = mr->start_beats() + mr->length_beats();
1698 const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
1700 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1702 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1705 y1 = y0 + std::max(1., floor(note_height()) - 1);
1707 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1709 if (!note->length()) {
1710 if (_active_notes && note->note() < 128) {
1711 Note* const old_rect = _active_notes[note->note()];
1713 /* There is an active note on this key, so we have a stuck
1714 note. Finish the old rectangle here. */
1715 old_rect->set_x1 (x1);
1716 old_rect->set_outline_all ();
1718 _active_notes[note->note()] = ev;
1720 /* outline all but right edge */
1721 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1722 ArdourCanvas::Rectangle::TOP|
1723 ArdourCanvas::Rectangle::LEFT|
1724 ArdourCanvas::Rectangle::BOTTOM));
1726 /* outline all edges */
1727 ev->set_outline_all ();
1730 // Update color in case velocity has changed
1731 const uint32_t base_col = ev->base_color();
1732 ev->set_fill_color(base_col);
1733 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1735 if (update_ghost_regions) {
1736 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1737 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1739 gr->update_note (ev);
1746 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1748 boost::shared_ptr<NoteType> note = ev->note();
1750 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1751 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1753 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1754 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1755 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1757 // see DnD note in MidiRegionView::apply_note_range() above
1758 if (y <= 0 || y >= _height) {
1764 ev->set_position (ArdourCanvas::Duple (x, y));
1765 ev->set_height (diamond_size);
1767 // Update color in case velocity has changed
1768 const uint32_t base_col = ev->base_color();
1769 ev->set_fill_color(base_col);
1770 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1772 if (update_ghost_regions) {
1773 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1774 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1776 gr->update_hit (ev);
1782 /** Add a MIDI note to the view (with length).
1784 * If in sustained mode, notes with length 0 will be considered active
1785 * notes, and resolve_note should be called when the corresponding note off
1786 * event arrives, to properly display the note.
1789 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1791 NoteBase* event = 0;
1793 if (midi_view()->note_mode() == Sustained) {
1795 Note* ev_rect = new Note (*this, _note_group, note);
1797 update_sustained (ev_rect);
1801 } else if (midi_view()->note_mode() == Percussive) {
1803 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1805 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1807 update_hit (ev_diamond);
1816 MidiGhostRegion* gr;
1818 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1819 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1820 gr->add_note(event);
1824 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1825 note_selected(event, true);
1828 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1829 event->show_velocity();
1832 event->on_channel_selection_change (get_selected_channels());
1833 _events.push_back(event);
1842 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1843 MidiStreamView* const view = mtv->midi_view();
1845 view->update_note_range (note->note());
1850 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1851 Evoral::Beats pos, Evoral::Beats len)
1853 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1855 /* potentially extend region to hold new note */
1857 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1858 framepos_t region_end = _region->last_frame();
1860 if (end_frame > region_end) {
1861 /* XX sets length in beats from audio space. make musical */
1862 _region->set_length (end_frame - _region->position(), 0);
1865 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1866 MidiStreamView* const view = mtv->midi_view();
1868 view->update_note_range(new_note->note());
1870 _marked_for_selection.clear ();
1872 start_note_diff_command (_("step add"));
1874 clear_editor_note_selection ();
1875 note_diff_add_note (new_note, true, false);
1879 // last_step_edit_note = new_note;
1883 MidiRegionView::step_sustain (Evoral::Beats beats)
1885 change_note_lengths (false, false, beats, false, true);
1888 /** Add a new patch change flag to the canvas.
1889 * @param patch the patch change to add
1890 * @param the text to display in the flag
1891 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1894 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1896 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1897 const double x = trackview.editor().sample_to_pixel (region_frames);
1899 double const height = midi_stream_view()->contents_height();
1901 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1902 // so we need to do something more sophisticated to keep its color
1903 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1905 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1906 new PatchChange(*this, group,
1912 _patch_change_outline,
1916 if (patch_change->item().width() < _pixel_width) {
1917 // Show unless patch change is beyond the region bounds
1918 if (region_frames < 0 || region_frames >= _region->length()) {
1919 patch_change->hide();
1921 patch_change->show();
1924 patch_change->hide ();
1927 _patch_changes.push_back (patch_change);
1931 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
1933 /* remove the canvas item */
1934 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1935 if ((*x)->patch() == pc->patch()) {
1936 _patch_changes.erase (x);
1942 MIDI::Name::PatchPrimaryKey
1943 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1945 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1948 /// Return true iff @p pc applies to the given time on the given channel.
1950 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1952 return pc->time() <= time && pc->channel() == channel;
1956 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1958 // The earliest event not before time
1959 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1961 // Go backwards until we find the latest PC for this channel, or the start
1962 while (i != _model->patch_changes().begin() &&
1963 (i == _model->patch_changes().end() ||
1964 !patch_applies(*i, time, channel))) {
1968 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1969 key.set_bank((*i)->bank());
1970 key.set_program((*i)->program ());
1978 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1980 string name = _("alter patch change");
1981 trackview.editor().begin_reversible_command (name);
1983 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1985 if (pc.patch()->program() != new_patch.program()) {
1986 c->change_program (pc.patch (), new_patch.program());
1989 int const new_bank = new_patch.bank();
1990 if (pc.patch()->bank() != new_bank) {
1991 c->change_bank (pc.patch (), new_bank);
1994 _model->apply_command (*trackview.session(), c);
1995 trackview.editor().commit_reversible_command ();
1997 remove_canvas_patch_change (&pc);
1998 display_patch_changes ();
2002 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2004 string name = _("alter patch change");
2005 trackview.editor().begin_reversible_command (name);
2006 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2008 if (old_change->time() != new_change.time()) {
2009 c->change_time (old_change, new_change.time());
2012 if (old_change->channel() != new_change.channel()) {
2013 c->change_channel (old_change, new_change.channel());
2016 if (old_change->program() != new_change.program()) {
2017 c->change_program (old_change, new_change.program());
2020 if (old_change->bank() != new_change.bank()) {
2021 c->change_bank (old_change, new_change.bank());
2024 _model->apply_command (*trackview.session(), c);
2025 trackview.editor().commit_reversible_command ();
2027 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2028 if ((*x)->patch() == old_change) {
2029 _patch_changes.erase (x);
2034 display_patch_changes ();
2037 /** Add a patch change to the region.
2038 * @param t Time in frames relative to region position
2039 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2040 * MidiTimeAxisView::get_channel_for_add())
2043 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2045 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2046 string name = _("add patch change");
2048 trackview.editor().begin_reversible_command (name);
2049 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2050 c->add (MidiModel::PatchChangePtr (
2051 new Evoral::PatchChange<Evoral::Beats> (
2052 absolute_frames_to_source_beats (_region->position() + t),
2053 mtv->get_channel_for_add(), patch.program(), patch.bank()
2058 _model->apply_command (*trackview.session(), c);
2059 trackview.editor().commit_reversible_command ();
2061 display_patch_changes ();
2065 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2067 trackview.editor().begin_reversible_command (_("move patch change"));
2068 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2069 c->change_time (pc.patch (), t);
2070 _model->apply_command (*trackview.session(), c);
2071 trackview.editor().commit_reversible_command ();
2073 display_patch_changes ();
2077 MidiRegionView::delete_patch_change (PatchChange* pc)
2079 trackview.editor().begin_reversible_command (_("delete patch change"));
2081 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2082 c->remove (pc->patch ());
2083 _model->apply_command (*trackview.session(), c);
2084 trackview.editor().commit_reversible_command ();
2086 remove_canvas_patch_change (pc);
2087 display_patch_changes ();
2091 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2093 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2095 key.set_bank(key.bank() + delta);
2097 key.set_program(key.program() + delta);
2099 change_patch_change(patch, key);
2103 MidiRegionView::note_deleted (NoteBase* cne)
2105 if (_entered_note && cne == _entered_note) {
2109 if (_selection.empty()) {
2113 _selection.erase (cne);
2117 MidiRegionView::delete_selection()
2119 if (_selection.empty()) {
2123 if (trackview.editor().drags()->active()) {
2127 start_note_diff_command (_("delete selection"));
2129 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2130 if ((*i)->selected()) {
2131 _note_diff_command->remove((*i)->note());
2139 hide_verbose_cursor ();
2143 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2145 start_note_diff_command (_("delete note"));
2146 _note_diff_command->remove (n);
2149 hide_verbose_cursor ();
2153 MidiRegionView::clear_editor_note_selection ()
2155 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2156 PublicEditor& editor(trackview.editor());
2157 editor.get_selection().clear_midi_notes();
2161 MidiRegionView::clear_selection ()
2163 clear_selection_internal();
2164 PublicEditor& editor(trackview.editor());
2165 editor.get_selection().remove(this);
2169 MidiRegionView::clear_selection_internal ()
2171 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2173 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2174 (*i)->set_selected(false);
2175 (*i)->hide_velocity();
2180 // Clearing selection entirely, ungrab keyboard
2181 Keyboard::magic_widget_drop_focus();
2182 _grabbed_keyboard = false;
2187 MidiRegionView::unique_select(NoteBase* ev)
2189 clear_editor_note_selection();
2190 add_to_selection(ev);
2194 MidiRegionView::select_all_notes ()
2196 clear_editor_note_selection ();
2198 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2199 add_to_selection (*i);
2204 MidiRegionView::select_range (framepos_t start, framepos_t end)
2206 clear_editor_note_selection ();
2208 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2209 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2210 if (t >= start && t <= end) {
2211 add_to_selection (*i);
2217 MidiRegionView::invert_selection ()
2219 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2220 if ((*i)->selected()) {
2221 remove_from_selection(*i);
2223 add_to_selection (*i);
2228 /** Used for selection undo/redo.
2229 The requested notes most likely won't exist in the view until the next model redisplay.
2232 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2235 list<Evoral::event_id_t>::iterator n;
2237 for (n = notes.begin(); n != notes.end(); ++n) {
2238 if ((cne = find_canvas_note(*n)) != 0) {
2239 add_to_selection (cne);
2241 _pending_note_selection.insert(*n);
2247 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2249 bool have_selection = !_selection.empty();
2250 uint8_t low_note = 127;
2251 uint8_t high_note = 0;
2252 MidiModel::Notes& notes (_model->notes());
2253 _optimization_iterator = _events.begin();
2255 if (extend && !have_selection) {
2259 /* scan existing selection to get note range */
2261 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2262 if ((*i)->note()->note() < low_note) {
2263 low_note = (*i)->note()->note();
2265 if ((*i)->note()->note() > high_note) {
2266 high_note = (*i)->note()->note();
2271 clear_editor_note_selection ();
2273 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2274 /* only note previously selected is the one we are
2275 * reselecting. treat this as cancelling the selection.
2282 low_note = min (low_note, notenum);
2283 high_note = max (high_note, notenum);
2286 _no_sound_notes = true;
2288 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2290 boost::shared_ptr<NoteType> note (*n);
2292 bool select = false;
2294 if (((1 << note->channel()) & channel_mask) != 0) {
2296 if ((note->note() >= low_note && note->note() <= high_note)) {
2299 } else if (note->note() == notenum) {
2305 if ((cne = find_canvas_note (note)) != 0) {
2306 // extend is false because we've taken care of it,
2307 // since it extends by time range, not pitch.
2308 note_selected (cne, add, false);
2312 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2316 _no_sound_notes = false;
2320 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2322 MidiModel::Notes& notes (_model->notes());
2323 _optimization_iterator = _events.begin();
2325 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2327 boost::shared_ptr<NoteType> note (*n);
2330 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2331 if ((cne = find_canvas_note (note)) != 0) {
2332 if (cne->selected()) {
2333 note_deselected (cne);
2335 note_selected (cne, true, false);
2343 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2346 clear_editor_note_selection();
2347 add_to_selection (ev);
2352 if (!ev->selected()) {
2353 add_to_selection (ev);
2357 /* find end of latest note selected, select all between that and the start of "ev" */
2359 Evoral::Beats earliest = Evoral::MaxBeats;
2360 Evoral::Beats latest = Evoral::Beats();
2362 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2363 if ((*i)->note()->end_time() > latest) {
2364 latest = (*i)->note()->end_time();
2366 if ((*i)->note()->time() < earliest) {
2367 earliest = (*i)->note()->time();
2371 if (ev->note()->end_time() > latest) {
2372 latest = ev->note()->end_time();
2375 if (ev->note()->time() < earliest) {
2376 earliest = ev->note()->time();
2379 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2381 /* find notes entirely within OR spanning the earliest..latest range */
2383 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2384 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2385 add_to_selection (*i);
2393 MidiRegionView::note_deselected(NoteBase* ev)
2395 remove_from_selection (ev);
2399 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2401 PublicEditor& editor = trackview.editor();
2403 // Convert to local coordinates
2404 const framepos_t p = _region->position();
2405 const double y = midi_view()->y_position();
2406 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2407 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2408 const double y0 = max(0.0, gy0 - y);
2409 const double y1 = max(0.0, gy1 - y);
2411 // TODO: Make this faster by storing the last updated selection rect, and only
2412 // adjusting things that are in the area that appears/disappeared.
2413 // We probably need a tree to be able to find events in O(log(n)) time.
2415 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2416 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2417 // Rectangles intersect
2418 if (!(*i)->selected()) {
2419 add_to_selection (*i);
2421 } else if ((*i)->selected() && !extend) {
2422 // Rectangles do not intersect
2423 remove_from_selection (*i);
2427 typedef RouteTimeAxisView::AutomationTracks ATracks;
2428 typedef std::list<Selectable*> Selectables;
2430 /* Add control points to selection. */
2431 const ATracks& atracks = midi_view()->automation_tracks();
2432 Selectables selectables;
2433 editor.get_selection().clear_points();
2434 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2435 a->second->get_selectables(start, end, gy0, gy1, selectables);
2436 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2437 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2439 editor.get_selection().add(cp);
2442 a->second->set_selected_points(editor.get_selection().points);
2447 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2453 // TODO: Make this faster by storing the last updated selection rect, and only
2454 // adjusting things that are in the area that appears/disappeared.
2455 // We probably need a tree to be able to find events in O(log(n)) time.
2457 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2458 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2459 // within y- (note-) range
2460 if (!(*i)->selected()) {
2461 add_to_selection (*i);
2463 } else if ((*i)->selected() && !extend) {
2464 remove_from_selection (*i);
2470 MidiRegionView::remove_from_selection (NoteBase* ev)
2472 Selection::iterator i = _selection.find (ev);
2474 if (i != _selection.end()) {
2475 _selection.erase (i);
2476 if (_selection.empty() && _grabbed_keyboard) {
2478 Keyboard::magic_widget_drop_focus();
2479 _grabbed_keyboard = false;
2483 ev->set_selected (false);
2484 ev->hide_velocity ();
2486 if (_selection.empty()) {
2487 PublicEditor& editor (trackview.editor());
2488 editor.get_selection().remove (this);
2493 MidiRegionView::add_to_selection (NoteBase* ev)
2495 const bool selection_was_empty = _selection.empty();
2497 if (_selection.insert (ev).second) {
2498 ev->set_selected (true);
2499 start_playing_midi_note ((ev)->note());
2500 if (selection_was_empty && _entered) {
2501 // Grab keyboard for moving notes with arrow keys
2502 Keyboard::magic_widget_grab_focus();
2503 _grabbed_keyboard = true;
2507 if (selection_was_empty) {
2508 PublicEditor& editor (trackview.editor());
2509 editor.get_selection().add (this);
2514 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2516 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2517 PossibleChord to_play;
2518 Evoral::Beats earliest = Evoral::MaxBeats;
2520 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2521 if ((*i)->note()->time() < earliest) {
2522 earliest = (*i)->note()->time();
2526 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2527 if ((*i)->note()->time() == earliest) {
2528 to_play.push_back ((*i)->note());
2530 (*i)->move_event(dx, dy);
2533 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2535 if (to_play.size() > 1) {
2537 PossibleChord shifted;
2539 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2540 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2541 moved_note->set_note (moved_note->note() + cumulative_dy);
2542 shifted.push_back (moved_note);
2545 start_playing_midi_chord (shifted);
2547 } else if (!to_play.empty()) {
2549 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2550 moved_note->set_note (moved_note->note() + cumulative_dy);
2551 start_playing_midi_note (moved_note);
2557 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2559 uint8_t lowest_note_in_selection = 127;
2560 uint8_t highest_note_in_selection = 0;
2561 uint8_t highest_note_difference = 0;
2563 // find highest and lowest notes first
2565 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2566 uint8_t pitch = (*i)->note()->note();
2567 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2568 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2572 cerr << "dnote: " << (int) dnote << endl;
2573 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2574 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2575 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2576 << int(highest_note_in_selection) << endl;
2577 cerr << "selection size: " << _selection.size() << endl;
2578 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2581 // Make sure the note pitch does not exceed the MIDI standard range
2582 if (highest_note_in_selection + dnote > 127) {
2583 highest_note_difference = highest_note_in_selection - 127;
2585 TempoMap& map (trackview.session()->tempo_map());
2587 start_note_diff_command (_("move notes"));
2589 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2591 double const start_qn = _region->quarter_note() - midi_region()->start_beats();
2592 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2593 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2598 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2600 uint8_t original_pitch = (*i)->note()->note();
2601 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2603 // keep notes in standard midi range
2604 clamp_to_0_127(new_pitch);
2606 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2607 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2609 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2614 // care about notes being moved beyond the upper/lower bounds on the canvas
2615 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2616 highest_note_in_selection > midi_stream_view()->highest_note()) {
2617 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2621 /** @param x Pixel relative to the region position.
2622 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2623 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2624 * @return Snapped frame relative to the region position.
2627 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2629 PublicEditor& editor (trackview.editor());
2630 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2633 /** @param x Pixel relative to the region position.
2634 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2635 * @return Snapped pixel relative to the region position.
2638 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2640 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2644 MidiRegionView::get_position_pixels()
2646 framepos_t region_frame = get_position();
2647 return trackview.editor().sample_to_pixel(region_frame);
2651 MidiRegionView::get_end_position_pixels()
2653 framepos_t frame = get_position() + get_duration ();
2654 return trackview.editor().sample_to_pixel(frame);
2658 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2660 /* the time converter will return the frame corresponding to `beats'
2661 relative to the start of the source. The start of the source
2662 is an implied position given by region->position - region->start
2664 const framepos_t source_start = _region->position() - _region->start();
2665 return source_start + _source_relative_time_converter.to (beats);
2669 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2671 /* the `frames' argument needs to be converted into a frame count
2672 relative to the start of the source before being passed in to the
2675 const framepos_t source_start = _region->position() - _region->start();
2676 return _source_relative_time_converter.from (frames - source_start);
2680 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2682 return _region_relative_time_converter.to(beats);
2686 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2688 return _region_relative_time_converter.from(frames);
2692 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2694 return _region_relative_time_converter_double.from(frames);
2698 MidiRegionView::begin_resizing (bool /*at_front*/)
2700 _resize_data.clear();
2702 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2703 Note *note = dynamic_cast<Note*> (*i);
2705 // only insert CanvasNotes into the map
2707 NoteResizeData *resize_data = new NoteResizeData();
2708 resize_data->note = note;
2710 // create a new SimpleRect from the note which will be the resize preview
2711 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2712 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2714 // calculate the colors: get the color settings
2715 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2716 UIConfiguration::instance().color ("midi note selected"),
2719 // make the resize preview notes more transparent and bright
2720 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2722 // calculate color based on note velocity
2723 resize_rect->set_fill_color (UINT_INTERPOLATE(
2724 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2728 resize_rect->set_outline_color (NoteBase::calculate_outline (
2729 UIConfiguration::instance().color ("midi note selected")));
2731 resize_data->resize_rect = resize_rect;
2732 _resize_data.push_back(resize_data);
2737 /** Update resizing notes while user drags.
2738 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2739 * @param at_front which end of the note (true == note on, false == note off)
2740 * @param delta_x change in mouse position since the start of the drag
2741 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2742 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2743 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2744 * as the \a primary note.
2745 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2746 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2749 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2751 TempoMap& tmap (trackview.session()->tempo_map());
2752 bool cursor_set = false;
2753 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2755 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2756 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2757 Note* canvas_note = (*i)->note;
2762 current_x = canvas_note->x0() + delta_x + snap_delta;
2764 current_x = primary->x0() + delta_x + snap_delta;
2768 current_x = canvas_note->x1() + delta_x + snap_delta;
2770 current_x = primary->x1() + delta_x + snap_delta;
2774 if (current_x < 0) {
2775 // This works even with snapping because RegionView::snap_frame_to_frame()
2776 // snaps forward if the snapped sample is before the beginning of the region
2779 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2780 current_x = trackview.editor().sample_to_pixel(_region->length());
2785 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2787 resize_rect->set_x0 (current_x - snap_delta);
2789 resize_rect->set_x1 (canvas_note->x1());
2792 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2794 resize_rect->set_x1 (current_x - snap_delta);
2796 resize_rect->set_x0 (canvas_note->x0());
2800 /* Convert snap delta from pixels to beats. */
2801 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2802 double snap_delta_beats = 0.0;
2805 /* negative beat offsets aren't allowed */
2806 if (snap_delta_samps > 0) {
2807 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2808 } else if (snap_delta_samps < 0) {
2809 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2814 int32_t divisions = 0;
2817 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2818 divisions = trackview.editor().get_grid_music_divisions (0);
2820 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2822 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2823 - midi_region()->beat()) + midi_region()->start_beats();
2825 Evoral::Beats len = Evoral::Beats();
2828 if (beats < canvas_note->note()->end_time()) {
2829 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2830 len += canvas_note->note()->length();
2833 if (beats >= canvas_note->note()->time()) {
2834 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2838 len = std::max(Evoral::Beats(1 / 512.0), len);
2841 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2842 show_verbose_cursor (buf, 0, 0);
2851 /** Finish resizing notes when the user releases the mouse button.
2852 * Parameters the same as for \a update_resizing().
2855 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2857 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2858 TempoMap& tmap (trackview.session()->tempo_map());
2860 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2861 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2863 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2864 Note* canvas_note = (*i)->note;
2865 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2867 /* Get the new x position for this resize, which is in pixels relative
2868 * to the region position.
2875 current_x = canvas_note->x0() + delta_x + snap_delta;
2877 current_x = primary->x0() + delta_x + snap_delta;
2881 current_x = canvas_note->x1() + delta_x + snap_delta;
2883 current_x = primary->x1() + delta_x + snap_delta;
2887 if (current_x < 0) {
2890 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2891 current_x = trackview.editor().sample_to_pixel(_region->length());
2894 /* Convert snap delta from pixels to beats with sign. */
2895 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2896 double snap_delta_beats = 0.0;
2899 if (snap_delta_samps > 0) {
2900 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2901 } else if (snap_delta_samps < 0) {
2902 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2906 uint32_t divisions = 0;
2907 /* Convert the new x position to a frame within the source */
2908 framepos_t current_fr;
2910 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2911 divisions = trackview.editor().get_grid_music_divisions (0);
2913 current_fr = trackview.editor().pixel_to_sample (current_x);
2916 /* and then to beats */
2917 const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
2918 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
2919 const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
2921 if (at_front && x_beats < canvas_note->note()->end_time()) {
2922 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2923 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2924 len += canvas_note->note()->length();
2927 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2932 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2933 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2934 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2941 _resize_data.clear();
2946 MidiRegionView::abort_resizing ()
2948 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2949 delete (*i)->resize_rect;
2953 _resize_data.clear ();
2957 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2959 uint8_t new_velocity;
2962 new_velocity = event->note()->velocity() + velocity;
2963 clamp_to_0_127(new_velocity);
2965 new_velocity = velocity;
2968 event->set_selected (event->selected()); // change color
2970 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2974 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2979 new_note = event->note()->note() + note;
2984 clamp_to_0_127 (new_note);
2985 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2989 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2991 bool change_start = false;
2992 bool change_length = false;
2993 Evoral::Beats new_start;
2994 Evoral::Beats new_length;
2996 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2998 front_delta: if positive - move the start of the note later in time (shortening it)
2999 if negative - move the start of the note earlier in time (lengthening it)
3001 end_delta: if positive - move the end of the note later in time (lengthening it)
3002 if negative - move the end of the note earlier in time (shortening it)
3005 if (!!front_delta) {
3006 if (front_delta < 0) {
3008 if (event->note()->time() < -front_delta) {
3009 new_start = Evoral::Beats();
3011 new_start = event->note()->time() + front_delta; // moves earlier
3014 /* start moved toward zero, so move the end point out to where it used to be.
3015 Note that front_delta is negative, so this increases the length.
3018 new_length = event->note()->length() - front_delta;
3019 change_start = true;
3020 change_length = true;
3024 Evoral::Beats new_pos = event->note()->time() + front_delta;
3026 if (new_pos < event->note()->end_time()) {
3027 new_start = event->note()->time() + front_delta;
3028 /* start moved toward the end, so move the end point back to where it used to be */
3029 new_length = event->note()->length() - front_delta;
3030 change_start = true;
3031 change_length = true;
3038 bool can_change = true;
3039 if (end_delta < 0) {
3040 if (event->note()->length() < -end_delta) {
3046 new_length = event->note()->length() + end_delta;
3047 change_length = true;
3052 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3055 if (change_length) {
3056 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3061 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3063 uint8_t new_channel;
3067 if (event->note()->channel() < -chn) {
3070 new_channel = event->note()->channel() + chn;
3073 new_channel = event->note()->channel() + chn;
3076 new_channel = (uint8_t) chn;
3079 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3083 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3085 Evoral::Beats new_time;
3089 if (event->note()->time() < -delta) {
3090 new_time = Evoral::Beats();
3092 new_time = event->note()->time() + delta;
3095 new_time = event->note()->time() + delta;
3101 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3105 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3107 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3111 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3116 if (_selection.empty()) {
3131 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3132 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3138 start_note_diff_command (_("change velocities"));
3140 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3141 Selection::iterator next = i;
3145 if (i == _selection.begin()) {
3146 change_note_velocity (*i, delta, true);
3147 value = (*i)->note()->velocity() + delta;
3149 change_note_velocity (*i, value, false);
3153 change_note_velocity (*i, delta, true);
3162 if (!_selection.empty()) {
3164 snprintf (buf, sizeof (buf), "Vel %d",
3165 (int) (*_selection.begin())->note()->velocity());
3166 show_verbose_cursor (buf, 10, 10);
3172 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3174 if (_selection.empty()) {
3191 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3193 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3197 if ((int8_t) (*i)->note()->note() + delta > 127) {
3204 start_note_diff_command (_("transpose"));
3206 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3207 Selection::iterator next = i;
3209 change_note_note (*i, delta, true);
3217 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3221 delta = Evoral::Beats(1.0/128.0);
3223 /* grab the current grid distance */
3224 delta = get_grid_beats(_region->position());
3232 start_note_diff_command (_("change note lengths"));
3234 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3235 Selection::iterator next = i;
3238 /* note the negation of the delta for start */
3241 (start ? -delta : Evoral::Beats()),
3242 (end ? delta : Evoral::Beats()));
3251 MidiRegionView::nudge_notes (bool forward, bool fine)
3253 if (_selection.empty()) {
3257 /* pick a note as the point along the timeline to get the nudge distance.
3258 its not necessarily the earliest note, so we may want to pull the notes out
3259 into a vector and sort before using the first one.
3262 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3263 Evoral::Beats delta;
3267 /* non-fine, move by 1 bar regardless of snap */
3268 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3270 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3272 /* grid is off - use nudge distance */
3275 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3276 delta = region_frames_to_region_beats (fabs ((double)distance));
3282 framepos_t next_pos = ref_point;
3285 if (max_framepos - 1 < next_pos) {
3289 if (next_pos == 0) {
3295 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3296 const framecnt_t distance = ref_point - next_pos;
3297 delta = region_frames_to_region_beats (fabs ((double)distance));
3308 start_note_diff_command (_("nudge"));
3310 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3311 Selection::iterator next = i;
3313 change_note_time (*i, delta, true);
3321 MidiRegionView::change_channel(uint8_t channel)
3323 start_note_diff_command(_("change channel"));
3324 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3325 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3333 MidiRegionView::note_entered(NoteBase* ev)
3337 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3339 if (_mouse_state == SelectTouchDragging) {
3341 note_selected (ev, true);
3343 } else if (editor->current_mouse_mode() == MouseContent) {
3345 remove_ghost_note ();
3346 show_verbose_cursor (ev->note ());
3348 } else if (editor->current_mouse_mode() == MouseDraw) {
3350 remove_ghost_note ();
3351 show_verbose_cursor (ev->note ());
3356 MidiRegionView::note_left (NoteBase*)
3360 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3361 (*i)->hide_velocity ();
3364 hide_verbose_cursor ();
3368 MidiRegionView::patch_entered (PatchChange* p)
3371 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3372 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3373 << _("Channel ") << ((int) p->patch()->channel() + 1);
3374 show_verbose_cursor (s.str(), 10, 20);
3375 p->item().grab_focus();
3379 MidiRegionView::patch_left (PatchChange *)
3381 hide_verbose_cursor ();
3382 /* focus will transfer back via the enter-notify event sent to this
3388 MidiRegionView::sysex_entered (SysEx* p)
3392 // need a way to extract text from p->_flag->_text
3394 // show_verbose_cursor (s.str(), 10, 20);
3395 p->item().grab_focus();
3399 MidiRegionView::sysex_left (SysEx *)
3401 hide_verbose_cursor ();
3402 /* focus will transfer back via the enter-notify event sent to this
3408 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3410 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3411 Editing::MouseMode mm = editor->current_mouse_mode();
3412 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3414 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3415 if (can_set_cursor && ctx) {
3416 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3417 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3418 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3419 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3421 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3427 MidiRegionView::get_fill_color() const
3429 const std::string mod_name = (_dragging ? "dragging region" :
3430 trackview.editor().internal_editing() ? "editable region" :
3433 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3434 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3435 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3436 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3438 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3442 MidiRegionView::midi_channel_mode_changed ()
3444 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3445 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3446 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3448 if (mode == ForceChannel) {
3449 mask = 0xFFFF; // Show all notes as active (below)
3452 // Update notes for selection
3453 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3454 (*i)->on_channel_selection_change (mask);
3457 _patch_changes.clear ();
3458 display_patch_changes ();
3462 MidiRegionView::instrument_settings_changed ()
3468 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3470 if (_selection.empty()) {
3474 PublicEditor& editor (trackview.editor());
3478 /* XXX what to do ? */
3482 editor.get_cut_buffer().add (selection_as_cut_buffer());
3490 start_note_diff_command();
3492 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3499 note_diff_remove_note (*i);
3509 MidiRegionView::selection_as_cut_buffer () const
3513 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3514 NoteType* n = (*i)->note().get();
3515 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3518 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3524 /** This method handles undo */
3526 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3528 bool commit = false;
3529 // Paste notes, if available
3530 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3531 if (m != selection.midi_notes.end()) {
3532 ctx.counts.increase_n_notes();
3533 if (!(*m)->empty()) {
3536 paste_internal(pos, ctx.count, ctx.times, **m);
3539 // Paste control points to automation children, if available
3540 typedef RouteTimeAxisView::AutomationTracks ATracks;
3541 const ATracks& atracks = midi_view()->automation_tracks();
3542 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3543 if (a->second->paste(pos, selection, ctx, sub_num)) {
3545 trackview.editor().begin_reversible_command (Operations::paste);
3552 trackview.editor().commit_reversible_command ();
3557 /** This method handles undo */
3559 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3565 start_note_diff_command (_("paste"));
3567 const Evoral::Beats snap_beats = get_grid_beats(pos);
3568 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3569 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3570 const Evoral::Beats duration = last_time - first_time;
3571 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3572 const Evoral::Beats paste_offset = snap_duration * paste_count;
3573 const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
3574 Evoral::Beats end_point = Evoral::Beats();
3576 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3579 duration, pos, _region->position(),
3582 clear_editor_note_selection ();
3584 for (int n = 0; n < (int) times; ++n) {
3586 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3588 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3589 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3590 copied_note->set_id (Evoral::next_event_id());
3592 /* make all newly added notes selected */
3594 note_diff_add_note (copied_note, true);
3595 end_point = copied_note->end_time();
3599 /* if we pasted past the current end of the region, extend the region */
3601 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3602 framepos_t region_end = _region->position() + _region->length() - 1;
3604 if (end_frame > region_end) {
3606 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3608 _region->clear_changes ();
3609 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3610 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3611 trackview.session()->add_command (new StatefulDiffCommand (_region));
3617 struct EventNoteTimeEarlyFirstComparator {
3618 bool operator() (NoteBase* a, NoteBase* b) {
3619 return a->note()->time() < b->note()->time();
3624 MidiRegionView::time_sort_events ()
3626 if (!_sort_needed) {
3630 EventNoteTimeEarlyFirstComparator cmp;
3633 _sort_needed = false;
3637 MidiRegionView::goto_next_note (bool add_to_selection)
3639 bool use_next = false;
3641 if (_events.back()->selected()) {
3645 time_sort_events ();
3647 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3648 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3650 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3651 if ((*i)->selected()) {
3654 } else if (use_next) {
3655 if (channel_mask & (1 << (*i)->note()->channel())) {
3656 if (!add_to_selection) {
3659 note_selected (*i, true, false);
3666 /* use the first one */
3668 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3669 unique_select (_events.front());
3674 MidiRegionView::goto_previous_note (bool add_to_selection)
3676 bool use_next = false;
3678 if (_events.front()->selected()) {
3682 time_sort_events ();
3684 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3685 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3687 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3688 if ((*i)->selected()) {
3691 } else if (use_next) {
3692 if (channel_mask & (1 << (*i)->note()->channel())) {
3693 if (!add_to_selection) {
3696 note_selected (*i, true, false);
3703 /* use the last one */
3705 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3706 unique_select (*(_events.rbegin()));
3711 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3713 bool had_selected = false;
3715 time_sort_events ();
3717 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3718 if ((*i)->selected()) {
3719 selected.insert ((*i)->note());
3720 had_selected = true;
3724 if (allow_all_if_none_selected && !had_selected) {
3725 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3726 selected.insert ((*i)->note());
3732 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3734 x = std::max(0.0, x);
3736 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3741 _note_group->canvas_to_item (x, y);
3743 PublicEditor& editor = trackview.editor ();
3745 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3747 const int32_t divisions = editor.get_grid_music_divisions (state);
3748 const bool shift_snap = midi_view()->note_mode() != Percussive;
3749 const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap);
3751 /* prevent Percussive mode from displaying a ghost hit at region end */
3752 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3753 _ghost_note->hide();
3754 hide_verbose_cursor ();
3758 /* ghost note may have been snapped before region */
3759 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3760 _ghost_note->hide();
3763 } else if (_ghost_note) {
3764 _ghost_note->show();
3767 /* calculate time in beats relative to start of source */
3768 const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3770 _ghost_note->note()->set_time (snapped_beats);
3771 _ghost_note->note()->set_length (length);
3772 _ghost_note->note()->set_note (y_to_note (y));
3773 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3774 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3775 /* the ghost note does not appear in ghost regions, so pass false in here */
3776 update_note (_ghost_note, false);
3778 show_verbose_cursor (_ghost_note->note ());
3782 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3784 remove_ghost_note ();
3786 boost::shared_ptr<NoteType> g (new NoteType);
3787 if (midi_view()->note_mode() == Sustained) {
3788 _ghost_note = new Note (*this, _note_group, g);
3790 _ghost_note = new Hit (*this, _note_group, 10, g);
3792 _ghost_note->set_ignore_events (true);
3793 _ghost_note->set_outline_color (0x000000aa);
3794 update_ghost_note (x, y, state);
3795 _ghost_note->show ();
3797 show_verbose_cursor (_ghost_note->note ());
3801 MidiRegionView::remove_ghost_note ()
3808 MidiRegionView::hide_verbose_cursor ()
3810 trackview.editor().verbose_cursor()->hide ();
3811 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3813 mtv->set_note_highlight (NO_MIDI_NOTE);
3818 MidiRegionView::snap_changed ()
3824 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3828 MidiRegionView::drop_down_keys ()
3830 _mouse_state = None;
3834 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3836 /* XXX: This is dead code. What was it for? */
3838 double note = y_to_note(y);
3840 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3842 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3844 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3845 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3846 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3847 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3852 bool add_mrv_selection = false;
3854 if (_selection.empty()) {
3855 add_mrv_selection = true;
3858 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3859 if (_selection.insert (*i).second) {
3860 (*i)->set_selected (true);
3864 if (add_mrv_selection) {
3865 PublicEditor& editor (trackview.editor());
3866 editor.get_selection().add (this);
3871 MidiRegionView::color_handler ()
3873 RegionView::color_handler ();
3875 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
3876 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
3878 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3879 (*i)->set_selected ((*i)->selected()); // will change color
3882 /* XXX probably more to do here */
3886 MidiRegionView::enable_display (bool yn)
3888 RegionView::enable_display (yn);
3892 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3894 if (_step_edit_cursor == 0) {
3895 ArdourCanvas::Item* const group = get_canvas_group();
3897 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3898 _step_edit_cursor->set_y0 (0);
3899 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3900 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3901 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3904 move_step_edit_cursor (pos);
3905 _step_edit_cursor->show ();
3909 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3911 _step_edit_cursor_position = pos;
3913 if (_step_edit_cursor) {
3914 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3915 _step_edit_cursor->set_x0 (pixel);
3916 set_step_edit_cursor_width (_step_edit_cursor_width);
3921 MidiRegionView::hide_step_edit_cursor ()
3923 if (_step_edit_cursor) {
3924 _step_edit_cursor->hide ();
3929 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3931 _step_edit_cursor_width = beats;
3933 if (_step_edit_cursor) {
3934 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3935 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3936 - region_beats_to_region_frames (_step_edit_cursor_position)));
3940 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3941 * @param w Source that the data will end up in.
3944 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3946 if (!_active_notes) {
3947 /* we aren't actively being recorded to */
3951 boost::shared_ptr<MidiSource> src = w.lock ();
3952 if (!src || src != midi_region()->midi_source()) {
3953 /* recorded data was not destined for our source */
3957 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3959 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3961 framepos_t back = max_framepos;
3963 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3964 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
3966 if (ev.is_channel_event()) {
3967 if (get_channel_mode() == FilterChannels) {
3968 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3974 /* convert from session frames to source beats */
3975 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3976 ev.time() - src->timeline_position() + _region->start());
3978 if (ev.type() == MIDI_CMD_NOTE_ON) {
3979 boost::shared_ptr<NoteType> note (
3980 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3982 add_note (note, true);
3984 /* fix up our note range */
3985 if (ev.note() < _current_range_min) {
3986 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3987 } else if (ev.note() > _current_range_max) {
3988 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3991 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3992 resolve_note (ev.note (), time_beats);
3998 midi_stream_view()->check_record_layers (region(), back);
4002 MidiRegionView::trim_front_starting ()
4004 /* We used to eparent the note group to the region view's parent, so that it didn't change.
4010 MidiRegionView::trim_front_ending ()
4012 if (_region->start() < 0) {
4013 /* Trim drag made start time -ve; fix this */
4014 midi_region()->fix_negative_start ();
4019 MidiRegionView::edit_patch_change (PatchChange* pc)
4021 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4023 int response = d.run();
4026 case Gtk::RESPONSE_ACCEPT:
4028 case Gtk::RESPONSE_REJECT:
4029 delete_patch_change (pc);
4035 change_patch_change (pc->patch(), d.patch ());
4039 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4042 // sysyex object doesn't have a pointer to a sysex event
4043 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4044 // c->remove (sysex->sysex());
4045 // _model->apply_command (*trackview.session(), c);
4047 //_sys_exes.clear ();
4048 // display_sysexes();
4052 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4054 using namespace MIDI::Name;
4057 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4059 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4061 MIDI::Name::PatchPrimaryKey patch_key;
4062 get_patch_key_at(n->time(), n->channel(), patch_key);
4063 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4066 patch_key.program(),
4072 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4074 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4075 (int) n->channel() + 1,
4076 (int) n->velocity());
4082 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4083 uint8_t new_value) const
4085 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4087 mtv->set_note_highlight (new_value);
4090 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4094 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4096 show_verbose_cursor_for_new_note_value(n, n->note());
4100 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4102 trackview.editor().verbose_cursor()->set (text);
4103 trackview.editor().verbose_cursor()->show ();
4104 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4108 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4110 if (_model->notes().empty()) {
4111 return 0x40; // No notes, use default
4114 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4115 if (m == _model->notes().begin()) {
4116 // Before the start, use the velocity of the first note
4117 return (*m)->velocity();
4118 } else if (m == _model->notes().end()) {
4119 // Past the end, use the velocity of the last note
4121 return (*m)->velocity();
4124 // Interpolate velocity of surrounding notes
4125 MidiModel::Notes::const_iterator n = m;
4128 const double frac = ((time - (*n)->time()).to_double() /
4129 ((*m)->time() - (*n)->time()).to_double());
4131 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4134 /** @param p A session framepos.
4135 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4136 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4137 * @return beat duration of p snapped to the grid subdivision underneath it.
4140 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
4142 TempoMap& map (trackview.session()->tempo_map());
4143 double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4145 if (divisions != 0 && shift_snap) {
4146 const double qaf = map.quarter_note_at_frame (p + _region->position());
4147 /* Hack so that we always snap to the note that we are over, instead of snapping
4148 to the next one if we're more than halfway through the one we're over.
4150 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4151 const double rem = eqaf - qaf;
4153 eqaf -= grid_beats.to_double();
4156 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4158 return Evoral::Beats (eqaf - session_start_off);
4162 MidiRegionView::get_channel_mode () const
4164 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4165 return rtav->midi_track()->get_playback_channel_mode();
4169 MidiRegionView::get_selected_channels () const
4171 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4172 return rtav->midi_track()->get_playback_channel_mask();
4177 MidiRegionView::get_grid_beats(framepos_t pos) const
4179 PublicEditor& editor = trackview.editor();
4180 bool success = false;
4181 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4183 beats = Evoral::Beats(1);
4188 MidiRegionView::y_to_note (double y) const
4190 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4191 + _current_range_min;
4195 } else if (n > 127) {
4199 /* min due to rounding and/or off-by-one errors */
4200 return min ((uint8_t) n, _current_range_max);
4204 MidiRegionView::note_to_y(uint8_t note) const
4206 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;