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 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
95 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
97 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
98 RouteTimeAxisView& tv,
99 boost::shared_ptr<MidiRegion> r,
101 uint32_t basic_color)
102 : RegionView (parent, tv, r, spu, basic_color)
103 , _current_range_min(0)
104 , _current_range_max(0)
105 , _region_relative_time_converter(r->session().tempo_map(), r->position())
106 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
107 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
109 , _note_group (new ArdourCanvas::Container (group))
110 , _note_diff_command (0)
112 , _step_edit_cursor (0)
113 , _step_edit_cursor_width (1.0)
114 , _step_edit_cursor_position (0.0)
115 , _channel_selection_scoped_note (0)
116 , _temporary_note_group (0)
119 , _sort_needed (true)
120 , _optimization_iterator (_events.end())
122 , _no_sound_notes (false)
123 , _last_display_zoom (0)
126 , _grabbed_keyboard (false)
128 , _mouse_changed_selection (false)
130 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
131 _note_group->raise_to_top();
132 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
134 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
135 connect_to_diskstream ();
137 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
139 PublicEditor& editor (trackview.editor());
140 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
143 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
144 RouteTimeAxisView& tv,
145 boost::shared_ptr<MidiRegion> r,
147 uint32_t basic_color,
149 TimeAxisViewItem::Visibility visibility)
150 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
151 , _current_range_min(0)
152 , _current_range_max(0)
153 , _region_relative_time_converter(r->session().tempo_map(), r->position())
154 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
155 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
157 , _note_group (new ArdourCanvas::Container (group))
158 , _note_diff_command (0)
160 , _step_edit_cursor (0)
161 , _step_edit_cursor_width (1.0)
162 , _step_edit_cursor_position (0.0)
163 , _channel_selection_scoped_note (0)
164 , _temporary_note_group (0)
167 , _sort_needed (true)
168 , _optimization_iterator (_events.end())
170 , _no_sound_notes (false)
171 , _last_display_zoom (0)
174 , _grabbed_keyboard (false)
176 , _mouse_changed_selection (false)
178 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
179 _note_group->raise_to_top();
181 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
183 connect_to_diskstream ();
185 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
187 PublicEditor& editor (trackview.editor());
188 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
192 MidiRegionView::parameter_changed (std::string const & p)
194 if (p == "display-first-midi-bank-as-zero") {
195 if (_enable_display) {
201 MidiRegionView::MidiRegionView (const MidiRegionView& other)
202 : sigc::trackable(other)
204 , _current_range_min(0)
205 , _current_range_max(0)
206 , _region_relative_time_converter(other.region_relative_time_converter())
207 , _source_relative_time_converter(other.source_relative_time_converter())
208 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
210 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
211 , _note_diff_command (0)
213 , _step_edit_cursor (0)
214 , _step_edit_cursor_width (1.0)
215 , _step_edit_cursor_position (0.0)
216 , _channel_selection_scoped_note (0)
217 , _temporary_note_group (0)
220 , _sort_needed (true)
221 , _optimization_iterator (_events.end())
223 , _no_sound_notes (false)
224 , _last_display_zoom (0)
227 , _grabbed_keyboard (false)
229 , _mouse_changed_selection (false)
234 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
235 : RegionView (other, boost::shared_ptr<Region> (region))
236 , _current_range_min(0)
237 , _current_range_max(0)
238 , _region_relative_time_converter(other.region_relative_time_converter())
239 , _source_relative_time_converter(other.source_relative_time_converter())
240 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
242 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
243 , _note_diff_command (0)
245 , _step_edit_cursor (0)
246 , _step_edit_cursor_width (1.0)
247 , _step_edit_cursor_position (0.0)
248 , _channel_selection_scoped_note (0)
249 , _temporary_note_group (0)
252 , _sort_needed (true)
253 , _optimization_iterator (_events.end())
255 , _no_sound_notes (false)
256 , _last_display_zoom (0)
259 , _grabbed_keyboard (false)
261 , _mouse_changed_selection (false)
267 MidiRegionView::init (bool wfd)
269 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
272 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
273 midi_region()->midi_source(0)->load_model(lm);
276 _model = midi_region()->midi_source(0)->model();
277 _enable_display = false;
278 fill_color_name = "midi frame base";
280 RegionView::init (false);
282 set_height (trackview.current_height());
285 region_sync_changed ();
286 region_resized (ARDOUR::bounds_change);
291 _enable_display = true;
294 display_model (_model);
298 reset_width_dependent_items (_pixel_width);
300 group->raise_to_top();
302 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
303 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
306 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
307 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
309 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
310 boost::bind (&MidiRegionView::snap_changed, this),
313 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
314 boost::bind (&MidiRegionView::mouse_mode_changed, this),
317 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
318 connect_to_diskstream ();
320 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
322 PublicEditor& editor (trackview.editor());
323 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
327 MidiRegionView::instrument_info () const
329 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
330 return route_ui->route()->instrument_info();
333 const boost::shared_ptr<ARDOUR::MidiRegion>
334 MidiRegionView::midi_region() const
336 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
340 MidiRegionView::connect_to_diskstream ()
342 midi_view()->midi_track()->DataRecorded.connect(
343 *this, invalidator(*this),
344 boost::bind (&MidiRegionView::data_recorded, this, _1),
349 MidiRegionView::canvas_group_event(GdkEvent* ev)
351 if (in_destructor || _recregion) {
355 if (!trackview.editor().internal_editing()) {
356 // not in internal edit mode, so just act like a normal region
357 return RegionView::canvas_group_event (ev);
363 case GDK_ENTER_NOTIFY:
364 _last_event_x = ev->crossing.x;
365 _last_event_y = ev->crossing.y;
366 enter_notify(&ev->crossing);
367 // set entered_regionview (among other things)
368 return RegionView::canvas_group_event (ev);
370 case GDK_LEAVE_NOTIFY:
371 _last_event_x = ev->crossing.x;
372 _last_event_y = ev->crossing.y;
373 leave_notify(&ev->crossing);
374 // reset entered_regionview (among other things)
375 return RegionView::canvas_group_event (ev);
378 if (scroll (&ev->scroll)) {
384 return key_press (&ev->key);
386 case GDK_KEY_RELEASE:
387 return key_release (&ev->key);
389 case GDK_BUTTON_PRESS:
390 return button_press (&ev->button);
392 case GDK_BUTTON_RELEASE:
393 r = button_release (&ev->button);
396 case GDK_MOTION_NOTIFY:
397 _last_event_x = ev->motion.x;
398 _last_event_y = ev->motion.y;
399 return motion (&ev->motion);
405 return RegionView::canvas_group_event (ev);
409 MidiRegionView::enter_notify (GdkEventCrossing* ev)
418 MidiRegionView::leave_notify (GdkEventCrossing*)
427 MidiRegionView::mouse_mode_changed ()
429 // Adjust frame colour (become more transparent for internal tools)
433 if (trackview.editor().internal_editing()) {
434 // Switched in to internal editing mode while entered
437 // Switched out of internal editing mode while entered
444 MidiRegionView::enter_internal()
446 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
447 // Show ghost note under pencil
448 create_ghost_note(_last_event_x, _last_event_y);
451 if (!_selection.empty()) {
452 // Grab keyboard for moving selected notes with arrow keys
453 Keyboard::magic_widget_grab_focus();
454 _grabbed_keyboard = true;
457 // Lower frame handles below notes so they don't steal events
458 if (frame_handle_start) {
459 frame_handle_start->lower_to_bottom();
461 if (frame_handle_end) {
462 frame_handle_end->lower_to_bottom();
467 MidiRegionView::leave_internal()
469 hide_verbose_cursor ();
470 remove_ghost_note ();
472 if (_grabbed_keyboard) {
473 Keyboard::magic_widget_drop_focus();
474 _grabbed_keyboard = false;
477 // Raise frame handles above notes so they catch events
478 if (frame_handle_start) {
479 frame_handle_start->raise_to_top();
481 if (frame_handle_end) {
482 frame_handle_end->raise_to_top();
487 MidiRegionView::button_press (GdkEventButton* ev)
489 if (ev->button != 1) {
493 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
494 MouseMode m = editor->current_mouse_mode();
496 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
497 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
500 if (_mouse_state != SelectTouchDragging) {
502 _pressed_button = ev->button;
503 _mouse_state = Pressed;
508 _pressed_button = ev->button;
509 _mouse_changed_selection = false;
515 MidiRegionView::button_release (GdkEventButton* ev)
517 double event_x, event_y;
519 if (ev->button != 1) {
526 group->canvas_to_item (event_x, event_y);
529 PublicEditor& editor = trackview.editor ();
531 _press_cursor_ctx.reset();
533 switch (_mouse_state) {
534 case Pressed: // Clicked
536 switch (editor.current_mouse_mode()) {
538 /* no motion occured - simple click */
540 _mouse_changed_selection = true;
546 _mouse_changed_selection = true;
548 if (Keyboard::is_insert_note_event(ev)) {
550 double event_x, event_y;
554 group->canvas_to_item (event_x, event_y);
556 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
557 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
566 Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
567 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
577 case SelectRectDragging:
579 editor.drags()->end_grab ((GdkEvent *) ev);
581 create_ghost_note (ev->x, ev->y);
589 if (_mouse_changed_selection) {
590 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
591 trackview.editor().commit_reversible_selection_op ();
598 MidiRegionView::motion (GdkEventMotion* ev)
600 PublicEditor& editor = trackview.editor ();
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);
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);
613 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
615 remove_ghost_note ();
616 hide_verbose_cursor ();
618 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
620 update_ghost_note (ev->x, ev->y);
623 /* any motion immediately hides velocity text that may have been visible */
625 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
626 (*i)->hide_velocity ();
629 switch (_mouse_state) {
632 if (_pressed_button == 1) {
634 MouseMode m = editor.current_mouse_mode();
636 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
637 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
638 _mouse_state = AddDragging;
639 remove_ghost_note ();
640 hide_verbose_cursor ();
642 } else if (m == MouseContent) {
643 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
644 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
646 _mouse_changed_selection = true;
648 _mouse_state = SelectRectDragging;
650 } else if (m == MouseRange) {
651 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
652 _mouse_state = SelectVerticalDragging;
659 case SelectRectDragging:
660 case SelectVerticalDragging:
662 editor.drags()->motion_handler ((GdkEvent *) ev, false);
665 case SelectTouchDragging:
673 /* we may be dragging some non-note object (eg. patch-change, sysex)
676 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
681 MidiRegionView::scroll (GdkEventScroll* ev)
683 if (_selection.empty()) {
687 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
688 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
689 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
690 * through so that it still works for navigation.
695 hide_verbose_cursor ();
697 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
698 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
699 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
701 if (ev->direction == GDK_SCROLL_UP) {
702 change_velocities (true, fine, false, together);
703 } else if (ev->direction == GDK_SCROLL_DOWN) {
704 change_velocities (false, fine, false, together);
706 /* left, right: we don't use them */
714 MidiRegionView::key_press (GdkEventKey* ev)
716 /* since GTK bindings are generally activated on press, and since
717 detectable auto-repeat is the name of the game and only sends
718 repeated presses, carry out key actions at key press, not release.
721 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
723 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
724 _mouse_state = SelectTouchDragging;
727 } else if (ev->keyval == GDK_Escape && unmodified) {
731 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
733 bool start = (ev->keyval == GDK_comma);
734 bool end = (ev->keyval == GDK_period);
735 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
736 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
738 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
742 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
744 if (_selection.empty()) {
751 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
753 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
755 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
756 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
758 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
761 trackview.editor().commit_reversible_selection_op();
765 } else if (ev->keyval == GDK_Up) {
767 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
768 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
769 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
771 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
772 change_velocities (true, fine, allow_smush, together);
774 transpose (true, fine, allow_smush);
778 } else if (ev->keyval == GDK_Down) {
780 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
781 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
782 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
784 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
785 change_velocities (false, fine, allow_smush, together);
787 transpose (false, fine, allow_smush);
791 } else if (ev->keyval == GDK_Left) {
793 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
794 nudge_notes (false, fine);
797 } else if (ev->keyval == GDK_Right) {
799 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
800 nudge_notes (true, fine);
803 } else if (ev->keyval == GDK_c && unmodified) {
807 } else if (ev->keyval == GDK_v && unmodified) {
816 MidiRegionView::key_release (GdkEventKey* ev)
818 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
826 MidiRegionView::channel_edit ()
828 if (_selection.empty()) {
832 /* pick a note somewhat at random (since Selection is a set<>) to
833 * provide the "current" channel for the dialog.
836 uint8_t current_channel = (*_selection.begin())->note()->channel ();
837 MidiChannelDialog channel_dialog (current_channel);
838 int ret = channel_dialog.run ();
841 case Gtk::RESPONSE_OK:
847 uint8_t new_channel = channel_dialog.active_channel ();
849 start_note_diff_command (_("channel edit"));
851 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
852 Selection::iterator next = i;
854 change_note_channel (*i, new_channel);
862 MidiRegionView::velocity_edit ()
864 if (_selection.empty()) {
868 /* pick a note somewhat at random (since Selection is a set<>) to
869 * provide the "current" velocity for the dialog.
872 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
873 MidiVelocityDialog velocity_dialog (current_velocity);
874 int ret = velocity_dialog.run ();
877 case Gtk::RESPONSE_OK:
883 uint8_t new_velocity = velocity_dialog.velocity ();
885 start_note_diff_command (_("velocity edit"));
887 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
888 Selection::iterator next = i;
890 change_note_velocity (*i, new_velocity, false);
898 MidiRegionView::show_list_editor ()
901 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
903 _list_editor->present ();
906 /** Add a note to the model, and the view, at a canvas (click) coordinate.
907 * \param t time in frames relative to the position of the region
908 * \param y vertical position in pixels
909 * \param length duration of the note in beats
910 * \param snap_t true to snap t to the grid, otherwise false.
913 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bool snap_t)
915 if (length < 2 * DBL_EPSILON) {
919 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
920 MidiStreamView* const view = mtv->midi_view();
922 // Start of note in frames relative to region start
924 framecnt_t grid_frames;
925 t = snap_frame_to_grid_underneath (t, grid_frames);
928 const MidiModel::TimeType beat_time = region_frames_to_region_beats(
929 t + _region->start());
931 const double note = view->y_to_note(y);
932 const uint8_t chan = mtv->get_channel_for_add();
933 const uint8_t velocity = get_velocity_for_add(beat_time);
935 const boost::shared_ptr<NoteType> new_note(
936 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
938 if (_model->contains (new_note)) {
942 view->update_note_range(new_note->note());
944 start_note_diff_command(_("add note"));
947 note_diff_add_note (new_note, true, false);
951 play_midi_note (new_note);
955 MidiRegionView::clear_events (bool with_selection_signal)
957 clear_selection (with_selection_signal);
960 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
961 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
966 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
971 _patch_changes.clear();
973 _optimization_iterator = _events.end();
977 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
981 content_connection.disconnect ();
982 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
983 /* Don't signal as nobody else needs to know until selection has been altered. */
984 clear_events (false);
986 if (_enable_display) {
992 MidiRegionView::start_note_diff_command (string name)
994 if (!_note_diff_command) {
995 trackview.editor().begin_reversible_command (name);
996 _note_diff_command = _model->new_note_diff_command (name);
1001 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1003 if (_note_diff_command) {
1004 _note_diff_command->add (note);
1007 _marked_for_selection.insert(note);
1009 if (show_velocity) {
1010 _marked_for_velocity.insert(note);
1015 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1017 if (_note_diff_command && ev->note()) {
1018 _note_diff_command->remove(ev->note());
1023 MidiRegionView::note_diff_add_change (NoteBase* ev,
1024 MidiModel::NoteDiffCommand::Property property,
1027 if (_note_diff_command) {
1028 _note_diff_command->change (ev->note(), property, val);
1033 MidiRegionView::note_diff_add_change (NoteBase* ev,
1034 MidiModel::NoteDiffCommand::Property property,
1037 if (_note_diff_command) {
1038 _note_diff_command->change (ev->note(), property, val);
1043 MidiRegionView::apply_diff (bool as_subcommand)
1046 bool commit = false;
1048 if (!_note_diff_command) {
1052 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1053 // Mark all selected notes for selection when model reloads
1054 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1055 _marked_for_selection.insert((*i)->note());
1059 midi_view()->midi_track()->midi_playlist()->region_edited(
1060 _region, _note_diff_command);
1062 if (as_subcommand) {
1063 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1065 _model->apply_command (*trackview.session(), _note_diff_command);
1069 _note_diff_command = 0;
1071 if (add_or_remove) {
1072 _marked_for_selection.clear();
1075 _marked_for_velocity.clear();
1077 trackview.editor().commit_reversible_command ();
1082 MidiRegionView::abort_command()
1084 delete _note_diff_command;
1085 _note_diff_command = 0;
1090 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1092 if (_optimization_iterator != _events.end()) {
1093 ++_optimization_iterator;
1096 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1097 return *_optimization_iterator;
1100 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1101 if ((*_optimization_iterator)->note() == note) {
1102 return *_optimization_iterator;
1109 /** This version finds any canvas note matching the supplied note. */
1111 MidiRegionView::find_canvas_note (NoteType note)
1113 Events::iterator it;
1115 for (it = _events.begin(); it != _events.end(); ++it) {
1116 if (*((*it)->note()) == note) {
1125 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1127 MidiModel::Notes notes;
1128 _model->get_notes (notes, op, val, chan_mask);
1130 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1131 NoteBase* cne = find_canvas_note (*n);
1139 MidiRegionView::redisplay_model()
1141 if (_active_notes) {
1142 // Currently recording
1143 const framecnt_t zoom = trackview.editor().get_current_zoom();
1144 if (zoom != _last_display_zoom) {
1145 /* Update resolved canvas notes to reflect changes in zoom without
1146 touching model. Leave active notes (with length 0) alone since
1147 they are being extended. */
1148 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1149 if ((*i)->note()->length() > 0) {
1153 _last_display_zoom = zoom;
1162 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1163 (*i)->invalidate ();
1166 MidiModel::ReadLock lock(_model->read_lock());
1168 MidiModel::Notes& notes (_model->notes());
1169 _optimization_iterator = _events.begin();
1171 bool empty_when_starting = _events.empty();
1173 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1175 boost::shared_ptr<NoteType> note (*n);
1179 if (note_in_region_range (note, visible)) {
1181 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1194 cne = add_note (note, visible);
1197 set<boost::shared_ptr<NoteType> >::iterator it;
1198 for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1199 if (*(*it) == *note) {
1200 add_to_selection (cne);
1206 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1213 /* remove note items that are no longer valid */
1215 if (!empty_when_starting) {
1216 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1217 if (!(*i)->valid ()) {
1219 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1220 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1222 gr->remove_note (*i);
1227 i = _events.erase (i);
1235 _patch_changes.clear();
1239 display_patch_changes ();
1241 _marked_for_selection.clear ();
1242 _marked_for_velocity.clear ();
1243 _pending_note_selection.clear ();
1245 /* we may have caused _events to contain things out of order (e.g. if a note
1246 moved earlier or later). we don't generally need them in time order, but
1247 make a note that a sort is required for those cases that require it.
1250 _sort_needed = true;
1254 MidiRegionView::display_patch_changes ()
1256 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1257 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1259 for (uint8_t i = 0; i < 16; ++i) {
1260 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1264 /** @param active_channel true to display patch changes fully, false to display
1265 * them `greyed-out' (as on an inactive channel)
1268 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1270 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1272 if ((*i)->channel() != channel) {
1276 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1277 add_canvas_patch_change (*i, patch_name, active_channel);
1282 MidiRegionView::display_sysexes()
1284 bool have_periodic_system_messages = false;
1285 bool display_periodic_messages = true;
1287 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1289 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1290 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1291 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1294 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1295 have_periodic_system_messages = true;
1301 if (have_periodic_system_messages) {
1302 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1304 /* get an approximate value for the number of samples per video frame */
1306 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1308 /* if we are zoomed out beyond than the cutoff (i.e. more
1309 * frames per pixel than frames per 4 video frames), don't
1310 * show periodic sysex messages.
1313 if (zoom > (video_frame*4)) {
1314 display_periodic_messages = false;
1318 display_periodic_messages = false;
1321 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1323 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
1324 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
1326 Evoral::Beats time = (*i)->time();
1329 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1330 if (!display_periodic_messages) {
1338 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1339 str << int((*i)->buffer()[b]);
1340 if (b != (*i)->size() -1) {
1344 string text = str.str();
1346 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1348 double height = midi_stream_view()->contents_height();
1350 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1351 // SysEx canvas object!!!
1353 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1354 new SysEx (*this, _note_group, text, height, x, 1.0));
1356 // Show unless message is beyond the region bounds
1357 if (time - _region->start() >= _region->length() || time < _region->start()) {
1363 _sys_exes.push_back(sysex);
1367 MidiRegionView::~MidiRegionView ()
1369 in_destructor = true;
1371 hide_verbose_cursor ();
1373 delete _list_editor;
1375 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1377 if (_active_notes) {
1381 _selection_cleared_connection.disconnect ();
1384 clear_events (false);
1387 delete _note_diff_command;
1388 delete _step_edit_cursor;
1389 delete _temporary_note_group;
1393 MidiRegionView::region_resized (const PropertyChange& what_changed)
1395 RegionView::region_resized(what_changed);
1397 if (what_changed.contains (ARDOUR::Properties::position)) {
1398 _region_relative_time_converter.set_origin_b(_region->position());
1399 _region_relative_time_converter_double.set_origin_b(_region->position());
1400 set_duration(_region->length(), 0);
1401 if (_enable_display) {
1406 if (what_changed.contains (ARDOUR::Properties::start) ||
1407 what_changed.contains (ARDOUR::Properties::position)) {
1408 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1413 MidiRegionView::reset_width_dependent_items (double pixel_width)
1415 RegionView::reset_width_dependent_items(pixel_width);
1417 if (_enable_display) {
1421 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1422 if ((*x)->canvas_item()->width() >= _pixel_width) {
1429 move_step_edit_cursor (_step_edit_cursor_position);
1430 set_step_edit_cursor_width (_step_edit_cursor_width);
1434 MidiRegionView::set_height (double height)
1436 double old_height = _height;
1437 RegionView::set_height(height);
1439 apply_note_range (midi_stream_view()->lowest_note(),
1440 midi_stream_view()->highest_note(),
1441 height != old_height);
1444 name_text->raise_to_top();
1447 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1448 (*x)->set_height (midi_stream_view()->contents_height());
1451 if (_step_edit_cursor) {
1452 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1457 /** Apply the current note range from the stream view
1458 * by repositioning/hiding notes as necessary
1461 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1463 if (!_enable_display) {
1467 if (!force && _current_range_min == min && _current_range_max == max) {
1471 _current_range_min = min;
1472 _current_range_max = max;
1474 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1475 NoteBase* event = *i;
1476 boost::shared_ptr<NoteType> note (event->note());
1478 if (note->note() < _current_range_min ||
1479 note->note() > _current_range_max) {
1485 if (Note* cnote = dynamic_cast<Note*>(event)) {
1487 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1488 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1490 if (y0 < 0 || y1 >= _height) {
1491 /* During DnD, the region uses the 'old/current'
1492 * midi_stream_view()'s range and its position/height calculation.
1494 * Ideally DnD would decouple the midi_stream_view() for the
1495 * region(s) being dragged and set it to the target's range
1496 * (or in case of the drop-zone, FullRange).
1497 * but I don't see how this can be done without major rework.
1499 * For now, just prevent visual bleeding of events in case
1500 * the target-track is smaller.
1508 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1515 MidiRegionView::add_ghost (TimeAxisView& tv)
1517 double unit_position = _region->position () / samples_per_pixel;
1518 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1519 MidiGhostRegion* ghost;
1521 if (mtv && mtv->midi_view()) {
1522 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1523 to allow having midi notes on top of note lines and waveforms.
1525 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1527 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1530 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1531 ghost->add_note(*i);
1534 ghost->set_height ();
1535 ghost->set_duration (_region->length() / samples_per_pixel);
1536 ghosts.push_back (ghost);
1542 /** Begin tracking note state for successive calls to add_event
1545 MidiRegionView::begin_write()
1547 if (_active_notes) {
1548 delete[] _active_notes;
1550 _active_notes = new Note*[128];
1551 for (unsigned i = 0; i < 128; ++i) {
1552 _active_notes[i] = 0;
1557 /** Destroy note state for add_event
1560 MidiRegionView::end_write()
1562 delete[] _active_notes;
1564 _marked_for_selection.clear();
1565 _marked_for_velocity.clear();
1569 /** Resolve an active MIDI note (while recording).
1572 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1574 if (midi_view()->note_mode() != Sustained) {
1578 if (_active_notes && _active_notes[note]) {
1579 /* Set note length so update_note() works. Note this is a local note
1580 for recording, not from a model, so we can safely mess with it. */
1581 _active_notes[note]->note()->set_length(
1582 end_time - _active_notes[note]->note()->time());
1584 /* End time is relative to the region being recorded. */
1585 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1587 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1588 _active_notes[note]->set_outline_all ();
1589 _active_notes[note] = 0;
1594 /** Extend active notes to rightmost edge of region (if length is changed)
1597 MidiRegionView::extend_active_notes()
1599 if (!_active_notes) {
1603 for (unsigned i = 0; i < 128; ++i) {
1604 if (_active_notes[i]) {
1605 _active_notes[i]->set_x1(
1606 trackview.editor().sample_to_pixel(_region->length()));
1612 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1614 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1618 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1620 if (!route_ui || !route_ui->midi_track()) {
1624 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1628 /* NotePlayer deletes itself */
1632 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1634 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1635 start_playing_midi_chord(notes);
1639 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1641 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1645 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1647 if (!route_ui || !route_ui->midi_track()) {
1651 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1653 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1662 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1664 /* This is imprecise due to all the conversion conversion involved, so only
1665 hide notes if they seem to start more than one tick before the start. */
1666 const framecnt_t tick_frames = Evoral::Beats::tick().to_ticks(trackview.session()->frame_rate());
1667 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1668 const bool outside = ((note_start_frames <= -tick_frames) ||
1669 (note_start_frames >= _region->length()));
1671 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1672 (note->note() <= midi_stream_view()->highest_note());
1678 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1682 if ((sus = dynamic_cast<Note*>(note))) {
1683 update_sustained(sus, update_ghost_regions);
1684 } else if ((hit = dynamic_cast<Hit*>(note))) {
1685 update_hit(hit, update_ghost_regions);
1689 /** Update a canvas note's size from its model note.
1690 * @param ev Canvas note to update.
1691 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1694 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1696 boost::shared_ptr<NoteType> note = ev->note();
1697 const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1698 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1703 /* trim note display to not overlap the end of its region */
1705 if (note->length() > 0) {
1706 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1707 ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1);
1709 ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1);
1712 ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
1714 if (!note->length()) {
1715 if (_active_notes && note->note() < 128) {
1716 Note* const old_rect = _active_notes[note->note()];
1718 /* There is an active note on this key, so we have a stuck
1719 note. Finish the old rectangle here. */
1720 old_rect->set_x1 (x);
1721 old_rect->set_outline_all ();
1723 _active_notes[note->note()] = ev;
1725 /* outline all but right edge */
1726 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1727 ArdourCanvas::Rectangle::TOP|
1728 ArdourCanvas::Rectangle::LEFT|
1729 ArdourCanvas::Rectangle::BOTTOM));
1731 /* outline all edges */
1732 ev->set_outline_all ();
1735 // Update color in case velocity has changed
1736 ev->set_fill_color(ev->base_color());
1737 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1739 if (update_ghost_regions) {
1740 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1741 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1743 gr->update_note (ev);
1750 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1752 boost::shared_ptr<NoteType> note = ev->note();
1754 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1755 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1756 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1757 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1759 // see DnD note in MidiRegionView::apply_note_range() above
1760 if (y <= 0 || y >= _height) {
1766 ev->set_position (ArdourCanvas::Duple (x, y));
1767 ev->set_height (diamond_size);
1769 // Update color in case velocity has changed
1770 ev->set_fill_color(ev->base_color());
1771 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1773 if (update_ghost_regions) {
1774 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1775 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1777 gr->update_note (ev);
1783 /** Add a MIDI note to the view (with length).
1785 * If in sustained mode, notes with length 0 will be considered active
1786 * notes, and resolve_note should be called when the corresponding note off
1787 * event arrives, to properly display the note.
1790 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1792 NoteBase* event = 0;
1794 if (midi_view()->note_mode() == Sustained) {
1796 Note* ev_rect = new Note (*this, _note_group, note);
1798 update_sustained (ev_rect);
1802 } else if (midi_view()->note_mode() == Percussive) {
1804 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1806 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1808 update_hit (ev_diamond);
1817 MidiGhostRegion* gr;
1819 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1820 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1821 gr->add_note(event);
1825 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1826 note_selected(event, true);
1829 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1830 event->show_velocity();
1833 event->on_channel_selection_change (get_selected_channels());
1834 _events.push_back(event);
1843 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1844 MidiStreamView* const view = mtv->midi_view();
1846 view->update_note_range (note->note());
1851 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1852 Evoral::Beats pos, Evoral::Beats len)
1854 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1856 /* potentially extend region to hold new note */
1858 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1859 framepos_t region_end = _region->last_frame();
1861 if (end_frame > region_end) {
1862 _region->set_length (end_frame - _region->position());
1865 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1866 MidiStreamView* const view = mtv->midi_view();
1868 view->update_note_range(new_note->note());
1870 _marked_for_selection.clear ();
1872 start_note_diff_command (_("step add"));
1875 note_diff_add_note (new_note, true, false);
1879 // last_step_edit_note = new_note;
1883 MidiRegionView::step_sustain (Evoral::Beats beats)
1885 change_note_lengths (false, false, beats, false, true);
1888 /** Add a new patch change flag to the canvas.
1889 * @param patch the patch change to add
1890 * @param the text to display in the flag
1891 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1894 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1896 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1897 const double x = trackview.editor().sample_to_pixel (region_frames);
1899 double const height = midi_stream_view()->contents_height();
1901 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1902 // so we need to do something more sophisticated to keep its color
1903 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1906 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1907 new PatchChange(*this, group,
1914 if (patch_change->item().width() < _pixel_width) {
1915 // Show unless patch change is beyond the region bounds
1916 if (region_frames < 0 || region_frames >= _region->length()) {
1917 patch_change->hide();
1919 patch_change->show();
1922 patch_change->hide ();
1925 _patch_changes.push_back (patch_change);
1928 MIDI::Name::PatchPrimaryKey
1929 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1931 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1934 /// Return true iff @p pc applies to the given time on the given channel.
1936 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1938 return pc->time() <= time && pc->channel() == channel;
1942 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1944 // The earliest event not before time
1945 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1947 // Go backwards until we find the latest PC for this channel, or the start
1948 while (i != _model->patch_changes().begin() &&
1949 (i == _model->patch_changes().end() ||
1950 !patch_applies(*i, time, channel))) {
1954 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1955 key.set_bank((*i)->bank());
1956 key.set_program((*i)->program ());
1964 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1966 string name = _("alter patch change");
1967 trackview.editor().begin_reversible_command (name);
1968 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1970 if (pc.patch()->program() != new_patch.program()) {
1971 c->change_program (pc.patch (), new_patch.program());
1974 int const new_bank = new_patch.bank();
1975 if (pc.patch()->bank() != new_bank) {
1976 c->change_bank (pc.patch (), new_bank);
1979 _model->apply_command (*trackview.session(), c);
1980 trackview.editor().commit_reversible_command ();
1982 _patch_changes.clear ();
1983 display_patch_changes ();
1987 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
1989 string name = _("alter patch change");
1990 trackview.editor().begin_reversible_command (name);
1991 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
1993 if (old_change->time() != new_change.time()) {
1994 c->change_time (old_change, new_change.time());
1997 if (old_change->channel() != new_change.channel()) {
1998 c->change_channel (old_change, new_change.channel());
2001 if (old_change->program() != new_change.program()) {
2002 c->change_program (old_change, new_change.program());
2005 if (old_change->bank() != new_change.bank()) {
2006 c->change_bank (old_change, new_change.bank());
2009 _model->apply_command (*trackview.session(), c);
2010 trackview.editor().commit_reversible_command ();
2012 _patch_changes.clear ();
2013 display_patch_changes ();
2016 /** Add a patch change to the region.
2017 * @param t Time in frames relative to region position
2018 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2019 * MidiTimeAxisView::get_channel_for_add())
2022 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2024 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2025 string name = _("add patch change");
2027 trackview.editor().begin_reversible_command (name);
2028 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2029 c->add (MidiModel::PatchChangePtr (
2030 new Evoral::PatchChange<Evoral::Beats> (
2031 absolute_frames_to_source_beats (_region->position() + t),
2032 mtv->get_channel_for_add(), patch.program(), patch.bank()
2037 _model->apply_command (*trackview.session(), c);
2038 trackview.editor().commit_reversible_command ();
2040 _patch_changes.clear ();
2041 display_patch_changes ();
2045 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2047 trackview.editor().begin_reversible_command (_("move patch change"));
2048 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2049 c->change_time (pc.patch (), t);
2050 _model->apply_command (*trackview.session(), c);
2051 trackview.editor().commit_reversible_command ();
2053 _patch_changes.clear ();
2054 display_patch_changes ();
2058 MidiRegionView::delete_patch_change (PatchChange* pc)
2060 trackview.editor().begin_reversible_command (_("delete patch change"));
2061 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2062 c->remove (pc->patch ());
2063 _model->apply_command (*trackview.session(), c);
2064 trackview.editor().commit_reversible_command ();
2066 _patch_changes.clear ();
2067 display_patch_changes ();
2071 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2073 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2075 key.set_bank(key.bank() + delta);
2077 key.set_program(key.program() + delta);
2079 change_patch_change(patch, key);
2083 MidiRegionView::note_deleted (NoteBase* cne)
2085 if (_selection.empty()) {
2089 _selection.erase (cne);
2093 MidiRegionView::delete_selection()
2095 if (_selection.empty()) {
2099 start_note_diff_command (_("delete selection"));
2101 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2102 if ((*i)->selected()) {
2103 _note_diff_command->remove((*i)->note());
2110 hide_verbose_cursor ();
2114 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2116 start_note_diff_command (_("delete note"));
2117 _note_diff_command->remove (n);
2120 hide_verbose_cursor ();
2124 MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
2126 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2128 Selection::iterator tmp = i;
2131 (*i)->set_selected (false);
2132 (*i)->hide_velocity ();
2133 _selection.erase (i);
2141 if (!ev && _entered) {
2142 // Clearing selection entirely, ungrab keyboard
2143 Keyboard::magic_widget_drop_focus();
2144 _grabbed_keyboard = false;
2147 /* this does not change the status of this regionview w.r.t the editor
2152 SelectionCleared (this); /* EMIT SIGNAL */
2157 MidiRegionView::unique_select(NoteBase* ev)
2159 const bool selection_was_empty = _selection.empty();
2161 clear_selection_except (ev);
2163 /* don't bother with checking to see if we should remove this
2164 regionview from the editor selection, since we're about to add
2165 another note, and thus put/keep this regionview in the editor
2169 if (!ev->selected()) {
2170 add_to_selection (ev);
2171 if (selection_was_empty && _entered) {
2172 // Grab keyboard for moving notes with arrow keys
2173 Keyboard::magic_widget_grab_focus();
2174 _grabbed_keyboard = true;
2180 MidiRegionView::select_all_notes ()
2184 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2185 add_to_selection (*i);
2190 MidiRegionView::select_range (framepos_t start, framepos_t end)
2194 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2195 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2196 if (t >= start && t <= end) {
2197 add_to_selection (*i);
2203 MidiRegionView::invert_selection ()
2205 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2206 if ((*i)->selected()) {
2207 remove_from_selection(*i);
2209 add_to_selection (*i);
2214 /** Used for selection undo/redo.
2215 The requested notes most likely won't exist in the view until the next model redisplay.
2218 MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
2221 list<boost::shared_ptr<NoteType> >::iterator n;
2223 for (n = notes.begin(); n != notes.end(); ++n) {
2224 if ((cne = find_canvas_note(*(*n))) != 0) {
2225 add_to_selection (cne);
2227 _pending_note_selection.insert(*n);
2233 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2235 bool have_selection = !_selection.empty();
2236 uint8_t low_note = 127;
2237 uint8_t high_note = 0;
2238 MidiModel::Notes& notes (_model->notes());
2239 _optimization_iterator = _events.begin();
2241 if (extend && !have_selection) {
2245 /* scan existing selection to get note range */
2247 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2248 if ((*i)->note()->note() < low_note) {
2249 low_note = (*i)->note()->note();
2251 if ((*i)->note()->note() > high_note) {
2252 high_note = (*i)->note()->note();
2259 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2260 /* only note previously selected is the one we are
2261 * reselecting. treat this as cancelling the selection.
2268 low_note = min (low_note, notenum);
2269 high_note = max (high_note, notenum);
2272 _no_sound_notes = true;
2274 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2276 boost::shared_ptr<NoteType> note (*n);
2278 bool select = false;
2280 if (((1 << note->channel()) & channel_mask) != 0) {
2282 if ((note->note() >= low_note && note->note() <= high_note)) {
2285 } else if (note->note() == notenum) {
2291 if ((cne = find_canvas_note (note)) != 0) {
2292 // extend is false because we've taken care of it,
2293 // since it extends by time range, not pitch.
2294 note_selected (cne, add, false);
2298 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2302 _no_sound_notes = false;
2306 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2308 MidiModel::Notes& notes (_model->notes());
2309 _optimization_iterator = _events.begin();
2311 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2313 boost::shared_ptr<NoteType> note (*n);
2316 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2317 if ((cne = find_canvas_note (note)) != 0) {
2318 if (cne->selected()) {
2319 note_deselected (cne);
2321 note_selected (cne, true, false);
2329 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2332 clear_selection_except (ev);
2333 if (!_selection.empty()) {
2334 PublicEditor& editor (trackview.editor());
2335 editor.get_selection().add (this);
2341 if (!ev->selected()) {
2342 add_to_selection (ev);
2346 /* find end of latest note selected, select all between that and the start of "ev" */
2348 Evoral::Beats earliest = Evoral::MaxBeats;
2349 Evoral::Beats latest = Evoral::Beats();
2351 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2352 if ((*i)->note()->end_time() > latest) {
2353 latest = (*i)->note()->end_time();
2355 if ((*i)->note()->time() < earliest) {
2356 earliest = (*i)->note()->time();
2360 if (ev->note()->end_time() > latest) {
2361 latest = ev->note()->end_time();
2364 if (ev->note()->time() < earliest) {
2365 earliest = ev->note()->time();
2368 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2370 /* find notes entirely within OR spanning the earliest..latest range */
2372 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2373 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2374 add_to_selection (*i);
2382 MidiRegionView::note_deselected(NoteBase* ev)
2384 remove_from_selection (ev);
2388 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2390 PublicEditor& editor = trackview.editor();
2392 // Convert to local coordinates
2393 const framepos_t p = _region->position();
2394 const double y = midi_view()->y_position();
2395 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2396 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2397 const double y0 = max(0.0, gy0 - y);
2398 const double y1 = max(0.0, gy1 - y);
2400 // TODO: Make this faster by storing the last updated selection rect, and only
2401 // adjusting things that are in the area that appears/disappeared.
2402 // We probably need a tree to be able to find events in O(log(n)) time.
2404 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2405 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2406 // Rectangles intersect
2407 if (!(*i)->selected()) {
2408 add_to_selection (*i);
2410 } else if ((*i)->selected() && !extend) {
2411 // Rectangles do not intersect
2412 remove_from_selection (*i);
2416 typedef RouteTimeAxisView::AutomationTracks ATracks;
2417 typedef std::list<Selectable*> Selectables;
2419 /* Add control points to selection. */
2420 const ATracks& atracks = midi_view()->automation_tracks();
2421 Selectables selectables;
2422 editor.get_selection().clear_points();
2423 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2424 a->second->get_selectables(start, end, gy0, gy1, selectables);
2425 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2426 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2428 editor.get_selection().add(cp);
2431 a->second->set_selected_points(editor.get_selection().points);
2436 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2442 // TODO: Make this faster by storing the last updated selection rect, and only
2443 // adjusting things that are in the area that appears/disappeared.
2444 // We probably need a tree to be able to find events in O(log(n)) time.
2446 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2447 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2448 // within y- (note-) range
2449 if (!(*i)->selected()) {
2450 add_to_selection (*i);
2452 } else if ((*i)->selected() && !extend) {
2453 remove_from_selection (*i);
2459 MidiRegionView::remove_from_selection (NoteBase* ev)
2461 Selection::iterator i = _selection.find (ev);
2463 if (i != _selection.end()) {
2464 _selection.erase (i);
2465 if (_selection.empty() && _grabbed_keyboard) {
2467 Keyboard::magic_widget_drop_focus();
2468 _grabbed_keyboard = false;
2472 ev->set_selected (false);
2473 ev->hide_velocity ();
2475 if (_selection.empty()) {
2476 PublicEditor& editor (trackview.editor());
2477 editor.get_selection().remove (this);
2482 MidiRegionView::add_to_selection (NoteBase* ev)
2484 const bool selection_was_empty = _selection.empty();
2486 if (_selection.insert (ev).second) {
2487 ev->set_selected (true);
2488 start_playing_midi_note ((ev)->note());
2489 if (selection_was_empty && _entered) {
2490 // Grab keyboard for moving notes with arrow keys
2491 Keyboard::magic_widget_grab_focus();
2492 _grabbed_keyboard = true;
2496 if (selection_was_empty) {
2497 PublicEditor& editor (trackview.editor());
2498 editor.get_selection().add (this);
2503 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2505 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2506 PossibleChord to_play;
2507 Evoral::Beats earliest = Evoral::MaxBeats;
2509 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2510 if ((*i)->note()->time() < earliest) {
2511 earliest = (*i)->note()->time();
2515 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2516 if ((*i)->note()->time() == earliest) {
2517 to_play.push_back ((*i)->note());
2519 (*i)->move_event(dx, dy);
2522 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2524 if (to_play.size() > 1) {
2526 PossibleChord shifted;
2528 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2529 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2530 moved_note->set_note (moved_note->note() + cumulative_dy);
2531 shifted.push_back (moved_note);
2534 start_playing_midi_chord (shifted);
2536 } else if (!to_play.empty()) {
2538 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2539 moved_note->set_note (moved_note->note() + cumulative_dy);
2540 start_playing_midi_note (moved_note);
2546 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2548 uint8_t lowest_note_in_selection = 127;
2549 uint8_t highest_note_in_selection = 0;
2550 uint8_t highest_note_difference = 0;
2552 // find highest and lowest notes first
2554 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2555 uint8_t pitch = (*i)->note()->note();
2556 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2557 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2561 cerr << "dnote: " << (int) dnote << endl;
2562 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2563 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2564 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2565 << int(highest_note_in_selection) << endl;
2566 cerr << "selection size: " << _selection.size() << endl;
2567 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2570 // Make sure the note pitch does not exceed the MIDI standard range
2571 if (highest_note_in_selection + dnote > 127) {
2572 highest_note_difference = highest_note_in_selection - 127;
2575 start_note_diff_command (_("move notes"));
2577 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2579 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2580 Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
2586 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2588 uint8_t original_pitch = (*i)->note()->note();
2589 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2591 // keep notes in standard midi range
2592 clamp_to_0_127(new_pitch);
2594 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2595 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2597 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2602 // care about notes being moved beyond the upper/lower bounds on the canvas
2603 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2604 highest_note_in_selection > midi_stream_view()->highest_note()) {
2605 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2609 /** @param x Pixel relative to the region position.
2610 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2611 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2612 * @return Snapped frame relative to the region position.
2615 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2617 PublicEditor& editor (trackview.editor());
2618 return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2621 /** @param x Pixel relative to the region position.
2622 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2623 * @return Snapped pixel relative to the region position.
2626 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2628 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2632 MidiRegionView::get_position_pixels()
2634 framepos_t region_frame = get_position();
2635 return trackview.editor().sample_to_pixel(region_frame);
2639 MidiRegionView::get_end_position_pixels()
2641 framepos_t frame = get_position() + get_duration ();
2642 return trackview.editor().sample_to_pixel(frame);
2646 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2648 /* the time converter will return the frame corresponding to `beats'
2649 relative to the start of the source. The start of the source
2650 is an implied position given by region->position - region->start
2652 const framepos_t source_start = _region->position() - _region->start();
2653 return source_start + _source_relative_time_converter.to (beats);
2657 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2659 /* the `frames' argument needs to be converted into a frame count
2660 relative to the start of the source before being passed in to the
2663 const framepos_t source_start = _region->position() - _region->start();
2664 return _source_relative_time_converter.from (frames - source_start);
2668 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2670 return _region_relative_time_converter.to(beats);
2674 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2676 return _region_relative_time_converter.from(frames);
2680 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2682 return _region_relative_time_converter_double.from(frames);
2686 MidiRegionView::begin_resizing (bool /*at_front*/)
2688 _resize_data.clear();
2690 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2691 Note *note = dynamic_cast<Note*> (*i);
2693 // only insert CanvasNotes into the map
2695 NoteResizeData *resize_data = new NoteResizeData();
2696 resize_data->note = note;
2698 // create a new SimpleRect from the note which will be the resize preview
2699 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2700 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2702 // calculate the colors: get the color settings
2703 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2704 UIConfiguration::instance().color ("midi note selected"),
2707 // make the resize preview notes more transparent and bright
2708 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2710 // calculate color based on note velocity
2711 resize_rect->set_fill_color (UINT_INTERPOLATE(
2712 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2716 resize_rect->set_outline_color (NoteBase::calculate_outline (
2717 UIConfiguration::instance().color ("midi note selected")));
2719 resize_data->resize_rect = resize_rect;
2720 _resize_data.push_back(resize_data);
2725 /** Update resizing notes while user drags.
2726 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2727 * @param at_front which end of the note (true == note on, false == note off)
2728 * @param delta_x change in mouse position since the start of the drag
2729 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2730 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2731 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2732 * as the \a primary note.
2733 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2734 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2737 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2739 bool cursor_set = false;
2740 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2742 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2743 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2744 Note* canvas_note = (*i)->note;
2749 current_x = canvas_note->x0() + delta_x + snap_delta;
2751 current_x = primary->x0() + delta_x + snap_delta;
2755 current_x = canvas_note->x1() + delta_x + snap_delta;
2757 current_x = primary->x1() + delta_x + snap_delta;
2761 if (current_x < 0) {
2762 // This works even with snapping because RegionView::snap_frame_to_frame()
2763 // snaps forward if the snapped sample is before the beginning of the region
2766 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2767 current_x = trackview.editor().sample_to_pixel(_region->length());
2772 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2774 resize_rect->set_x0 (current_x - snap_delta);
2776 resize_rect->set_x1 (canvas_note->x1());
2779 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2781 resize_rect->set_x1 (current_x - snap_delta);
2783 resize_rect->set_x0 (canvas_note->x0());
2787 /* Convert snap delta from pixels to beats. */
2788 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2789 double snap_delta_beats = 0.0;
2792 /* negative beat offsets aren't allowed */
2793 if (snap_delta_samps > 0) {
2794 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2795 } else if (snap_delta_samps < 0) {
2796 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2800 const double snapped_x = (with_snap ? snap_pixel_to_sample (current_x, ensure_snap) : trackview.editor ().pixel_to_sample (current_x));
2801 Evoral::Beats beats = region_frames_to_region_beats (snapped_x);
2802 Evoral::Beats len = Evoral::Beats();
2805 if (beats < canvas_note->note()->end_time()) {
2806 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2807 len += canvas_note->note()->length();
2810 if (beats >= canvas_note->note()->time()) {
2811 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2815 len = std::max(Evoral::Beats(1 / 512.0), len);
2818 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2819 show_verbose_cursor (buf, 0, 0);
2828 /** Finish resizing notes when the user releases the mouse button.
2829 * Parameters the same as for \a update_resizing().
2832 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2834 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2836 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2837 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2839 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2840 Note* canvas_note = (*i)->note;
2841 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2843 /* Get the new x position for this resize, which is in pixels relative
2844 * to the region position.
2851 current_x = canvas_note->x0() + delta_x + snap_delta;
2853 current_x = primary->x0() + delta_x + snap_delta;
2857 current_x = canvas_note->x1() + delta_x + snap_delta;
2859 current_x = primary->x1() + delta_x + snap_delta;
2863 if (current_x < 0) {
2866 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2867 current_x = trackview.editor().sample_to_pixel(_region->length());
2870 /* Convert snap delta from pixels to beats with sign. */
2871 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2872 double snap_delta_beats = 0.0;
2875 if (snap_delta_samps > 0) {
2876 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2877 } else if (snap_delta_samps < 0) {
2878 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2882 /* Convert the new x position to a frame within the source */
2883 framepos_t current_fr;
2885 current_fr = snap_pixel_to_sample (current_x, ensure_snap) + _region->start ();
2887 current_fr = trackview.editor().pixel_to_sample (current_x) + _region->start ();
2890 /* and then to beats */
2891 const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr);
2893 if (at_front && x_beats < canvas_note->note()->end_time()) {
2894 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2895 Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2896 len += canvas_note->note()->length();
2899 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2904 Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2905 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2906 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2913 _resize_data.clear();
2918 MidiRegionView::abort_resizing ()
2920 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2921 delete (*i)->resize_rect;
2925 _resize_data.clear ();
2929 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2931 uint8_t new_velocity;
2934 new_velocity = event->note()->velocity() + velocity;
2935 clamp_to_0_127(new_velocity);
2937 new_velocity = velocity;
2940 event->set_selected (event->selected()); // change color
2942 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2946 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2951 new_note = event->note()->note() + note;
2956 clamp_to_0_127 (new_note);
2957 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2961 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
2963 bool change_start = false;
2964 bool change_length = false;
2965 Evoral::Beats new_start;
2966 Evoral::Beats new_length;
2968 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2970 front_delta: if positive - move the start of the note later in time (shortening it)
2971 if negative - move the start of the note earlier in time (lengthening it)
2973 end_delta: if positive - move the end of the note later in time (lengthening it)
2974 if negative - move the end of the note earlier in time (shortening it)
2977 if (!!front_delta) {
2978 if (front_delta < 0) {
2980 if (event->note()->time() < -front_delta) {
2981 new_start = Evoral::Beats();
2983 new_start = event->note()->time() + front_delta; // moves earlier
2986 /* start moved toward zero, so move the end point out to where it used to be.
2987 Note that front_delta is negative, so this increases the length.
2990 new_length = event->note()->length() - front_delta;
2991 change_start = true;
2992 change_length = true;
2996 Evoral::Beats new_pos = event->note()->time() + front_delta;
2998 if (new_pos < event->note()->end_time()) {
2999 new_start = event->note()->time() + front_delta;
3000 /* start moved toward the end, so move the end point back to where it used to be */
3001 new_length = event->note()->length() - front_delta;
3002 change_start = true;
3003 change_length = true;
3010 bool can_change = true;
3011 if (end_delta < 0) {
3012 if (event->note()->length() < -end_delta) {
3018 new_length = event->note()->length() + end_delta;
3019 change_length = true;
3024 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3027 if (change_length) {
3028 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3033 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3035 uint8_t new_channel;
3039 if (event->note()->channel() < -chn) {
3042 new_channel = event->note()->channel() + chn;
3045 new_channel = event->note()->channel() + chn;
3048 new_channel = (uint8_t) chn;
3051 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3055 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3057 Evoral::Beats new_time;
3061 if (event->note()->time() < -delta) {
3062 new_time = Evoral::Beats();
3064 new_time = event->note()->time() + delta;
3067 new_time = event->note()->time() + delta;
3073 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3077 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3079 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3083 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3088 if (_selection.empty()) {
3103 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3104 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3110 start_note_diff_command (_("change velocities"));
3112 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3113 Selection::iterator next = i;
3117 if (i == _selection.begin()) {
3118 change_note_velocity (*i, delta, true);
3119 value = (*i)->note()->velocity() + delta;
3121 change_note_velocity (*i, value, false);
3125 change_note_velocity (*i, delta, true);
3134 if (!_selection.empty()) {
3136 snprintf (buf, sizeof (buf), "Vel %d",
3137 (int) (*_selection.begin())->note()->velocity());
3138 show_verbose_cursor (buf, 10, 10);
3144 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3146 if (_selection.empty()) {
3163 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3165 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3169 if ((int8_t) (*i)->note()->note() + delta > 127) {
3176 start_note_diff_command (_("transpose"));
3178 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3179 Selection::iterator next = i;
3181 change_note_note (*i, delta, true);
3189 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3193 delta = Evoral::Beats(1.0/128.0);
3195 /* grab the current grid distance */
3196 delta = get_grid_beats(_region->position());
3204 start_note_diff_command (_("change note lengths"));
3206 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3207 Selection::iterator next = i;
3210 /* note the negation of the delta for start */
3213 (start ? -delta : Evoral::Beats()),
3214 (end ? delta : Evoral::Beats()));
3223 MidiRegionView::nudge_notes (bool forward, bool fine)
3225 if (_selection.empty()) {
3229 /* pick a note as the point along the timeline to get the nudge distance.
3230 its not necessarily the earliest note, so we may want to pull the notes out
3231 into a vector and sort before using the first one.
3234 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3235 Evoral::Beats delta;
3239 /* non-fine, move by 1 bar regardless of snap */
3240 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
3242 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3244 /* grid is off - use nudge distance */
3247 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3248 delta = region_frames_to_region_beats (fabs ((double)distance));
3254 framepos_t next_pos = ref_point;
3257 if (max_framepos - 1 < next_pos) {
3261 if (next_pos == 0) {
3267 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3268 const framecnt_t distance = ref_point - next_pos;
3269 delta = region_frames_to_region_beats (fabs ((double)distance));
3280 start_note_diff_command (_("nudge"));
3282 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3283 Selection::iterator next = i;
3285 change_note_time (*i, delta, true);
3293 MidiRegionView::change_channel(uint8_t channel)
3295 start_note_diff_command(_("change channel"));
3296 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3297 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3305 MidiRegionView::note_entered(NoteBase* ev)
3307 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3309 if (_mouse_state == SelectTouchDragging) {
3310 note_selected (ev, true);
3311 } else if (editor->current_mouse_mode() == MouseContent) {
3312 show_verbose_cursor (ev->note ());
3313 } else if (editor->current_mouse_mode() == MouseDraw) {
3314 show_verbose_cursor (ev->note ());
3319 MidiRegionView::note_left (NoteBase*)
3321 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3322 (*i)->hide_velocity ();
3325 hide_verbose_cursor ();
3329 MidiRegionView::patch_entered (PatchChange* p)
3332 /* XXX should get patch name if we can */
3333 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3334 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3335 << _("Channel ") << ((int) p->patch()->channel() + 1);
3336 show_verbose_cursor (s.str(), 10, 20);
3337 p->item().grab_focus();
3341 MidiRegionView::patch_left (PatchChange *)
3343 hide_verbose_cursor ();
3344 /* focus will transfer back via the enter-notify event sent to this
3350 MidiRegionView::sysex_entered (SysEx* p)
3354 // need a way to extract text from p->_flag->_text
3356 // show_verbose_cursor (s.str(), 10, 20);
3357 p->item().grab_focus();
3361 MidiRegionView::sysex_left (SysEx *)
3363 hide_verbose_cursor ();
3364 /* focus will transfer back via the enter-notify event sent to this
3370 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3372 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3373 Editing::MouseMode mm = editor->current_mouse_mode();
3374 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3376 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3377 if (can_set_cursor && ctx) {
3378 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3379 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3380 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3381 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3383 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3389 MidiRegionView::get_fill_color() const
3391 const std::string mod_name = (_dragging ? "dragging region" :
3392 trackview.editor().internal_editing() ? "editable region" :
3395 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3396 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3397 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3398 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3400 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3404 MidiRegionView::midi_channel_mode_changed ()
3406 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3407 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3408 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3410 if (mode == ForceChannel) {
3411 mask = 0xFFFF; // Show all notes as active (below)
3414 // Update notes for selection
3415 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3416 (*i)->on_channel_selection_change (mask);
3419 _patch_changes.clear ();
3420 display_patch_changes ();
3424 MidiRegionView::instrument_settings_changed ()
3430 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3432 if (_selection.empty()) {
3436 PublicEditor& editor (trackview.editor());
3440 /* XXX what to do ? */
3444 editor.get_cut_buffer().add (selection_as_cut_buffer());
3452 start_note_diff_command();
3454 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3461 note_diff_remove_note (*i);
3471 MidiRegionView::selection_as_cut_buffer () const
3475 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3476 NoteType* n = (*i)->note().get();
3477 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3480 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3486 /** This method handles undo */
3488 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
3490 bool commit = false;
3491 // Paste notes, if available
3492 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3493 if (m != selection.midi_notes.end()) {
3494 ctx.counts.increase_n_notes();
3495 if (!(*m)->empty()) { commit = true; }
3496 paste_internal(pos, ctx.count, ctx.times, **m);
3499 // Paste control points to automation children, if available
3500 typedef RouteTimeAxisView::AutomationTracks ATracks;
3501 const ATracks& atracks = midi_view()->automation_tracks();
3502 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3503 if (a->second->paste(pos, selection, ctx)) {
3509 trackview.editor().commit_reversible_command ();
3514 /** This method handles undo */
3516 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3522 start_note_diff_command (_("paste"));
3524 const Evoral::Beats snap_beats = get_grid_beats(pos);
3525 const Evoral::Beats first_time = (*mcb.notes().begin())->time();
3526 const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
3527 const Evoral::Beats duration = last_time - first_time;
3528 const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3529 const Evoral::Beats paste_offset = snap_duration * paste_count;
3530 const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3531 Evoral::Beats end_point = Evoral::Beats();
3533 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3536 duration, pos, _region->position(),
3541 for (int n = 0; n < (int) times; ++n) {
3543 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3545 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3546 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3547 copied_note->set_id (Evoral::next_event_id());
3549 /* make all newly added notes selected */
3551 note_diff_add_note (copied_note, true);
3552 end_point = copied_note->end_time();
3556 /* if we pasted past the current end of the region, extend the region */
3558 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3559 framepos_t region_end = _region->position() + _region->length() - 1;
3561 if (end_frame > region_end) {
3563 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3565 _region->clear_changes ();
3566 _region->set_length (end_frame - _region->position());
3567 trackview.session()->add_command (new StatefulDiffCommand (_region));
3573 struct EventNoteTimeEarlyFirstComparator {
3574 bool operator() (NoteBase* a, NoteBase* b) {
3575 return a->note()->time() < b->note()->time();
3580 MidiRegionView::time_sort_events ()
3582 if (!_sort_needed) {
3586 EventNoteTimeEarlyFirstComparator cmp;
3589 _sort_needed = false;
3593 MidiRegionView::goto_next_note (bool add_to_selection)
3595 bool use_next = false;
3597 if (_events.back()->selected()) {
3601 time_sort_events ();
3603 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3604 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3606 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3607 if ((*i)->selected()) {
3610 } else if (use_next) {
3611 if (channel_mask & (1 << (*i)->note()->channel())) {
3612 if (!add_to_selection) {
3615 note_selected (*i, true, false);
3622 /* use the first one */
3624 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3625 unique_select (_events.front());
3630 MidiRegionView::goto_previous_note (bool add_to_selection)
3632 bool use_next = false;
3634 if (_events.front()->selected()) {
3638 time_sort_events ();
3640 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3641 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3643 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3644 if ((*i)->selected()) {
3647 } else if (use_next) {
3648 if (channel_mask & (1 << (*i)->note()->channel())) {
3649 if (!add_to_selection) {
3652 note_selected (*i, true, false);
3659 /* use the last one */
3661 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3662 unique_select (*(_events.rbegin()));
3667 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3669 bool had_selected = false;
3671 time_sort_events ();
3673 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3674 if ((*i)->selected()) {
3675 selected.insert ((*i)->note());
3676 had_selected = true;
3680 if (allow_all_if_none_selected && !had_selected) {
3681 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3682 selected.insert ((*i)->note());
3688 MidiRegionView::update_ghost_note (double x, double y)
3690 x = std::max(0.0, x);
3692 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3697 _note_group->canvas_to_item (x, y);
3699 PublicEditor& editor = trackview.editor ();
3701 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3702 framecnt_t grid_frames;
3703 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3705 /* calculate time in beats relative to start of source */
3706 const Evoral::Beats length = get_grid_beats(unsnapped_frame);
3707 const Evoral::Beats time = std::max(
3709 absolute_frames_to_source_beats (f + _region->position ()));
3711 _ghost_note->note()->set_time (time);
3712 _ghost_note->note()->set_length (length);
3713 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3714 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3715 _ghost_note->note()->set_velocity (get_velocity_for_add (time));
3717 /* the ghost note does not appear in ghost regions, so pass false in here */
3718 update_note (_ghost_note, false);
3720 show_verbose_cursor (_ghost_note->note ());
3724 MidiRegionView::create_ghost_note (double x, double y)
3726 remove_ghost_note ();
3728 boost::shared_ptr<NoteType> g (new NoteType);
3729 if (midi_view()->note_mode() == Sustained) {
3730 _ghost_note = new Note (*this, _note_group, g);
3732 _ghost_note = new Hit (*this, _note_group, 10, g);
3734 _ghost_note->set_ignore_events (true);
3735 _ghost_note->set_outline_color (0x000000aa);
3736 update_ghost_note (x, y);
3737 _ghost_note->show ();
3739 show_verbose_cursor (_ghost_note->note ());
3743 MidiRegionView::remove_ghost_note ()
3750 MidiRegionView::hide_verbose_cursor ()
3752 trackview.editor().verbose_cursor()->hide ();
3753 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3755 mtv->set_note_highlight (NO_MIDI_NOTE);
3760 MidiRegionView::snap_changed ()
3766 create_ghost_note (_last_ghost_x, _last_ghost_y);
3770 MidiRegionView::drop_down_keys ()
3772 _mouse_state = None;
3776 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3778 /* XXX: This is dead code. What was it for? */
3780 double note = midi_stream_view()->y_to_note(y);
3782 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3784 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3786 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3787 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3788 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3789 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3794 bool add_mrv_selection = false;
3796 if (_selection.empty()) {
3797 add_mrv_selection = true;
3800 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3801 if (_selection.insert (*i).second) {
3802 (*i)->set_selected (true);
3806 if (add_mrv_selection) {
3807 PublicEditor& editor (trackview.editor());
3808 editor.get_selection().add (this);
3813 MidiRegionView::color_handler ()
3815 RegionView::color_handler ();
3817 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3818 (*i)->set_selected ((*i)->selected()); // will change color
3821 /* XXX probably more to do here */
3825 MidiRegionView::enable_display (bool yn)
3827 RegionView::enable_display (yn);
3834 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3836 if (_step_edit_cursor == 0) {
3837 ArdourCanvas::Item* const group = get_canvas_group();
3839 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3840 _step_edit_cursor->set_y0 (0);
3841 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3842 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3843 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3846 move_step_edit_cursor (pos);
3847 _step_edit_cursor->show ();
3851 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3853 _step_edit_cursor_position = pos;
3855 if (_step_edit_cursor) {
3856 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3857 _step_edit_cursor->set_x0 (pixel);
3858 set_step_edit_cursor_width (_step_edit_cursor_width);
3863 MidiRegionView::hide_step_edit_cursor ()
3865 if (_step_edit_cursor) {
3866 _step_edit_cursor->hide ();
3871 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3873 _step_edit_cursor_width = beats;
3875 if (_step_edit_cursor) {
3876 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
3880 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3881 * @param w Source that the data will end up in.
3884 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3886 if (!_active_notes) {
3887 /* we aren't actively being recorded to */
3891 boost::shared_ptr<MidiSource> src = w.lock ();
3892 if (!src || src != midi_region()->midi_source()) {
3893 /* recorded data was not destined for our source */
3897 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3899 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3901 framepos_t back = max_framepos;
3903 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3904 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3906 if (ev.is_channel_event()) {
3907 if (get_channel_mode() == FilterChannels) {
3908 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3914 /* convert from session frames to source beats */
3915 Evoral::Beats const time_beats = _source_relative_time_converter.from(
3916 ev.time() - src->timeline_position() + _region->start());
3918 if (ev.type() == MIDI_CMD_NOTE_ON) {
3919 boost::shared_ptr<NoteType> note (
3920 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
3922 add_note (note, true);
3924 /* fix up our note range */
3925 if (ev.note() < _current_range_min) {
3926 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3927 } else if (ev.note() > _current_range_max) {
3928 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3931 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3932 resolve_note (ev.note (), time_beats);
3938 midi_stream_view()->check_record_layers (region(), back);
3942 MidiRegionView::trim_front_starting ()
3944 /* Reparent the note group to the region view's parent, so that it doesn't change
3945 when the region view is trimmed.
3947 _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3948 _temporary_note_group->move (group->position ());
3949 _note_group->reparent (_temporary_note_group);
3953 MidiRegionView::trim_front_ending ()
3955 _note_group->reparent (group);
3956 delete _temporary_note_group;
3957 _temporary_note_group = 0;
3959 if (_region->start() < 0) {
3960 /* Trim drag made start time -ve; fix this */
3961 midi_region()->fix_negative_start ();
3966 MidiRegionView::edit_patch_change (PatchChange* pc)
3968 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3970 int response = d.run();
3973 case Gtk::RESPONSE_ACCEPT:
3975 case Gtk::RESPONSE_REJECT:
3976 delete_patch_change (pc);
3982 change_patch_change (pc->patch(), d.patch ());
3986 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
3989 // sysyex object doesn't have a pointer to a sysex event
3990 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3991 // c->remove (sysex->sysex());
3992 // _model->apply_command (*trackview.session(), c);
3994 //_sys_exes.clear ();
3995 // display_sysexes();
3999 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4001 using namespace MIDI::Name;
4005 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4007 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4009 MIDI::Name::PatchPrimaryKey patch_key;
4010 get_patch_key_at(n->time(), n->channel(), patch_key);
4011 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4014 patch_key.program(),
4017 mtv->set_note_highlight (n->note());
4021 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4023 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
4024 (int) n->channel() + 1,
4025 (int) n->velocity());
4027 show_verbose_cursor(buf, 10, 20);
4031 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4033 trackview.editor().verbose_cursor()->set (text);
4034 trackview.editor().verbose_cursor()->show ();
4035 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4039 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4041 if (_model->notes().empty()) {
4042 return 0x40; // No notes, use default
4045 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4046 if (m == _model->notes().begin()) {
4047 // Before the start, use the velocity of the first note
4048 return (*m)->velocity();
4049 } else if (m == _model->notes().end()) {
4050 // Past the end, use the velocity of the last note
4052 return (*m)->velocity();
4055 // Interpolate velocity of surrounding notes
4056 MidiModel::Notes::const_iterator n = m;
4059 const double frac = ((time - (*n)->time()).to_double() /
4060 ((*m)->time() - (*n)->time()).to_double());
4062 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4065 /** @param p A session framepos.
4066 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
4067 * @return p snapped to the grid subdivision underneath it.
4070 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
4072 PublicEditor& editor = trackview.editor ();
4074 const Evoral::Beats grid_beats = get_grid_beats(p);
4076 grid_frames = region_beats_to_region_frames (grid_beats);
4078 /* Hack so that we always snap to the note that we are over, instead of snapping
4079 to the next one if we're more than halfway through the one we're over.
4081 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
4082 p -= grid_frames / 2;
4085 return snap_frame_to_frame (p);
4088 /** Called when the selection has been cleared in any MidiRegionView.
4089 * @param rv MidiRegionView that the selection was cleared in.
4092 MidiRegionView::selection_cleared (MidiRegionView* rv)
4098 /* Clear our selection in sympathy; but don't signal the fact */
4099 clear_selection (false);
4103 MidiRegionView::get_channel_mode () const
4105 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4106 return rtav->midi_track()->get_playback_channel_mode();
4110 MidiRegionView::get_selected_channels () const
4112 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4113 return rtav->midi_track()->get_playback_channel_mask();
4118 MidiRegionView::get_grid_beats(framepos_t pos) const
4120 PublicEditor& editor = trackview.editor();
4121 bool success = false;
4122 Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos);
4124 beats = Evoral::Beats(1);