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)
125 , _note_entered (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)
168 , _note_entered (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)
216 , _note_entered (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)
248 , _note_entered (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 _note_entered 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 (!_note_entered) {
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 ();
467 _note_entered = false;
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));
554 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, ev->state);
556 clear_editor_note_selection ();
563 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
564 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, ev->state);
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 (!_note_entered) {
602 if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
603 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
604 _mouse_state != AddDragging) {
606 create_ghost_note (ev->x, ev->y, ev->state);
608 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
609 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
611 update_ghost_note (ev->x, ev->y, ev->state);
613 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
615 remove_ghost_note ();
616 hide_verbose_cursor ();
618 } else if (editor.current_mouse_mode() == MouseDraw) {
621 update_ghost_note (ev->x, ev->y, ev->state);
624 create_ghost_note (ev->x, ev->y, ev->state);
629 /* any motion immediately hides velocity text that may have been visible */
631 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
632 (*i)->hide_velocity ();
635 switch (_mouse_state) {
638 if (_pressed_button == 1) {
640 MouseMode m = editor.current_mouse_mode();
642 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
643 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
644 _mouse_state = AddDragging;
645 remove_ghost_note ();
646 hide_verbose_cursor ();
648 } else if (m == MouseContent) {
649 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
650 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
651 clear_editor_note_selection ();
652 _mouse_changed_selection = true;
654 _mouse_state = SelectRectDragging;
656 } else if (m == MouseRange) {
657 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
658 _mouse_state = SelectVerticalDragging;
665 case SelectRectDragging:
666 case SelectVerticalDragging:
668 editor.drags()->motion_handler ((GdkEvent *) ev, false);
671 case SelectTouchDragging:
679 /* we may be dragging some non-note object (eg. patch-change, sysex)
682 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
687 MidiRegionView::scroll (GdkEventScroll* ev)
689 if (_selection.empty()) {
693 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
694 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
695 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
696 * through so that it still works for navigation.
701 hide_verbose_cursor ();
703 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
704 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
705 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
707 if (ev->direction == GDK_SCROLL_UP) {
708 change_velocities (true, fine, false, together);
709 } else if (ev->direction == GDK_SCROLL_DOWN) {
710 change_velocities (false, fine, false, together);
712 /* left, right: we don't use them */
720 MidiRegionView::key_press (GdkEventKey* ev)
722 /* since GTK bindings are generally activated on press, and since
723 detectable auto-repeat is the name of the game and only sends
724 repeated presses, carry out key actions at key press, not release.
727 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
729 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
730 _mouse_state = SelectTouchDragging;
733 } else if (ev->keyval == GDK_Escape && unmodified) {
734 clear_editor_note_selection ();
737 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
739 bool start = (ev->keyval == GDK_comma);
740 bool end = (ev->keyval == GDK_period);
741 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
742 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
744 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
748 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
750 if (_selection.empty()) {
757 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
759 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
761 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
762 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
764 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
767 trackview.editor().commit_reversible_selection_op();
771 } else if (ev->keyval == GDK_Up) {
773 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
774 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
775 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
777 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
778 change_velocities (true, fine, allow_smush, together);
780 transpose (true, fine, allow_smush);
784 } else if (ev->keyval == GDK_Down) {
786 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
787 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
788 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
790 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
791 change_velocities (false, fine, allow_smush, together);
793 transpose (false, fine, allow_smush);
797 } else if (ev->keyval == GDK_Left) {
799 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
800 nudge_notes (false, fine);
803 } else if (ev->keyval == GDK_Right) {
805 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
806 nudge_notes (true, fine);
809 } else if (ev->keyval == GDK_c && unmodified) {
813 } else if (ev->keyval == GDK_v && unmodified) {
822 MidiRegionView::key_release (GdkEventKey* ev)
824 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
832 MidiRegionView::channel_edit ()
834 if (_selection.empty()) {
838 /* pick a note somewhat at random (since Selection is a set<>) to
839 * provide the "current" channel for the dialog.
842 uint8_t current_channel = (*_selection.begin())->note()->channel ();
843 MidiChannelDialog channel_dialog (current_channel);
844 int ret = channel_dialog.run ();
847 case Gtk::RESPONSE_OK:
853 uint8_t new_channel = channel_dialog.active_channel ();
855 start_note_diff_command (_("channel edit"));
857 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
858 Selection::iterator next = i;
860 change_note_channel (*i, new_channel);
868 MidiRegionView::velocity_edit ()
870 if (_selection.empty()) {
874 /* pick a note somewhat at random (since Selection is a set<>) to
875 * provide the "current" velocity for the dialog.
878 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
879 MidiVelocityDialog velocity_dialog (current_velocity);
880 int ret = velocity_dialog.run ();
883 case Gtk::RESPONSE_OK:
889 uint8_t new_velocity = velocity_dialog.velocity ();
891 start_note_diff_command (_("velocity edit"));
893 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
894 Selection::iterator next = i;
896 change_note_velocity (*i, new_velocity, false);
904 MidiRegionView::show_list_editor ()
907 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
909 _list_editor->present ();
912 /** Add a note to the model, and the view, at a canvas (click) coordinate.
913 * \param t time in frames relative to the position of the region
914 * \param y vertical position in pixels
915 * \param length duration of the note in beats
916 * \param snap_t true to snap t to the grid, otherwise false.
919 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state)
921 if (length < 2 * DBL_EPSILON) {
925 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
926 MidiStreamView* const view = mtv->midi_view();
927 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
932 TempoMap& map (trackview.session()->tempo_map());
934 // Start of note in frames relative to region start
935 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
936 const double snapped_qn = map.exact_qn_at_frame (t + _region->position(), divisions);
937 /* make start region start relative */
938 const double start_qn = (_region->pulse() - mr->start_pulse()) * 4.0;
939 Evoral::Beats beat_time = Evoral::Beats (snapped_qn - start_qn);
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 (NoteType note)
1125 Events::iterator it;
1127 for (it = _events.begin(); it != _events.end(); ++it) {
1128 if (*((*it)->note()) == note) {
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<boost::shared_ptr<NoteType> >::iterator it;
1209 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1210 if (*(*it) == *note) {
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_height ();
1549 ghost->set_duration (_region->length() / samples_per_pixel);
1550 ghosts.push_back (ghost);
1556 /** Begin tracking note state for successive calls to add_event
1559 MidiRegionView::begin_write()
1561 if (_active_notes) {
1562 delete[] _active_notes;
1564 _active_notes = new Note*[128];
1565 for (unsigned i = 0; i < 128; ++i) {
1566 _active_notes[i] = 0;
1571 /** Destroy note state for add_event
1574 MidiRegionView::end_write()
1576 delete[] _active_notes;
1578 _marked_for_selection.clear();
1579 _marked_for_velocity.clear();
1583 /** Resolve an active MIDI note (while recording).
1586 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1588 if (midi_view()->note_mode() != Sustained) {
1592 if (_active_notes && _active_notes[note]) {
1593 /* Set note length so update_note() works. Note this is a local note
1594 for recording, not from a model, so we can safely mess with it. */
1595 _active_notes[note]->note()->set_length(
1596 end_time - _active_notes[note]->note()->time());
1598 /* End time is relative to the region being recorded. */
1599 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1601 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1602 _active_notes[note]->set_outline_all ();
1603 _active_notes[note] = 0;
1608 /** Extend active notes to rightmost edge of region (if length is changed)
1611 MidiRegionView::extend_active_notes()
1613 if (!_active_notes) {
1617 for (unsigned i = 0; i < 128; ++i) {
1618 if (_active_notes[i]) {
1619 _active_notes[i]->set_x1(
1620 trackview.editor().sample_to_pixel(_region->length()));
1626 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1628 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1632 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1634 if (!route_ui || !route_ui->midi_track()) {
1638 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1642 /* NotePlayer deletes itself */
1646 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1648 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1649 start_playing_midi_chord(notes);
1653 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1655 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1659 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1661 if (!route_ui || !route_ui->midi_track()) {
1665 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1667 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1676 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1678 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1679 const bool outside = (note->time() < midi_reg->start_pulse() * 4.0 ||
1680 note->time() > (midi_reg->start_pulse() + midi_reg->length_pulse()) * 4.0);
1682 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1683 (note->note() <= midi_stream_view()->highest_note());
1689 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1693 if ((sus = dynamic_cast<Note*>(note))) {
1694 update_sustained(sus, update_ghost_regions);
1695 } else if ((hit = dynamic_cast<Hit*>(note))) {
1696 update_hit(hit, update_ghost_regions);
1700 /** Update a canvas note's size from its model note.
1701 * @param ev Canvas note to update.
1702 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1705 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1707 TempoMap& map (trackview.session()->tempo_map());
1708 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1709 boost::shared_ptr<NoteType> note = ev->note();
1710 const double qn_note_time = note->time().to_double() + ((_region->pulse() - mr->start_pulse()) * 4.0);
1711 const framepos_t note_start_frames = map.frame_at_quarter_note (qn_note_time) - _region->position();
1712 const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1714 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1717 /* trim note display to not overlap the end of its region */
1718 if (note->length() > 0) {
1719 Evoral::Beats note_end_time = note->end_time();
1721 if (note->end_time().to_double() > (mr->start_pulse() + mr->length_pulse()) * 4.0) {
1722 note_end_time = Evoral::Beats ((mr->start_pulse() + mr->length_pulse()) * 4.0);
1724 const double session_qn_start = (_region->pulse() - mr->start_pulse()) * 4.0;
1725 const double quarter_note_end_time = session_qn_start + note_end_time.to_double();
1727 const framepos_t note_end_frames = map.frame_at_quarter_note (quarter_note_end_time) - _region->position();
1728 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1730 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1733 y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1);
1735 ArdourCanvas::Rect rect (x0, y0, x1, y1);
1738 if (!note->length()) {
1739 if (_active_notes && note->note() < 128) {
1740 Note* const old_rect = _active_notes[note->note()];
1742 /* There is an active note on this key, so we have a stuck
1743 note. Finish the old rectangle here. */
1744 old_rect->set_x1 (x1);
1745 old_rect->set_outline_all ();
1747 _active_notes[note->note()] = ev;
1749 /* outline all but right edge */
1750 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1751 ArdourCanvas::Rectangle::TOP|
1752 ArdourCanvas::Rectangle::LEFT|
1753 ArdourCanvas::Rectangle::BOTTOM));
1755 /* outline all edges */
1756 ev->set_outline_all ();
1759 // Update color in case velocity has changed
1760 //const uint32_t base_col = ev->base_color();
1761 //ev->set_fill_color(base_col);
1762 //ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1764 if (update_ghost_regions) {
1765 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1766 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1768 gr->update_note (ev);
1775 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1777 boost::shared_ptr<NoteType> note = ev->note();
1779 const double note_time_qn = note->time().to_double() + ((_region->pulse() - midi_region()->start_pulse()) * 4.0);
1780 const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1782 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1783 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1784 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1786 // see DnD note in MidiRegionView::apply_note_range() above
1787 if (y <= 0 || y >= _height) {
1793 ev->set_position (ArdourCanvas::Duple (x, y));
1794 ev->set_height (diamond_size);
1796 // Update color in case velocity has changed
1797 ev->set_fill_color(ev->base_color());
1798 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1800 if (update_ghost_regions) {
1801 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1802 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1804 gr->update_note (ev);
1810 /** Add a MIDI note to the view (with length).
1812 * If in sustained mode, notes with length 0 will be considered active
1813 * notes, and resolve_note should be called when the corresponding note off
1814 * event arrives, to properly display the note.
1817 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1819 NoteBase* event = 0;
1821 if (midi_view()->note_mode() == Sustained) {
1823 Note* ev_rect = new Note (*this, _note_group, note);
1825 update_sustained (ev_rect);
1829 } else if (midi_view()->note_mode() == Percussive) {
1831 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1833 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1835 update_hit (ev_diamond);
1844 MidiGhostRegion* gr;
1846 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1847 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1848 gr->add_note(event);
1852 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1853 note_selected(event, true);
1856 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1857 event->show_velocity();
1860 event->on_channel_selection_change (get_selected_channels());
1861 _events.push_back(event);
1870 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1871 MidiStreamView* const view = mtv->midi_view();
1873 view->update_note_range (note->note());
1878 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1879 Evoral::Beats pos, Evoral::Beats len)
1881 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1883 /* potentially extend region to hold new note */
1885 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1886 framepos_t region_end = _region->last_frame();
1888 if (end_frame > region_end) {
1889 /* XX sets length in beats from audio space. make musical */
1890 _region->set_length (end_frame - _region->position(), 0);
1893 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1894 MidiStreamView* const view = mtv->midi_view();
1896 view->update_note_range(new_note->note());
1898 _marked_for_selection.clear ();
1900 start_note_diff_command (_("step add"));
1902 clear_editor_note_selection ();
1903 note_diff_add_note (new_note, true, false);
1907 // last_step_edit_note = new_note;
1911 MidiRegionView::step_sustain (Evoral::Beats beats)
1913 change_note_lengths (false, false, beats, false, true);
1916 /** Add a new patch change flag to the canvas.
1917 * @param patch the patch change to add
1918 * @param the text to display in the flag
1919 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1922 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1924 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1925 const double x = trackview.editor().sample_to_pixel (region_frames);
1927 double const height = midi_stream_view()->contents_height();
1929 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1930 // so we need to do something more sophisticated to keep its color
1931 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1934 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1935 new PatchChange(*this, group,
1942 if (patch_change->item().width() < _pixel_width) {
1943 // Show unless patch change is beyond the region bounds
1944 if (region_frames < 0 || region_frames >= _region->length()) {
1945 patch_change->hide();
1947 patch_change->show();
1950 patch_change->hide ();
1953 _patch_changes.push_back (patch_change);
1956 MIDI::Name::PatchPrimaryKey
1957 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1959 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1962 /// Return true iff @p pc applies to the given time on the given channel.
1964 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1966 return pc->time() <= time && pc->channel() == channel;
1970 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1972 // The earliest event not before time
1973 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1975 // Go backwards until we find the latest PC for this channel, or the start
1976 while (i != _model->patch_changes().begin() &&
1977 (i == _model->patch_changes().end() ||
1978 !patch_applies(*i, time, channel))) {
1982 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1983 key.set_bank((*i)->bank());
1984 key.set_program((*i)->program ());
1992 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1994 string name = _("alter patch change");
1995 trackview.editor().begin_reversible_command (name);
1996 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1998 if (pc.patch()->program() != new_patch.program()) {
1999 c->change_program (pc.patch (), new_patch.program());
2002 int const new_bank = new_patch.bank();
2003 if (pc.patch()->bank() != new_bank) {
2004 c->change_bank (pc.patch (), new_bank);
2007 _model->apply_command (*trackview.session(), c);
2008 trackview.editor().commit_reversible_command ();
2010 _patch_changes.clear ();
2011 display_patch_changes ();
2015 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2017 string name = _("alter patch change");
2018 trackview.editor().begin_reversible_command (name);
2019 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2021 if (old_change->time() != new_change.time()) {
2022 c->change_time (old_change, new_change.time());
2025 if (old_change->channel() != new_change.channel()) {
2026 c->change_channel (old_change, new_change.channel());
2029 if (old_change->program() != new_change.program()) {
2030 c->change_program (old_change, new_change.program());
2033 if (old_change->bank() != new_change.bank()) {
2034 c->change_bank (old_change, new_change.bank());
2037 _model->apply_command (*trackview.session(), c);
2038 trackview.editor().commit_reversible_command ();
2040 _patch_changes.clear ();
2041 display_patch_changes ();
2044 /** Add a patch change to the region.
2045 * @param t Time in frames relative to region position
2046 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2047 * MidiTimeAxisView::get_channel_for_add())
2050 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2052 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2053 string name = _("add patch change");
2055 trackview.editor().begin_reversible_command (name);
2056 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2057 c->add (MidiModel::PatchChangePtr (
2058 new Evoral::PatchChange<Evoral::Beats> (
2059 absolute_frames_to_source_beats (_region->position() + t),
2060 mtv->get_channel_for_add(), patch.program(), patch.bank()
2065 _model->apply_command (*trackview.session(), c);
2066 trackview.editor().commit_reversible_command ();
2068 _patch_changes.clear ();
2069 display_patch_changes ();
2073 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2075 trackview.editor().begin_reversible_command (_("move patch change"));
2076 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2077 c->change_time (pc.patch (), t);
2078 _model->apply_command (*trackview.session(), c);
2079 trackview.editor().commit_reversible_command ();
2081 _patch_changes.clear ();
2082 display_patch_changes ();
2086 MidiRegionView::delete_patch_change (PatchChange* pc)
2088 trackview.editor().begin_reversible_command (_("delete patch change"));
2089 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2090 c->remove (pc->patch ());
2091 _model->apply_command (*trackview.session(), c);
2092 trackview.editor().commit_reversible_command ();
2094 _patch_changes.clear ();
2095 display_patch_changes ();
2099 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2101 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2103 key.set_bank(key.bank() + delta);
2105 key.set_program(key.program() + delta);
2107 change_patch_change(patch, key);
2111 MidiRegionView::note_deleted (NoteBase* cne)
2113 if (_selection.empty()) {
2117 _selection.erase (cne);
2121 MidiRegionView::delete_selection()
2123 if (_selection.empty()) {
2127 start_note_diff_command (_("delete selection"));
2129 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2130 if ((*i)->selected()) {
2131 _note_diff_command->remove((*i)->note());
2138 hide_verbose_cursor ();
2142 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2144 start_note_diff_command (_("delete note"));
2145 _note_diff_command->remove (n);
2148 hide_verbose_cursor ();
2152 MidiRegionView::clear_editor_note_selection ()
2154 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2155 PublicEditor& editor(trackview.editor());
2156 editor.get_selection().clear_midi_notes();
2160 MidiRegionView::clear_selection ()
2162 clear_selection_internal();
2163 PublicEditor& editor(trackview.editor());
2164 editor.get_selection().remove(this);
2168 MidiRegionView::clear_selection_internal ()
2170 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2172 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2173 (*i)->set_selected(false);
2174 (*i)->hide_velocity();
2179 // Clearing selection entirely, ungrab keyboard
2180 Keyboard::magic_widget_drop_focus();
2181 _grabbed_keyboard = false;
2186 MidiRegionView::unique_select(NoteBase* ev)
2188 clear_editor_note_selection();
2189 add_to_selection(ev);
2193 MidiRegionView::select_all_notes ()
2195 clear_editor_note_selection ();
2197 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2198 add_to_selection (*i);
2203 MidiRegionView::select_range (framepos_t start, framepos_t end)
2205 clear_editor_note_selection ();
2207 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2208 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2209 if (t >= start && t <= end) {
2210 add_to_selection (*i);
2216 MidiRegionView::invert_selection ()
2218 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2219 if ((*i)->selected()) {
2220 remove_from_selection(*i);
2222 add_to_selection (*i);
2227 /** Used for selection undo/redo.
2228 The requested notes most likely won't exist in the view until the next model redisplay.
2231 MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
2234 list<boost::shared_ptr<NoteType> >::iterator n;
2236 for (n = notes.begin(); n != notes.end(); ++n) {
2237 if ((cne = find_canvas_note(*(*n))) != 0) {
2238 add_to_selection (cne);
2240 _pending_note_selection.insert(*n);
2246 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2248 bool have_selection = !_selection.empty();
2249 uint8_t low_note = 127;
2250 uint8_t high_note = 0;
2251 MidiModel::Notes& notes (_model->notes());
2252 _optimization_iterator = _events.begin();
2254 if (extend && !have_selection) {
2258 /* scan existing selection to get note range */
2260 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2261 if ((*i)->note()->note() < low_note) {
2262 low_note = (*i)->note()->note();
2264 if ((*i)->note()->note() > high_note) {
2265 high_note = (*i)->note()->note();
2270 clear_editor_note_selection ();
2272 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2273 /* only note previously selected is the one we are
2274 * reselecting. treat this as cancelling the selection.
2281 low_note = min (low_note, notenum);
2282 high_note = max (high_note, notenum);
2285 _no_sound_notes = true;
2287 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2289 boost::shared_ptr<NoteType> note (*n);
2291 bool select = false;
2293 if (((1 << note->channel()) & channel_mask) != 0) {
2295 if ((note->note() >= low_note && note->note() <= high_note)) {
2298 } else if (note->note() == notenum) {
2304 if ((cne = find_canvas_note (note)) != 0) {
2305 // extend is false because we've taken care of it,
2306 // since it extends by time range, not pitch.
2307 note_selected (cne, add, false);
2311 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2315 _no_sound_notes = false;
2319 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2321 MidiModel::Notes& notes (_model->notes());
2322 _optimization_iterator = _events.begin();
2324 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2326 boost::shared_ptr<NoteType> note (*n);
2329 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2330 if ((cne = find_canvas_note (note)) != 0) {
2331 if (cne->selected()) {
2332 note_deselected (cne);
2334 note_selected (cne, true, false);
2342 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2345 clear_editor_note_selection();
2346 add_to_selection (ev);
2351 if (!ev->selected()) {
2352 add_to_selection (ev);
2356 /* find end of latest note selected, select all between that and the start of "ev" */
2358 Evoral::Beats earliest = Evoral::MaxBeats;
2359 Evoral::Beats latest = Evoral::Beats();
2361 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2362 if ((*i)->note()->end_time() > latest) {
2363 latest = (*i)->note()->end_time();
2365 if ((*i)->note()->time() < earliest) {
2366 earliest = (*i)->note()->time();
2370 if (ev->note()->end_time() > latest) {
2371 latest = ev->note()->end_time();
2374 if (ev->note()->time() < earliest) {
2375 earliest = ev->note()->time();
2378 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2380 /* find notes entirely within OR spanning the earliest..latest range */
2382 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2383 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2384 add_to_selection (*i);
2392 MidiRegionView::note_deselected(NoteBase* ev)
2394 remove_from_selection (ev);
2398 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2400 PublicEditor& editor = trackview.editor();
2402 // Convert to local coordinates
2403 const framepos_t p = _region->position();
2404 const double y = midi_view()->y_position();
2405 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2406 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2407 const double y0 = max(0.0, gy0 - y);
2408 const double y1 = max(0.0, gy1 - y);
2410 // TODO: Make this faster by storing the last updated selection rect, and only
2411 // adjusting things that are in the area that appears/disappeared.
2412 // We probably need a tree to be able to find events in O(log(n)) time.
2414 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2415 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2416 // Rectangles intersect
2417 if (!(*i)->selected()) {
2418 add_to_selection (*i);
2420 } else if ((*i)->selected() && !extend) {
2421 // Rectangles do not intersect
2422 remove_from_selection (*i);
2426 typedef RouteTimeAxisView::AutomationTracks ATracks;
2427 typedef std::list<Selectable*> Selectables;
2429 /* Add control points to selection. */
2430 const ATracks& atracks = midi_view()->automation_tracks();
2431 Selectables selectables;
2432 editor.get_selection().clear_points();
2433 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2434 a->second->get_selectables(start, end, gy0, gy1, selectables);
2435 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2436 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2438 editor.get_selection().add(cp);
2441 a->second->set_selected_points(editor.get_selection().points);
2446 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2452 // TODO: Make this faster by storing the last updated selection rect, and only
2453 // adjusting things that are in the area that appears/disappeared.
2454 // We probably need a tree to be able to find events in O(log(n)) time.
2456 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2457 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2458 // within y- (note-) range
2459 if (!(*i)->selected()) {
2460 add_to_selection (*i);
2462 } else if ((*i)->selected() && !extend) {
2463 remove_from_selection (*i);
2469 MidiRegionView::remove_from_selection (NoteBase* ev)
2471 Selection::iterator i = _selection.find (ev);
2473 if (i != _selection.end()) {
2474 _selection.erase (i);
2475 if (_selection.empty() && _grabbed_keyboard) {
2477 Keyboard::magic_widget_drop_focus();
2478 _grabbed_keyboard = false;
2482 ev->set_selected (false);
2483 ev->hide_velocity ();
2485 if (_selection.empty()) {
2486 PublicEditor& editor (trackview.editor());
2487 editor.get_selection().remove (this);
2492 MidiRegionView::add_to_selection (NoteBase* ev)
2494 const bool selection_was_empty = _selection.empty();
2496 if (_selection.insert (ev).second) {
2497 ev->set_selected (true);
2498 start_playing_midi_note ((ev)->note());
2499 if (selection_was_empty && _entered) {
2500 // Grab keyboard for moving notes with arrow keys
2501 Keyboard::magic_widget_grab_focus();
2502 _grabbed_keyboard = true;
2506 if (selection_was_empty) {
2507 PublicEditor& editor (trackview.editor());
2508 editor.get_selection().add (this);
2513 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2515 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2516 PossibleChord to_play;
2517 Evoral::Beats earliest = Evoral::MaxBeats;
2519 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2520 if ((*i)->note()->time() < earliest) {
2521 earliest = (*i)->note()->time();
2525 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2526 if ((*i)->note()->time() == earliest) {
2527 to_play.push_back ((*i)->note());
2529 (*i)->move_event(dx, dy);
2532 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2534 if (to_play.size() > 1) {
2536 PossibleChord shifted;
2538 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2539 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2540 moved_note->set_note (moved_note->note() + cumulative_dy);
2541 shifted.push_back (moved_note);
2544 start_playing_midi_chord (shifted);
2546 } else if (!to_play.empty()) {
2548 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2549 moved_note->set_note (moved_note->note() + cumulative_dy);
2550 start_playing_midi_note (moved_note);
2556 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2558 uint8_t lowest_note_in_selection = 127;
2559 uint8_t highest_note_in_selection = 0;
2560 uint8_t highest_note_difference = 0;
2562 // find highest and lowest notes first
2564 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2565 uint8_t pitch = (*i)->note()->note();
2566 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2567 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2571 cerr << "dnote: " << (int) dnote << endl;
2572 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2573 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2574 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2575 << int(highest_note_in_selection) << endl;
2576 cerr << "selection size: " << _selection.size() << endl;
2577 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2580 // Make sure the note pitch does not exceed the MIDI standard range
2581 if (highest_note_in_selection + dnote > 127) {
2582 highest_note_difference = highest_note_in_selection - 127;
2584 TempoMap& map (trackview.session()->tempo_map());
2586 start_note_diff_command (_("move notes"));
2588 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2590 double const qn_note_start = (_region->pulse() - midi_region()->start_pulse()) * 4.0;
2591 framepos_t new_frames = map.frame_at_quarter_note (qn_note_start + (*i)->note()->time().to_double()) + dt;
2592 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - qn_note_start);
2597 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2599 uint8_t original_pitch = (*i)->note()->note();
2600 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2602 // keep notes in standard midi range
2603 clamp_to_0_127(new_pitch);
2605 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2606 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2608 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2613 // care about notes being moved beyond the upper/lower bounds on the canvas
2614 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2615 highest_note_in_selection > midi_stream_view()->highest_note()) {
2616 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2620 /** @param x Pixel relative to the region position.
2621 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2622 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2623 * @return Snapped frame relative to the region position.
2626 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2628 PublicEditor& editor (trackview.editor());
2629 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2632 /** @param x Pixel relative to the region position.
2633 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2634 * @return Snapped pixel relative to the region position.
2637 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2639 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2643 MidiRegionView::get_position_pixels()
2645 framepos_t region_frame = get_position();
2646 return trackview.editor().sample_to_pixel(region_frame);
2650 MidiRegionView::get_end_position_pixels()
2652 framepos_t frame = get_position() + get_duration ();
2653 return trackview.editor().sample_to_pixel(frame);
2657 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2659 /* the time converter will return the frame corresponding to `beats'
2660 relative to the start of the source. The start of the source
2661 is an implied position given by region->position - region->start
2663 const framepos_t source_start = _region->position() - _region->start();
2664 return source_start + _source_relative_time_converter.to (beats);
2668 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2670 /* the `frames' argument needs to be converted into a frame count
2671 relative to the start of the source before being passed in to the
2674 const framepos_t source_start = _region->position() - _region->start();
2675 return _source_relative_time_converter.from (frames - source_start);
2679 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2681 return _region_relative_time_converter.to(beats);
2685 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2687 return _region_relative_time_converter.from(frames);
2691 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2693 return _region_relative_time_converter_double.from(frames);
2697 MidiRegionView::begin_resizing (bool /*at_front*/)
2699 _resize_data.clear();
2701 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2702 Note *note = dynamic_cast<Note*> (*i);
2704 // only insert CanvasNotes into the map
2706 NoteResizeData *resize_data = new NoteResizeData();
2707 resize_data->note = note;
2709 // create a new SimpleRect from the note which will be the resize preview
2710 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2711 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2713 // calculate the colors: get the color settings
2714 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2715 UIConfiguration::instance().color ("midi note selected"),
2718 // make the resize preview notes more transparent and bright
2719 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2721 // calculate color based on note velocity
2722 resize_rect->set_fill_color (UINT_INTERPOLATE(
2723 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2727 resize_rect->set_outline_color (NoteBase::calculate_outline (
2728 UIConfiguration::instance().color ("midi note selected")));
2730 resize_data->resize_rect = resize_rect;
2731 _resize_data.push_back(resize_data);
2736 /** Update resizing notes while user drags.
2737 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2738 * @param at_front which end of the note (true == note on, false == note off)
2739 * @param delta_x change in mouse position since the start of the drag
2740 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2741 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2742 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2743 * as the \a primary note.
2744 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2745 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2748 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2750 TempoMap& tmap (trackview.session()->tempo_map());
2751 bool cursor_set = false;
2752 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2754 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2755 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2756 Note* canvas_note = (*i)->note;
2761 current_x = canvas_note->x0() + delta_x + snap_delta;
2763 current_x = primary->x0() + delta_x + snap_delta;
2767 current_x = canvas_note->x1() + delta_x + snap_delta;
2769 current_x = primary->x1() + delta_x + snap_delta;
2773 if (current_x < 0) {
2774 // This works even with snapping because RegionView::snap_frame_to_frame()
2775 // snaps forward if the snapped sample is before the beginning of the region
2778 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2779 current_x = trackview.editor().sample_to_pixel(_region->length());
2784 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2786 resize_rect->set_x0 (current_x - snap_delta);
2788 resize_rect->set_x1 (canvas_note->x1());
2791 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2793 resize_rect->set_x1 (current_x - snap_delta);
2795 resize_rect->set_x0 (canvas_note->x0());
2799 /* Convert snap delta from pixels to beats. */
2800 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2801 double snap_delta_beats = 0.0;
2804 /* negative beat offsets aren't allowed */
2805 if (snap_delta_samps > 0) {
2806 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2807 } else if (snap_delta_samps < 0) {
2808 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2813 int32_t divisions = 0;
2816 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2817 divisions = trackview.editor().get_grid_music_divisions (0);
2819 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2821 const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2822 - midi_region()->beat()) + midi_region()->start_beats();
2824 Evoral::Beats len = Evoral::Beats();
2827 if (beats < canvas_note->note()->end_time()) {
2828 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2829 len += canvas_note->note()->length();
2832 if (beats >= canvas_note->note()->time()) {
2833 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2837 len = std::max(Evoral::Beats(1 / 512.0), len);
2840 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2841 show_verbose_cursor (buf, 0, 0);
2850 /** Finish resizing notes when the user releases the mouse button.
2851 * Parameters the same as for \a update_resizing().
2854 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2856 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2857 TempoMap& tmap (trackview.session()->tempo_map());
2859 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2860 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2862 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2863 Note* canvas_note = (*i)->note;
2864 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2866 /* Get the new x position for this resize, which is in pixels relative
2867 * to the region position.
2874 current_x = canvas_note->x0() + delta_x + snap_delta;
2876 current_x = primary->x0() + delta_x + snap_delta;
2880 current_x = canvas_note->x1() + delta_x + snap_delta;
2882 current_x = primary->x1() + delta_x + snap_delta;
2886 if (current_x < 0) {
2889 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2890 current_x = trackview.editor().sample_to_pixel(_region->length());
2893 /* Convert snap delta from pixels to beats with sign. */
2894 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2895 double snap_delta_beats = 0.0;
2898 if (snap_delta_samps > 0) {
2899 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2900 } else if (snap_delta_samps < 0) {
2901 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2905 uint32_t divisions = 0;
2906 /* Convert the new x position to a frame within the source */
2907 framepos_t current_fr;
2909 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2910 divisions = trackview.editor().get_grid_music_divisions (0);
2912 current_fr = trackview.editor().pixel_to_sample (current_x);
2915 /* and then to beats */
2916 const double e_baf = tmap.exact_beat_at_frame (current_fr + midi_region()->position(), divisions);
2917 const double quarter_note_start_beat = tmap.quarter_note_at_beat (_region->beat() - midi_region()->start_beats().to_double());
2918 const Evoral::Beats x_beats = Evoral::Beats (tmap.quarter_note_at_beat (e_baf) - quarter_note_start_beat);
2920 if (at_front && x_beats < canvas_note->note()->end_time()) {
2921 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2922 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2923 len += canvas_note->note()->length();
2926 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2931 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2932 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2933 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2940 _resize_data.clear();
2945 MidiRegionView::abort_resizing ()
2947 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2948 delete (*i)->resize_rect;
2952 _resize_data.clear ();
2956 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2958 uint8_t new_velocity;
2961 new_velocity = event->note()->velocity() + velocity;
2962 clamp_to_0_127(new_velocity);
2964 new_velocity = velocity;
2967 event->set_selected (event->selected()); // change color
2969 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2973 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2978 new_note = event->note()->note() + note;
2983 clamp_to_0_127 (new_note);
2984 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2988 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2990 bool change_start = false;
2991 bool change_length = false;
2992 Evoral::Beats new_start;
2993 Evoral::Beats new_length;
2995 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2997 front_delta: if positive - move the start of the note later in time (shortening it)
2998 if negative - move the start of the note earlier in time (lengthening it)
3000 end_delta: if positive - move the end of the note later in time (lengthening it)
3001 if negative - move the end of the note earlier in time (shortening it)
3004 if (!!front_delta) {
3005 if (front_delta < 0) {
3007 if (event->note()->time() < -front_delta) {
3008 new_start = Evoral::Beats();
3010 new_start = event->note()->time() + front_delta; // moves earlier
3013 /* start moved toward zero, so move the end point out to where it used to be.
3014 Note that front_delta is negative, so this increases the length.
3017 new_length = event->note()->length() - front_delta;
3018 change_start = true;
3019 change_length = true;
3023 Evoral::Beats new_pos = event->note()->time() + front_delta;
3025 if (new_pos < event->note()->end_time()) {
3026 new_start = event->note()->time() + front_delta;
3027 /* start moved toward the end, so move the end point back to where it used to be */
3028 new_length = event->note()->length() - front_delta;
3029 change_start = true;
3030 change_length = true;
3037 bool can_change = true;
3038 if (end_delta < 0) {
3039 if (event->note()->length() < -end_delta) {
3045 new_length = event->note()->length() + end_delta;
3046 change_length = true;
3051 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3054 if (change_length) {
3055 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3060 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3062 uint8_t new_channel;
3066 if (event->note()->channel() < -chn) {
3069 new_channel = event->note()->channel() + chn;
3072 new_channel = event->note()->channel() + chn;
3075 new_channel = (uint8_t) chn;
3078 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3082 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3084 Evoral::Beats new_time;
3088 if (event->note()->time() < -delta) {
3089 new_time = Evoral::Beats();
3091 new_time = event->note()->time() + delta;
3094 new_time = event->note()->time() + delta;
3100 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3104 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3106 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3110 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3115 if (_selection.empty()) {
3130 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3131 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3137 start_note_diff_command (_("change velocities"));
3139 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3140 Selection::iterator next = i;
3144 if (i == _selection.begin()) {
3145 change_note_velocity (*i, delta, true);
3146 value = (*i)->note()->velocity() + delta;
3148 change_note_velocity (*i, value, false);
3152 change_note_velocity (*i, delta, true);
3161 if (!_selection.empty()) {
3163 snprintf (buf, sizeof (buf), "Vel %d",
3164 (int) (*_selection.begin())->note()->velocity());
3165 show_verbose_cursor (buf, 10, 10);
3171 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3173 if (_selection.empty()) {
3190 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3192 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3196 if ((int8_t) (*i)->note()->note() + delta > 127) {
3203 start_note_diff_command (_("transpose"));
3205 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3206 Selection::iterator next = i;
3208 change_note_note (*i, delta, true);
3216 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3220 delta = Evoral::Beats(1.0/128.0);
3222 /* grab the current grid distance */
3223 delta = get_grid_beats(_region->position());
3231 start_note_diff_command (_("change note lengths"));
3233 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3234 Selection::iterator next = i;
3237 /* note the negation of the delta for start */
3240 (start ? -delta : Evoral::Beats()),
3241 (end ? delta : Evoral::Beats()));
3250 MidiRegionView::nudge_notes (bool forward, bool fine)
3252 if (_selection.empty()) {
3256 /* pick a note as the point along the timeline to get the nudge distance.
3257 its not necessarily the earliest note, so we may want to pull the notes out
3258 into a vector and sort before using the first one.
3261 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3262 Evoral::Beats delta;
3266 /* non-fine, move by 1 bar regardless of snap */
3267 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3269 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3271 /* grid is off - use nudge distance */
3274 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3275 delta = region_frames_to_region_beats (fabs ((double)distance));
3281 framepos_t next_pos = ref_point;
3284 if (max_framepos - 1 < next_pos) {
3288 if (next_pos == 0) {
3294 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3295 const framecnt_t distance = ref_point - next_pos;
3296 delta = region_frames_to_region_beats (fabs ((double)distance));
3307 start_note_diff_command (_("nudge"));
3309 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3310 Selection::iterator next = i;
3312 change_note_time (*i, delta, true);
3320 MidiRegionView::change_channel(uint8_t channel)
3322 start_note_diff_command(_("change channel"));
3323 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3324 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3332 MidiRegionView::note_entered(NoteBase* ev)
3334 _note_entered = true;
3336 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3338 if (_mouse_state == SelectTouchDragging) {
3340 note_selected (ev, true);
3342 } else if (editor->current_mouse_mode() == MouseContent) {
3344 remove_ghost_note ();
3345 show_verbose_cursor (ev->note ());
3347 } else if (editor->current_mouse_mode() == MouseDraw) {
3349 remove_ghost_note ();
3350 show_verbose_cursor (ev->note ());
3355 MidiRegionView::note_left (NoteBase*)
3357 _note_entered = false;
3359 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3360 (*i)->hide_velocity ();
3363 hide_verbose_cursor ();
3367 MidiRegionView::patch_entered (PatchChange* p)
3370 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3371 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3372 << _("Channel ") << ((int) p->patch()->channel() + 1);
3373 show_verbose_cursor (s.str(), 10, 20);
3374 p->item().grab_focus();
3378 MidiRegionView::patch_left (PatchChange *)
3380 hide_verbose_cursor ();
3381 /* focus will transfer back via the enter-notify event sent to this
3387 MidiRegionView::sysex_entered (SysEx* p)
3391 // need a way to extract text from p->_flag->_text
3393 // show_verbose_cursor (s.str(), 10, 20);
3394 p->item().grab_focus();
3398 MidiRegionView::sysex_left (SysEx *)
3400 hide_verbose_cursor ();
3401 /* focus will transfer back via the enter-notify event sent to this
3407 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3409 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3410 Editing::MouseMode mm = editor->current_mouse_mode();
3411 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3413 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3414 if (can_set_cursor && ctx) {
3415 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3416 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3417 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3418 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3420 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3426 MidiRegionView::get_fill_color() const
3428 const std::string mod_name = (_dragging ? "dragging region" :
3429 trackview.editor().internal_editing() ? "editable region" :
3432 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3433 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3434 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3435 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3437 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3441 MidiRegionView::midi_channel_mode_changed ()
3443 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3444 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3445 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3447 if (mode == ForceChannel) {
3448 mask = 0xFFFF; // Show all notes as active (below)
3451 // Update notes for selection
3452 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3453 (*i)->on_channel_selection_change (mask);
3456 _patch_changes.clear ();
3457 display_patch_changes ();
3461 MidiRegionView::instrument_settings_changed ()
3467 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3469 if (_selection.empty()) {
3473 PublicEditor& editor (trackview.editor());
3477 /* XXX what to do ? */
3481 editor.get_cut_buffer().add (selection_as_cut_buffer());
3489 start_note_diff_command();
3491 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3498 note_diff_remove_note (*i);
3508 MidiRegionView::selection_as_cut_buffer () const
3512 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3513 NoteType* n = (*i)->note().get();
3514 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3517 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3523 /** This method handles undo */
3525 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3527 bool commit = false;
3528 // Paste notes, if available
3529 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3530 if (m != selection.midi_notes.end()) {
3531 ctx.counts.increase_n_notes();
3532 if (!(*m)->empty()) {
3535 paste_internal(pos, ctx.count, ctx.times, **m);
3538 // Paste control points to automation children, if available
3539 typedef RouteTimeAxisView::AutomationTracks ATracks;
3540 const ATracks& atracks = midi_view()->automation_tracks();
3541 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3542 if (a->second->paste(pos, selection, ctx, sub_num)) {
3544 trackview.editor().begin_reversible_command (Operations::paste);
3551 trackview.editor().commit_reversible_command ();
3556 /** This method handles undo */
3558 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3564 start_note_diff_command (_("paste"));
3566 const Evoral::Beats snap_beats = get_grid_beats(pos);
3567 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3568 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3569 const Evoral::Beats duration = last_time - first_time;
3570 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3571 const Evoral::Beats paste_offset = snap_duration * paste_count;
3572 const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3573 Evoral::Beats end_point = Evoral::Beats();
3575 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3578 duration, pos, _region->position(),
3581 clear_editor_note_selection ();
3583 for (int n = 0; n < (int) times; ++n) {
3585 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3587 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3588 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3589 copied_note->set_id (Evoral::next_event_id());
3591 /* make all newly added notes selected */
3593 note_diff_add_note (copied_note, true);
3594 end_point = copied_note->end_time();
3598 /* if we pasted past the current end of the region, extend the region */
3600 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3601 framepos_t region_end = _region->position() + _region->length() - 1;
3603 if (end_frame > region_end) {
3605 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3607 _region->clear_changes ();
3608 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3609 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3610 trackview.session()->add_command (new StatefulDiffCommand (_region));
3616 struct EventNoteTimeEarlyFirstComparator {
3617 bool operator() (NoteBase* a, NoteBase* b) {
3618 return a->note()->time() < b->note()->time();
3623 MidiRegionView::time_sort_events ()
3625 if (!_sort_needed) {
3629 EventNoteTimeEarlyFirstComparator cmp;
3632 _sort_needed = false;
3636 MidiRegionView::goto_next_note (bool add_to_selection)
3638 bool use_next = false;
3640 if (_events.back()->selected()) {
3644 time_sort_events ();
3646 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3647 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3649 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3650 if ((*i)->selected()) {
3653 } else if (use_next) {
3654 if (channel_mask & (1 << (*i)->note()->channel())) {
3655 if (!add_to_selection) {
3658 note_selected (*i, true, false);
3665 /* use the first one */
3667 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3668 unique_select (_events.front());
3673 MidiRegionView::goto_previous_note (bool add_to_selection)
3675 bool use_next = false;
3677 if (_events.front()->selected()) {
3681 time_sort_events ();
3683 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3684 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3686 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3687 if ((*i)->selected()) {
3690 } else if (use_next) {
3691 if (channel_mask & (1 << (*i)->note()->channel())) {
3692 if (!add_to_selection) {
3695 note_selected (*i, true, false);
3702 /* use the last one */
3704 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3705 unique_select (*(_events.rbegin()));
3710 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3712 bool had_selected = false;
3714 time_sort_events ();
3716 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3717 if ((*i)->selected()) {
3718 selected.insert ((*i)->note());
3719 had_selected = true;
3723 if (allow_all_if_none_selected && !had_selected) {
3724 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3725 selected.insert ((*i)->note());
3731 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3733 x = std::max(0.0, x);
3735 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3740 _note_group->canvas_to_item (x, y);
3742 PublicEditor& editor = trackview.editor ();
3744 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3746 const int32_t divisions = editor.get_grid_music_divisions (state);
3747 const double snapped_region_qn = snap_frame_to_grid_underneath (unsnapped_frame, divisions).to_double();
3749 Evoral::Beats snapped_beats = Evoral::Beats (snapped_region_qn);
3750 /* calculate time in beats relative to start of source */
3751 const Evoral::Beats length = get_grid_beats(unsnapped_frame);
3753 _ghost_note->note()->set_time (snapped_beats);
3754 _ghost_note->note()->set_length (length);
3755 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3756 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3757 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3758 /* the ghost note does not appear in ghost regions, so pass false in here */
3759 update_note (_ghost_note, false);
3761 show_verbose_cursor (_ghost_note->note ());
3765 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3767 remove_ghost_note ();
3769 boost::shared_ptr<NoteType> g (new NoteType);
3770 if (midi_view()->note_mode() == Sustained) {
3771 _ghost_note = new Note (*this, _note_group, g);
3773 _ghost_note = new Hit (*this, _note_group, 10, g);
3775 _ghost_note->set_ignore_events (true);
3776 _ghost_note->set_outline_color (0x000000aa);
3777 update_ghost_note (x, y, state);
3778 _ghost_note->show ();
3780 show_verbose_cursor (_ghost_note->note ());
3784 MidiRegionView::remove_ghost_note ()
3791 MidiRegionView::hide_verbose_cursor ()
3793 trackview.editor().verbose_cursor()->hide ();
3794 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3796 mtv->set_note_highlight (NO_MIDI_NOTE);
3801 MidiRegionView::snap_changed ()
3807 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3811 MidiRegionView::drop_down_keys ()
3813 _mouse_state = None;
3817 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3819 /* XXX: This is dead code. What was it for? */
3821 double note = midi_stream_view()->y_to_note(y);
3823 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3825 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3827 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3828 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3829 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3830 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3835 bool add_mrv_selection = false;
3837 if (_selection.empty()) {
3838 add_mrv_selection = true;
3841 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3842 if (_selection.insert (*i).second) {
3843 (*i)->set_selected (true);
3847 if (add_mrv_selection) {
3848 PublicEditor& editor (trackview.editor());
3849 editor.get_selection().add (this);
3854 MidiRegionView::color_handler ()
3856 RegionView::color_handler ();
3858 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3859 (*i)->set_selected ((*i)->selected()); // will change color
3862 /* XXX probably more to do here */
3866 MidiRegionView::enable_display (bool yn)
3868 RegionView::enable_display (yn);
3872 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3874 if (_step_edit_cursor == 0) {
3875 ArdourCanvas::Item* const group = get_canvas_group();
3877 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3878 _step_edit_cursor->set_y0 (0);
3879 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3880 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3881 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3884 move_step_edit_cursor (pos);
3885 _step_edit_cursor->show ();
3889 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3891 _step_edit_cursor_position = pos;
3893 if (_step_edit_cursor) {
3894 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3895 _step_edit_cursor->set_x0 (pixel);
3896 set_step_edit_cursor_width (_step_edit_cursor_width);
3901 MidiRegionView::hide_step_edit_cursor ()
3903 if (_step_edit_cursor) {
3904 _step_edit_cursor->hide ();
3909 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3911 _step_edit_cursor_width = beats;
3913 if (_step_edit_cursor) {
3914 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3915 region_beats_to_region_frames (_step_edit_cursor_position + beats)
3916 - region_beats_to_region_frames (_step_edit_cursor_position)));
3920 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3921 * @param w Source that the data will end up in.
3924 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3926 if (!_active_notes) {
3927 /* we aren't actively being recorded to */
3931 boost::shared_ptr<MidiSource> src = w.lock ();
3932 if (!src || src != midi_region()->midi_source()) {
3933 /* recorded data was not destined for our source */
3937 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3939 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3941 framepos_t back = max_framepos;
3943 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3944 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3946 if (ev.is_channel_event()) {
3947 if (get_channel_mode() == FilterChannels) {
3948 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3954 /* convert from session frames to source beats */
3955 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3956 ev.time() - src->timeline_position() + _region->start());
3958 if (ev.type() == MIDI_CMD_NOTE_ON) {
3959 boost::shared_ptr<NoteType> note (
3960 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3962 add_note (note, true);
3964 /* fix up our note range */
3965 if (ev.note() < _current_range_min) {
3966 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3967 } else if (ev.note() > _current_range_max) {
3968 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3971 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3972 resolve_note (ev.note (), time_beats);
3978 midi_stream_view()->check_record_layers (region(), back);
3982 MidiRegionView::trim_front_starting ()
3984 /* We used to eparent the note group to the region view's parent, so that it didn't change.
3990 MidiRegionView::trim_front_ending ()
3992 if (_region->start() < 0) {
3993 /* Trim drag made start time -ve; fix this */
3994 midi_region()->fix_negative_start ();
3999 MidiRegionView::edit_patch_change (PatchChange* pc)
4001 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4003 int response = d.run();
4006 case Gtk::RESPONSE_ACCEPT:
4008 case Gtk::RESPONSE_REJECT:
4009 delete_patch_change (pc);
4015 change_patch_change (pc->patch(), d.patch ());
4019 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4022 // sysyex object doesn't have a pointer to a sysex event
4023 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4024 // c->remove (sysex->sysex());
4025 // _model->apply_command (*trackview.session(), c);
4027 //_sys_exes.clear ();
4028 // display_sysexes();
4032 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4034 using namespace MIDI::Name;
4037 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4039 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4041 MIDI::Name::PatchPrimaryKey patch_key;
4042 get_patch_key_at(n->time(), n->channel(), patch_key);
4043 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4046 patch_key.program(),
4052 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4054 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4055 (int) n->channel() + 1,
4056 (int) n->velocity());
4062 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4063 uint8_t new_value) const
4065 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4067 mtv->set_note_highlight (new_value);
4070 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4074 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4076 show_verbose_cursor_for_new_note_value(n, n->note());
4080 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4082 trackview.editor().verbose_cursor()->set (text);
4083 trackview.editor().verbose_cursor()->show ();
4084 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4088 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4090 if (_model->notes().empty()) {
4091 return 0x40; // No notes, use default
4094 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4095 if (m == _model->notes().begin()) {
4096 // Before the start, use the velocity of the first note
4097 return (*m)->velocity();
4098 } else if (m == _model->notes().end()) {
4099 // Past the end, use the velocity of the last note
4101 return (*m)->velocity();
4104 // Interpolate velocity of surrounding notes
4105 MidiModel::Notes::const_iterator n = m;
4108 const double frac = ((time - (*n)->time()).to_double() /
4109 ((*m)->time() - (*n)->time()).to_double());
4111 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4114 /** @param p A session framepos.
4115 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4116 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4117 * @return beat duration of p snapped to the grid subdivision underneath it.
4120 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions) const
4122 TempoMap& map (trackview.session()->tempo_map());
4124 const double qaf = map.quarter_note_at_frame (p + _region->position());
4126 if (divisions != 0) {
4127 eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4129 /* Hack so that we always snap to the note that we are over, instead of snapping
4130 to the next one if we're more than halfway through the one we're over.
4132 const Evoral::Beats grid_beats = get_grid_beats (p);
4133 const double rem = fmod (qaf, grid_beats.to_double());
4134 if (rem >= grid_beats.to_double() / 2.0) {
4135 eqaf -= grid_beats.to_double();
4141 return Evoral::Beats (eqaf - ((_region->pulse() - midi_region()->start_pulse()) * 4.0));
4145 MidiRegionView::get_channel_mode () const
4147 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4148 return rtav->midi_track()->get_playback_channel_mode();
4152 MidiRegionView::get_selected_channels () const
4154 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4155 return rtav->midi_track()->get_playback_channel_mask();
4160 MidiRegionView::get_grid_beats(framepos_t pos) const
4162 PublicEditor& editor = trackview.editor();
4163 bool success = false;
4164 Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
4166 beats = Evoral::Beats(1);