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_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_track.h"
39 #include "ardour/operations.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIEvent.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
47 #include "canvas/debug.h"
48 #include "canvas/text.h"
50 #include "automation_region_view.h"
51 #include "automation_time_axis.h"
52 #include "control_point.h"
55 #include "editor_drag.h"
56 #include "ghostregion.h"
57 #include "gui_thread.h"
58 #include "item_counts.h"
60 #include "midi_channel_dialog.h"
61 #include "midi_cut_buffer.h"
62 #include "midi_list_editor.h"
63 #include "midi_region_view.h"
64 #include "midi_streamview.h"
65 #include "midi_time_axis.h"
66 #include "midi_util.h"
67 #include "midi_velocity_dialog.h"
68 #include "mouse_cursors.h"
69 #include "note_player.h"
70 #include "paste_context.h"
71 #include "public_editor.h"
72 #include "route_time_axis.h"
73 #include "rgb_macros.h"
74 #include "selection.h"
75 #include "streamview.h"
76 #include "patch_change_dialog.h"
77 #include "verbose_cursor.h"
78 #include "ardour_ui.h"
81 #include "patch_change.h"
86 using namespace ARDOUR;
88 using namespace Editing;
90 using Gtkmm2ext::Keyboard;
92 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
94 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
96 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
97 RouteTimeAxisView& tv,
98 boost::shared_ptr<MidiRegion> r,
100 uint32_t basic_color)
101 : RegionView (parent, tv, r, spu, basic_color)
102 , _current_range_min(0)
103 , _current_range_max(0)
104 , _region_relative_time_converter(r->session().tempo_map(), r->position())
105 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
107 , _note_group (new ArdourCanvas::Container (group))
108 , _note_diff_command (0)
110 , _step_edit_cursor (0)
111 , _step_edit_cursor_width (1.0)
112 , _step_edit_cursor_position (0.0)
113 , _channel_selection_scoped_note (0)
114 , _temporary_note_group (0)
117 , _sort_needed (true)
118 , _optimization_iterator (_events.end())
120 , _no_sound_notes (false)
121 , _last_display_zoom (0)
124 , _grabbed_keyboard (false)
127 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
128 _note_group->raise_to_top();
129 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
131 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
132 connect_to_diskstream ();
134 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
136 PublicEditor& editor (trackview.editor());
137 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
140 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
141 RouteTimeAxisView& tv,
142 boost::shared_ptr<MidiRegion> r,
144 uint32_t basic_color,
146 TimeAxisViewItem::Visibility visibility)
147 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
148 , _current_range_min(0)
149 , _current_range_max(0)
150 , _region_relative_time_converter(r->session().tempo_map(), r->position())
151 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
153 , _note_group (new ArdourCanvas::Container (group))
154 , _note_diff_command (0)
156 , _step_edit_cursor (0)
157 , _step_edit_cursor_width (1.0)
158 , _step_edit_cursor_position (0.0)
159 , _channel_selection_scoped_note (0)
160 , _temporary_note_group (0)
163 , _sort_needed (true)
164 , _optimization_iterator (_events.end())
166 , _no_sound_notes (false)
167 , _last_display_zoom (0)
170 , _grabbed_keyboard (false)
173 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
174 _note_group->raise_to_top();
176 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
178 connect_to_diskstream ();
180 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
182 PublicEditor& editor (trackview.editor());
183 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
187 MidiRegionView::parameter_changed (std::string const & p)
189 if (p == "display-first-midi-bank-as-zero") {
190 if (_enable_display) {
196 MidiRegionView::MidiRegionView (const MidiRegionView& other)
197 : sigc::trackable(other)
199 , _current_range_min(0)
200 , _current_range_max(0)
201 , _region_relative_time_converter(other.region_relative_time_converter())
202 , _source_relative_time_converter(other.source_relative_time_converter())
204 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
205 , _note_diff_command (0)
207 , _step_edit_cursor (0)
208 , _step_edit_cursor_width (1.0)
209 , _step_edit_cursor_position (0.0)
210 , _channel_selection_scoped_note (0)
211 , _temporary_note_group (0)
214 , _sort_needed (true)
215 , _optimization_iterator (_events.end())
217 , _no_sound_notes (false)
218 , _last_display_zoom (0)
221 , _grabbed_keyboard (false)
227 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
228 : RegionView (other, boost::shared_ptr<Region> (region))
229 , _current_range_min(0)
230 , _current_range_max(0)
231 , _region_relative_time_converter(other.region_relative_time_converter())
232 , _source_relative_time_converter(other.source_relative_time_converter())
234 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
235 , _note_diff_command (0)
237 , _step_edit_cursor (0)
238 , _step_edit_cursor_width (1.0)
239 , _step_edit_cursor_position (0.0)
240 , _channel_selection_scoped_note (0)
241 , _temporary_note_group (0)
244 , _sort_needed (true)
245 , _optimization_iterator (_events.end())
247 , _no_sound_notes (false)
248 , _last_display_zoom (0)
251 , _grabbed_keyboard (false)
258 MidiRegionView::init (bool wfd)
260 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
262 NoteBase::NoteBaseDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
263 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
267 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
268 midi_region()->midi_source(0)->load_model(lm);
271 _model = midi_region()->midi_source(0)->model();
272 _enable_display = false;
273 fill_color_name = "midi frame base";
275 RegionView::init (false);
277 set_height (trackview.current_height());
280 region_sync_changed ();
281 region_resized (ARDOUR::bounds_change);
286 _enable_display = true;
289 display_model (_model);
293 reset_width_dependent_items (_pixel_width);
295 group->raise_to_top();
297 midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
298 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
301 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
302 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
304 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
305 boost::bind (&MidiRegionView::snap_changed, this),
308 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
309 boost::bind (&MidiRegionView::mouse_mode_changed, this),
312 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
313 connect_to_diskstream ();
315 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
317 PublicEditor& editor (trackview.editor());
318 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
322 MidiRegionView::instrument_info () const
324 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
325 return route_ui->route()->instrument_info();
328 const boost::shared_ptr<ARDOUR::MidiRegion>
329 MidiRegionView::midi_region() const
331 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
335 MidiRegionView::connect_to_diskstream ()
337 midi_view()->midi_track()->DataRecorded.connect(
338 *this, invalidator(*this),
339 boost::bind (&MidiRegionView::data_recorded, this, _1),
344 MidiRegionView::canvas_group_event(GdkEvent* ev)
346 if (in_destructor || _recregion) {
350 if (!trackview.editor().internal_editing()) {
351 // not in internal edit mode, so just act like a normal region
352 return RegionView::canvas_group_event (ev);
358 case GDK_ENTER_NOTIFY:
359 _last_event_x = ev->crossing.x;
360 _last_event_y = ev->crossing.y;
361 enter_notify(&ev->crossing);
362 // set entered_regionview (among other things)
363 return RegionView::canvas_group_event (ev);
365 case GDK_LEAVE_NOTIFY:
366 _last_event_x = ev->crossing.x;
367 _last_event_y = ev->crossing.y;
368 leave_notify(&ev->crossing);
369 // reset entered_regionview (among other things)
370 return RegionView::canvas_group_event (ev);
373 if (scroll (&ev->scroll)) {
379 return key_press (&ev->key);
381 case GDK_KEY_RELEASE:
382 return key_release (&ev->key);
384 case GDK_BUTTON_PRESS:
385 return button_press (&ev->button);
387 case GDK_BUTTON_RELEASE:
388 r = button_release (&ev->button);
389 _note_player.reset();
392 case GDK_MOTION_NOTIFY:
393 _last_event_x = ev->motion.x;
394 _last_event_y = ev->motion.y;
395 return motion (&ev->motion);
401 return RegionView::canvas_group_event (ev);
405 MidiRegionView::enter_notify (GdkEventCrossing* ev)
414 MidiRegionView::leave_notify (GdkEventCrossing*)
423 MidiRegionView::mouse_mode_changed ()
425 // Adjust frame colour (become more transparent for internal tools)
429 if (trackview.editor().internal_editing()) {
430 // Switched in to internal editing mode while entered
433 // Switched out of internal editing mode while entered
440 MidiRegionView::enter_internal()
442 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
443 // Show ghost note under pencil
444 create_ghost_note(_last_event_x, _last_event_y);
447 if (!_selection.empty()) {
448 // Grab keyboard for moving selected notes with arrow keys
449 Keyboard::magic_widget_grab_focus();
450 _grabbed_keyboard = true;
453 // Lower frame handles below notes so they don't steal events
454 if (frame_handle_start) {
455 frame_handle_start->lower_to_bottom();
457 if (frame_handle_end) {
458 frame_handle_end->lower_to_bottom();
463 MidiRegionView::leave_internal()
465 trackview.editor().verbose_cursor()->hide ();
466 remove_ghost_note ();
468 if (_grabbed_keyboard) {
469 Keyboard::magic_widget_drop_focus();
470 _grabbed_keyboard = false;
473 // Raise frame handles above notes so they catch events
474 if (frame_handle_start) {
475 frame_handle_start->raise_to_top();
477 if (frame_handle_end) {
478 frame_handle_end->raise_to_top();
483 MidiRegionView::button_press (GdkEventButton* ev)
485 if (ev->button != 1) {
489 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
490 MouseMode m = editor->current_mouse_mode();
492 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
493 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
496 if (_mouse_state != SelectTouchDragging) {
498 _pressed_button = ev->button;
499 _mouse_state = Pressed;
504 _pressed_button = ev->button;
510 MidiRegionView::button_release (GdkEventButton* ev)
512 double event_x, event_y;
514 if (ev->button != 1) {
521 group->canvas_to_item (event_x, event_y);
524 PublicEditor& editor = trackview.editor ();
526 _press_cursor_ctx.reset();
528 switch (_mouse_state) {
529 case Pressed: // Clicked
531 switch (editor.current_mouse_mode()) {
533 /* no motion occured - simple click */
542 if (Keyboard::is_insert_note_event(ev)) {
544 double event_x, event_y;
548 group->canvas_to_item (event_x, event_y);
550 Evoral::MusicalTime beats = get_grid_beats(editor.pixel_to_sample(event_x));
552 /* Shorten the length by 1 tick so that we can add a new note at the next
553 grid snap without it overlapping this one.
555 beats -= Evoral::MusicalTime::tick();
557 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
564 Evoral::MusicalTime beats = get_grid_beats(editor.pixel_to_sample(event_x));
566 /* Shorten the length by 1 tick so that we can add a new note at the next
567 grid snap without it overlapping this one.
569 beats -= Evoral::MusicalTime::tick();
571 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
582 case SelectRectDragging:
584 editor.drags()->end_grab ((GdkEvent *) ev);
586 create_ghost_note (ev->x, ev->y);
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 editor.verbose_cursor()->hide ();
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 editor.verbose_cursor()->hide ();
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)) {
647 _mouse_state = SelectRectDragging;
649 } else if (m == MouseRange) {
650 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
651 _mouse_state = SelectVerticalDragging;
658 case SelectRectDragging:
659 case SelectVerticalDragging:
661 editor.drags()->motion_handler ((GdkEvent *) ev, false);
664 case SelectTouchDragging:
672 /* we may be dragging some non-note object (eg. patch-change, sysex)
675 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
680 MidiRegionView::scroll (GdkEventScroll* ev)
682 if (_selection.empty()) {
686 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
687 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
688 it still works for zoom.
693 trackview.editor().verbose_cursor()->hide ();
695 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
696 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
698 if (ev->direction == GDK_SCROLL_UP) {
699 change_velocities (true, fine, false, together);
700 } else if (ev->direction == GDK_SCROLL_DOWN) {
701 change_velocities (false, fine, false, together);
703 /* left, right: we don't use them */
711 MidiRegionView::key_press (GdkEventKey* ev)
713 /* since GTK bindings are generally activated on press, and since
714 detectable auto-repeat is the name of the game and only sends
715 repeated presses, carry out key actions at key press, not release.
718 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
720 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
721 _mouse_state = SelectTouchDragging;
724 } else if (ev->keyval == GDK_Escape && unmodified) {
728 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
730 bool start = (ev->keyval == GDK_comma);
731 bool end = (ev->keyval == GDK_period);
732 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
733 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
735 change_note_lengths (fine, shorter, Evoral::MusicalTime(), start, end);
739 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
741 if (_selection.empty()) {
748 } else if (ev->keyval == GDK_Tab) {
750 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
751 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
753 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
757 } else if (ev->keyval == GDK_ISO_Left_Tab) {
759 /* Shift-TAB generates ISO Left Tab, for some reason */
761 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
762 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
764 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
770 } else if (ev->keyval == GDK_Up) {
772 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
773 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
774 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
776 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
777 change_velocities (true, fine, allow_smush, together);
779 transpose (true, fine, allow_smush);
783 } else if (ev->keyval == GDK_Down) {
785 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
786 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
787 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
789 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
790 change_velocities (false, fine, allow_smush, together);
792 transpose (false, fine, allow_smush);
796 } else if (ev->keyval == GDK_Left) {
798 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
799 nudge_notes (false, fine);
802 } else if (ev->keyval == GDK_Right) {
804 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
805 nudge_notes (true, fine);
808 } else if (ev->keyval == GDK_c && unmodified) {
812 } else if (ev->keyval == GDK_v && unmodified) {
821 MidiRegionView::key_release (GdkEventKey* ev)
823 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
831 MidiRegionView::channel_edit ()
833 if (_selection.empty()) {
837 /* pick a note somewhat at random (since Selection is a set<>) to
838 * provide the "current" channel for the dialog.
841 uint8_t current_channel = (*_selection.begin())->note()->channel ();
842 MidiChannelDialog channel_dialog (current_channel);
843 int ret = channel_dialog.run ();
846 case Gtk::RESPONSE_OK:
852 uint8_t new_channel = channel_dialog.active_channel ();
854 start_note_diff_command (_("channel edit"));
856 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
857 Selection::iterator next = i;
859 change_note_channel (*i, new_channel);
867 MidiRegionView::velocity_edit ()
869 if (_selection.empty()) {
873 /* pick a note somewhat at random (since Selection is a set<>) to
874 * provide the "current" velocity for the dialog.
877 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
878 MidiVelocityDialog velocity_dialog (current_velocity);
879 int ret = velocity_dialog.run ();
882 case Gtk::RESPONSE_OK:
888 uint8_t new_velocity = velocity_dialog.velocity ();
890 start_note_diff_command (_("velocity edit"));
892 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
893 Selection::iterator next = i;
895 change_note_velocity (*i, new_velocity, false);
903 MidiRegionView::show_list_editor ()
906 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
908 _list_editor->present ();
911 /** Add a note to the model, and the view, at a canvas (click) coordinate.
912 * \param t time in frames relative to the position of the region
913 * \param y vertical position in pixels
914 * \param length duration of the note in beats
915 * \param snap_t true to snap t to the grid, otherwise false.
918 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::MusicalTime length, bool snap_t)
920 if (length < 2 * DBL_EPSILON) {
924 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
925 MidiStreamView* const view = mtv->midi_view();
927 // Start of note in frames relative to region start
929 framecnt_t grid_frames;
930 t = snap_frame_to_grid_underneath (t, grid_frames);
933 const MidiModel::TimeType beat_time = region_frames_to_region_beats(
934 t + _region->start());
936 const double note = view->y_to_note(y);
937 const uint8_t chan = mtv->get_channel_for_add();
938 const uint8_t velocity = get_velocity_for_add(beat_time);
940 const boost::shared_ptr<NoteType> new_note(
941 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
943 if (_model->contains (new_note)) {
947 view->update_note_range(new_note->note());
949 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
951 _model->apply_command(*trackview.session(), cmd);
953 play_midi_note (new_note);
957 MidiRegionView::clear_events (bool with_selection_signal)
959 clear_selection (with_selection_signal);
962 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
963 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
968 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
973 _patch_changes.clear();
975 _optimization_iterator = _events.end();
979 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
983 content_connection.disconnect ();
984 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
988 if (_enable_display) {
994 MidiRegionView::start_note_diff_command (string name)
996 if (!_note_diff_command) {
997 _note_diff_command = _model->new_note_diff_command (name);
1002 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1004 if (_note_diff_command) {
1005 _note_diff_command->add (note);
1008 _marked_for_selection.insert(note);
1010 if (show_velocity) {
1011 _marked_for_velocity.insert(note);
1016 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1018 if (_note_diff_command && ev->note()) {
1019 _note_diff_command->remove(ev->note());
1024 MidiRegionView::note_diff_add_change (NoteBase* ev,
1025 MidiModel::NoteDiffCommand::Property property,
1028 if (_note_diff_command) {
1029 _note_diff_command->change (ev->note(), property, val);
1034 MidiRegionView::note_diff_add_change (NoteBase* ev,
1035 MidiModel::NoteDiffCommand::Property property,
1036 Evoral::MusicalTime val)
1038 if (_note_diff_command) {
1039 _note_diff_command->change (ev->note(), property, val);
1044 MidiRegionView::apply_diff (bool as_subcommand)
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 if (as_subcommand) {
1060 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1062 _model->apply_command (*trackview.session(), _note_diff_command);
1065 _note_diff_command = 0;
1066 midi_view()->midi_track()->playlist_modified();
1068 if (add_or_remove) {
1069 _marked_for_selection.clear();
1072 _marked_for_velocity.clear();
1076 MidiRegionView::abort_command()
1078 delete _note_diff_command;
1079 _note_diff_command = 0;
1084 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1086 if (_optimization_iterator != _events.end()) {
1087 ++_optimization_iterator;
1090 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1091 return *_optimization_iterator;
1094 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1095 if ((*_optimization_iterator)->note() == note) {
1096 return *_optimization_iterator;
1104 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1106 MidiModel::Notes notes;
1107 _model->get_notes (notes, op, val, chan_mask);
1109 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1110 NoteBase* cne = find_canvas_note (*n);
1118 MidiRegionView::redisplay_model()
1120 if (_active_notes) {
1121 // Currently recording
1122 const framecnt_t zoom = trackview.editor().get_current_zoom();
1123 if (zoom != _last_display_zoom) {
1124 /* Update resolved canvas notes to reflect changes in zoom without
1125 touching model. Leave active notes (with length 0) alone since
1126 they are being extended. */
1127 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1128 if ((*i)->note()->length() > 0) {
1132 _last_display_zoom = zoom;
1141 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1142 (*i)->invalidate ();
1145 MidiModel::ReadLock lock(_model->read_lock());
1147 MidiModel::Notes& notes (_model->notes());
1148 _optimization_iterator = _events.begin();
1150 bool empty_when_starting = _events.empty();
1152 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1154 boost::shared_ptr<NoteType> note (*n);
1158 if (note_in_region_range (note, visible)) {
1160 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1173 add_note (note, visible);
1178 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1186 /* remove note items that are no longer valid */
1188 if (!empty_when_starting) {
1189 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1190 if (!(*i)->valid ()) {
1192 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1193 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1195 gr->remove_note (*i);
1200 i = _events.erase (i);
1208 _patch_changes.clear();
1212 display_patch_changes ();
1214 _marked_for_selection.clear ();
1215 _marked_for_velocity.clear ();
1217 /* we may have caused _events to contain things out of order (e.g. if a note
1218 moved earlier or later). we don't generally need them in time order, but
1219 make a note that a sort is required for those cases that require it.
1222 _sort_needed = true;
1226 MidiRegionView::display_patch_changes ()
1228 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1229 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1231 for (uint8_t i = 0; i < 16; ++i) {
1232 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1236 /** @param active_channel true to display patch changes fully, false to display
1237 * them `greyed-out' (as on an inactive channel)
1240 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1242 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1244 if ((*i)->channel() != channel) {
1248 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1249 add_canvas_patch_change (*i, patch_name, active_channel);
1254 MidiRegionView::display_sysexes()
1256 bool have_periodic_system_messages = false;
1257 bool display_periodic_messages = true;
1259 if (!ARDOUR_UI::config()->get_never_display_periodic_midi()) {
1261 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1262 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1263 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1266 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1267 have_periodic_system_messages = true;
1273 if (have_periodic_system_messages) {
1274 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1276 /* get an approximate value for the number of samples per video frame */
1278 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1280 /* if we are zoomed out beyond than the cutoff (i.e. more
1281 * frames per pixel than frames per 4 video frames), don't
1282 * show periodic sysex messages.
1285 if (zoom > (video_frame*4)) {
1286 display_periodic_messages = false;
1290 display_periodic_messages = false;
1293 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1295 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1296 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1298 Evoral::MusicalTime time = (*i)->time();
1301 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1302 if (!display_periodic_messages) {
1310 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1311 str << int((*i)->buffer()[b]);
1312 if (b != (*i)->size() -1) {
1316 string text = str.str();
1318 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1320 double height = midi_stream_view()->contents_height();
1322 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1323 // SysEx canvas object!!!
1325 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1326 new SysEx (*this, _note_group, text, height, x, 1.0));
1328 // Show unless message is beyond the region bounds
1329 if (time - _region->start() >= _region->length() || time < _region->start()) {
1335 _sys_exes.push_back(sysex);
1339 MidiRegionView::~MidiRegionView ()
1341 in_destructor = true;
1343 trackview.editor().verbose_cursor()->hide ();
1345 note_delete_connection.disconnect ();
1347 delete _list_editor;
1349 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1351 if (_active_notes) {
1355 _selection_cleared_connection.disconnect ();
1358 clear_events (false);
1361 delete _note_diff_command;
1362 delete _step_edit_cursor;
1363 delete _temporary_note_group;
1367 MidiRegionView::region_resized (const PropertyChange& what_changed)
1369 RegionView::region_resized(what_changed);
1371 if (what_changed.contains (ARDOUR::Properties::position)) {
1372 _region_relative_time_converter.set_origin_b(_region->position());
1373 set_duration(_region->length(), 0);
1374 if (_enable_display) {
1379 if (what_changed.contains (ARDOUR::Properties::start) ||
1380 what_changed.contains (ARDOUR::Properties::position)) {
1381 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1386 MidiRegionView::reset_width_dependent_items (double pixel_width)
1388 RegionView::reset_width_dependent_items(pixel_width);
1390 if (_enable_display) {
1394 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1395 if ((*x)->canvas_item()->width() >= _pixel_width) {
1402 move_step_edit_cursor (_step_edit_cursor_position);
1403 set_step_edit_cursor_width (_step_edit_cursor_width);
1407 MidiRegionView::set_height (double height)
1409 double old_height = _height;
1410 RegionView::set_height(height);
1412 apply_note_range (midi_stream_view()->lowest_note(),
1413 midi_stream_view()->highest_note(),
1414 height != old_height);
1417 name_text->raise_to_top();
1420 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1421 (*x)->set_height (midi_stream_view()->contents_height());
1424 if (_step_edit_cursor) {
1425 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1430 /** Apply the current note range from the stream view
1431 * by repositioning/hiding notes as necessary
1434 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1436 if (!_enable_display) {
1440 if (!force && _current_range_min == min && _current_range_max == max) {
1444 _current_range_min = min;
1445 _current_range_max = max;
1447 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1448 NoteBase* event = *i;
1449 boost::shared_ptr<NoteType> note (event->note());
1451 if (note->note() < _current_range_min ||
1452 note->note() > _current_range_max) {
1458 if (Note* cnote = dynamic_cast<Note*>(event)) {
1460 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1461 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1466 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1473 MidiRegionView::add_ghost (TimeAxisView& tv)
1475 double unit_position = _region->position () / samples_per_pixel;
1476 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1477 MidiGhostRegion* ghost;
1479 if (mtv && mtv->midi_view()) {
1480 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1481 to allow having midi notes on top of note lines and waveforms.
1483 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1485 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1488 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1489 ghost->add_note(*i);
1492 ghost->set_height ();
1493 ghost->set_duration (_region->length() / samples_per_pixel);
1494 ghosts.push_back (ghost);
1496 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1502 /** Begin tracking note state for successive calls to add_event
1505 MidiRegionView::begin_write()
1507 if (_active_notes) {
1508 delete[] _active_notes;
1510 _active_notes = new Note*[128];
1511 for (unsigned i = 0; i < 128; ++i) {
1512 _active_notes[i] = 0;
1517 /** Destroy note state for add_event
1520 MidiRegionView::end_write()
1522 delete[] _active_notes;
1524 _marked_for_selection.clear();
1525 _marked_for_velocity.clear();
1529 /** Resolve an active MIDI note (while recording).
1532 MidiRegionView::resolve_note(uint8_t note, Evoral::MusicalTime end_time)
1534 if (midi_view()->note_mode() != Sustained) {
1538 if (_active_notes && _active_notes[note]) {
1539 /* Set note length so update_note() works. Note this is a local note
1540 for recording, not from a model, so we can safely mess with it. */
1541 _active_notes[note]->note()->set_length(
1542 end_time - _active_notes[note]->note()->time());
1544 /* End time is relative to the region being recorded. */
1545 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1547 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1548 _active_notes[note]->set_outline_all ();
1549 _active_notes[note] = 0;
1554 /** Extend active notes to rightmost edge of region (if length is changed)
1557 MidiRegionView::extend_active_notes()
1559 if (!_active_notes) {
1563 for (unsigned i = 0; i < 128; ++i) {
1564 if (_active_notes[i]) {
1565 _active_notes[i]->set_x1(
1566 trackview.editor().sample_to_pixel(_region->length()));
1572 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1574 if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1578 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1580 if (!route_ui || !route_ui->midi_track()) {
1584 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1588 /* NotePlayer deletes itself */
1592 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1594 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1595 start_playing_midi_chord(notes);
1599 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1601 if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1605 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1607 if (!route_ui || !route_ui->midi_track()) {
1611 _note_player = boost::shared_ptr<NotePlayer>(new NotePlayer(route_ui->midi_track()));
1613 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1614 _note_player->add (*n);
1617 _note_player->on ();
1622 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1624 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1625 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1627 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1628 (note->note() <= midi_stream_view()->highest_note());
1634 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1638 if ((sus = dynamic_cast<Note*>(note))) {
1639 update_sustained(sus, update_ghost_regions);
1640 } else if ((hit = dynamic_cast<Hit*>(note))) {
1641 update_hit(hit, update_ghost_regions);
1645 /** Update a canvas note's size from its model note.
1646 * @param ev Canvas note to update.
1647 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1650 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1652 boost::shared_ptr<NoteType> note = ev->note();
1653 const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1654 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1659 /* trim note display to not overlap the end of its region */
1661 if (note->length() > 0) {
1662 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1663 ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames));
1665 ev->set_x1 (trackview.editor().sample_to_pixel (_region->length()));
1668 ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
1670 if (!note->length()) {
1671 if (_active_notes && note->note() < 128) {
1672 Note* const old_rect = _active_notes[note->note()];
1673 if (old_rect && old_rect != ev) {
1674 /* There is an active note on this key, but it's not this note,
1675 so we have a stuck note. Finish the old rectangle here. */
1676 old_rect->set_x1 (x);
1677 old_rect->set_outline_all ();
1679 _active_notes[note->note()] = ev;
1681 /* outline all but right edge */
1682 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1683 ArdourCanvas::Rectangle::TOP|
1684 ArdourCanvas::Rectangle::LEFT|
1685 ArdourCanvas::Rectangle::BOTTOM));
1687 /* outline all edges */
1688 ev->set_outline_all ();
1691 // Update color in case velocity has changed
1692 ev->set_fill_color(ev->base_color());
1693 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1695 if (update_ghost_regions) {
1696 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1697 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1699 gr->update_note (ev);
1706 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1708 boost::shared_ptr<NoteType> note = ev->note();
1710 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1711 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1712 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1713 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1715 ev->set_position (ArdourCanvas::Duple (x, y));
1716 ev->set_height (diamond_size);
1718 // Update color in case velocity has changed
1719 ev->set_fill_color(ev->base_color());
1720 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1722 if (update_ghost_regions) {
1723 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1724 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1726 gr->update_note (ev);
1732 /** Add a MIDI note to the view (with length).
1734 * If in sustained mode, notes with length 0 will be considered active
1735 * notes, and resolve_note should be called when the corresponding note off
1736 * event arrives, to properly display the note.
1739 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1741 NoteBase* event = 0;
1743 if (midi_view()->note_mode() == Sustained) {
1745 Note* ev_rect = new Note (*this, _note_group, note);
1747 update_sustained (ev_rect);
1751 } else if (midi_view()->note_mode() == Percussive) {
1753 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1755 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1757 update_hit (ev_diamond);
1766 MidiGhostRegion* gr;
1768 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1769 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1770 gr->add_note(event);
1774 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1775 note_selected(event, true);
1778 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1779 event->show_velocity();
1782 event->on_channel_selection_change (get_selected_channels());
1783 _events.push_back(event);
1792 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1793 MidiStreamView* const view = mtv->midi_view();
1795 view->update_note_range (note->note());
1799 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1800 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1802 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1804 /* potentially extend region to hold new note */
1806 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1807 framepos_t region_end = _region->last_frame();
1809 if (end_frame > region_end) {
1810 _region->set_length (end_frame - _region->position());
1813 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1814 MidiStreamView* const view = mtv->midi_view();
1816 view->update_note_range(new_note->note());
1818 _marked_for_selection.clear ();
1821 start_note_diff_command (_("step add"));
1822 note_diff_add_note (new_note, true, false);
1825 // last_step_edit_note = new_note;
1829 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1831 change_note_lengths (false, false, beats, false, true);
1834 /** Add a new patch change flag to the canvas.
1835 * @param patch the patch change to add
1836 * @param the text to display in the flag
1837 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1840 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1842 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1843 const double x = trackview.editor().sample_to_pixel (region_frames);
1845 double const height = midi_stream_view()->contents_height();
1847 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1848 // so we need to do something more sophisticated to keep its color
1849 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1852 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1853 new PatchChange(*this, group,
1860 if (patch_change->item().width() < _pixel_width) {
1861 // Show unless patch change is beyond the region bounds
1862 if (region_frames < 0 || region_frames >= _region->length()) {
1863 patch_change->hide();
1865 patch_change->show();
1868 patch_change->hide ();
1871 _patch_changes.push_back (patch_change);
1874 MIDI::Name::PatchPrimaryKey
1875 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1877 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1880 /// Return true iff @p pc applies to the given time on the given channel.
1882 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::MusicalTime time, uint8_t channel)
1884 return pc->time() <= time && pc->channel() == channel;
1888 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1890 // The earliest event not before time
1891 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1893 // Go backwards until we find the latest PC for this channel, or the start
1894 while (i != _model->patch_changes().begin() &&
1895 (i == _model->patch_changes().end() ||
1896 !patch_applies(*i, time, channel))) {
1900 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1901 key.set_bank((*i)->bank());
1902 key.set_program((*i)->program ());
1910 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1912 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1914 if (pc.patch()->program() != new_patch.program()) {
1915 c->change_program (pc.patch (), new_patch.program());
1918 int const new_bank = new_patch.bank();
1919 if (pc.patch()->bank() != new_bank) {
1920 c->change_bank (pc.patch (), new_bank);
1923 _model->apply_command (*trackview.session(), c);
1925 _patch_changes.clear ();
1926 display_patch_changes ();
1930 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1932 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1934 if (old_change->time() != new_change.time()) {
1935 c->change_time (old_change, new_change.time());
1938 if (old_change->channel() != new_change.channel()) {
1939 c->change_channel (old_change, new_change.channel());
1942 if (old_change->program() != new_change.program()) {
1943 c->change_program (old_change, new_change.program());
1946 if (old_change->bank() != new_change.bank()) {
1947 c->change_bank (old_change, new_change.bank());
1950 _model->apply_command (*trackview.session(), c);
1952 _patch_changes.clear ();
1953 display_patch_changes ();
1956 /** Add a patch change to the region.
1957 * @param t Time in frames relative to region position
1958 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1959 * MidiTimeAxisView::get_channel_for_add())
1962 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1964 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1966 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1967 c->add (MidiModel::PatchChangePtr (
1968 new Evoral::PatchChange<Evoral::MusicalTime> (
1969 absolute_frames_to_source_beats (_region->position() + t),
1970 mtv->get_channel_for_add(), patch.program(), patch.bank()
1975 _model->apply_command (*trackview.session(), c);
1977 _patch_changes.clear ();
1978 display_patch_changes ();
1982 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::MusicalTime t)
1984 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1985 c->change_time (pc.patch (), t);
1986 _model->apply_command (*trackview.session(), c);
1988 _patch_changes.clear ();
1989 display_patch_changes ();
1993 MidiRegionView::delete_patch_change (PatchChange* pc)
1995 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1996 c->remove (pc->patch ());
1997 _model->apply_command (*trackview.session(), c);
1999 _patch_changes.clear ();
2000 display_patch_changes ();
2004 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2006 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2008 key.set_bank(key.bank() + delta);
2010 key.set_program(key.program() + delta);
2012 change_patch_change(patch, key);
2016 MidiRegionView::maybe_remove_deleted_note_from_selection (NoteBase* cne)
2018 if (_selection.empty()) {
2022 _selection.erase (cne);
2026 MidiRegionView::delete_selection()
2028 if (_selection.empty()) {
2032 start_note_diff_command (_("delete selection"));
2034 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2035 if ((*i)->selected()) {
2036 _note_diff_command->remove((*i)->note());
2046 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2048 start_note_diff_command (_("delete note"));
2049 _note_diff_command->remove (n);
2052 trackview.editor().verbose_cursor()->hide ();
2056 MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
2058 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2060 Selection::iterator tmp = i;
2063 (*i)->set_selected (false);
2064 (*i)->hide_velocity ();
2065 _selection.erase (i);
2073 if (!ev && _entered) {
2074 // Clearing selection entirely, ungrab keyboard
2075 Keyboard::magic_widget_drop_focus();
2076 _grabbed_keyboard = false;
2079 /* this does not change the status of this regionview w.r.t the editor
2084 SelectionCleared (this); /* EMIT SIGNAL */
2089 MidiRegionView::unique_select(NoteBase* ev)
2091 const bool selection_was_empty = _selection.empty();
2093 clear_selection_except (ev);
2095 /* don't bother with checking to see if we should remove this
2096 regionview from the editor selection, since we're about to add
2097 another note, and thus put/keep this regionview in the editor
2101 if (!ev->selected()) {
2102 add_to_selection (ev);
2103 if (selection_was_empty && _entered) {
2104 // Grab keyboard for moving notes with arrow keys
2105 Keyboard::magic_widget_grab_focus();
2106 _grabbed_keyboard = true;
2112 MidiRegionView::select_all_notes ()
2116 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2117 add_to_selection (*i);
2122 MidiRegionView::select_range (framepos_t start, framepos_t end)
2126 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2127 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2128 if (t >= start && t <= end) {
2129 add_to_selection (*i);
2135 MidiRegionView::invert_selection ()
2137 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2138 if ((*i)->selected()) {
2139 remove_from_selection(*i);
2141 add_to_selection (*i);
2147 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2149 bool have_selection = !_selection.empty();
2150 uint8_t low_note = 127;
2151 uint8_t high_note = 0;
2152 MidiModel::Notes& notes (_model->notes());
2153 _optimization_iterator = _events.begin();
2155 if (extend && !have_selection) {
2159 /* scan existing selection to get note range */
2161 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2162 if ((*i)->note()->note() < low_note) {
2163 low_note = (*i)->note()->note();
2165 if ((*i)->note()->note() > high_note) {
2166 high_note = (*i)->note()->note();
2173 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2174 /* only note previously selected is the one we are
2175 * reselecting. treat this as cancelling the selection.
2182 low_note = min (low_note, notenum);
2183 high_note = max (high_note, notenum);
2186 _no_sound_notes = true;
2188 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2190 boost::shared_ptr<NoteType> note (*n);
2192 bool select = false;
2194 if (((1 << note->channel()) & channel_mask) != 0) {
2196 if ((note->note() >= low_note && note->note() <= high_note)) {
2199 } else if (note->note() == notenum) {
2205 if ((cne = find_canvas_note (note)) != 0) {
2206 // extend is false because we've taken care of it,
2207 // since it extends by time range, not pitch.
2208 note_selected (cne, add, false);
2212 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2216 _no_sound_notes = false;
2220 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2222 MidiModel::Notes& notes (_model->notes());
2223 _optimization_iterator = _events.begin();
2225 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2227 boost::shared_ptr<NoteType> note (*n);
2230 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2231 if ((cne = find_canvas_note (note)) != 0) {
2232 if (cne->selected()) {
2233 note_deselected (cne);
2235 note_selected (cne, true, false);
2243 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2246 clear_selection_except (ev);
2247 if (!_selection.empty()) {
2248 PublicEditor& editor (trackview.editor());
2249 editor.get_selection().add (this);
2255 if (!ev->selected()) {
2256 add_to_selection (ev);
2260 /* find end of latest note selected, select all between that and the start of "ev" */
2262 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2263 Evoral::MusicalTime latest = Evoral::MusicalTime();
2265 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2266 if ((*i)->note()->end_time() > latest) {
2267 latest = (*i)->note()->end_time();
2269 if ((*i)->note()->time() < earliest) {
2270 earliest = (*i)->note()->time();
2274 if (ev->note()->end_time() > latest) {
2275 latest = ev->note()->end_time();
2278 if (ev->note()->time() < earliest) {
2279 earliest = ev->note()->time();
2282 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2284 /* find notes entirely within OR spanning the earliest..latest range */
2286 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2287 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2288 add_to_selection (*i);
2296 MidiRegionView::note_deselected(NoteBase* ev)
2298 remove_from_selection (ev);
2302 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2304 PublicEditor& editor = trackview.editor();
2306 // Convert to local coordinates
2307 const framepos_t p = _region->position();
2308 const double y = midi_view()->y_position();
2309 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2310 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2311 const double y0 = max(0.0, gy0 - y);
2312 const double y1 = max(0.0, gy1 - y);
2314 // TODO: Make this faster by storing the last updated selection rect, and only
2315 // adjusting things that are in the area that appears/disappeared.
2316 // We probably need a tree to be able to find events in O(log(n)) time.
2318 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2319 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2320 // Rectangles intersect
2321 if (!(*i)->selected()) {
2322 add_to_selection (*i);
2324 } else if ((*i)->selected() && !extend) {
2325 // Rectangles do not intersect
2326 remove_from_selection (*i);
2330 typedef RouteTimeAxisView::AutomationTracks ATracks;
2331 typedef std::list<Selectable*> Selectables;
2333 /* Add control points to selection. */
2334 const ATracks& atracks = midi_view()->automation_tracks();
2335 Selectables selectables;
2336 editor.get_selection().clear_points();
2337 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2338 a->second->get_selectables(start, end, gy0, gy1, selectables);
2339 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2340 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2342 editor.get_selection().add(cp);
2345 a->second->set_selected_points(editor.get_selection().points);
2350 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2356 // TODO: Make this faster by storing the last updated selection rect, and only
2357 // adjusting things that are in the area that appears/disappeared.
2358 // We probably need a tree to be able to find events in O(log(n)) time.
2360 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2361 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2362 // within y- (note-) range
2363 if (!(*i)->selected()) {
2364 add_to_selection (*i);
2366 } else if ((*i)->selected() && !extend) {
2367 remove_from_selection (*i);
2373 MidiRegionView::remove_from_selection (NoteBase* ev)
2375 Selection::iterator i = _selection.find (ev);
2377 if (i != _selection.end()) {
2378 _selection.erase (i);
2379 if (_selection.empty() && _grabbed_keyboard) {
2381 Keyboard::magic_widget_drop_focus();
2382 _grabbed_keyboard = false;
2386 ev->set_selected (false);
2387 ev->hide_velocity ();
2389 if (_selection.empty()) {
2390 PublicEditor& editor (trackview.editor());
2391 editor.get_selection().remove (this);
2396 MidiRegionView::add_to_selection (NoteBase* ev)
2398 const bool selection_was_empty = _selection.empty();
2400 if (_selection.insert (ev).second) {
2401 ev->set_selected (true);
2402 start_playing_midi_note ((ev)->note());
2403 if (selection_was_empty && _entered) {
2404 // Grab keyboard for moving notes with arrow keys
2405 Keyboard::magic_widget_grab_focus();
2406 _grabbed_keyboard = true;
2410 if (selection_was_empty) {
2411 PublicEditor& editor (trackview.editor());
2412 editor.get_selection().add (this);
2417 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2419 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2420 PossibleChord to_play;
2421 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2423 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2424 if ((*i)->note()->time() < earliest) {
2425 earliest = (*i)->note()->time();
2429 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2430 if ((*i)->note()->time() == earliest) {
2431 to_play.push_back ((*i)->note());
2433 (*i)->move_event(dx, dy);
2436 if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) {
2438 if (to_play.size() > 1) {
2440 PossibleChord shifted;
2442 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2443 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2444 moved_note->set_note (moved_note->note() + cumulative_dy);
2445 shifted.push_back (moved_note);
2448 start_playing_midi_chord (shifted);
2450 } else if (!to_play.empty()) {
2452 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2453 moved_note->set_note (moved_note->note() + cumulative_dy);
2454 start_playing_midi_note (moved_note);
2460 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2462 uint8_t lowest_note_in_selection = 127;
2463 uint8_t highest_note_in_selection = 0;
2464 uint8_t highest_note_difference = 0;
2466 // find highest and lowest notes first
2468 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2469 uint8_t pitch = (*i)->note()->note();
2470 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2471 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2475 cerr << "dnote: " << (int) dnote << endl;
2476 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2477 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2478 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2479 << int(highest_note_in_selection) << endl;
2480 cerr << "selection size: " << _selection.size() << endl;
2481 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2484 // Make sure the note pitch does not exceed the MIDI standard range
2485 if (highest_note_in_selection + dnote > 127) {
2486 highest_note_difference = highest_note_in_selection - 127;
2489 start_note_diff_command (_("move notes"));
2491 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2493 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2494 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2500 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2502 uint8_t original_pitch = (*i)->note()->note();
2503 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2505 // keep notes in standard midi range
2506 clamp_to_0_127(new_pitch);
2508 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2509 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2511 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2516 // care about notes being moved beyond the upper/lower bounds on the canvas
2517 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2518 highest_note_in_selection > midi_stream_view()->highest_note()) {
2519 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2523 /** @param x Pixel relative to the region position.
2524 * @return Snapped frame relative to the region position.
2527 MidiRegionView::snap_pixel_to_sample(double x)
2529 PublicEditor& editor (trackview.editor());
2530 return snap_frame_to_frame (editor.pixel_to_sample (x));
2533 /** @param x Pixel relative to the region position.
2534 * @return Snapped pixel relative to the region position.
2537 MidiRegionView::snap_to_pixel(double x)
2539 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x));
2543 MidiRegionView::get_position_pixels()
2545 framepos_t region_frame = get_position();
2546 return trackview.editor().sample_to_pixel(region_frame);
2550 MidiRegionView::get_end_position_pixels()
2552 framepos_t frame = get_position() + get_duration ();
2553 return trackview.editor().sample_to_pixel(frame);
2557 MidiRegionView::source_beats_to_absolute_frames(Evoral::MusicalTime beats) const
2559 /* the time converter will return the frame corresponding to `beats'
2560 relative to the start of the source. The start of the source
2561 is an implied position given by region->position - region->start
2563 const framepos_t source_start = _region->position() - _region->start();
2564 return source_start + _source_relative_time_converter.to (beats);
2568 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2570 /* the `frames' argument needs to be converted into a frame count
2571 relative to the start of the source before being passed in to the
2574 const framepos_t source_start = _region->position() - _region->start();
2575 return _source_relative_time_converter.from (frames - source_start);
2579 MidiRegionView::region_beats_to_region_frames(Evoral::MusicalTime beats) const
2581 return _region_relative_time_converter.to(beats);
2585 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2587 return _region_relative_time_converter.from(frames);
2591 MidiRegionView::begin_resizing (bool /*at_front*/)
2593 _resize_data.clear();
2595 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2596 Note *note = dynamic_cast<Note*> (*i);
2598 // only insert CanvasNotes into the map
2600 NoteResizeData *resize_data = new NoteResizeData();
2601 resize_data->note = note;
2603 // create a new SimpleRect from the note which will be the resize preview
2604 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2605 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2607 // calculate the colors: get the color settings
2608 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2609 ARDOUR_UI::config()->color ("midi note selected"),
2612 // make the resize preview notes more transparent and bright
2613 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2615 // calculate color based on note velocity
2616 resize_rect->set_fill_color (UINT_INTERPOLATE(
2617 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2621 resize_rect->set_outline_color (NoteBase::calculate_outline (
2622 ARDOUR_UI::config()->color ("midi note selected")));
2624 resize_data->resize_rect = resize_rect;
2625 _resize_data.push_back(resize_data);
2630 /** Update resizing notes while user drags.
2631 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2632 * @param at_front which end of the note (true == note on, false == note off)
2633 * @param delta_x change in mouse position since the start of the drag
2634 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2635 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2636 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2637 * as the \a primary note.
2640 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2642 bool cursor_set = false;
2644 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2645 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2646 Note* canvas_note = (*i)->note;
2651 current_x = canvas_note->x0() + delta_x;
2653 current_x = primary->x0() + delta_x;
2657 current_x = canvas_note->x1() + delta_x;
2659 current_x = primary->x1() + delta_x;
2663 if (current_x < 0) {
2664 // This works even with snapping because RegionView::snap_frame_to_frame()
2665 // snaps forward if the snapped sample is before the beginning of the region
2668 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2669 current_x = trackview.editor().sample_to_pixel(_region->length());
2673 resize_rect->set_x0 (snap_to_pixel(current_x));
2674 resize_rect->set_x1 (canvas_note->x1());
2676 resize_rect->set_x1 (snap_to_pixel(current_x));
2677 resize_rect->set_x0 (canvas_note->x0());
2681 const double snapped_x = snap_pixel_to_sample (current_x);
2682 Evoral::MusicalTime beats = region_frames_to_region_beats (snapped_x);
2683 Evoral::MusicalTime len = Evoral::MusicalTime();
2686 if (beats < canvas_note->note()->end_time()) {
2687 len = canvas_note->note()->time() - beats;
2688 len += canvas_note->note()->length();
2691 if (beats >= canvas_note->note()->time()) {
2692 len = beats - canvas_note->note()->time();
2697 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2698 show_verbose_cursor (buf, 0, 0);
2707 /** Finish resizing notes when the user releases the mouse button.
2708 * Parameters the same as for \a update_resizing().
2711 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2713 start_note_diff_command (_("resize notes"));
2715 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2716 Note* canvas_note = (*i)->note;
2717 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2719 /* Get the new x position for this resize, which is in pixels relative
2720 * to the region position.
2727 current_x = canvas_note->x0() + delta_x;
2729 current_x = primary->x0() + delta_x;
2733 current_x = canvas_note->x1() + delta_x;
2735 current_x = primary->x1() + delta_x;
2739 if (current_x < 0) {
2742 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2743 current_x = trackview.editor().sample_to_pixel(_region->length());
2746 /* Convert that to a frame within the source */
2747 current_x = snap_pixel_to_sample (current_x) + _region->start ();
2749 /* and then to beats */
2750 const Evoral::MusicalTime x_beats = region_frames_to_region_beats (current_x);
2752 if (at_front && x_beats < canvas_note->note()->end_time()) {
2753 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats);
2755 Evoral::MusicalTime len = canvas_note->note()->time() - x_beats;
2756 len += canvas_note->note()->length();
2759 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2764 const Evoral::MusicalTime len = x_beats - canvas_note->note()->time();
2767 /* XXX convert to beats */
2768 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2776 _resize_data.clear();
2781 MidiRegionView::abort_resizing ()
2783 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2784 delete (*i)->resize_rect;
2788 _resize_data.clear ();
2792 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2794 uint8_t new_velocity;
2797 new_velocity = event->note()->velocity() + velocity;
2798 clamp_to_0_127(new_velocity);
2800 new_velocity = velocity;
2803 event->set_selected (event->selected()); // change color
2805 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2809 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2814 new_note = event->note()->note() + note;
2819 clamp_to_0_127 (new_note);
2820 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2824 MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2826 bool change_start = false;
2827 bool change_length = false;
2828 Evoral::MusicalTime new_start;
2829 Evoral::MusicalTime new_length;
2831 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2833 front_delta: if positive - move the start of the note later in time (shortening it)
2834 if negative - move the start of the note earlier in time (lengthening it)
2836 end_delta: if positive - move the end of the note later in time (lengthening it)
2837 if negative - move the end of the note earlier in time (shortening it)
2840 if (!!front_delta) {
2841 if (front_delta < 0) {
2843 if (event->note()->time() < -front_delta) {
2844 new_start = Evoral::MusicalTime();
2846 new_start = event->note()->time() + front_delta; // moves earlier
2849 /* start moved toward zero, so move the end point out to where it used to be.
2850 Note that front_delta is negative, so this increases the length.
2853 new_length = event->note()->length() - front_delta;
2854 change_start = true;
2855 change_length = true;
2859 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2861 if (new_pos < event->note()->end_time()) {
2862 new_start = event->note()->time() + front_delta;
2863 /* start moved toward the end, so move the end point back to where it used to be */
2864 new_length = event->note()->length() - front_delta;
2865 change_start = true;
2866 change_length = true;
2873 bool can_change = true;
2874 if (end_delta < 0) {
2875 if (event->note()->length() < -end_delta) {
2881 new_length = event->note()->length() + end_delta;
2882 change_length = true;
2887 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2890 if (change_length) {
2891 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2896 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
2898 uint8_t new_channel;
2902 if (event->note()->channel() < -chn) {
2905 new_channel = event->note()->channel() + chn;
2908 new_channel = event->note()->channel() + chn;
2911 new_channel = (uint8_t) chn;
2914 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2918 MidiRegionView::change_note_time (NoteBase* event, Evoral::MusicalTime delta, bool relative)
2920 Evoral::MusicalTime new_time;
2924 if (event->note()->time() < -delta) {
2925 new_time = Evoral::MusicalTime();
2927 new_time = event->note()->time() + delta;
2930 new_time = event->note()->time() + delta;
2936 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2940 MidiRegionView::change_note_length (NoteBase* event, Evoral::MusicalTime t)
2942 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2946 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2951 if (_selection.empty()) {
2966 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2967 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
2973 start_note_diff_command (_("change velocities"));
2975 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2976 Selection::iterator next = i;
2980 if (i == _selection.begin()) {
2981 change_note_velocity (*i, delta, true);
2982 value = (*i)->note()->velocity() + delta;
2984 change_note_velocity (*i, value, false);
2988 change_note_velocity (*i, delta, true);
2997 if (!_selection.empty()) {
2999 snprintf (buf, sizeof (buf), "Vel %d",
3000 (int) (*_selection.begin())->note()->velocity());
3001 show_verbose_cursor (buf, 10, 10);
3007 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3009 if (_selection.empty()) {
3026 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3028 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3032 if ((int8_t) (*i)->note()->note() + delta > 127) {
3039 start_note_diff_command (_("transpose"));
3041 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3042 Selection::iterator next = i;
3044 change_note_note (*i, delta, true);
3052 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
3056 delta = Evoral::MusicalTime(1.0/128.0);
3058 /* grab the current grid distance */
3059 delta = get_grid_beats(_region->position());
3067 start_note_diff_command (_("change note lengths"));
3069 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3070 Selection::iterator next = i;
3073 /* note the negation of the delta for start */
3076 (start ? -delta : Evoral::MusicalTime()),
3077 (end ? delta : Evoral::MusicalTime()));
3086 MidiRegionView::nudge_notes (bool forward, bool fine)
3088 if (_selection.empty()) {
3092 /* pick a note as the point along the timeline to get the nudge distance.
3093 its not necessarily the earliest note, so we may want to pull the notes out
3094 into a vector and sort before using the first one.
3097 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3098 Evoral::MusicalTime delta;
3102 /* non-fine, move by 1 bar regardless of snap */
3103 delta = Evoral::MusicalTime(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
3105 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3107 /* grid is off - use nudge distance */
3110 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3111 delta = region_frames_to_region_beats (fabs ((double)distance));
3117 framepos_t next_pos = ref_point;
3120 if (max_framepos - 1 < next_pos) {
3124 if (next_pos == 0) {
3130 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3131 const framecnt_t distance = ref_point - next_pos;
3132 delta = region_frames_to_region_beats (fabs ((double)distance));
3143 start_note_diff_command (_("nudge"));
3145 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3146 Selection::iterator next = i;
3148 change_note_time (*i, delta, true);
3156 MidiRegionView::change_channel(uint8_t channel)
3158 start_note_diff_command(_("change channel"));
3159 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3160 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3168 MidiRegionView::note_entered(NoteBase* ev)
3170 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3172 if (_mouse_state == SelectTouchDragging) {
3173 note_selected (ev, true);
3174 } else if (editor->current_mouse_mode() == MouseContent) {
3175 show_verbose_cursor (ev->note ());
3176 } else if (editor->current_mouse_mode() == MouseDraw) {
3177 show_verbose_cursor (ev->note ());
3182 MidiRegionView::note_left (NoteBase*)
3184 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3186 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3187 (*i)->hide_velocity ();
3190 editor->verbose_cursor()->hide ();
3194 MidiRegionView::patch_entered (PatchChange* p)
3197 /* XXX should get patch name if we can */
3198 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3199 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3200 << _("Channel ") << ((int) p->patch()->channel() + 1);
3201 show_verbose_cursor (s.str(), 10, 20);
3202 p->item().grab_focus();
3206 MidiRegionView::patch_left (PatchChange *)
3208 trackview.editor().verbose_cursor()->hide ();
3209 /* focus will transfer back via the enter-notify event sent to this
3215 MidiRegionView::sysex_entered (SysEx* p)
3219 // need a way to extract text from p->_flag->_text
3221 // show_verbose_cursor (s.str(), 10, 20);
3222 p->item().grab_focus();
3226 MidiRegionView::sysex_left (SysEx *)
3228 trackview.editor().verbose_cursor()->hide ();
3229 /* focus will transfer back via the enter-notify event sent to this
3235 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3237 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3238 Editing::MouseMode mm = editor->current_mouse_mode();
3239 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3241 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3242 if (can_set_cursor && ctx) {
3243 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3244 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3245 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3246 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3248 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3254 MidiRegionView::get_fill_color() const
3256 const std::string mod_name = (_dragging ? "dragging region" :
3257 trackview.editor().internal_editing() ? "editable region" :
3260 return ARDOUR_UI::config()->color_mod ("selected region base", mod_name);
3261 } else if (high_enough_for_name || !ARDOUR_UI::config()->get_color_regions_using_track_color()) {
3262 return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name);
3264 return ARDOUR_UI::config()->color_mod (fill_color, mod_name);
3268 MidiRegionView::midi_channel_mode_changed ()
3270 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3271 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3272 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3274 if (mode == ForceChannel) {
3275 mask = 0xFFFF; // Show all notes as active (below)
3278 // Update notes for selection
3279 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3280 (*i)->on_channel_selection_change (mask);
3283 _patch_changes.clear ();
3284 display_patch_changes ();
3288 MidiRegionView::instrument_settings_changed ()
3294 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3296 if (_selection.empty()) {
3300 PublicEditor& editor (trackview.editor());
3304 /* XXX what to do ? */
3308 editor.get_cut_buffer().add (selection_as_cut_buffer());
3316 start_note_diff_command();
3318 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3325 note_diff_remove_note (*i);
3335 MidiRegionView::selection_as_cut_buffer () const
3339 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3340 NoteType* n = (*i)->note().get();
3341 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3344 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3350 /** This method handles undo */
3352 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
3354 // Paste notes, if available
3355 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3356 if (m != selection.midi_notes.end()) {
3357 ctx.counts.increase_n_notes();
3358 paste_internal(pos, ctx.count, ctx.times, **m);
3361 // Paste control points to automation children, if available
3362 typedef RouteTimeAxisView::AutomationTracks ATracks;
3363 const ATracks& atracks = midi_view()->automation_tracks();
3364 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3365 a->second->paste(pos, selection, ctx);
3371 /** This method handles undo */
3373 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3379 start_note_diff_command (_("paste"));
3381 const Evoral::MusicalTime snap_beats = get_grid_beats(pos);
3382 const Evoral::MusicalTime first_time = (*mcb.notes().begin())->time();
3383 const Evoral::MusicalTime last_time = (*mcb.notes().rbegin())->end_time();
3384 const Evoral::MusicalTime duration = last_time - first_time;
3385 const Evoral::MusicalTime snap_duration = duration.snap_to(snap_beats);
3386 const Evoral::MusicalTime paste_offset = snap_duration * paste_count;
3387 const Evoral::MusicalTime pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3388 Evoral::MusicalTime end_point = Evoral::MusicalTime();
3390 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3393 duration, pos, _region->position(),
3398 for (int n = 0; n < (int) times; ++n) {
3400 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3402 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3403 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3405 /* make all newly added notes selected */
3407 note_diff_add_note (copied_note, true);
3408 end_point = copied_note->end_time();
3412 /* if we pasted past the current end of the region, extend the region */
3414 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3415 framepos_t region_end = _region->position() + _region->length() - 1;
3417 if (end_frame > region_end) {
3419 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3421 _region->clear_changes ();
3422 _region->set_length (end_frame - _region->position());
3423 trackview.session()->add_command (new StatefulDiffCommand (_region));
3429 struct EventNoteTimeEarlyFirstComparator {
3430 bool operator() (NoteBase* a, NoteBase* b) {
3431 return a->note()->time() < b->note()->time();
3436 MidiRegionView::time_sort_events ()
3438 if (!_sort_needed) {
3442 EventNoteTimeEarlyFirstComparator cmp;
3445 _sort_needed = false;
3449 MidiRegionView::goto_next_note (bool add_to_selection)
3451 bool use_next = false;
3453 if (_events.back()->selected()) {
3457 time_sort_events ();
3459 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3460 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3462 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3463 if ((*i)->selected()) {
3466 } else if (use_next) {
3467 if (channel_mask & (1 << (*i)->note()->channel())) {
3468 if (!add_to_selection) {
3471 note_selected (*i, true, false);
3478 /* use the first one */
3480 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3481 unique_select (_events.front());
3486 MidiRegionView::goto_previous_note (bool add_to_selection)
3488 bool use_next = false;
3490 if (_events.front()->selected()) {
3494 time_sort_events ();
3496 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3497 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3499 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3500 if ((*i)->selected()) {
3503 } else if (use_next) {
3504 if (channel_mask & (1 << (*i)->note()->channel())) {
3505 if (!add_to_selection) {
3508 note_selected (*i, true, false);
3515 /* use the last one */
3517 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3518 unique_select (*(_events.rbegin()));
3523 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3525 bool had_selected = false;
3527 time_sort_events ();
3529 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3530 if ((*i)->selected()) {
3531 selected.insert ((*i)->note());
3532 had_selected = true;
3536 if (allow_all_if_none_selected && !had_selected) {
3537 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3538 selected.insert ((*i)->note());
3544 MidiRegionView::update_ghost_note (double x, double y)
3546 x = std::max(0.0, x);
3548 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3553 _note_group->canvas_to_item (x, y);
3555 PublicEditor& editor = trackview.editor ();
3557 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3558 framecnt_t grid_frames;
3559 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3561 /* calculate time in beats relative to start of source */
3562 const Evoral::MusicalTime length = get_grid_beats(unsnapped_frame);
3563 const Evoral::MusicalTime time = std::max(
3564 Evoral::MusicalTime(),
3565 absolute_frames_to_source_beats (f + _region->position ()));
3567 _ghost_note->note()->set_time (time);
3568 _ghost_note->note()->set_length (length);
3569 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3570 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3571 _ghost_note->note()->set_velocity (get_velocity_for_add (time));
3573 /* the ghost note does not appear in ghost regions, so pass false in here */
3574 update_note (_ghost_note, false);
3576 show_verbose_cursor (_ghost_note->note ());
3580 MidiRegionView::create_ghost_note (double x, double y)
3582 remove_ghost_note ();
3584 boost::shared_ptr<NoteType> g (new NoteType);
3585 if (midi_view()->note_mode() == Sustained) {
3586 _ghost_note = new Note (*this, _note_group, g);
3588 _ghost_note = new Hit (*this, _note_group, 10, g);
3590 _ghost_note->set_ignore_events (true);
3591 _ghost_note->set_outline_color (0x000000aa);
3592 update_ghost_note (x, y);
3593 _ghost_note->show ();
3595 show_verbose_cursor (_ghost_note->note ());
3599 MidiRegionView::remove_ghost_note ()
3606 MidiRegionView::snap_changed ()
3612 create_ghost_note (_last_ghost_x, _last_ghost_y);
3616 MidiRegionView::drop_down_keys ()
3618 _mouse_state = None;
3622 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3624 /* XXX: This is dead code. What was it for? */
3626 double note = midi_stream_view()->y_to_note(y);
3628 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3630 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3632 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3633 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3634 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3635 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3640 bool add_mrv_selection = false;
3642 if (_selection.empty()) {
3643 add_mrv_selection = true;
3646 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3647 if (_selection.insert (*i).second) {
3648 (*i)->set_selected (true);
3652 if (add_mrv_selection) {
3653 PublicEditor& editor (trackview.editor());
3654 editor.get_selection().add (this);
3659 MidiRegionView::color_handler ()
3661 RegionView::color_handler ();
3663 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3664 (*i)->set_selected ((*i)->selected()); // will change color
3667 /* XXX probably more to do here */
3671 MidiRegionView::enable_display (bool yn)
3673 RegionView::enable_display (yn);
3680 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3682 if (_step_edit_cursor == 0) {
3683 ArdourCanvas::Item* const group = get_canvas_group();
3685 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3686 _step_edit_cursor->set_y0 (0);
3687 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3688 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3689 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3692 move_step_edit_cursor (pos);
3693 _step_edit_cursor->show ();
3697 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3699 _step_edit_cursor_position = pos;
3701 if (_step_edit_cursor) {
3702 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3703 _step_edit_cursor->set_x0 (pixel);
3704 set_step_edit_cursor_width (_step_edit_cursor_width);
3709 MidiRegionView::hide_step_edit_cursor ()
3711 if (_step_edit_cursor) {
3712 _step_edit_cursor->hide ();
3717 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3719 _step_edit_cursor_width = beats;
3721 if (_step_edit_cursor) {
3722 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
3726 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3727 * @param w Source that the data will end up in.
3730 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3732 if (!_active_notes) {
3733 /* we aren't actively being recorded to */
3737 boost::shared_ptr<MidiSource> src = w.lock ();
3738 if (!src || src != midi_region()->midi_source()) {
3739 /* recorded data was not destined for our source */
3743 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3745 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3747 framepos_t back = max_framepos;
3749 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3750 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3752 if (ev.is_channel_event()) {
3753 if (get_channel_mode() == FilterChannels) {
3754 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3760 /* convert from session frames to source beats */
3761 Evoral::MusicalTime const time_beats = _source_relative_time_converter.from(
3762 ev.time() - src->timeline_position() + _region->start());
3764 if (ev.type() == MIDI_CMD_NOTE_ON) {
3765 boost::shared_ptr<NoteType> note (
3766 new NoteType (ev.channel(), time_beats, Evoral::MusicalTime(), ev.note(), ev.velocity()));
3768 add_note (note, true);
3770 /* fix up our note range */
3771 if (ev.note() < _current_range_min) {
3772 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3773 } else if (ev.note() > _current_range_max) {
3774 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3777 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3778 resolve_note (ev.note (), time_beats);
3784 midi_stream_view()->check_record_layers (region(), back);
3788 MidiRegionView::trim_front_starting ()
3790 /* Reparent the note group to the region view's parent, so that it doesn't change
3791 when the region view is trimmed.
3793 _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3794 _temporary_note_group->move (group->position ());
3795 _note_group->reparent (_temporary_note_group);
3799 MidiRegionView::trim_front_ending ()
3801 _note_group->reparent (group);
3802 delete _temporary_note_group;
3803 _temporary_note_group = 0;
3805 if (_region->start() < 0) {
3806 /* Trim drag made start time -ve; fix this */
3807 midi_region()->fix_negative_start ();
3812 MidiRegionView::edit_patch_change (PatchChange* pc)
3814 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3816 int response = d.run();
3819 case Gtk::RESPONSE_ACCEPT:
3821 case Gtk::RESPONSE_REJECT:
3822 delete_patch_change (pc);
3828 change_patch_change (pc->patch(), d.patch ());
3832 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
3835 // sysyex object doesn't have a pointer to a sysex event
3836 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3837 // c->remove (sysex->sysex());
3838 // _model->apply_command (*trackview.session(), c);
3840 //_sys_exes.clear ();
3841 // display_sysexes();
3845 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3847 using namespace MIDI::Name;
3851 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3853 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
3855 MIDI::Name::PatchPrimaryKey patch_key;
3856 get_patch_key_at(n->time(), n->channel(), patch_key);
3857 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
3860 patch_key.program(),
3866 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
3868 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
3869 (int) n->channel() + 1,
3870 (int) n->velocity());
3872 show_verbose_cursor(buf, 10, 20);
3876 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3878 trackview.editor().verbose_cursor()->set (text);
3879 trackview.editor().verbose_cursor()->show ();
3880 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
3884 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
3886 if (_model->notes().empty()) {
3887 return 0x40; // No notes, use default
3890 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
3891 if (m == _model->notes().begin()) {
3892 // Before the start, use the velocity of the first note
3893 return (*m)->velocity();
3894 } else if (m == _model->notes().end()) {
3895 // Past the end, use the velocity of the last note
3897 return (*m)->velocity();
3900 // Interpolate velocity of surrounding notes
3901 MidiModel::Notes::const_iterator n = m;
3904 const double frac = ((time - (*n)->time()).to_double() /
3905 ((*m)->time() - (*n)->time()).to_double());
3907 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
3910 /** @param p A session framepos.
3911 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3912 * @return p snapped to the grid subdivision underneath it.
3915 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3917 PublicEditor& editor = trackview.editor ();
3919 const Evoral::MusicalTime grid_beats = get_grid_beats(p);
3921 grid_frames = region_beats_to_region_frames (grid_beats);
3923 /* Hack so that we always snap to the note that we are over, instead of snapping
3924 to the next one if we're more than halfway through the one we're over.
3926 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3927 p -= grid_frames / 2;
3930 return snap_frame_to_frame (p);
3933 /** Called when the selection has been cleared in any MidiRegionView.
3934 * @param rv MidiRegionView that the selection was cleared in.
3937 MidiRegionView::selection_cleared (MidiRegionView* rv)
3943 /* Clear our selection in sympathy; but don't signal the fact */
3944 clear_selection (false);
3948 MidiRegionView::note_button_release ()
3950 _note_player.reset();
3954 MidiRegionView::get_channel_mode () const
3956 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
3957 return rtav->midi_track()->get_playback_channel_mode();
3961 MidiRegionView::get_selected_channels () const
3963 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
3964 return rtav->midi_track()->get_playback_channel_mask();
3969 MidiRegionView::get_grid_beats(framepos_t pos) const
3971 PublicEditor& editor = trackview.editor();
3972 bool success = false;
3973 Evoral::MusicalTime beats = editor.get_grid_type_as_beats(success, pos);
3975 beats = Evoral::MusicalTime(1);