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;
505 _pressed_button = ev->button;
506 _mouse_changed_selection = false;
512 MidiRegionView::button_release (GdkEventButton* ev)
514 double event_x, event_y;
516 if (ev->button != 1) {
523 group->canvas_to_item (event_x, event_y);
526 PublicEditor& editor = trackview.editor ();
528 _press_cursor_ctx.reset();
530 switch (_mouse_state) {
531 case Pressed: // Clicked
533 switch (editor.current_mouse_mode()) {
535 /* no motion occurred - simple click */
536 clear_editor_note_selection ();
537 _mouse_changed_selection = true;
543 _mouse_changed_selection = true;
545 if (Keyboard::is_insert_note_event(ev)) {
547 double event_x, event_y;
551 group->canvas_to_item (event_x, event_y);
553 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x) + _region->position());
554 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, ev->state, true);
556 clear_editor_note_selection ();
563 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x) + _region->position());
564 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, ev->state, true);
575 /* Only create a ghost note when we added a note, not when we were drag-selecting. */
576 create_ghost_note (ev->x, ev->y, ev->state);
577 case SelectRectDragging:
578 editor.drags()->end_grab ((GdkEvent *) ev);
587 if (_mouse_changed_selection) {
588 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
589 trackview.editor().commit_reversible_selection_op ();
596 MidiRegionView::motion (GdkEventMotion* ev)
598 PublicEditor& editor = trackview.editor ();
600 if (!_entered_note) {
602 if (_mouse_state == AddDragging) {
604 remove_ghost_note ();
607 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
608 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
609 _mouse_state != AddDragging) {
611 create_ghost_note (ev->x, ev->y, ev->state);
613 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
614 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
616 update_ghost_note (ev->x, ev->y, ev->state);
618 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
620 remove_ghost_note ();
621 hide_verbose_cursor ();
623 } else if (editor.current_mouse_mode() == MouseDraw) {
626 update_ghost_note (ev->x, ev->y, ev->state);
629 create_ghost_note (ev->x, ev->y, ev->state);
634 /* any motion immediately hides velocity text that may have been visible */
636 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
637 (*i)->hide_velocity ();
640 switch (_mouse_state) {
643 if (_pressed_button == 1) {
645 MouseMode m = editor.current_mouse_mode();
647 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
648 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
649 _mouse_state = AddDragging;
650 remove_ghost_note ();
651 hide_verbose_cursor ();
653 } else if (m == MouseContent) {
654 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
655 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
656 clear_editor_note_selection ();
657 _mouse_changed_selection = true;
659 _mouse_state = SelectRectDragging;
661 } else if (m == MouseRange) {
662 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
663 _mouse_state = SelectVerticalDragging;
670 case SelectRectDragging:
671 case SelectVerticalDragging:
673 editor.drags()->motion_handler ((GdkEvent *) ev, false);
676 case SelectTouchDragging:
684 /* we may be dragging some non-note object (eg. patch-change, sysex)
687 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
692 MidiRegionView::scroll (GdkEventScroll* ev)
694 if (_selection.empty()) {
698 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
699 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
700 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
701 * through so that it still works for navigation.
706 hide_verbose_cursor ();
708 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
709 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
710 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
712 if (ev->direction == GDK_SCROLL_UP) {
713 change_velocities (true, fine, false, together);
714 } else if (ev->direction == GDK_SCROLL_DOWN) {
715 change_velocities (false, fine, false, together);
717 /* left, right: we don't use them */
725 MidiRegionView::key_press (GdkEventKey* ev)
727 /* since GTK bindings are generally activated on press, and since
728 detectable auto-repeat is the name of the game and only sends
729 repeated presses, carry out key actions at key press, not release.
732 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
734 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
735 _mouse_state = SelectTouchDragging;
738 } else if (ev->keyval == GDK_Escape && unmodified) {
739 clear_editor_note_selection ();
742 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
744 bool start = (ev->keyval == GDK_comma);
745 bool end = (ev->keyval == GDK_period);
746 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
747 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
749 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
753 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
755 if (_selection.empty()) {
762 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
764 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
766 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
767 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
769 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
772 trackview.editor().commit_reversible_selection_op();
776 } else if (ev->keyval == GDK_Up) {
778 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
779 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
780 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
782 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
783 change_velocities (true, fine, allow_smush, together);
785 transpose (true, fine, allow_smush);
789 } else if (ev->keyval == GDK_Down) {
791 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
792 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
793 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
795 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
796 change_velocities (false, fine, allow_smush, together);
798 transpose (false, fine, allow_smush);
802 } else if (ev->keyval == GDK_Left) {
804 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
805 nudge_notes (false, fine);
808 } else if (ev->keyval == GDK_Right) {
810 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
811 nudge_notes (true, fine);
814 } else if (ev->keyval == GDK_c && unmodified) {
818 } else if (ev->keyval == GDK_v && unmodified) {
827 MidiRegionView::key_release (GdkEventKey* ev)
829 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
837 MidiRegionView::channel_edit ()
839 if (_selection.empty()) {
843 /* pick a note somewhat at random (since Selection is a set<>) to
844 * provide the "current" channel for the dialog.
847 uint8_t current_channel = (*_selection.begin())->note()->channel ();
848 MidiChannelDialog channel_dialog (current_channel);
849 int ret = channel_dialog.run ();
852 case Gtk::RESPONSE_OK:
858 uint8_t new_channel = channel_dialog.active_channel ();
860 start_note_diff_command (_("channel edit"));
862 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
863 Selection::iterator next = i;
865 change_note_channel (*i, new_channel);
873 MidiRegionView::velocity_edit ()
875 if (_selection.empty()) {
879 /* pick a note somewhat at random (since Selection is a set<>) to
880 * provide the "current" velocity for the dialog.
883 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
884 MidiVelocityDialog velocity_dialog (current_velocity);
885 int ret = velocity_dialog.run ();
888 case Gtk::RESPONSE_OK:
894 uint8_t new_velocity = velocity_dialog.velocity ();
896 start_note_diff_command (_("velocity edit"));
898 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
899 Selection::iterator next = i;
901 change_note_velocity (*i, new_velocity, false);
909 MidiRegionView::show_list_editor ()
912 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
914 _list_editor->present ();
917 /** Add a note to the model, and the view, at a canvas (click) coordinate.
918 * \param t time in frames relative to the position of the region
919 * \param y vertical position in pixels
920 * \param length duration of the note in beats
921 * \param snap_t true to snap t to the grid, otherwise false.
924 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
926 if (length < 2 * DBL_EPSILON) {
930 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
931 MidiStreamView* const view = mtv->midi_view();
932 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
938 // Start of note in frames relative to region start
939 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
940 Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
942 const double note = view->y_to_note(y);
943 const uint8_t chan = mtv->get_channel_for_add();
944 const uint8_t velocity = get_velocity_for_add(beat_time);
946 const boost::shared_ptr<NoteType> new_note(
947 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
949 if (_model->contains (new_note)) {
953 view->update_note_range(new_note->note());
955 start_note_diff_command(_("add note"));
957 clear_editor_note_selection ();
958 note_diff_add_note (new_note, true, false);
962 play_midi_note (new_note);
966 MidiRegionView::clear_events ()
968 // clear selection without signaling
969 clear_selection_internal ();
972 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
973 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
978 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
983 _patch_changes.clear();
985 _optimization_iterator = _events.end();
989 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
993 content_connection.disconnect ();
994 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
995 /* Don't signal as nobody else needs to know until selection has been altered. */
998 if (_enable_display) {
1004 MidiRegionView::start_note_diff_command (string name)
1006 if (!_note_diff_command) {
1007 trackview.editor().begin_reversible_command (name);
1008 _note_diff_command = _model->new_note_diff_command (name);
1013 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1015 if (_note_diff_command) {
1016 _note_diff_command->add (note);
1019 _marked_for_selection.insert(note);
1021 if (show_velocity) {
1022 _marked_for_velocity.insert(note);
1027 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1029 if (_note_diff_command && ev->note()) {
1030 _note_diff_command->remove(ev->note());
1035 MidiRegionView::note_diff_add_change (NoteBase* ev,
1036 MidiModel::NoteDiffCommand::Property property,
1039 if (_note_diff_command) {
1040 _note_diff_command->change (ev->note(), property, val);
1045 MidiRegionView::note_diff_add_change (NoteBase* ev,
1046 MidiModel::NoteDiffCommand::Property property,
1049 if (_note_diff_command) {
1050 _note_diff_command->change (ev->note(), property, val);
1055 MidiRegionView::apply_diff (bool as_subcommand)
1058 bool commit = false;
1060 if (!_note_diff_command) {
1064 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1065 // Mark all selected notes for selection when model reloads
1066 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1067 _marked_for_selection.insert((*i)->note());
1071 midi_view()->midi_track()->midi_playlist()->region_edited(
1072 _region, _note_diff_command);
1074 if (as_subcommand) {
1075 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1077 _model->apply_command (*trackview.session(), _note_diff_command);
1081 _note_diff_command = 0;
1083 if (add_or_remove) {
1084 _marked_for_selection.clear();
1087 _marked_for_velocity.clear();
1089 trackview.editor().commit_reversible_command ();
1094 MidiRegionView::abort_command()
1096 delete _note_diff_command;
1097 _note_diff_command = 0;
1098 clear_editor_note_selection();
1102 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1104 if (_optimization_iterator != _events.end()) {
1105 ++_optimization_iterator;
1108 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1109 return *_optimization_iterator;
1112 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1113 if ((*_optimization_iterator)->note() == note) {
1114 return *_optimization_iterator;
1121 /** This version finds any canvas note matching the supplied note. */
1123 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1125 Events::iterator it;
1127 for (it = _events.begin(); it != _events.end(); ++it) {
1128 if ((*it)->note()->id() == id) {
1137 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1139 MidiModel::Notes notes;
1140 _model->get_notes (notes, op, val, chan_mask);
1142 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1143 NoteBase* cne = find_canvas_note (*n);
1151 MidiRegionView::redisplay_model()
1153 if (_active_notes) {
1154 // Currently recording
1155 const framecnt_t zoom = trackview.editor().get_current_zoom();
1156 if (zoom != _last_display_zoom) {
1157 /* Update resolved canvas notes to reflect changes in zoom without
1158 touching model. Leave active notes (with length 0) alone since
1159 they are being extended. */
1160 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1161 if ((*i)->note()->length() > 0) {
1165 _last_display_zoom = zoom;
1174 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1175 (*i)->invalidate ();
1178 MidiModel::ReadLock lock(_model->read_lock());
1180 MidiModel::Notes& notes (_model->notes());
1181 _optimization_iterator = _events.begin();
1183 bool empty_when_starting = _events.empty();
1186 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1188 boost::shared_ptr<NoteType> note (*n);
1191 if (note_in_region_range (note, visible)) {
1193 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1205 cne = add_note (note, visible);
1208 set<Evoral::event_id_t>::iterator it;
1209 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1210 if ((*it) == note->id()) {
1211 add_to_selection (cne);
1217 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1224 /* remove note items that are no longer valid */
1226 if (!empty_when_starting) {
1227 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1228 if (!(*i)->valid ()) {
1230 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1231 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1233 gr->remove_note (*i);
1238 i = _events.erase (i);
1246 _patch_changes.clear();
1250 display_patch_changes ();
1252 _marked_for_selection.clear ();
1253 _marked_for_velocity.clear ();
1254 _pending_note_selection.clear ();
1256 /* we may have caused _events to contain things out of order (e.g. if a note
1257 moved earlier or later). we don't generally need them in time order, but
1258 make a note that a sort is required for those cases that require it.
1261 _sort_needed = true;
1265 MidiRegionView::display_patch_changes ()
1267 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1268 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1270 for (uint8_t i = 0; i < 16; ++i) {
1271 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1275 /** @param active_channel true to display patch changes fully, false to display
1276 * them `greyed-out' (as on an inactive channel)
1279 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1281 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1283 if ((*i)->channel() != channel) {
1287 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1288 add_canvas_patch_change (*i, patch_name, active_channel);
1293 MidiRegionView::display_sysexes()
1295 bool have_periodic_system_messages = false;
1296 bool display_periodic_messages = true;
1298 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1300 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1301 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1302 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1305 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1306 have_periodic_system_messages = true;
1312 if (have_periodic_system_messages) {
1313 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1315 /* get an approximate value for the number of samples per video frame */
1317 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1319 /* if we are zoomed out beyond than the cutoff (i.e. more
1320 * frames per pixel than frames per 4 video frames), don't
1321 * show periodic sysex messages.
1324 if (zoom > (video_frame*4)) {
1325 display_periodic_messages = false;
1329 display_periodic_messages = false;
1332 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1334 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1335 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1337 Evoral::Beats time = (*i)->time();
1340 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1341 if (!display_periodic_messages) {
1349 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1350 str << int((*i)->buffer()[b]);
1351 if (b != (*i)->size() -1) {
1355 string text = str.str();
1357 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1359 double height = midi_stream_view()->contents_height();
1361 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1362 // SysEx canvas object!!!
1364 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1365 new SysEx (*this, _note_group, text, height, x, 1.0));
1367 // Show unless message is beyond the region bounds
1368 if (time - _region->start() >= _region->length() || time < _region->start()) {
1374 _sys_exes.push_back(sysex);
1378 MidiRegionView::~MidiRegionView ()
1380 in_destructor = true;
1382 hide_verbose_cursor ();
1384 delete _list_editor;
1386 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1388 if (_active_notes) {
1396 delete _note_diff_command;
1397 delete _step_edit_cursor;
1401 MidiRegionView::region_resized (const PropertyChange& what_changed)
1403 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1405 if (what_changed.contains (ARDOUR::Properties::position)) {
1406 _region_relative_time_converter.set_origin_b(_region->position());
1407 _region_relative_time_converter_double.set_origin_b(_region->position());
1408 /* reset_width dependent_items() redisplays model */
1412 if (what_changed.contains (ARDOUR::Properties::start) ||
1413 what_changed.contains (ARDOUR::Properties::position)) {
1414 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1416 /* catch end and start trim so we can update the view*/
1417 if (!what_changed.contains (ARDOUR::Properties::start) &&
1418 what_changed.contains (ARDOUR::Properties::length)) {
1419 enable_display (true);
1420 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1421 what_changed.contains (ARDOUR::Properties::length)) {
1422 enable_display (true);
1427 MidiRegionView::reset_width_dependent_items (double pixel_width)
1429 RegionView::reset_width_dependent_items(pixel_width);
1431 if (_enable_display) {
1435 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1436 if ((*x)->canvas_item()->width() >= _pixel_width) {
1443 move_step_edit_cursor (_step_edit_cursor_position);
1444 set_step_edit_cursor_width (_step_edit_cursor_width);
1448 MidiRegionView::set_height (double height)
1450 double old_height = _height;
1451 RegionView::set_height(height);
1453 apply_note_range (midi_stream_view()->lowest_note(),
1454 midi_stream_view()->highest_note(),
1455 height != old_height);
1458 name_text->raise_to_top();
1461 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1462 (*x)->set_height (midi_stream_view()->contents_height());
1465 if (_step_edit_cursor) {
1466 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1471 /** Apply the current note range from the stream view
1472 * by repositioning/hiding notes as necessary
1475 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1477 if (!_enable_display) {
1481 if (!force && _current_range_min == min && _current_range_max == max) {
1485 _current_range_min = min;
1486 _current_range_max = max;
1488 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1489 NoteBase* event = *i;
1490 boost::shared_ptr<NoteType> note (event->note());
1492 if (note->note() < _current_range_min ||
1493 note->note() > _current_range_max) {
1499 if (Note* cnote = dynamic_cast<Note*>(event)) {
1501 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1502 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1504 if (y0 < 0 || y1 >= _height) {
1505 /* During DnD, the region uses the 'old/current'
1506 * midi_stream_view()'s range and its position/height calculation.
1508 * Ideally DnD would decouple the midi_stream_view() for the
1509 * region(s) being dragged and set it to the target's range
1510 * (or in case of the drop-zone, FullRange).
1511 * but I don't see how this can be done without major rework.
1513 * For now, just prevent visual bleeding of events in case
1514 * the target-track is smaller.
1522 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1529 MidiRegionView::add_ghost (TimeAxisView& tv)
1531 double unit_position = _region->position () / samples_per_pixel;
1532 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1533 MidiGhostRegion* ghost;
1535 if (mtv && mtv->midi_view()) {
1536 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1537 to allow having midi notes on top of note lines and waveforms.
1539 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1541 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1544 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1545 ghost->add_note(*i);
1548 ghost->set_colors ();
1549 ghost->set_height ();
1550 ghost->set_duration (_region->length() / samples_per_pixel);
1551 ghosts.push_back (ghost);
1557 /** Begin tracking note state for successive calls to add_event
1560 MidiRegionView::begin_write()
1562 if (_active_notes) {
1563 delete[] _active_notes;
1565 _active_notes = new Note*[128];
1566 for (unsigned i = 0; i < 128; ++i) {
1567 _active_notes[i] = 0;
1572 /** Destroy note state for add_event
1575 MidiRegionView::end_write()
1577 delete[] _active_notes;
1579 _marked_for_selection.clear();
1580 _marked_for_velocity.clear();
1584 /** Resolve an active MIDI note (while recording).
1587 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1589 if (midi_view()->note_mode() != Sustained) {
1593 if (_active_notes && _active_notes[note]) {
1594 /* Set note length so update_note() works. Note this is a local note
1595 for recording, not from a model, so we can safely mess with it. */
1596 _active_notes[note]->note()->set_length(
1597 end_time - _active_notes[note]->note()->time());
1599 /* End time is relative to the region being recorded. */
1600 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1602 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1603 _active_notes[note]->set_outline_all ();
1604 _active_notes[note] = 0;
1609 /** Extend active notes to rightmost edge of region (if length is changed)
1612 MidiRegionView::extend_active_notes()
1614 if (!_active_notes) {
1618 for (unsigned i = 0; i < 128; ++i) {
1619 if (_active_notes[i]) {
1620 _active_notes[i]->set_x1(
1621 trackview.editor().sample_to_pixel(_region->length()));
1627 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1629 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1633 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1635 if (!route_ui || !route_ui->midi_track()) {
1639 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1643 /* NotePlayer deletes itself */
1647 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1649 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1650 start_playing_midi_chord(notes);
1654 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1656 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1660 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1662 if (!route_ui || !route_ui->midi_track()) {
1666 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1668 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1677 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1679 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1681 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1682 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1683 note->time().to_double() > midi_reg->start_beats() + midi_reg->length_beats());
1685 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1686 (note->note() <= midi_stream_view()->highest_note());
1692 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1696 if ((sus = dynamic_cast<Note*>(note))) {
1697 update_sustained(sus, update_ghost_regions);
1698 } else if ((hit = dynamic_cast<Hit*>(note))) {
1699 update_hit(hit, update_ghost_regions);
1703 /** Update a canvas note's size from its model note.
1704 * @param ev Canvas note to update.
1705 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1708 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1710 TempoMap& map (trackview.session()->tempo_map());
1711 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1712 boost::shared_ptr<NoteType> note = ev->note();
1714 const double session_source_start = _region->quarter_note() - mr->start_beats();
1715 const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1717 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1719 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1722 /* trim note display to not overlap the end of its region */
1723 if (note->length() > 0) {
1724 double note_end_time = note->end_time().to_double();
1726 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1727 note_end_time = mr->start_beats() + mr->length_beats();
1730 const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
1732 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1734 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1737 y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1);
1739 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1741 if (!note->length()) {
1742 if (_active_notes && note->note() < 128) {
1743 Note* const old_rect = _active_notes[note->note()];
1745 /* There is an active note on this key, so we have a stuck
1746 note. Finish the old rectangle here. */
1747 old_rect->set_x1 (x1);
1748 old_rect->set_outline_all ();
1750 _active_notes[note->note()] = ev;
1752 /* outline all but right edge */
1753 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1754 ArdourCanvas::Rectangle::TOP|
1755 ArdourCanvas::Rectangle::LEFT|
1756 ArdourCanvas::Rectangle::BOTTOM));
1758 /* outline all edges */
1759 ev->set_outline_all ();
1762 // Update color in case velocity has changed
1763 const uint32_t base_col = ev->base_color();
1764 ev->set_fill_color(base_col);
1765 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1767 if (update_ghost_regions) {
1768 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1769 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1771 gr->update_note (ev);
1778 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1780 boost::shared_ptr<NoteType> note = ev->note();
1782 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1783 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1785 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1786 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1787 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1789 // see DnD note in MidiRegionView::apply_note_range() above
1790 if (y <= 0 || y >= _height) {
1796 ev->set_position (ArdourCanvas::Duple (x, y));
1797 ev->set_height (diamond_size);
1799 // Update color in case velocity has changed
1800 const uint32_t base_col = ev->base_color();
1801 ev->set_fill_color(base_col);
1802 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1804 if (update_ghost_regions) {
1805 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1806 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1808 gr->update_hit (ev);
1814 /** Add a MIDI note to the view (with length).
1816 * If in sustained mode, notes with length 0 will be considered active
1817 * notes, and resolve_note should be called when the corresponding note off
1818 * event arrives, to properly display the note.
1821 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1823 NoteBase* event = 0;
1825 if (midi_view()->note_mode() == Sustained) {
1827 Note* ev_rect = new Note (*this, _note_group, note);
1829 update_sustained (ev_rect);
1833 } else if (midi_view()->note_mode() == Percussive) {
1835 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1837 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1839 update_hit (ev_diamond);
1848 MidiGhostRegion* gr;
1850 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1851 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1852 gr->add_note(event);
1856 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1857 note_selected(event, true);
1860 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1861 event->show_velocity();
1864 event->on_channel_selection_change (get_selected_channels());
1865 _events.push_back(event);
1874 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1875 MidiStreamView* const view = mtv->midi_view();
1877 view->update_note_range (note->note());
1882 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1883 Evoral::Beats pos, Evoral::Beats len)
1885 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1887 /* potentially extend region to hold new note */
1889 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1890 framepos_t region_end = _region->last_frame();
1892 if (end_frame > region_end) {
1893 /* XX sets length in beats from audio space. make musical */
1894 _region->set_length (end_frame - _region->position(), 0);
1897 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1898 MidiStreamView* const view = mtv->midi_view();
1900 view->update_note_range(new_note->note());
1902 _marked_for_selection.clear ();
1904 start_note_diff_command (_("step add"));
1906 clear_editor_note_selection ();
1907 note_diff_add_note (new_note, true, false);
1911 // last_step_edit_note = new_note;
1915 MidiRegionView::step_sustain (Evoral::Beats beats)
1917 change_note_lengths (false, false, beats, false, true);
1920 /** Add a new patch change flag to the canvas.
1921 * @param patch the patch change to add
1922 * @param the text to display in the flag
1923 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1926 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1928 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1929 const double x = trackview.editor().sample_to_pixel (region_frames);
1931 double const height = midi_stream_view()->contents_height();
1933 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1934 // so we need to do something more sophisticated to keep its color
1935 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1938 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1939 new PatchChange(*this, group,
1946 if (patch_change->item().width() < _pixel_width) {
1947 // Show unless patch change is beyond the region bounds
1948 if (region_frames < 0 || region_frames >= _region->length()) {
1949 patch_change->hide();
1951 patch_change->show();
1954 patch_change->hide ();
1957 _patch_changes.push_back (patch_change);
1960 MIDI::Name::PatchPrimaryKey
1961 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1963 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1966 /// Return true iff @p pc applies to the given time on the given channel.
1968 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1970 return pc->time() <= time && pc->channel() == channel;
1974 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1976 // The earliest event not before time
1977 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1979 // Go backwards until we find the latest PC for this channel, or the start
1980 while (i != _model->patch_changes().begin() &&
1981 (i == _model->patch_changes().end() ||
1982 !patch_applies(*i, time, channel))) {
1986 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1987 key.set_bank((*i)->bank());
1988 key.set_program((*i)->program ());
1996 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1998 string name = _("alter patch change");
1999 trackview.editor().begin_reversible_command (name);
2000 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2002 if (pc.patch()->program() != new_patch.program()) {
2003 c->change_program (pc.patch (), new_patch.program());
2006 int const new_bank = new_patch.bank();
2007 if (pc.patch()->bank() != new_bank) {
2008 c->change_bank (pc.patch (), new_bank);
2011 _model->apply_command (*trackview.session(), c);
2012 trackview.editor().commit_reversible_command ();
2014 _patch_changes.clear ();
2015 display_patch_changes ();
2019 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2021 string name = _("alter patch change");
2022 trackview.editor().begin_reversible_command (name);
2023 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2025 if (old_change->time() != new_change.time()) {
2026 c->change_time (old_change, new_change.time());
2029 if (old_change->channel() != new_change.channel()) {
2030 c->change_channel (old_change, new_change.channel());
2033 if (old_change->program() != new_change.program()) {
2034 c->change_program (old_change, new_change.program());
2037 if (old_change->bank() != new_change.bank()) {
2038 c->change_bank (old_change, new_change.bank());
2041 _model->apply_command (*trackview.session(), c);
2042 trackview.editor().commit_reversible_command ();
2044 _patch_changes.clear ();
2045 display_patch_changes ();
2048 /** Add a patch change to the region.
2049 * @param t Time in frames relative to region position
2050 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2051 * MidiTimeAxisView::get_channel_for_add())
2054 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2056 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2057 string name = _("add patch change");
2059 trackview.editor().begin_reversible_command (name);
2060 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2061 c->add (MidiModel::PatchChangePtr (
2062 new Evoral::PatchChange<Evoral::Beats> (
2063 absolute_frames_to_source_beats (_region->position() + t),
2064 mtv->get_channel_for_add(), patch.program(), patch.bank()
2069 _model->apply_command (*trackview.session(), c);
2070 trackview.editor().commit_reversible_command ();
2072 _patch_changes.clear ();
2073 display_patch_changes ();
2077 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2079 trackview.editor().begin_reversible_command (_("move patch change"));
2080 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2081 c->change_time (pc.patch (), t);
2082 _model->apply_command (*trackview.session(), c);
2083 trackview.editor().commit_reversible_command ();
2085 _patch_changes.clear ();
2086 display_patch_changes ();
2090 MidiRegionView::delete_patch_change (PatchChange* pc)
2092 trackview.editor().begin_reversible_command (_("delete patch change"));
2093 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2094 c->remove (pc->patch ());
2095 _model->apply_command (*trackview.session(), c);
2096 trackview.editor().commit_reversible_command ();
2098 _patch_changes.clear ();
2099 display_patch_changes ();
2103 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2105 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2107 key.set_bank(key.bank() + delta);
2109 key.set_program(key.program() + delta);
2111 change_patch_change(patch, key);
2115 MidiRegionView::note_deleted (NoteBase* cne)
2117 if (_entered_note && cne == _entered_note) {
2121 if (_selection.empty()) {
2125 _selection.erase (cne);
2129 MidiRegionView::delete_selection()
2131 if (_selection.empty()) {
2135 if (trackview.editor().drags()->active()) {
2139 start_note_diff_command (_("delete selection"));
2141 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2142 if ((*i)->selected()) {
2143 _note_diff_command->remove((*i)->note());
2151 hide_verbose_cursor ();
2155 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2157 start_note_diff_command (_("delete note"));
2158 _note_diff_command->remove (n);
2161 hide_verbose_cursor ();
2165 MidiRegionView::clear_editor_note_selection ()
2167 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2168 PublicEditor& editor(trackview.editor());
2169 editor.get_selection().clear_midi_notes();
2173 MidiRegionView::clear_selection ()
2175 clear_selection_internal();
2176 PublicEditor& editor(trackview.editor());
2177 editor.get_selection().remove(this);
2181 MidiRegionView::clear_selection_internal ()
2183 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2185 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2186 (*i)->set_selected(false);
2187 (*i)->hide_velocity();
2192 // Clearing selection entirely, ungrab keyboard
2193 Keyboard::magic_widget_drop_focus();
2194 _grabbed_keyboard = false;
2199 MidiRegionView::unique_select(NoteBase* ev)
2201 clear_editor_note_selection();
2202 add_to_selection(ev);
2206 MidiRegionView::select_all_notes ()
2208 clear_editor_note_selection ();
2210 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2211 add_to_selection (*i);
2216 MidiRegionView::select_range (framepos_t start, framepos_t end)
2218 clear_editor_note_selection ();
2220 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2221 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2222 if (t >= start && t <= end) {
2223 add_to_selection (*i);
2229 MidiRegionView::invert_selection ()
2231 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2232 if ((*i)->selected()) {
2233 remove_from_selection(*i);
2235 add_to_selection (*i);
2240 /** Used for selection undo/redo.
2241 The requested notes most likely won't exist in the view until the next model redisplay.
2244 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2247 list<Evoral::event_id_t>::iterator n;
2249 for (n = notes.begin(); n != notes.end(); ++n) {
2250 if ((cne = find_canvas_note(*n)) != 0) {
2251 add_to_selection (cne);
2253 _pending_note_selection.insert(*n);
2259 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2261 bool have_selection = !_selection.empty();
2262 uint8_t low_note = 127;
2263 uint8_t high_note = 0;
2264 MidiModel::Notes& notes (_model->notes());
2265 _optimization_iterator = _events.begin();
2267 if (extend && !have_selection) {
2271 /* scan existing selection to get note range */
2273 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2274 if ((*i)->note()->note() < low_note) {
2275 low_note = (*i)->note()->note();
2277 if ((*i)->note()->note() > high_note) {
2278 high_note = (*i)->note()->note();
2283 clear_editor_note_selection ();
2285 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2286 /* only note previously selected is the one we are
2287 * reselecting. treat this as cancelling the selection.
2294 low_note = min (low_note, notenum);
2295 high_note = max (high_note, notenum);
2298 _no_sound_notes = true;
2300 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2302 boost::shared_ptr<NoteType> note (*n);
2304 bool select = false;
2306 if (((1 << note->channel()) & channel_mask) != 0) {
2308 if ((note->note() >= low_note && note->note() <= high_note)) {
2311 } else if (note->note() == notenum) {
2317 if ((cne = find_canvas_note (note)) != 0) {
2318 // extend is false because we've taken care of it,
2319 // since it extends by time range, not pitch.
2320 note_selected (cne, add, false);
2324 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2328 _no_sound_notes = false;
2332 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2334 MidiModel::Notes& notes (_model->notes());
2335 _optimization_iterator = _events.begin();
2337 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2339 boost::shared_ptr<NoteType> note (*n);
2342 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2343 if ((cne = find_canvas_note (note)) != 0) {
2344 if (cne->selected()) {
2345 note_deselected (cne);
2347 note_selected (cne, true, false);
2355 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2358 clear_editor_note_selection();
2359 add_to_selection (ev);
2364 if (!ev->selected()) {
2365 add_to_selection (ev);
2369 /* find end of latest note selected, select all between that and the start of "ev" */
2371 Evoral::Beats earliest = Evoral::MaxBeats;
2372 Evoral::Beats latest = Evoral::Beats();
2374 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2375 if ((*i)->note()->end_time() > latest) {
2376 latest = (*i)->note()->end_time();
2378 if ((*i)->note()->time() < earliest) {
2379 earliest = (*i)->note()->time();
2383 if (ev->note()->end_time() > latest) {
2384 latest = ev->note()->end_time();
2387 if (ev->note()->time() < earliest) {
2388 earliest = ev->note()->time();
2391 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2393 /* find notes entirely within OR spanning the earliest..latest range */
2395 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2396 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2397 add_to_selection (*i);
2405 MidiRegionView::note_deselected(NoteBase* ev)
2407 remove_from_selection (ev);
2411 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2413 PublicEditor& editor = trackview.editor();
2415 // Convert to local coordinates
2416 const framepos_t p = _region->position();
2417 const double y = midi_view()->y_position();
2418 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2419 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2420 const double y0 = max(0.0, gy0 - y);
2421 const double y1 = max(0.0, gy1 - y);
2423 // TODO: Make this faster by storing the last updated selection rect, and only
2424 // adjusting things that are in the area that appears/disappeared.
2425 // We probably need a tree to be able to find events in O(log(n)) time.
2427 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2428 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2429 // Rectangles intersect
2430 if (!(*i)->selected()) {
2431 add_to_selection (*i);
2433 } else if ((*i)->selected() && !extend) {
2434 // Rectangles do not intersect
2435 remove_from_selection (*i);
2439 typedef RouteTimeAxisView::AutomationTracks ATracks;
2440 typedef std::list<Selectable*> Selectables;
2442 /* Add control points to selection. */
2443 const ATracks& atracks = midi_view()->automation_tracks();
2444 Selectables selectables;
2445 editor.get_selection().clear_points();
2446 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2447 a->second->get_selectables(start, end, gy0, gy1, selectables);
2448 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2449 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2451 editor.get_selection().add(cp);
2454 a->second->set_selected_points(editor.get_selection().points);
2459 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2465 // TODO: Make this faster by storing the last updated selection rect, and only
2466 // adjusting things that are in the area that appears/disappeared.
2467 // We probably need a tree to be able to find events in O(log(n)) time.
2469 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2470 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2471 // within y- (note-) range
2472 if (!(*i)->selected()) {
2473 add_to_selection (*i);
2475 } else if ((*i)->selected() && !extend) {
2476 remove_from_selection (*i);
2482 MidiRegionView::remove_from_selection (NoteBase* ev)
2484 Selection::iterator i = _selection.find (ev);
2486 if (i != _selection.end()) {
2487 _selection.erase (i);
2488 if (_selection.empty() && _grabbed_keyboard) {
2490 Keyboard::magic_widget_drop_focus();
2491 _grabbed_keyboard = false;
2495 ev->set_selected (false);
2496 ev->hide_velocity ();
2498 if (_selection.empty()) {
2499 PublicEditor& editor (trackview.editor());
2500 editor.get_selection().remove (this);
2505 MidiRegionView::add_to_selection (NoteBase* ev)
2507 const bool selection_was_empty = _selection.empty();
2509 if (_selection.insert (ev).second) {
2510 ev->set_selected (true);
2511 start_playing_midi_note ((ev)->note());
2512 if (selection_was_empty && _entered) {
2513 // Grab keyboard for moving notes with arrow keys
2514 Keyboard::magic_widget_grab_focus();
2515 _grabbed_keyboard = true;
2519 if (selection_was_empty) {
2520 PublicEditor& editor (trackview.editor());
2521 editor.get_selection().add (this);
2526 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2528 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2529 PossibleChord to_play;
2530 Evoral::Beats earliest = Evoral::MaxBeats;
2532 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2533 if ((*i)->note()->time() < earliest) {
2534 earliest = (*i)->note()->time();
2538 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2539 if ((*i)->note()->time() == earliest) {
2540 to_play.push_back ((*i)->note());
2542 (*i)->move_event(dx, dy);
2545 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2547 if (to_play.size() > 1) {
2549 PossibleChord shifted;
2551 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2552 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2553 moved_note->set_note (moved_note->note() + cumulative_dy);
2554 shifted.push_back (moved_note);
2557 start_playing_midi_chord (shifted);
2559 } else if (!to_play.empty()) {
2561 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2562 moved_note->set_note (moved_note->note() + cumulative_dy);
2563 start_playing_midi_note (moved_note);
2569 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2571 uint8_t lowest_note_in_selection = 127;
2572 uint8_t highest_note_in_selection = 0;
2573 uint8_t highest_note_difference = 0;
2575 // find highest and lowest notes first
2577 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2578 uint8_t pitch = (*i)->note()->note();
2579 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2580 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2584 cerr << "dnote: " << (int) dnote << endl;
2585 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2586 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2587 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2588 << int(highest_note_in_selection) << endl;
2589 cerr << "selection size: " << _selection.size() << endl;
2590 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2593 // Make sure the note pitch does not exceed the MIDI standard range
2594 if (highest_note_in_selection + dnote > 127) {
2595 highest_note_difference = highest_note_in_selection - 127;
2597 TempoMap& map (trackview.session()->tempo_map());
2599 start_note_diff_command (_("move notes"));
2601 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2603 double const start_qn = _region->quarter_note() - midi_region()->start_beats();
2604 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2605 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2610 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2612 uint8_t original_pitch = (*i)->note()->note();
2613 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2615 // keep notes in standard midi range
2616 clamp_to_0_127(new_pitch);
2618 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2619 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2621 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2626 // care about notes being moved beyond the upper/lower bounds on the canvas
2627 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2628 highest_note_in_selection > midi_stream_view()->highest_note()) {
2629 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2633 /** @param x Pixel relative to the region position.
2634 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2635 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2636 * @return Snapped frame relative to the region position.
2639 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2641 PublicEditor& editor (trackview.editor());
2642 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2645 /** @param x Pixel relative to the region position.
2646 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2647 * @return Snapped pixel relative to the region position.
2650 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2652 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2656 MidiRegionView::get_position_pixels()
2658 framepos_t region_frame = get_position();
2659 return trackview.editor().sample_to_pixel(region_frame);
2663 MidiRegionView::get_end_position_pixels()
2665 framepos_t frame = get_position() + get_duration ();
2666 return trackview.editor().sample_to_pixel(frame);
2670 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2672 /* the time converter will return the frame corresponding to `beats'
2673 relative to the start of the source. The start of the source
2674 is an implied position given by region->position - region->start
2676 const framepos_t source_start = _region->position() - _region->start();
2677 return source_start + _source_relative_time_converter.to (beats);
2681 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2683 /* the `frames' argument needs to be converted into a frame count
2684 relative to the start of the source before being passed in to the
2687 const framepos_t source_start = _region->position() - _region->start();
2688 return _source_relative_time_converter.from (frames - source_start);
2692 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2694 return _region_relative_time_converter.to(beats);
2698 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2700 return _region_relative_time_converter.from(frames);
2704 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2706 return _region_relative_time_converter_double.from(frames);
2710 MidiRegionView::begin_resizing (bool /*at_front*/)
2712 _resize_data.clear();
2714 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2715 Note *note = dynamic_cast<Note*> (*i);
2717 // only insert CanvasNotes into the map
2719 NoteResizeData *resize_data = new NoteResizeData();
2720 resize_data->note = note;
2722 // create a new SimpleRect from the note which will be the resize preview
2723 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2724 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2726 // calculate the colors: get the color settings
2727 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2728 UIConfiguration::instance().color ("midi note selected"),
2731 // make the resize preview notes more transparent and bright
2732 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2734 // calculate color based on note velocity
2735 resize_rect->set_fill_color (UINT_INTERPOLATE(
2736 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2740 resize_rect->set_outline_color (NoteBase::calculate_outline (
2741 UIConfiguration::instance().color ("midi note selected")));
2743 resize_data->resize_rect = resize_rect;
2744 _resize_data.push_back(resize_data);
2749 /** Update resizing notes while user drags.
2750 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2751 * @param at_front which end of the note (true == note on, false == note off)
2752 * @param delta_x change in mouse position since the start of the drag
2753 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2754 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2755 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2756 * as the \a primary note.
2757 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2758 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2761 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2763 TempoMap& tmap (trackview.session()->tempo_map());
2764 bool cursor_set = false;
2765 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2767 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2768 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2769 Note* canvas_note = (*i)->note;
2774 current_x = canvas_note->x0() + delta_x + snap_delta;
2776 current_x = primary->x0() + delta_x + snap_delta;
2780 current_x = canvas_note->x1() + delta_x + snap_delta;
2782 current_x = primary->x1() + delta_x + snap_delta;
2786 if (current_x < 0) {
2787 // This works even with snapping because RegionView::snap_frame_to_frame()
2788 // snaps forward if the snapped sample is before the beginning of the region
2791 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2792 current_x = trackview.editor().sample_to_pixel(_region->length());
2797 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2799 resize_rect->set_x0 (current_x - snap_delta);
2801 resize_rect->set_x1 (canvas_note->x1());
2804 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2806 resize_rect->set_x1 (current_x - snap_delta);
2808 resize_rect->set_x0 (canvas_note->x0());
2812 /* Convert snap delta from pixels to beats. */
2813 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2814 double snap_delta_beats = 0.0;
2817 /* negative beat offsets aren't allowed */
2818 if (snap_delta_samps > 0) {
2819 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2820 } else if (snap_delta_samps < 0) {
2821 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2826 int32_t divisions = 0;
2829 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2830 divisions = trackview.editor().get_grid_music_divisions (0);
2832 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2834 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2835 - midi_region()->beat()) + midi_region()->start_beats();
2837 Evoral::Beats len = Evoral::Beats();
2840 if (beats < canvas_note->note()->end_time()) {
2841 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2842 len += canvas_note->note()->length();
2845 if (beats >= canvas_note->note()->time()) {
2846 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2850 len = std::max(Evoral::Beats(1 / 512.0), len);
2853 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2854 show_verbose_cursor (buf, 0, 0);
2863 /** Finish resizing notes when the user releases the mouse button.
2864 * Parameters the same as for \a update_resizing().
2867 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2869 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2870 TempoMap& tmap (trackview.session()->tempo_map());
2872 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2873 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2875 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2876 Note* canvas_note = (*i)->note;
2877 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2879 /* Get the new x position for this resize, which is in pixels relative
2880 * to the region position.
2887 current_x = canvas_note->x0() + delta_x + snap_delta;
2889 current_x = primary->x0() + delta_x + snap_delta;
2893 current_x = canvas_note->x1() + delta_x + snap_delta;
2895 current_x = primary->x1() + delta_x + snap_delta;
2899 if (current_x < 0) {
2902 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2903 current_x = trackview.editor().sample_to_pixel(_region->length());
2906 /* Convert snap delta from pixels to beats with sign. */
2907 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2908 double snap_delta_beats = 0.0;
2911 if (snap_delta_samps > 0) {
2912 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2913 } else if (snap_delta_samps < 0) {
2914 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2918 uint32_t divisions = 0;
2919 /* Convert the new x position to a frame within the source */
2920 framepos_t current_fr;
2922 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2923 divisions = trackview.editor().get_grid_music_divisions (0);
2925 current_fr = trackview.editor().pixel_to_sample (current_x);
2928 /* and then to beats */
2929 const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
2930 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
2931 const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
2933 if (at_front && x_beats < canvas_note->note()->end_time()) {
2934 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2935 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2936 len += canvas_note->note()->length();
2939 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2944 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2945 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2946 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2953 _resize_data.clear();
2958 MidiRegionView::abort_resizing ()
2960 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2961 delete (*i)->resize_rect;
2965 _resize_data.clear ();
2969 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2971 uint8_t new_velocity;
2974 new_velocity = event->note()->velocity() + velocity;
2975 clamp_to_0_127(new_velocity);
2977 new_velocity = velocity;
2980 event->set_selected (event->selected()); // change color
2982 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2986 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2991 new_note = event->note()->note() + note;
2996 clamp_to_0_127 (new_note);
2997 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3001 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
3003 bool change_start = false;
3004 bool change_length = false;
3005 Evoral::Beats new_start;
3006 Evoral::Beats new_length;
3008 /* NOTE: the semantics of the two delta arguments are slightly subtle:
3010 front_delta: if positive - move the start of the note later in time (shortening it)
3011 if negative - move the start of the note earlier in time (lengthening it)
3013 end_delta: if positive - move the end of the note later in time (lengthening it)
3014 if negative - move the end of the note earlier in time (shortening it)
3017 if (!!front_delta) {
3018 if (front_delta < 0) {
3020 if (event->note()->time() < -front_delta) {
3021 new_start = Evoral::Beats();
3023 new_start = event->note()->time() + front_delta; // moves earlier
3026 /* start moved toward zero, so move the end point out to where it used to be.
3027 Note that front_delta is negative, so this increases the length.
3030 new_length = event->note()->length() - front_delta;
3031 change_start = true;
3032 change_length = true;
3036 Evoral::Beats new_pos = event->note()->time() + front_delta;
3038 if (new_pos < event->note()->end_time()) {
3039 new_start = event->note()->time() + front_delta;
3040 /* start moved toward the end, so move the end point back to where it used to be */
3041 new_length = event->note()->length() - front_delta;
3042 change_start = true;
3043 change_length = true;
3050 bool can_change = true;
3051 if (end_delta < 0) {
3052 if (event->note()->length() < -end_delta) {
3058 new_length = event->note()->length() + end_delta;
3059 change_length = true;
3064 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3067 if (change_length) {
3068 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3073 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3075 uint8_t new_channel;
3079 if (event->note()->channel() < -chn) {
3082 new_channel = event->note()->channel() + chn;
3085 new_channel = event->note()->channel() + chn;
3088 new_channel = (uint8_t) chn;
3091 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3095 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3097 Evoral::Beats new_time;
3101 if (event->note()->time() < -delta) {
3102 new_time = Evoral::Beats();
3104 new_time = event->note()->time() + delta;
3107 new_time = event->note()->time() + delta;
3113 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3117 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3119 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3123 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3128 if (_selection.empty()) {
3143 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3144 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3150 start_note_diff_command (_("change velocities"));
3152 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3153 Selection::iterator next = i;
3157 if (i == _selection.begin()) {
3158 change_note_velocity (*i, delta, true);
3159 value = (*i)->note()->velocity() + delta;
3161 change_note_velocity (*i, value, false);
3165 change_note_velocity (*i, delta, true);
3174 if (!_selection.empty()) {
3176 snprintf (buf, sizeof (buf), "Vel %d",
3177 (int) (*_selection.begin())->note()->velocity());
3178 show_verbose_cursor (buf, 10, 10);
3184 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3186 if (_selection.empty()) {
3203 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3205 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3209 if ((int8_t) (*i)->note()->note() + delta > 127) {
3216 start_note_diff_command (_("transpose"));
3218 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3219 Selection::iterator next = i;
3221 change_note_note (*i, delta, true);
3229 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3233 delta = Evoral::Beats(1.0/128.0);
3235 /* grab the current grid distance */
3236 delta = get_grid_beats(_region->position());
3244 start_note_diff_command (_("change note lengths"));
3246 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3247 Selection::iterator next = i;
3250 /* note the negation of the delta for start */
3253 (start ? -delta : Evoral::Beats()),
3254 (end ? delta : Evoral::Beats()));
3263 MidiRegionView::nudge_notes (bool forward, bool fine)
3265 if (_selection.empty()) {
3269 /* pick a note as the point along the timeline to get the nudge distance.
3270 its not necessarily the earliest note, so we may want to pull the notes out
3271 into a vector and sort before using the first one.
3274 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3275 Evoral::Beats delta;
3279 /* non-fine, move by 1 bar regardless of snap */
3280 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3282 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3284 /* grid is off - use nudge distance */
3287 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3288 delta = region_frames_to_region_beats (fabs ((double)distance));
3294 framepos_t next_pos = ref_point;
3297 if (max_framepos - 1 < next_pos) {
3301 if (next_pos == 0) {
3307 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3308 const framecnt_t distance = ref_point - next_pos;
3309 delta = region_frames_to_region_beats (fabs ((double)distance));
3320 start_note_diff_command (_("nudge"));
3322 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3323 Selection::iterator next = i;
3325 change_note_time (*i, delta, true);
3333 MidiRegionView::change_channel(uint8_t channel)
3335 start_note_diff_command(_("change channel"));
3336 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3337 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3345 MidiRegionView::note_entered(NoteBase* ev)
3349 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3351 if (_mouse_state == SelectTouchDragging) {
3353 note_selected (ev, true);
3355 } else if (editor->current_mouse_mode() == MouseContent) {
3357 remove_ghost_note ();
3358 show_verbose_cursor (ev->note ());
3360 } else if (editor->current_mouse_mode() == MouseDraw) {
3362 remove_ghost_note ();
3363 show_verbose_cursor (ev->note ());
3368 MidiRegionView::note_left (NoteBase*)
3372 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3373 (*i)->hide_velocity ();
3376 hide_verbose_cursor ();
3380 MidiRegionView::patch_entered (PatchChange* p)
3383 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3384 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3385 << _("Channel ") << ((int) p->patch()->channel() + 1);
3386 show_verbose_cursor (s.str(), 10, 20);
3387 p->item().grab_focus();
3391 MidiRegionView::patch_left (PatchChange *)
3393 hide_verbose_cursor ();
3394 /* focus will transfer back via the enter-notify event sent to this
3400 MidiRegionView::sysex_entered (SysEx* p)
3404 // need a way to extract text from p->_flag->_text
3406 // show_verbose_cursor (s.str(), 10, 20);
3407 p->item().grab_focus();
3411 MidiRegionView::sysex_left (SysEx *)
3413 hide_verbose_cursor ();
3414 /* focus will transfer back via the enter-notify event sent to this
3420 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3422 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3423 Editing::MouseMode mm = editor->current_mouse_mode();
3424 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3426 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3427 if (can_set_cursor && ctx) {
3428 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3429 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3430 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3431 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3433 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3439 MidiRegionView::get_fill_color() const
3441 const std::string mod_name = (_dragging ? "dragging region" :
3442 trackview.editor().internal_editing() ? "editable region" :
3445 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3446 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3447 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3448 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3450 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3454 MidiRegionView::midi_channel_mode_changed ()
3456 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3457 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3458 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3460 if (mode == ForceChannel) {
3461 mask = 0xFFFF; // Show all notes as active (below)
3464 // Update notes for selection
3465 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3466 (*i)->on_channel_selection_change (mask);
3469 _patch_changes.clear ();
3470 display_patch_changes ();
3474 MidiRegionView::instrument_settings_changed ()
3480 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3482 if (_selection.empty()) {
3486 PublicEditor& editor (trackview.editor());
3490 /* XXX what to do ? */
3494 editor.get_cut_buffer().add (selection_as_cut_buffer());
3502 start_note_diff_command();
3504 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3511 note_diff_remove_note (*i);
3521 MidiRegionView::selection_as_cut_buffer () const
3525 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3526 NoteType* n = (*i)->note().get();
3527 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3530 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3536 /** This method handles undo */
3538 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3540 bool commit = false;
3541 // Paste notes, if available
3542 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3543 if (m != selection.midi_notes.end()) {
3544 ctx.counts.increase_n_notes();
3545 if (!(*m)->empty()) {
3548 paste_internal(pos, ctx.count, ctx.times, **m);
3551 // Paste control points to automation children, if available
3552 typedef RouteTimeAxisView::AutomationTracks ATracks;
3553 const ATracks& atracks = midi_view()->automation_tracks();
3554 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3555 if (a->second->paste(pos, selection, ctx, sub_num)) {
3557 trackview.editor().begin_reversible_command (Operations::paste);
3564 trackview.editor().commit_reversible_command ();
3569 /** This method handles undo */
3571 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3577 start_note_diff_command (_("paste"));
3579 const Evoral::Beats snap_beats = get_grid_beats(pos);
3580 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3581 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3582 const Evoral::Beats duration = last_time - first_time;
3583 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3584 const Evoral::Beats paste_offset = snap_duration * paste_count;
3585 const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
3586 Evoral::Beats end_point = Evoral::Beats();
3588 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3591 duration, pos, _region->position(),
3594 clear_editor_note_selection ();
3596 for (int n = 0; n < (int) times; ++n) {
3598 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3600 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3601 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3602 copied_note->set_id (Evoral::next_event_id());
3604 /* make all newly added notes selected */
3606 note_diff_add_note (copied_note, true);
3607 end_point = copied_note->end_time();
3611 /* if we pasted past the current end of the region, extend the region */
3613 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3614 framepos_t region_end = _region->position() + _region->length() - 1;
3616 if (end_frame > region_end) {
3618 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3620 _region->clear_changes ();
3621 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3622 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3623 trackview.session()->add_command (new StatefulDiffCommand (_region));
3629 struct EventNoteTimeEarlyFirstComparator {
3630 bool operator() (NoteBase* a, NoteBase* b) {
3631 return a->note()->time() < b->note()->time();
3636 MidiRegionView::time_sort_events ()
3638 if (!_sort_needed) {
3642 EventNoteTimeEarlyFirstComparator cmp;
3645 _sort_needed = false;
3649 MidiRegionView::goto_next_note (bool add_to_selection)
3651 bool use_next = false;
3653 if (_events.back()->selected()) {
3657 time_sort_events ();
3659 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3660 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3662 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3663 if ((*i)->selected()) {
3666 } else if (use_next) {
3667 if (channel_mask & (1 << (*i)->note()->channel())) {
3668 if (!add_to_selection) {
3671 note_selected (*i, true, false);
3678 /* use the first one */
3680 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3681 unique_select (_events.front());
3686 MidiRegionView::goto_previous_note (bool add_to_selection)
3688 bool use_next = false;
3690 if (_events.front()->selected()) {
3694 time_sort_events ();
3696 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3697 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3699 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3700 if ((*i)->selected()) {
3703 } else if (use_next) {
3704 if (channel_mask & (1 << (*i)->note()->channel())) {
3705 if (!add_to_selection) {
3708 note_selected (*i, true, false);
3715 /* use the last one */
3717 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3718 unique_select (*(_events.rbegin()));
3723 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3725 bool had_selected = false;
3727 time_sort_events ();
3729 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3730 if ((*i)->selected()) {
3731 selected.insert ((*i)->note());
3732 had_selected = true;
3736 if (allow_all_if_none_selected && !had_selected) {
3737 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3738 selected.insert ((*i)->note());
3744 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3746 x = std::max(0.0, x);
3748 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3753 _note_group->canvas_to_item (x, y);
3755 PublicEditor& editor = trackview.editor ();
3757 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3759 const int32_t divisions = editor.get_grid_music_divisions (state);
3760 const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, true);
3762 /* ghost note may have been snapped before region */
3763 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3764 _ghost_note->hide();
3767 } else if (_ghost_note) {
3768 _ghost_note->show();
3771 /* calculate time in beats relative to start of source */
3772 const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3774 _ghost_note->note()->set_time (snapped_beats);
3775 _ghost_note->note()->set_length (length);
3776 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3777 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3778 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3779 /* the ghost note does not appear in ghost regions, so pass false in here */
3780 update_note (_ghost_note, false);
3782 show_verbose_cursor (_ghost_note->note ());
3786 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3788 remove_ghost_note ();
3790 boost::shared_ptr<NoteType> g (new NoteType);
3791 if (midi_view()->note_mode() == Sustained) {
3792 _ghost_note = new Note (*this, _note_group, g);
3794 _ghost_note = new Hit (*this, _note_group, 10, g);
3796 _ghost_note->set_ignore_events (true);
3797 _ghost_note->set_outline_color (0x000000aa);
3798 update_ghost_note (x, y, state);
3799 _ghost_note->show ();
3801 show_verbose_cursor (_ghost_note->note ());
3805 MidiRegionView::remove_ghost_note ()
3812 MidiRegionView::hide_verbose_cursor ()
3814 trackview.editor().verbose_cursor()->hide ();
3815 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3817 mtv->set_note_highlight (NO_MIDI_NOTE);
3822 MidiRegionView::snap_changed ()
3828 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3832 MidiRegionView::drop_down_keys ()
3834 _mouse_state = None;
3838 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3840 /* XXX: This is dead code. What was it for? */
3842 double note = midi_stream_view()->y_to_note(y);
3844 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3846 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3848 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3849 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3850 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3851 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3856 bool add_mrv_selection = false;
3858 if (_selection.empty()) {
3859 add_mrv_selection = true;
3862 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3863 if (_selection.insert (*i).second) {
3864 (*i)->set_selected (true);
3868 if (add_mrv_selection) {
3869 PublicEditor& editor (trackview.editor());
3870 editor.get_selection().add (this);
3875 MidiRegionView::color_handler ()
3877 RegionView::color_handler ();
3879 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3880 (*i)->set_selected ((*i)->selected()); // will change color
3883 /* XXX probably more to do here */
3887 MidiRegionView::enable_display (bool yn)
3889 RegionView::enable_display (yn);
3893 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3895 if (_step_edit_cursor == 0) {
3896 ArdourCanvas::Item* const group = get_canvas_group();
3898 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3899 _step_edit_cursor->set_y0 (0);
3900 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3901 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3902 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3905 move_step_edit_cursor (pos);
3906 _step_edit_cursor->show ();
3910 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3912 _step_edit_cursor_position = pos;
3914 if (_step_edit_cursor) {
3915 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3916 _step_edit_cursor->set_x0 (pixel);
3917 set_step_edit_cursor_width (_step_edit_cursor_width);
3922 MidiRegionView::hide_step_edit_cursor ()
3924 if (_step_edit_cursor) {
3925 _step_edit_cursor->hide ();
3930 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3932 _step_edit_cursor_width = beats;
3934 if (_step_edit_cursor) {
3935 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3936 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3937 - region_beats_to_region_frames (_step_edit_cursor_position)));
3941 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3942 * @param w Source that the data will end up in.
3945 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3947 if (!_active_notes) {
3948 /* we aren't actively being recorded to */
3952 boost::shared_ptr<MidiSource> src = w.lock ();
3953 if (!src || src != midi_region()->midi_source()) {
3954 /* recorded data was not destined for our source */
3958 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3960 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3962 framepos_t back = max_framepos;
3964 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3965 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3967 if (ev.is_channel_event()) {
3968 if (get_channel_mode() == FilterChannels) {
3969 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3975 /* convert from session frames to source beats */
3976 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3977 ev.time() - src->timeline_position() + _region->start());
3979 if (ev.type() == MIDI_CMD_NOTE_ON) {
3980 boost::shared_ptr<NoteType> note (
3981 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3983 add_note (note, true);
3985 /* fix up our note range */
3986 if (ev.note() < _current_range_min) {
3987 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3988 } else if (ev.note() > _current_range_max) {
3989 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3992 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3993 resolve_note (ev.note (), time_beats);
3999 midi_stream_view()->check_record_layers (region(), back);
4003 MidiRegionView::trim_front_starting ()
4005 /* We used to eparent the note group to the region view's parent, so that it didn't change.
4011 MidiRegionView::trim_front_ending ()
4013 if (_region->start() < 0) {
4014 /* Trim drag made start time -ve; fix this */
4015 midi_region()->fix_negative_start ();
4020 MidiRegionView::edit_patch_change (PatchChange* pc)
4022 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4024 int response = d.run();
4027 case Gtk::RESPONSE_ACCEPT:
4029 case Gtk::RESPONSE_REJECT:
4030 delete_patch_change (pc);
4036 change_patch_change (pc->patch(), d.patch ());
4040 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4043 // sysyex object doesn't have a pointer to a sysex event
4044 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4045 // c->remove (sysex->sysex());
4046 // _model->apply_command (*trackview.session(), c);
4048 //_sys_exes.clear ();
4049 // display_sysexes();
4053 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4055 using namespace MIDI::Name;
4058 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4060 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4062 MIDI::Name::PatchPrimaryKey patch_key;
4063 get_patch_key_at(n->time(), n->channel(), patch_key);
4064 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4067 patch_key.program(),
4073 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4075 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4076 (int) n->channel() + 1,
4077 (int) n->velocity());
4083 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4084 uint8_t new_value) const
4086 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4088 mtv->set_note_highlight (new_value);
4091 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4095 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4097 show_verbose_cursor_for_new_note_value(n, n->note());
4101 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4103 trackview.editor().verbose_cursor()->set (text);
4104 trackview.editor().verbose_cursor()->show ();
4105 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4109 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4111 if (_model->notes().empty()) {
4112 return 0x40; // No notes, use default
4115 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4116 if (m == _model->notes().begin()) {
4117 // Before the start, use the velocity of the first note
4118 return (*m)->velocity();
4119 } else if (m == _model->notes().end()) {
4120 // Past the end, use the velocity of the last note
4122 return (*m)->velocity();
4125 // Interpolate velocity of surrounding notes
4126 MidiModel::Notes::const_iterator n = m;
4129 const double frac = ((time - (*n)->time()).to_double() /
4130 ((*m)->time() - (*n)->time()).to_double());
4132 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4135 /** @param p A session framepos.
4136 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4137 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4138 * @return beat duration of p snapped to the grid subdivision underneath it.
4141 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
4143 TempoMap& map (trackview.session()->tempo_map());
4144 double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4146 if (divisions != 0 && shift_snap) {
4147 const double qaf = map.quarter_note_at_frame (p + _region->position());
4148 /* Hack so that we always snap to the note that we are over, instead of snapping
4149 to the next one if we're more than halfway through the one we're over.
4151 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4152 const double rem = eqaf - qaf;
4154 eqaf -= grid_beats.to_double();
4157 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4159 return Evoral::Beats (eqaf - session_start_off);
4163 MidiRegionView::get_channel_mode () const
4165 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4166 return rtav->midi_track()->get_playback_channel_mode();
4170 MidiRegionView::get_selected_channels () const
4172 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4173 return rtav->midi_track()->get_playback_channel_mask();
4178 MidiRegionView::get_grid_beats(framepos_t pos) const
4180 PublicEditor& editor = trackview.editor();
4181 bool success = false;
4182 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4184 beats = Evoral::Beats(1);