2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "midi++/midnam_patch.h"
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
81 #include "patch_change.h"
83 #include "ui_config.h"
87 using namespace ARDOUR;
89 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
95 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
96 RouteTimeAxisView& tv,
97 boost::shared_ptr<MidiRegion> r,
100 : RegionView (parent, tv, r, spu, basic_color)
101 , _current_range_min(0)
102 , _current_range_max(0)
103 , _region_relative_time_converter(r->session().tempo_map(), r->position())
104 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
105 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
107 , _note_group (new ArdourCanvas::Container (group))
108 , _note_diff_command (0)
110 , _step_edit_cursor (0)
111 , _step_edit_cursor_width (1.0)
112 , _step_edit_cursor_position (0.0)
113 , _channel_selection_scoped_note (0)
116 , _sort_needed (true)
117 , _optimization_iterator (_events.end())
119 , _no_sound_notes (false)
120 , _last_display_zoom (0)
123 , _grabbed_keyboard (false)
126 , _mouse_changed_selection (false)
128 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
129 _note_group->raise_to_top();
130 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
132 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
133 connect_to_diskstream ();
136 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
137 RouteTimeAxisView& tv,
138 boost::shared_ptr<MidiRegion> r,
140 uint32_t basic_color,
142 TimeAxisViewItem::Visibility visibility)
143 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
144 , _current_range_min(0)
145 , _current_range_max(0)
146 , _region_relative_time_converter(r->session().tempo_map(), r->position())
147 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
148 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
150 , _note_group (new ArdourCanvas::Container (group))
151 , _note_diff_command (0)
153 , _step_edit_cursor (0)
154 , _step_edit_cursor_width (1.0)
155 , _step_edit_cursor_position (0.0)
156 , _channel_selection_scoped_note (0)
159 , _sort_needed (true)
160 , _optimization_iterator (_events.end())
162 , _no_sound_notes (false)
163 , _last_display_zoom (0)
166 , _grabbed_keyboard (false)
169 , _mouse_changed_selection (false)
171 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
172 _note_group->raise_to_top();
174 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
176 connect_to_diskstream ();
180 MidiRegionView::parameter_changed (std::string const & p)
182 if (p == "display-first-midi-bank-as-zero") {
183 if (_enable_display) {
189 MidiRegionView::MidiRegionView (const MidiRegionView& other)
190 : sigc::trackable(other)
192 , _current_range_min(0)
193 , _current_range_max(0)
194 , _region_relative_time_converter(other.region_relative_time_converter())
195 , _source_relative_time_converter(other.source_relative_time_converter())
196 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
198 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
199 , _note_diff_command (0)
201 , _step_edit_cursor (0)
202 , _step_edit_cursor_width (1.0)
203 , _step_edit_cursor_position (0.0)
204 , _channel_selection_scoped_note (0)
207 , _sort_needed (true)
208 , _optimization_iterator (_events.end())
210 , _no_sound_notes (false)
211 , _last_display_zoom (0)
214 , _grabbed_keyboard (false)
217 , _mouse_changed_selection (false)
222 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
223 : RegionView (other, boost::shared_ptr<Region> (region))
224 , _current_range_min(0)
225 , _current_range_max(0)
226 , _region_relative_time_converter(other.region_relative_time_converter())
227 , _source_relative_time_converter(other.source_relative_time_converter())
228 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
230 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
231 , _note_diff_command (0)
233 , _step_edit_cursor (0)
234 , _step_edit_cursor_width (1.0)
235 , _step_edit_cursor_position (0.0)
236 , _channel_selection_scoped_note (0)
239 , _sort_needed (true)
240 , _optimization_iterator (_events.end())
242 , _no_sound_notes (false)
243 , _last_display_zoom (0)
246 , _grabbed_keyboard (false)
249 , _mouse_changed_selection (false)
255 MidiRegionView::init (bool wfd)
257 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
260 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
261 midi_region()->midi_source(0)->load_model(lm);
264 _model = midi_region()->midi_source(0)->model();
265 _enable_display = false;
266 fill_color_name = "midi frame base";
268 RegionView::init (false);
270 //set_height (trackview.current_height());
273 region_sync_changed ();
274 region_resized (ARDOUR::bounds_change);
279 _enable_display = true;
282 display_model (_model);
286 reset_width_dependent_items (_pixel_width);
288 group->raise_to_top();
290 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
291 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
294 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
295 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
297 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
298 boost::bind (&MidiRegionView::snap_changed, this),
301 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
302 boost::bind (&MidiRegionView::mouse_mode_changed, this),
305 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
306 connect_to_diskstream ();
310 MidiRegionView::instrument_info () const
312 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
313 return route_ui->route()->instrument_info();
316 const boost::shared_ptr<ARDOUR::MidiRegion>
317 MidiRegionView::midi_region() const
319 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
323 MidiRegionView::connect_to_diskstream ()
325 midi_view()->midi_track()->DataRecorded.connect(
326 *this, invalidator(*this),
327 boost::bind (&MidiRegionView::data_recorded, this, _1),
332 MidiRegionView::canvas_group_event(GdkEvent* ev)
334 if (in_destructor || _recregion) {
338 if (!trackview.editor().internal_editing()) {
339 // not in internal edit mode, so just act like a normal region
340 return RegionView::canvas_group_event (ev);
346 case GDK_ENTER_NOTIFY:
347 _last_event_x = ev->crossing.x;
348 _last_event_y = ev->crossing.y;
349 enter_notify(&ev->crossing);
350 // set entered_regionview (among other things)
351 return RegionView::canvas_group_event (ev);
353 case GDK_LEAVE_NOTIFY:
354 _last_event_x = ev->crossing.x;
355 _last_event_y = ev->crossing.y;
356 leave_notify(&ev->crossing);
357 // reset entered_regionview (among other things)
358 return RegionView::canvas_group_event (ev);
361 if (scroll (&ev->scroll)) {
367 return key_press (&ev->key);
369 case GDK_KEY_RELEASE:
370 return key_release (&ev->key);
372 case GDK_BUTTON_PRESS:
373 return button_press (&ev->button);
375 case GDK_BUTTON_RELEASE:
376 r = button_release (&ev->button);
379 case GDK_MOTION_NOTIFY:
380 _last_event_x = ev->motion.x;
381 _last_event_y = ev->motion.y;
382 return motion (&ev->motion);
388 return RegionView::canvas_group_event (ev);
392 MidiRegionView::enter_notify (GdkEventCrossing* ev)
394 enter_internal (ev->state);
401 MidiRegionView::leave_notify (GdkEventCrossing*)
410 MidiRegionView::mouse_mode_changed ()
412 // Adjust frame colour (become more transparent for internal tools)
416 if (!trackview.editor().internal_editing()) {
417 /* Switched out of internal editing mode while entered.
418 Only necessary for leave as a mouse_mode_change over a region
419 automatically triggers an enter event. */
422 else if (trackview.editor().current_mouse_mode() == MouseContent) {
423 // hide cursor and ghost note after changing to internal edit mode
424 remove_ghost_note ();
426 /* XXX This is problematic as the function is executed for every region
427 and only for one region _entered_note can be true. Still it's
428 necessary as to hide the verbose cursor when we're changing from
429 draw mode to internal edit mode. These lines are the reason why
430 in some situations no verbose cursor is shown when we enter internal
431 edit mode over a note. */
432 if (!_entered_note) {
433 hide_verbose_cursor ();
440 MidiRegionView::enter_internal (uint32_t state)
442 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
443 // Show ghost note under pencil
444 create_ghost_note(_last_event_x, _last_event_y, state);
447 if (!_selection.empty()) {
448 // Grab keyboard for moving selected notes with arrow keys
449 Keyboard::magic_widget_grab_focus();
450 _grabbed_keyboard = true;
453 // Lower frame handles below notes so they don't steal events
454 if (frame_handle_start) {
455 frame_handle_start->lower_to_bottom();
457 if (frame_handle_end) {
458 frame_handle_end->lower_to_bottom();
463 MidiRegionView::leave_internal()
465 hide_verbose_cursor ();
466 remove_ghost_note ();
469 if (_grabbed_keyboard) {
470 Keyboard::magic_widget_drop_focus();
471 _grabbed_keyboard = false;
474 // Raise frame handles above notes so they catch events
475 if (frame_handle_start) {
476 frame_handle_start->raise_to_top();
478 if (frame_handle_end) {
479 frame_handle_end->raise_to_top();
484 MidiRegionView::button_press (GdkEventButton* ev)
486 if (ev->button != 1) {
490 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
491 MouseMode m = editor->current_mouse_mode();
493 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
494 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
497 if (_mouse_state != SelectTouchDragging) {
499 _pressed_button = ev->button;
500 _mouse_state = Pressed;
502 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
503 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
504 _mouse_state = AddDragging;
505 remove_ghost_note ();
506 hide_verbose_cursor ();
512 _pressed_button = ev->button;
513 _mouse_changed_selection = false;
519 MidiRegionView::button_release (GdkEventButton* ev)
521 double event_x, event_y;
523 if (ev->button != 1) {
530 group->canvas_to_item (event_x, event_y);
533 PublicEditor& editor = trackview.editor ();
535 _press_cursor_ctx.reset();
537 switch (_mouse_state) {
538 case Pressed: // Clicked
540 switch (editor.current_mouse_mode()) {
542 /* no motion occurred - simple click */
543 clear_editor_note_selection ();
544 _mouse_changed_selection = true;
550 _mouse_changed_selection = true;
551 clear_editor_note_selection ();
566 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
567 we don't want one when we were drag-selecting either. */
568 case SelectRectDragging:
569 editor.drags()->end_grab ((GdkEvent *) ev);
578 if (_mouse_changed_selection) {
579 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
580 trackview.editor().commit_reversible_selection_op ();
587 MidiRegionView::motion (GdkEventMotion* ev)
589 PublicEditor& editor = trackview.editor ();
591 if (!_entered_note) {
593 if (_mouse_state == AddDragging) {
595 remove_ghost_note ();
598 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
599 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
600 _mouse_state != AddDragging) {
602 create_ghost_note (ev->x, ev->y, ev->state);
604 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
605 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
607 update_ghost_note (ev->x, ev->y, ev->state);
609 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
611 remove_ghost_note ();
612 hide_verbose_cursor ();
614 } else if (editor.current_mouse_mode() == MouseDraw) {
617 update_ghost_note (ev->x, ev->y, ev->state);
620 create_ghost_note (ev->x, ev->y, ev->state);
625 /* any motion immediately hides velocity text that may have been visible */
627 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
628 (*i)->hide_velocity ();
631 switch (_mouse_state) {
634 if (_pressed_button == 1) {
636 MouseMode m = editor.current_mouse_mode();
638 if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
639 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
640 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
641 clear_editor_note_selection ();
642 _mouse_changed_selection = true;
644 _mouse_state = SelectRectDragging;
646 } else if (m == MouseRange) {
647 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
648 _mouse_state = SelectVerticalDragging;
655 case SelectRectDragging:
656 case SelectVerticalDragging:
658 editor.drags()->motion_handler ((GdkEvent *) ev, false);
661 case SelectTouchDragging:
669 /* we may be dragging some non-note object (eg. patch-change, sysex)
672 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
677 MidiRegionView::scroll (GdkEventScroll* ev)
679 if (_selection.empty()) {
683 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
684 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
685 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
686 * through so that it still works for navigation.
691 hide_verbose_cursor ();
693 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
694 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
695 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
697 if (ev->direction == GDK_SCROLL_UP) {
698 change_velocities (true, fine, false, together);
699 } else if (ev->direction == GDK_SCROLL_DOWN) {
700 change_velocities (false, fine, false, together);
702 /* left, right: we don't use them */
710 MidiRegionView::key_press (GdkEventKey* ev)
712 /* since GTK bindings are generally activated on press, and since
713 detectable auto-repeat is the name of the game and only sends
714 repeated presses, carry out key actions at key press, not release.
717 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
719 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
720 _mouse_state = SelectTouchDragging;
723 } else if (ev->keyval == GDK_Escape && unmodified) {
724 clear_editor_note_selection ();
727 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
729 bool start = (ev->keyval == GDK_comma);
730 bool end = (ev->keyval == GDK_period);
731 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
732 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
734 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
738 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
740 if (_selection.empty()) {
747 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
749 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
751 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
752 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
754 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
757 trackview.editor().commit_reversible_selection_op();
761 } else if (ev->keyval == GDK_Up) {
763 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
764 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
765 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
767 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
768 change_velocities (true, fine, allow_smush, together);
770 transpose (true, fine, allow_smush);
774 } else if (ev->keyval == GDK_Down) {
776 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
777 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
778 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
780 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
781 change_velocities (false, fine, allow_smush, together);
783 transpose (false, fine, allow_smush);
787 } else if (ev->keyval == GDK_Left) {
789 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
790 nudge_notes (false, fine);
793 } else if (ev->keyval == GDK_Right) {
795 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
796 nudge_notes (true, fine);
799 } else if (ev->keyval == GDK_c && unmodified) {
803 } else if (ev->keyval == GDK_v && unmodified) {
812 MidiRegionView::key_release (GdkEventKey* ev)
814 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
822 MidiRegionView::channel_edit ()
824 if (_selection.empty()) {
828 /* pick a note somewhat at random (since Selection is a set<>) to
829 * provide the "current" channel for the dialog.
832 uint8_t current_channel = (*_selection.begin())->note()->channel ();
833 MidiChannelDialog channel_dialog (current_channel);
834 int ret = channel_dialog.run ();
837 case Gtk::RESPONSE_OK:
843 uint8_t new_channel = channel_dialog.active_channel ();
845 start_note_diff_command (_("channel edit"));
847 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
848 Selection::iterator next = i;
850 change_note_channel (*i, new_channel);
858 MidiRegionView::velocity_edit ()
860 if (_selection.empty()) {
864 /* pick a note somewhat at random (since Selection is a set<>) to
865 * provide the "current" velocity for the dialog.
868 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
869 MidiVelocityDialog velocity_dialog (current_velocity);
870 int ret = velocity_dialog.run ();
873 case Gtk::RESPONSE_OK:
879 uint8_t new_velocity = velocity_dialog.velocity ();
881 start_note_diff_command (_("velocity edit"));
883 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
884 Selection::iterator next = i;
886 change_note_velocity (*i, new_velocity, false);
894 MidiRegionView::show_list_editor ()
897 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
899 _list_editor->present ();
902 /** Add a note to the model, and the view, at a canvas (click) coordinate.
903 * \param t time in frames relative to the position of the region
904 * \param y vertical position in pixels
905 * \param length duration of the note in beats
906 * \param snap_t true to snap t to the grid, otherwise false.
909 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
911 if (length < 2 * DBL_EPSILON) {
915 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
916 MidiStreamView* const view = mtv->midi_view();
917 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
923 // Start of note in frames relative to region start
924 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
925 Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
927 const double note = view->y_to_note(y);
928 const uint8_t chan = mtv->get_channel_for_add();
929 const uint8_t velocity = get_velocity_for_add(beat_time);
931 const boost::shared_ptr<NoteType> new_note(
932 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
934 if (_model->contains (new_note)) {
938 view->update_note_range(new_note->note());
940 start_note_diff_command(_("add note"));
942 clear_editor_note_selection ();
943 note_diff_add_note (new_note, true, false);
947 play_midi_note (new_note);
951 MidiRegionView::clear_events ()
953 // clear selection without signaling
954 clear_selection_internal ();
957 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
958 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
963 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
968 _patch_changes.clear();
970 _optimization_iterator = _events.end();
974 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
978 content_connection.disconnect ();
979 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
980 /* Don't signal as nobody else needs to know until selection has been altered. */
983 if (_enable_display) {
989 MidiRegionView::start_note_diff_command (string name)
991 if (!_note_diff_command) {
992 trackview.editor().begin_reversible_command (name);
993 _note_diff_command = _model->new_note_diff_command (name);
998 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1000 if (_note_diff_command) {
1001 _note_diff_command->add (note);
1004 _marked_for_selection.insert(note);
1006 if (show_velocity) {
1007 _marked_for_velocity.insert(note);
1012 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1014 if (_note_diff_command && ev->note()) {
1015 _note_diff_command->remove(ev->note());
1020 MidiRegionView::note_diff_add_change (NoteBase* ev,
1021 MidiModel::NoteDiffCommand::Property property,
1024 if (_note_diff_command) {
1025 _note_diff_command->change (ev->note(), property, val);
1030 MidiRegionView::note_diff_add_change (NoteBase* ev,
1031 MidiModel::NoteDiffCommand::Property property,
1034 if (_note_diff_command) {
1035 _note_diff_command->change (ev->note(), property, val);
1040 MidiRegionView::apply_diff (bool as_subcommand)
1043 bool commit = false;
1045 if (!_note_diff_command) {
1049 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1050 // Mark all selected notes for selection when model reloads
1051 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1052 _marked_for_selection.insert((*i)->note());
1056 midi_view()->midi_track()->midi_playlist()->region_edited(
1057 _region, _note_diff_command);
1059 if (as_subcommand) {
1060 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1062 _model->apply_command (*trackview.session(), _note_diff_command);
1066 _note_diff_command = 0;
1068 if (add_or_remove) {
1069 _marked_for_selection.clear();
1072 _marked_for_velocity.clear();
1074 trackview.editor().commit_reversible_command ();
1079 MidiRegionView::abort_command()
1081 delete _note_diff_command;
1082 _note_diff_command = 0;
1083 clear_editor_note_selection();
1087 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1089 if (_optimization_iterator != _events.end()) {
1090 ++_optimization_iterator;
1093 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1094 return *_optimization_iterator;
1097 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1098 if ((*_optimization_iterator)->note() == note) {
1099 return *_optimization_iterator;
1106 /** This version finds any canvas note matching the supplied note. */
1108 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1110 Events::iterator it;
1112 for (it = _events.begin(); it != _events.end(); ++it) {
1113 if ((*it)->note()->id() == id) {
1122 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1124 MidiModel::Notes notes;
1125 _model->get_notes (notes, op, val, chan_mask);
1127 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1128 NoteBase* cne = find_canvas_note (*n);
1136 MidiRegionView::redisplay_model()
1138 if (_active_notes) {
1139 // Currently recording
1140 const framecnt_t zoom = trackview.editor().get_current_zoom();
1141 if (zoom != _last_display_zoom) {
1142 /* Update resolved canvas notes to reflect changes in zoom without
1143 touching model. Leave active notes (with length 0) alone since
1144 they are being extended. */
1145 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1146 if ((*i)->note()->length() > 0) {
1150 _last_display_zoom = zoom;
1159 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1160 (*i)->invalidate ();
1163 MidiModel::ReadLock lock(_model->read_lock());
1165 MidiModel::Notes& notes (_model->notes());
1166 _optimization_iterator = _events.begin();
1168 bool empty_when_starting = _events.empty();
1171 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1173 boost::shared_ptr<NoteType> note (*n);
1176 if (note_in_region_range (note, visible)) {
1178 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1190 cne = add_note (note, visible);
1193 set<Evoral::event_id_t>::iterator it;
1194 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1195 if ((*it) == note->id()) {
1196 add_to_selection (cne);
1202 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1209 /* remove note items that are no longer valid */
1211 if (!empty_when_starting) {
1212 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1213 if (!(*i)->valid ()) {
1215 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1216 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1218 gr->remove_note (*i);
1223 i = _events.erase (i);
1231 _patch_changes.clear();
1235 display_patch_changes ();
1237 _marked_for_selection.clear ();
1238 _marked_for_velocity.clear ();
1239 _pending_note_selection.clear ();
1241 /* we may have caused _events to contain things out of order (e.g. if a note
1242 moved earlier or later). we don't generally need them in time order, but
1243 make a note that a sort is required for those cases that require it.
1246 _sort_needed = true;
1250 MidiRegionView::display_patch_changes ()
1252 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1253 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1255 for (uint8_t i = 0; i < 16; ++i) {
1256 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1260 /** @param active_channel true to display patch changes fully, false to display
1261 * them `greyed-out' (as on an inactive channel)
1264 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1266 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1268 if ((*i)->channel() != channel) {
1272 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1273 add_canvas_patch_change (*i, patch_name, active_channel);
1278 MidiRegionView::display_sysexes()
1280 bool have_periodic_system_messages = false;
1281 bool display_periodic_messages = true;
1283 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1285 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1286 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1287 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1290 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1291 have_periodic_system_messages = true;
1297 if (have_periodic_system_messages) {
1298 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1300 /* get an approximate value for the number of samples per video frame */
1302 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1304 /* if we are zoomed out beyond than the cutoff (i.e. more
1305 * frames per pixel than frames per 4 video frames), don't
1306 * show periodic sysex messages.
1309 if (zoom > (video_frame*4)) {
1310 display_periodic_messages = false;
1314 display_periodic_messages = false;
1317 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1319 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1320 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1322 Evoral::Beats time = (*i)->time();
1325 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1326 if (!display_periodic_messages) {
1334 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1335 str << int((*i)->buffer()[b]);
1336 if (b != (*i)->size() -1) {
1340 string text = str.str();
1342 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1344 double height = midi_stream_view()->contents_height();
1346 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1347 // SysEx canvas object!!!
1349 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1350 new SysEx (*this, _note_group, text, height, x, 1.0));
1352 // Show unless message is beyond the region bounds
1353 if (time - _region->start() >= _region->length() || time < _region->start()) {
1359 _sys_exes.push_back(sysex);
1363 MidiRegionView::~MidiRegionView ()
1365 in_destructor = true;
1367 hide_verbose_cursor ();
1369 delete _list_editor;
1371 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1373 if (_active_notes) {
1381 delete _note_diff_command;
1382 delete _step_edit_cursor;
1386 MidiRegionView::region_resized (const PropertyChange& what_changed)
1388 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1390 if (what_changed.contains (ARDOUR::Properties::position)) {
1391 _region_relative_time_converter.set_origin_b(_region->position());
1392 _region_relative_time_converter_double.set_origin_b(_region->position());
1393 /* reset_width dependent_items() redisplays model */
1397 if (what_changed.contains (ARDOUR::Properties::start) ||
1398 what_changed.contains (ARDOUR::Properties::position)) {
1399 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1401 /* catch end and start trim so we can update the view*/
1402 if (!what_changed.contains (ARDOUR::Properties::start) &&
1403 what_changed.contains (ARDOUR::Properties::length)) {
1404 enable_display (true);
1405 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1406 what_changed.contains (ARDOUR::Properties::length)) {
1407 enable_display (true);
1412 MidiRegionView::reset_width_dependent_items (double pixel_width)
1414 RegionView::reset_width_dependent_items(pixel_width);
1416 if (_enable_display) {
1420 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1421 if ((*x)->canvas_item()->width() >= _pixel_width) {
1428 move_step_edit_cursor (_step_edit_cursor_position);
1429 set_step_edit_cursor_width (_step_edit_cursor_width);
1433 MidiRegionView::set_height (double height)
1435 double old_height = _height;
1436 RegionView::set_height(height);
1438 apply_note_range (midi_stream_view()->lowest_note(),
1439 midi_stream_view()->highest_note(),
1440 height != old_height);
1443 name_text->raise_to_top();
1446 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1447 (*x)->set_height (midi_stream_view()->contents_height());
1450 if (_step_edit_cursor) {
1451 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1456 /** Apply the current note range from the stream view
1457 * by repositioning/hiding notes as necessary
1460 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1462 if (!_enable_display) {
1466 if (!force && _current_range_min == min && _current_range_max == max) {
1470 _current_range_min = min;
1471 _current_range_max = max;
1473 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1474 NoteBase* event = *i;
1475 boost::shared_ptr<NoteType> note (event->note());
1477 if (note->note() < _current_range_min ||
1478 note->note() > _current_range_max) {
1484 if (Note* cnote = dynamic_cast<Note*>(event)) {
1486 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1487 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1489 if (y0 < 0 || y1 >= _height) {
1490 /* During DnD, the region uses the 'old/current'
1491 * midi_stream_view()'s range and its position/height calculation.
1493 * Ideally DnD would decouple the midi_stream_view() for the
1494 * region(s) being dragged and set it to the target's range
1495 * (or in case of the drop-zone, FullRange).
1496 * but I don't see how this can be done without major rework.
1498 * For now, just prevent visual bleeding of events in case
1499 * the target-track is smaller.
1507 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1514 MidiRegionView::add_ghost (TimeAxisView& tv)
1516 double unit_position = _region->position () / samples_per_pixel;
1517 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1518 MidiGhostRegion* ghost;
1520 if (mtv && mtv->midi_view()) {
1521 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1522 to allow having midi notes on top of note lines and waveforms.
1524 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1526 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1529 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1530 ghost->add_note(*i);
1533 ghost->set_colors ();
1534 ghost->set_height ();
1535 ghost->set_duration (_region->length() / samples_per_pixel);
1536 ghosts.push_back (ghost);
1542 /** Begin tracking note state for successive calls to add_event
1545 MidiRegionView::begin_write()
1547 if (_active_notes) {
1548 delete[] _active_notes;
1550 _active_notes = new Note*[128];
1551 for (unsigned i = 0; i < 128; ++i) {
1552 _active_notes[i] = 0;
1557 /** Destroy note state for add_event
1560 MidiRegionView::end_write()
1562 delete[] _active_notes;
1564 _marked_for_selection.clear();
1565 _marked_for_velocity.clear();
1569 /** Resolve an active MIDI note (while recording).
1572 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1574 if (midi_view()->note_mode() != Sustained) {
1578 if (_active_notes && _active_notes[note]) {
1579 /* Set note length so update_note() works. Note this is a local note
1580 for recording, not from a model, so we can safely mess with it. */
1581 _active_notes[note]->note()->set_length(
1582 end_time - _active_notes[note]->note()->time());
1584 /* End time is relative to the region being recorded. */
1585 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1587 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1588 _active_notes[note]->set_outline_all ();
1589 _active_notes[note] = 0;
1594 /** Extend active notes to rightmost edge of region (if length is changed)
1597 MidiRegionView::extend_active_notes()
1599 if (!_active_notes) {
1603 for (unsigned i = 0; i < 128; ++i) {
1604 if (_active_notes[i]) {
1605 _active_notes[i]->set_x1(
1606 trackview.editor().sample_to_pixel(_region->length()));
1612 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1614 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1618 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1620 if (!route_ui || !route_ui->midi_track()) {
1624 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1628 /* NotePlayer deletes itself */
1632 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1634 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1635 start_playing_midi_chord(notes);
1639 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1641 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1645 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1647 if (!route_ui || !route_ui->midi_track()) {
1651 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1653 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1662 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1664 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1666 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1667 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1668 note->time().to_double() > midi_reg->start_beats() + midi_reg->length_beats());
1670 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1671 (note->note() <= midi_stream_view()->highest_note());
1677 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1681 if ((sus = dynamic_cast<Note*>(note))) {
1682 update_sustained(sus, update_ghost_regions);
1683 } else if ((hit = dynamic_cast<Hit*>(note))) {
1684 update_hit(hit, update_ghost_regions);
1688 /** Update a canvas note's size from its model note.
1689 * @param ev Canvas note to update.
1690 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1693 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1695 TempoMap& map (trackview.session()->tempo_map());
1696 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1697 boost::shared_ptr<NoteType> note = ev->note();
1699 const double session_source_start = _region->quarter_note() - mr->start_beats();
1700 const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1702 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1704 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1707 /* trim note display to not overlap the end of its region */
1708 if (note->length() > 0) {
1709 double note_end_time = note->end_time().to_double();
1711 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1712 note_end_time = mr->start_beats() + mr->length_beats();
1715 const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
1717 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1719 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1722 y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1);
1724 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1726 if (!note->length()) {
1727 if (_active_notes && note->note() < 128) {
1728 Note* const old_rect = _active_notes[note->note()];
1730 /* There is an active note on this key, so we have a stuck
1731 note. Finish the old rectangle here. */
1732 old_rect->set_x1 (x1);
1733 old_rect->set_outline_all ();
1735 _active_notes[note->note()] = ev;
1737 /* outline all but right edge */
1738 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1739 ArdourCanvas::Rectangle::TOP|
1740 ArdourCanvas::Rectangle::LEFT|
1741 ArdourCanvas::Rectangle::BOTTOM));
1743 /* outline all edges */
1744 ev->set_outline_all ();
1747 // Update color in case velocity has changed
1748 const uint32_t base_col = ev->base_color();
1749 ev->set_fill_color(base_col);
1750 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1752 if (update_ghost_regions) {
1753 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1754 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1756 gr->update_note (ev);
1763 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1765 boost::shared_ptr<NoteType> note = ev->note();
1767 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1768 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1770 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1771 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1772 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1774 // see DnD note in MidiRegionView::apply_note_range() above
1775 if (y <= 0 || y >= _height) {
1781 ev->set_position (ArdourCanvas::Duple (x, y));
1782 ev->set_height (diamond_size);
1784 // Update color in case velocity has changed
1785 const uint32_t base_col = ev->base_color();
1786 ev->set_fill_color(base_col);
1787 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1789 if (update_ghost_regions) {
1790 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1791 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1793 gr->update_hit (ev);
1799 /** Add a MIDI note to the view (with length).
1801 * If in sustained mode, notes with length 0 will be considered active
1802 * notes, and resolve_note should be called when the corresponding note off
1803 * event arrives, to properly display the note.
1806 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1808 NoteBase* event = 0;
1810 if (midi_view()->note_mode() == Sustained) {
1812 Note* ev_rect = new Note (*this, _note_group, note);
1814 update_sustained (ev_rect);
1818 } else if (midi_view()->note_mode() == Percussive) {
1820 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1822 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1824 update_hit (ev_diamond);
1833 MidiGhostRegion* gr;
1835 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1836 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1837 gr->add_note(event);
1841 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1842 note_selected(event, true);
1845 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1846 event->show_velocity();
1849 event->on_channel_selection_change (get_selected_channels());
1850 _events.push_back(event);
1859 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1860 MidiStreamView* const view = mtv->midi_view();
1862 view->update_note_range (note->note());
1867 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1868 Evoral::Beats pos, Evoral::Beats len)
1870 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1872 /* potentially extend region to hold new note */
1874 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1875 framepos_t region_end = _region->last_frame();
1877 if (end_frame > region_end) {
1878 /* XX sets length in beats from audio space. make musical */
1879 _region->set_length (end_frame - _region->position(), 0);
1882 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1883 MidiStreamView* const view = mtv->midi_view();
1885 view->update_note_range(new_note->note());
1887 _marked_for_selection.clear ();
1889 start_note_diff_command (_("step add"));
1891 clear_editor_note_selection ();
1892 note_diff_add_note (new_note, true, false);
1896 // last_step_edit_note = new_note;
1900 MidiRegionView::step_sustain (Evoral::Beats beats)
1902 change_note_lengths (false, false, beats, false, true);
1905 /** Add a new patch change flag to the canvas.
1906 * @param patch the patch change to add
1907 * @param the text to display in the flag
1908 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1911 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1913 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1914 const double x = trackview.editor().sample_to_pixel (region_frames);
1916 double const height = midi_stream_view()->contents_height();
1918 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1919 // so we need to do something more sophisticated to keep its color
1920 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1923 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1924 new PatchChange(*this, group,
1931 if (patch_change->item().width() < _pixel_width) {
1932 // Show unless patch change is beyond the region bounds
1933 if (region_frames < 0 || region_frames >= _region->length()) {
1934 patch_change->hide();
1936 patch_change->show();
1939 patch_change->hide ();
1942 _patch_changes.push_back (patch_change);
1945 MIDI::Name::PatchPrimaryKey
1946 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1948 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1951 /// Return true iff @p pc applies to the given time on the given channel.
1953 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1955 return pc->time() <= time && pc->channel() == channel;
1959 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1961 // The earliest event not before time
1962 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1964 // Go backwards until we find the latest PC for this channel, or the start
1965 while (i != _model->patch_changes().begin() &&
1966 (i == _model->patch_changes().end() ||
1967 !patch_applies(*i, time, channel))) {
1971 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1972 key.set_bank((*i)->bank());
1973 key.set_program((*i)->program ());
1981 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1983 string name = _("alter patch change");
1984 trackview.editor().begin_reversible_command (name);
1985 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1987 if (pc.patch()->program() != new_patch.program()) {
1988 c->change_program (pc.patch (), new_patch.program());
1991 int const new_bank = new_patch.bank();
1992 if (pc.patch()->bank() != new_bank) {
1993 c->change_bank (pc.patch (), new_bank);
1996 _model->apply_command (*trackview.session(), c);
1997 trackview.editor().commit_reversible_command ();
1999 _patch_changes.clear ();
2000 display_patch_changes ();
2004 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2006 string name = _("alter patch change");
2007 trackview.editor().begin_reversible_command (name);
2008 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2010 if (old_change->time() != new_change.time()) {
2011 c->change_time (old_change, new_change.time());
2014 if (old_change->channel() != new_change.channel()) {
2015 c->change_channel (old_change, new_change.channel());
2018 if (old_change->program() != new_change.program()) {
2019 c->change_program (old_change, new_change.program());
2022 if (old_change->bank() != new_change.bank()) {
2023 c->change_bank (old_change, new_change.bank());
2026 _model->apply_command (*trackview.session(), c);
2027 trackview.editor().commit_reversible_command ();
2029 _patch_changes.clear ();
2030 display_patch_changes ();
2033 /** Add a patch change to the region.
2034 * @param t Time in frames relative to region position
2035 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2036 * MidiTimeAxisView::get_channel_for_add())
2039 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2041 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2042 string name = _("add patch change");
2044 trackview.editor().begin_reversible_command (name);
2045 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2046 c->add (MidiModel::PatchChangePtr (
2047 new Evoral::PatchChange<Evoral::Beats> (
2048 absolute_frames_to_source_beats (_region->position() + t),
2049 mtv->get_channel_for_add(), patch.program(), patch.bank()
2054 _model->apply_command (*trackview.session(), c);
2055 trackview.editor().commit_reversible_command ();
2057 _patch_changes.clear ();
2058 display_patch_changes ();
2062 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2064 trackview.editor().begin_reversible_command (_("move patch change"));
2065 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2066 c->change_time (pc.patch (), t);
2067 _model->apply_command (*trackview.session(), c);
2068 trackview.editor().commit_reversible_command ();
2070 _patch_changes.clear ();
2071 display_patch_changes ();
2075 MidiRegionView::delete_patch_change (PatchChange* pc)
2077 trackview.editor().begin_reversible_command (_("delete patch change"));
2078 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2079 c->remove (pc->patch ());
2080 _model->apply_command (*trackview.session(), c);
2081 trackview.editor().commit_reversible_command ();
2083 _patch_changes.clear ();
2084 display_patch_changes ();
2088 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2090 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2092 key.set_bank(key.bank() + delta);
2094 key.set_program(key.program() + delta);
2096 change_patch_change(patch, key);
2100 MidiRegionView::note_deleted (NoteBase* cne)
2102 if (_entered_note && cne == _entered_note) {
2106 if (_selection.empty()) {
2110 _selection.erase (cne);
2114 MidiRegionView::delete_selection()
2116 if (_selection.empty()) {
2120 if (trackview.editor().drags()->active()) {
2124 start_note_diff_command (_("delete selection"));
2126 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2127 if ((*i)->selected()) {
2128 _note_diff_command->remove((*i)->note());
2136 hide_verbose_cursor ();
2140 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2142 start_note_diff_command (_("delete note"));
2143 _note_diff_command->remove (n);
2146 hide_verbose_cursor ();
2150 MidiRegionView::clear_editor_note_selection ()
2152 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2153 PublicEditor& editor(trackview.editor());
2154 editor.get_selection().clear_midi_notes();
2158 MidiRegionView::clear_selection ()
2160 clear_selection_internal();
2161 PublicEditor& editor(trackview.editor());
2162 editor.get_selection().remove(this);
2166 MidiRegionView::clear_selection_internal ()
2168 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2170 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2171 (*i)->set_selected(false);
2172 (*i)->hide_velocity();
2177 // Clearing selection entirely, ungrab keyboard
2178 Keyboard::magic_widget_drop_focus();
2179 _grabbed_keyboard = false;
2184 MidiRegionView::unique_select(NoteBase* ev)
2186 clear_editor_note_selection();
2187 add_to_selection(ev);
2191 MidiRegionView::select_all_notes ()
2193 clear_editor_note_selection ();
2195 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2196 add_to_selection (*i);
2201 MidiRegionView::select_range (framepos_t start, framepos_t end)
2203 clear_editor_note_selection ();
2205 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2206 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2207 if (t >= start && t <= end) {
2208 add_to_selection (*i);
2214 MidiRegionView::invert_selection ()
2216 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2217 if ((*i)->selected()) {
2218 remove_from_selection(*i);
2220 add_to_selection (*i);
2225 /** Used for selection undo/redo.
2226 The requested notes most likely won't exist in the view until the next model redisplay.
2229 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2232 list<Evoral::event_id_t>::iterator n;
2234 for (n = notes.begin(); n != notes.end(); ++n) {
2235 if ((cne = find_canvas_note(*n)) != 0) {
2236 add_to_selection (cne);
2238 _pending_note_selection.insert(*n);
2244 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2246 bool have_selection = !_selection.empty();
2247 uint8_t low_note = 127;
2248 uint8_t high_note = 0;
2249 MidiModel::Notes& notes (_model->notes());
2250 _optimization_iterator = _events.begin();
2252 if (extend && !have_selection) {
2256 /* scan existing selection to get note range */
2258 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2259 if ((*i)->note()->note() < low_note) {
2260 low_note = (*i)->note()->note();
2262 if ((*i)->note()->note() > high_note) {
2263 high_note = (*i)->note()->note();
2268 clear_editor_note_selection ();
2270 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2271 /* only note previously selected is the one we are
2272 * reselecting. treat this as cancelling the selection.
2279 low_note = min (low_note, notenum);
2280 high_note = max (high_note, notenum);
2283 _no_sound_notes = true;
2285 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2287 boost::shared_ptr<NoteType> note (*n);
2289 bool select = false;
2291 if (((1 << note->channel()) & channel_mask) != 0) {
2293 if ((note->note() >= low_note && note->note() <= high_note)) {
2296 } else if (note->note() == notenum) {
2302 if ((cne = find_canvas_note (note)) != 0) {
2303 // extend is false because we've taken care of it,
2304 // since it extends by time range, not pitch.
2305 note_selected (cne, add, false);
2309 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2313 _no_sound_notes = false;
2317 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2319 MidiModel::Notes& notes (_model->notes());
2320 _optimization_iterator = _events.begin();
2322 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2324 boost::shared_ptr<NoteType> note (*n);
2327 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2328 if ((cne = find_canvas_note (note)) != 0) {
2329 if (cne->selected()) {
2330 note_deselected (cne);
2332 note_selected (cne, true, false);
2340 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2343 clear_editor_note_selection();
2344 add_to_selection (ev);
2349 if (!ev->selected()) {
2350 add_to_selection (ev);
2354 /* find end of latest note selected, select all between that and the start of "ev" */
2356 Evoral::Beats earliest = Evoral::MaxBeats;
2357 Evoral::Beats latest = Evoral::Beats();
2359 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2360 if ((*i)->note()->end_time() > latest) {
2361 latest = (*i)->note()->end_time();
2363 if ((*i)->note()->time() < earliest) {
2364 earliest = (*i)->note()->time();
2368 if (ev->note()->end_time() > latest) {
2369 latest = ev->note()->end_time();
2372 if (ev->note()->time() < earliest) {
2373 earliest = ev->note()->time();
2376 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2378 /* find notes entirely within OR spanning the earliest..latest range */
2380 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2381 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2382 add_to_selection (*i);
2390 MidiRegionView::note_deselected(NoteBase* ev)
2392 remove_from_selection (ev);
2396 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2398 PublicEditor& editor = trackview.editor();
2400 // Convert to local coordinates
2401 const framepos_t p = _region->position();
2402 const double y = midi_view()->y_position();
2403 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2404 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2405 const double y0 = max(0.0, gy0 - y);
2406 const double y1 = max(0.0, gy1 - y);
2408 // TODO: Make this faster by storing the last updated selection rect, and only
2409 // adjusting things that are in the area that appears/disappeared.
2410 // We probably need a tree to be able to find events in O(log(n)) time.
2412 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2413 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2414 // Rectangles intersect
2415 if (!(*i)->selected()) {
2416 add_to_selection (*i);
2418 } else if ((*i)->selected() && !extend) {
2419 // Rectangles do not intersect
2420 remove_from_selection (*i);
2424 typedef RouteTimeAxisView::AutomationTracks ATracks;
2425 typedef std::list<Selectable*> Selectables;
2427 /* Add control points to selection. */
2428 const ATracks& atracks = midi_view()->automation_tracks();
2429 Selectables selectables;
2430 editor.get_selection().clear_points();
2431 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2432 a->second->get_selectables(start, end, gy0, gy1, selectables);
2433 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2434 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2436 editor.get_selection().add(cp);
2439 a->second->set_selected_points(editor.get_selection().points);
2444 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2450 // TODO: Make this faster by storing the last updated selection rect, and only
2451 // adjusting things that are in the area that appears/disappeared.
2452 // We probably need a tree to be able to find events in O(log(n)) time.
2454 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2455 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2456 // within y- (note-) range
2457 if (!(*i)->selected()) {
2458 add_to_selection (*i);
2460 } else if ((*i)->selected() && !extend) {
2461 remove_from_selection (*i);
2467 MidiRegionView::remove_from_selection (NoteBase* ev)
2469 Selection::iterator i = _selection.find (ev);
2471 if (i != _selection.end()) {
2472 _selection.erase (i);
2473 if (_selection.empty() && _grabbed_keyboard) {
2475 Keyboard::magic_widget_drop_focus();
2476 _grabbed_keyboard = false;
2480 ev->set_selected (false);
2481 ev->hide_velocity ();
2483 if (_selection.empty()) {
2484 PublicEditor& editor (trackview.editor());
2485 editor.get_selection().remove (this);
2490 MidiRegionView::add_to_selection (NoteBase* ev)
2492 const bool selection_was_empty = _selection.empty();
2494 if (_selection.insert (ev).second) {
2495 ev->set_selected (true);
2496 start_playing_midi_note ((ev)->note());
2497 if (selection_was_empty && _entered) {
2498 // Grab keyboard for moving notes with arrow keys
2499 Keyboard::magic_widget_grab_focus();
2500 _grabbed_keyboard = true;
2504 if (selection_was_empty) {
2505 PublicEditor& editor (trackview.editor());
2506 editor.get_selection().add (this);
2511 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2513 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2514 PossibleChord to_play;
2515 Evoral::Beats earliest = Evoral::MaxBeats;
2517 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2518 if ((*i)->note()->time() < earliest) {
2519 earliest = (*i)->note()->time();
2523 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2524 if ((*i)->note()->time() == earliest) {
2525 to_play.push_back ((*i)->note());
2527 (*i)->move_event(dx, dy);
2530 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2532 if (to_play.size() > 1) {
2534 PossibleChord shifted;
2536 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2537 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2538 moved_note->set_note (moved_note->note() + cumulative_dy);
2539 shifted.push_back (moved_note);
2542 start_playing_midi_chord (shifted);
2544 } else if (!to_play.empty()) {
2546 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2547 moved_note->set_note (moved_note->note() + cumulative_dy);
2548 start_playing_midi_note (moved_note);
2554 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2556 uint8_t lowest_note_in_selection = 127;
2557 uint8_t highest_note_in_selection = 0;
2558 uint8_t highest_note_difference = 0;
2560 // find highest and lowest notes first
2562 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2563 uint8_t pitch = (*i)->note()->note();
2564 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2565 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2569 cerr << "dnote: " << (int) dnote << endl;
2570 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2571 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2572 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2573 << int(highest_note_in_selection) << endl;
2574 cerr << "selection size: " << _selection.size() << endl;
2575 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2578 // Make sure the note pitch does not exceed the MIDI standard range
2579 if (highest_note_in_selection + dnote > 127) {
2580 highest_note_difference = highest_note_in_selection - 127;
2582 TempoMap& map (trackview.session()->tempo_map());
2584 start_note_diff_command (_("move notes"));
2586 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2588 double const start_qn = _region->quarter_note() - midi_region()->start_beats();
2589 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2590 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2595 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2597 uint8_t original_pitch = (*i)->note()->note();
2598 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2600 // keep notes in standard midi range
2601 clamp_to_0_127(new_pitch);
2603 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2604 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2606 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2611 // care about notes being moved beyond the upper/lower bounds on the canvas
2612 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2613 highest_note_in_selection > midi_stream_view()->highest_note()) {
2614 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2618 /** @param x Pixel relative to the region position.
2619 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2620 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2621 * @return Snapped frame relative to the region position.
2624 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2626 PublicEditor& editor (trackview.editor());
2627 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2630 /** @param x Pixel relative to the region position.
2631 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2632 * @return Snapped pixel relative to the region position.
2635 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2637 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2641 MidiRegionView::get_position_pixels()
2643 framepos_t region_frame = get_position();
2644 return trackview.editor().sample_to_pixel(region_frame);
2648 MidiRegionView::get_end_position_pixels()
2650 framepos_t frame = get_position() + get_duration ();
2651 return trackview.editor().sample_to_pixel(frame);
2655 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2657 /* the time converter will return the frame corresponding to `beats'
2658 relative to the start of the source. The start of the source
2659 is an implied position given by region->position - region->start
2661 const framepos_t source_start = _region->position() - _region->start();
2662 return source_start + _source_relative_time_converter.to (beats);
2666 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2668 /* the `frames' argument needs to be converted into a frame count
2669 relative to the start of the source before being passed in to the
2672 const framepos_t source_start = _region->position() - _region->start();
2673 return _source_relative_time_converter.from (frames - source_start);
2677 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2679 return _region_relative_time_converter.to(beats);
2683 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2685 return _region_relative_time_converter.from(frames);
2689 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2691 return _region_relative_time_converter_double.from(frames);
2695 MidiRegionView::begin_resizing (bool /*at_front*/)
2697 _resize_data.clear();
2699 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2700 Note *note = dynamic_cast<Note*> (*i);
2702 // only insert CanvasNotes into the map
2704 NoteResizeData *resize_data = new NoteResizeData();
2705 resize_data->note = note;
2707 // create a new SimpleRect from the note which will be the resize preview
2708 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2709 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2711 // calculate the colors: get the color settings
2712 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2713 UIConfiguration::instance().color ("midi note selected"),
2716 // make the resize preview notes more transparent and bright
2717 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2719 // calculate color based on note velocity
2720 resize_rect->set_fill_color (UINT_INTERPOLATE(
2721 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2725 resize_rect->set_outline_color (NoteBase::calculate_outline (
2726 UIConfiguration::instance().color ("midi note selected")));
2728 resize_data->resize_rect = resize_rect;
2729 _resize_data.push_back(resize_data);
2734 /** Update resizing notes while user drags.
2735 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2736 * @param at_front which end of the note (true == note on, false == note off)
2737 * @param delta_x change in mouse position since the start of the drag
2738 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2739 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2740 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2741 * as the \a primary note.
2742 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2743 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2746 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2748 TempoMap& tmap (trackview.session()->tempo_map());
2749 bool cursor_set = false;
2750 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2752 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2753 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2754 Note* canvas_note = (*i)->note;
2759 current_x = canvas_note->x0() + delta_x + snap_delta;
2761 current_x = primary->x0() + delta_x + snap_delta;
2765 current_x = canvas_note->x1() + delta_x + snap_delta;
2767 current_x = primary->x1() + delta_x + snap_delta;
2771 if (current_x < 0) {
2772 // This works even with snapping because RegionView::snap_frame_to_frame()
2773 // snaps forward if the snapped sample is before the beginning of the region
2776 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2777 current_x = trackview.editor().sample_to_pixel(_region->length());
2782 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2784 resize_rect->set_x0 (current_x - snap_delta);
2786 resize_rect->set_x1 (canvas_note->x1());
2789 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2791 resize_rect->set_x1 (current_x - snap_delta);
2793 resize_rect->set_x0 (canvas_note->x0());
2797 /* Convert snap delta from pixels to beats. */
2798 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2799 double snap_delta_beats = 0.0;
2802 /* negative beat offsets aren't allowed */
2803 if (snap_delta_samps > 0) {
2804 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2805 } else if (snap_delta_samps < 0) {
2806 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2811 int32_t divisions = 0;
2814 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2815 divisions = trackview.editor().get_grid_music_divisions (0);
2817 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2819 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2820 - midi_region()->beat()) + midi_region()->start_beats();
2822 Evoral::Beats len = Evoral::Beats();
2825 if (beats < canvas_note->note()->end_time()) {
2826 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2827 len += canvas_note->note()->length();
2830 if (beats >= canvas_note->note()->time()) {
2831 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2835 len = std::max(Evoral::Beats(1 / 512.0), len);
2838 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2839 show_verbose_cursor (buf, 0, 0);
2848 /** Finish resizing notes when the user releases the mouse button.
2849 * Parameters the same as for \a update_resizing().
2852 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2854 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2855 TempoMap& tmap (trackview.session()->tempo_map());
2857 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2858 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2860 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2861 Note* canvas_note = (*i)->note;
2862 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2864 /* Get the new x position for this resize, which is in pixels relative
2865 * to the region position.
2872 current_x = canvas_note->x0() + delta_x + snap_delta;
2874 current_x = primary->x0() + delta_x + snap_delta;
2878 current_x = canvas_note->x1() + delta_x + snap_delta;
2880 current_x = primary->x1() + delta_x + snap_delta;
2884 if (current_x < 0) {
2887 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2888 current_x = trackview.editor().sample_to_pixel(_region->length());
2891 /* Convert snap delta from pixels to beats with sign. */
2892 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2893 double snap_delta_beats = 0.0;
2896 if (snap_delta_samps > 0) {
2897 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2898 } else if (snap_delta_samps < 0) {
2899 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2903 uint32_t divisions = 0;
2904 /* Convert the new x position to a frame within the source */
2905 framepos_t current_fr;
2907 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2908 divisions = trackview.editor().get_grid_music_divisions (0);
2910 current_fr = trackview.editor().pixel_to_sample (current_x);
2913 /* and then to beats */
2914 const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
2915 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
2916 const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
2918 if (at_front && x_beats < canvas_note->note()->end_time()) {
2919 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2920 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2921 len += canvas_note->note()->length();
2924 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2929 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2930 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2931 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2938 _resize_data.clear();
2943 MidiRegionView::abort_resizing ()
2945 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2946 delete (*i)->resize_rect;
2950 _resize_data.clear ();
2954 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2956 uint8_t new_velocity;
2959 new_velocity = event->note()->velocity() + velocity;
2960 clamp_to_0_127(new_velocity);
2962 new_velocity = velocity;
2965 event->set_selected (event->selected()); // change color
2967 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2971 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2976 new_note = event->note()->note() + note;
2981 clamp_to_0_127 (new_note);
2982 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2986 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2988 bool change_start = false;
2989 bool change_length = false;
2990 Evoral::Beats new_start;
2991 Evoral::Beats new_length;
2993 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2995 front_delta: if positive - move the start of the note later in time (shortening it)
2996 if negative - move the start of the note earlier in time (lengthening it)
2998 end_delta: if positive - move the end of the note later in time (lengthening it)
2999 if negative - move the end of the note earlier in time (shortening it)
3002 if (!!front_delta) {
3003 if (front_delta < 0) {
3005 if (event->note()->time() < -front_delta) {
3006 new_start = Evoral::Beats();
3008 new_start = event->note()->time() + front_delta; // moves earlier
3011 /* start moved toward zero, so move the end point out to where it used to be.
3012 Note that front_delta is negative, so this increases the length.
3015 new_length = event->note()->length() - front_delta;
3016 change_start = true;
3017 change_length = true;
3021 Evoral::Beats new_pos = event->note()->time() + front_delta;
3023 if (new_pos < event->note()->end_time()) {
3024 new_start = event->note()->time() + front_delta;
3025 /* start moved toward the end, so move the end point back to where it used to be */
3026 new_length = event->note()->length() - front_delta;
3027 change_start = true;
3028 change_length = true;
3035 bool can_change = true;
3036 if (end_delta < 0) {
3037 if (event->note()->length() < -end_delta) {
3043 new_length = event->note()->length() + end_delta;
3044 change_length = true;
3049 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3052 if (change_length) {
3053 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3058 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3060 uint8_t new_channel;
3064 if (event->note()->channel() < -chn) {
3067 new_channel = event->note()->channel() + chn;
3070 new_channel = event->note()->channel() + chn;
3073 new_channel = (uint8_t) chn;
3076 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3080 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3082 Evoral::Beats new_time;
3086 if (event->note()->time() < -delta) {
3087 new_time = Evoral::Beats();
3089 new_time = event->note()->time() + delta;
3092 new_time = event->note()->time() + delta;
3098 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3102 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3104 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3108 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3113 if (_selection.empty()) {
3128 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3129 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3135 start_note_diff_command (_("change velocities"));
3137 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3138 Selection::iterator next = i;
3142 if (i == _selection.begin()) {
3143 change_note_velocity (*i, delta, true);
3144 value = (*i)->note()->velocity() + delta;
3146 change_note_velocity (*i, value, false);
3150 change_note_velocity (*i, delta, true);
3159 if (!_selection.empty()) {
3161 snprintf (buf, sizeof (buf), "Vel %d",
3162 (int) (*_selection.begin())->note()->velocity());
3163 show_verbose_cursor (buf, 10, 10);
3169 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3171 if (_selection.empty()) {
3188 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3190 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3194 if ((int8_t) (*i)->note()->note() + delta > 127) {
3201 start_note_diff_command (_("transpose"));
3203 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3204 Selection::iterator next = i;
3206 change_note_note (*i, delta, true);
3214 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3218 delta = Evoral::Beats(1.0/128.0);
3220 /* grab the current grid distance */
3221 delta = get_grid_beats(_region->position());
3229 start_note_diff_command (_("change note lengths"));
3231 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3232 Selection::iterator next = i;
3235 /* note the negation of the delta for start */
3238 (start ? -delta : Evoral::Beats()),
3239 (end ? delta : Evoral::Beats()));
3248 MidiRegionView::nudge_notes (bool forward, bool fine)
3250 if (_selection.empty()) {
3254 /* pick a note as the point along the timeline to get the nudge distance.
3255 its not necessarily the earliest note, so we may want to pull the notes out
3256 into a vector and sort before using the first one.
3259 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3260 Evoral::Beats delta;
3264 /* non-fine, move by 1 bar regardless of snap */
3265 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3267 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3269 /* grid is off - use nudge distance */
3272 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3273 delta = region_frames_to_region_beats (fabs ((double)distance));
3279 framepos_t next_pos = ref_point;
3282 if (max_framepos - 1 < next_pos) {
3286 if (next_pos == 0) {
3292 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3293 const framecnt_t distance = ref_point - next_pos;
3294 delta = region_frames_to_region_beats (fabs ((double)distance));
3305 start_note_diff_command (_("nudge"));
3307 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3308 Selection::iterator next = i;
3310 change_note_time (*i, delta, true);
3318 MidiRegionView::change_channel(uint8_t channel)
3320 start_note_diff_command(_("change channel"));
3321 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3322 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3330 MidiRegionView::note_entered(NoteBase* ev)
3334 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3336 if (_mouse_state == SelectTouchDragging) {
3338 note_selected (ev, true);
3340 } else if (editor->current_mouse_mode() == MouseContent) {
3342 remove_ghost_note ();
3343 show_verbose_cursor (ev->note ());
3345 } else if (editor->current_mouse_mode() == MouseDraw) {
3347 remove_ghost_note ();
3348 show_verbose_cursor (ev->note ());
3353 MidiRegionView::note_left (NoteBase*)
3357 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3358 (*i)->hide_velocity ();
3361 hide_verbose_cursor ();
3365 MidiRegionView::patch_entered (PatchChange* p)
3368 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3369 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3370 << _("Channel ") << ((int) p->patch()->channel() + 1);
3371 show_verbose_cursor (s.str(), 10, 20);
3372 p->item().grab_focus();
3376 MidiRegionView::patch_left (PatchChange *)
3378 hide_verbose_cursor ();
3379 /* focus will transfer back via the enter-notify event sent to this
3385 MidiRegionView::sysex_entered (SysEx* p)
3389 // need a way to extract text from p->_flag->_text
3391 // show_verbose_cursor (s.str(), 10, 20);
3392 p->item().grab_focus();
3396 MidiRegionView::sysex_left (SysEx *)
3398 hide_verbose_cursor ();
3399 /* focus will transfer back via the enter-notify event sent to this
3405 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3407 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3408 Editing::MouseMode mm = editor->current_mouse_mode();
3409 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3411 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3412 if (can_set_cursor && ctx) {
3413 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3414 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3415 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3416 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3418 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3424 MidiRegionView::get_fill_color() const
3426 const std::string mod_name = (_dragging ? "dragging region" :
3427 trackview.editor().internal_editing() ? "editable region" :
3430 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3431 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3432 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3433 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3435 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3439 MidiRegionView::midi_channel_mode_changed ()
3441 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3442 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3443 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3445 if (mode == ForceChannel) {
3446 mask = 0xFFFF; // Show all notes as active (below)
3449 // Update notes for selection
3450 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3451 (*i)->on_channel_selection_change (mask);
3454 _patch_changes.clear ();
3455 display_patch_changes ();
3459 MidiRegionView::instrument_settings_changed ()
3465 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3467 if (_selection.empty()) {
3471 PublicEditor& editor (trackview.editor());
3475 /* XXX what to do ? */
3479 editor.get_cut_buffer().add (selection_as_cut_buffer());
3487 start_note_diff_command();
3489 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3496 note_diff_remove_note (*i);
3506 MidiRegionView::selection_as_cut_buffer () const
3510 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3511 NoteType* n = (*i)->note().get();
3512 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3515 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3521 /** This method handles undo */
3523 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3525 bool commit = false;
3526 // Paste notes, if available
3527 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3528 if (m != selection.midi_notes.end()) {
3529 ctx.counts.increase_n_notes();
3530 if (!(*m)->empty()) {
3533 paste_internal(pos, ctx.count, ctx.times, **m);
3536 // Paste control points to automation children, if available
3537 typedef RouteTimeAxisView::AutomationTracks ATracks;
3538 const ATracks& atracks = midi_view()->automation_tracks();
3539 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3540 if (a->second->paste(pos, selection, ctx, sub_num)) {
3542 trackview.editor().begin_reversible_command (Operations::paste);
3549 trackview.editor().commit_reversible_command ();
3554 /** This method handles undo */
3556 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3562 start_note_diff_command (_("paste"));
3564 const Evoral::Beats snap_beats = get_grid_beats(pos);
3565 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3566 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3567 const Evoral::Beats duration = last_time - first_time;
3568 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3569 const Evoral::Beats paste_offset = snap_duration * paste_count;
3570 const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
3571 Evoral::Beats end_point = Evoral::Beats();
3573 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3576 duration, pos, _region->position(),
3579 clear_editor_note_selection ();
3581 for (int n = 0; n < (int) times; ++n) {
3583 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3585 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3586 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3587 copied_note->set_id (Evoral::next_event_id());
3589 /* make all newly added notes selected */
3591 note_diff_add_note (copied_note, true);
3592 end_point = copied_note->end_time();
3596 /* if we pasted past the current end of the region, extend the region */
3598 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3599 framepos_t region_end = _region->position() + _region->length() - 1;
3601 if (end_frame > region_end) {
3603 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3605 _region->clear_changes ();
3606 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3607 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3608 trackview.session()->add_command (new StatefulDiffCommand (_region));
3614 struct EventNoteTimeEarlyFirstComparator {
3615 bool operator() (NoteBase* a, NoteBase* b) {
3616 return a->note()->time() < b->note()->time();
3621 MidiRegionView::time_sort_events ()
3623 if (!_sort_needed) {
3627 EventNoteTimeEarlyFirstComparator cmp;
3630 _sort_needed = false;
3634 MidiRegionView::goto_next_note (bool add_to_selection)
3636 bool use_next = false;
3638 if (_events.back()->selected()) {
3642 time_sort_events ();
3644 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3645 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3647 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3648 if ((*i)->selected()) {
3651 } else if (use_next) {
3652 if (channel_mask & (1 << (*i)->note()->channel())) {
3653 if (!add_to_selection) {
3656 note_selected (*i, true, false);
3663 /* use the first one */
3665 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3666 unique_select (_events.front());
3671 MidiRegionView::goto_previous_note (bool add_to_selection)
3673 bool use_next = false;
3675 if (_events.front()->selected()) {
3679 time_sort_events ();
3681 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3682 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3684 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3685 if ((*i)->selected()) {
3688 } else if (use_next) {
3689 if (channel_mask & (1 << (*i)->note()->channel())) {
3690 if (!add_to_selection) {
3693 note_selected (*i, true, false);
3700 /* use the last one */
3702 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3703 unique_select (*(_events.rbegin()));
3708 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3710 bool had_selected = false;
3712 time_sort_events ();
3714 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3715 if ((*i)->selected()) {
3716 selected.insert ((*i)->note());
3717 had_selected = true;
3721 if (allow_all_if_none_selected && !had_selected) {
3722 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3723 selected.insert ((*i)->note());
3729 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3731 x = std::max(0.0, x);
3733 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3738 _note_group->canvas_to_item (x, y);
3740 PublicEditor& editor = trackview.editor ();
3742 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3744 const int32_t divisions = editor.get_grid_music_divisions (state);
3745 const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, true);
3747 /* ghost note may have been snapped before region */
3748 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3749 _ghost_note->hide();
3752 } else if (_ghost_note) {
3753 _ghost_note->show();
3756 /* calculate time in beats relative to start of source */
3757 const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3759 _ghost_note->note()->set_time (snapped_beats);
3760 _ghost_note->note()->set_length (length);
3761 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3762 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3763 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3764 /* the ghost note does not appear in ghost regions, so pass false in here */
3765 update_note (_ghost_note, false);
3767 show_verbose_cursor (_ghost_note->note ());
3771 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3773 remove_ghost_note ();
3775 boost::shared_ptr<NoteType> g (new NoteType);
3776 if (midi_view()->note_mode() == Sustained) {
3777 _ghost_note = new Note (*this, _note_group, g);
3779 _ghost_note = new Hit (*this, _note_group, 10, g);
3781 _ghost_note->set_ignore_events (true);
3782 _ghost_note->set_outline_color (0x000000aa);
3783 update_ghost_note (x, y, state);
3784 _ghost_note->show ();
3786 show_verbose_cursor (_ghost_note->note ());
3790 MidiRegionView::remove_ghost_note ()
3797 MidiRegionView::hide_verbose_cursor ()
3799 trackview.editor().verbose_cursor()->hide ();
3800 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3802 mtv->set_note_highlight (NO_MIDI_NOTE);
3807 MidiRegionView::snap_changed ()
3813 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3817 MidiRegionView::drop_down_keys ()
3819 _mouse_state = None;
3823 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3825 /* XXX: This is dead code. What was it for? */
3827 double note = midi_stream_view()->y_to_note(y);
3829 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3831 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3833 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3834 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3835 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3836 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3841 bool add_mrv_selection = false;
3843 if (_selection.empty()) {
3844 add_mrv_selection = true;
3847 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3848 if (_selection.insert (*i).second) {
3849 (*i)->set_selected (true);
3853 if (add_mrv_selection) {
3854 PublicEditor& editor (trackview.editor());
3855 editor.get_selection().add (this);
3860 MidiRegionView::color_handler ()
3862 RegionView::color_handler ();
3864 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3865 (*i)->set_selected ((*i)->selected()); // will change color
3868 /* XXX probably more to do here */
3872 MidiRegionView::enable_display (bool yn)
3874 RegionView::enable_display (yn);
3878 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3880 if (_step_edit_cursor == 0) {
3881 ArdourCanvas::Item* const group = get_canvas_group();
3883 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3884 _step_edit_cursor->set_y0 (0);
3885 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3886 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3887 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3890 move_step_edit_cursor (pos);
3891 _step_edit_cursor->show ();
3895 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3897 _step_edit_cursor_position = pos;
3899 if (_step_edit_cursor) {
3900 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3901 _step_edit_cursor->set_x0 (pixel);
3902 set_step_edit_cursor_width (_step_edit_cursor_width);
3907 MidiRegionView::hide_step_edit_cursor ()
3909 if (_step_edit_cursor) {
3910 _step_edit_cursor->hide ();
3915 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3917 _step_edit_cursor_width = beats;
3919 if (_step_edit_cursor) {
3920 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3921 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3922 - region_beats_to_region_frames (_step_edit_cursor_position)));
3926 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3927 * @param w Source that the data will end up in.
3930 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3932 if (!_active_notes) {
3933 /* we aren't actively being recorded to */
3937 boost::shared_ptr<MidiSource> src = w.lock ();
3938 if (!src || src != midi_region()->midi_source()) {
3939 /* recorded data was not destined for our source */
3943 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3945 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3947 framepos_t back = max_framepos;
3949 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3950 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3952 if (ev.is_channel_event()) {
3953 if (get_channel_mode() == FilterChannels) {
3954 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3960 /* convert from session frames to source beats */
3961 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3962 ev.time() - src->timeline_position() + _region->start());
3964 if (ev.type() == MIDI_CMD_NOTE_ON) {
3965 boost::shared_ptr<NoteType> note (
3966 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3968 add_note (note, true);
3970 /* fix up our note range */
3971 if (ev.note() < _current_range_min) {
3972 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3973 } else if (ev.note() > _current_range_max) {
3974 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3977 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3978 resolve_note (ev.note (), time_beats);
3984 midi_stream_view()->check_record_layers (region(), back);
3988 MidiRegionView::trim_front_starting ()
3990 /* We used to eparent the note group to the region view's parent, so that it didn't change.
3996 MidiRegionView::trim_front_ending ()
3998 if (_region->start() < 0) {
3999 /* Trim drag made start time -ve; fix this */
4000 midi_region()->fix_negative_start ();
4005 MidiRegionView::edit_patch_change (PatchChange* pc)
4007 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4009 int response = d.run();
4012 case Gtk::RESPONSE_ACCEPT:
4014 case Gtk::RESPONSE_REJECT:
4015 delete_patch_change (pc);
4021 change_patch_change (pc->patch(), d.patch ());
4025 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4028 // sysyex object doesn't have a pointer to a sysex event
4029 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4030 // c->remove (sysex->sysex());
4031 // _model->apply_command (*trackview.session(), c);
4033 //_sys_exes.clear ();
4034 // display_sysexes();
4038 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4040 using namespace MIDI::Name;
4043 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4045 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4047 MIDI::Name::PatchPrimaryKey patch_key;
4048 get_patch_key_at(n->time(), n->channel(), patch_key);
4049 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4052 patch_key.program(),
4058 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4060 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4061 (int) n->channel() + 1,
4062 (int) n->velocity());
4068 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4069 uint8_t new_value) const
4071 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4073 mtv->set_note_highlight (new_value);
4076 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4080 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4082 show_verbose_cursor_for_new_note_value(n, n->note());
4086 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4088 trackview.editor().verbose_cursor()->set (text);
4089 trackview.editor().verbose_cursor()->show ();
4090 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4094 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4096 if (_model->notes().empty()) {
4097 return 0x40; // No notes, use default
4100 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4101 if (m == _model->notes().begin()) {
4102 // Before the start, use the velocity of the first note
4103 return (*m)->velocity();
4104 } else if (m == _model->notes().end()) {
4105 // Past the end, use the velocity of the last note
4107 return (*m)->velocity();
4110 // Interpolate velocity of surrounding notes
4111 MidiModel::Notes::const_iterator n = m;
4114 const double frac = ((time - (*n)->time()).to_double() /
4115 ((*m)->time() - (*n)->time()).to_double());
4117 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4120 /** @param p A session framepos.
4121 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4122 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4123 * @return beat duration of p snapped to the grid subdivision underneath it.
4126 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
4128 TempoMap& map (trackview.session()->tempo_map());
4129 double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4131 if (divisions != 0 && shift_snap) {
4132 const double qaf = map.quarter_note_at_frame (p + _region->position());
4133 /* Hack so that we always snap to the note that we are over, instead of snapping
4134 to the next one if we're more than halfway through the one we're over.
4136 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4137 const double rem = eqaf - qaf;
4139 eqaf -= grid_beats.to_double();
4142 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4144 return Evoral::Beats (eqaf - session_start_off);
4148 MidiRegionView::get_channel_mode () const
4150 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4151 return rtav->midi_track()->get_playback_channel_mode();
4155 MidiRegionView::get_selected_channels () const
4157 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4158 return rtav->midi_track()->get_playback_channel_mask();
4163 MidiRegionView::get_grid_beats(framepos_t pos) const
4165 PublicEditor& editor = trackview.editor();
4166 bool success = false;
4167 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4169 beats = Evoral::Beats(1);