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)
123 , _grabbed_keyboard (false)
126 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
127 _note_group->raise_to_top();
128 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
130 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
131 connect_to_diskstream ();
133 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
135 PublicEditor& editor (trackview.editor());
136 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
139 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
140 RouteTimeAxisView& tv,
141 boost::shared_ptr<MidiRegion> r,
143 uint32_t basic_color,
145 TimeAxisViewItem::Visibility visibility)
146 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
147 , _current_range_min(0)
148 , _current_range_max(0)
149 , _region_relative_time_converter(r->session().tempo_map(), r->position())
150 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
152 , _note_group (new ArdourCanvas::Container (parent))
153 , _note_diff_command (0)
155 , _step_edit_cursor (0)
156 , _step_edit_cursor_width (1.0)
157 , _step_edit_cursor_position (0.0)
158 , _channel_selection_scoped_note (0)
159 , _temporary_note_group (0)
162 , _sort_needed (true)
163 , _optimization_iterator (_events.end())
165 , _no_sound_notes (false)
168 , _grabbed_keyboard (false)
171 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
172 _note_group->raise_to_top();
174 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
176 connect_to_diskstream ();
178 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
180 PublicEditor& editor (trackview.editor());
181 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
185 MidiRegionView::parameter_changed (std::string const & p)
187 if (p == "display-first-midi-bank-as-zero") {
188 if (_enable_display) {
194 MidiRegionView::MidiRegionView (const MidiRegionView& other)
195 : sigc::trackable(other)
197 , _current_range_min(0)
198 , _current_range_max(0)
199 , _region_relative_time_converter(other.region_relative_time_converter())
200 , _source_relative_time_converter(other.source_relative_time_converter())
202 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
203 , _note_diff_command (0)
205 , _step_edit_cursor (0)
206 , _step_edit_cursor_width (1.0)
207 , _step_edit_cursor_position (0.0)
208 , _channel_selection_scoped_note (0)
209 , _temporary_note_group (0)
212 , _sort_needed (true)
213 , _optimization_iterator (_events.end())
215 , _no_sound_notes (false)
218 , _grabbed_keyboard (false)
224 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
225 : RegionView (other, boost::shared_ptr<Region> (region))
226 , _current_range_min(0)
227 , _current_range_max(0)
228 , _region_relative_time_converter(other.region_relative_time_converter())
229 , _source_relative_time_converter(other.source_relative_time_converter())
231 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
232 , _note_diff_command (0)
234 , _step_edit_cursor (0)
235 , _step_edit_cursor_width (1.0)
236 , _step_edit_cursor_position (0.0)
237 , _channel_selection_scoped_note (0)
238 , _temporary_note_group (0)
241 , _sort_needed (true)
242 , _optimization_iterator (_events.end())
244 , _no_sound_notes (false)
247 , _grabbed_keyboard (false)
254 MidiRegionView::init (bool wfd)
256 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
258 NoteBase::NoteBaseDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
259 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
263 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
264 midi_region()->midi_source(0)->load_model(lm);
267 _model = midi_region()->midi_source(0)->model();
268 _enable_display = false;
269 fill_color_name = "midi frame base";
271 RegionView::init (false);
273 set_height (trackview.current_height());
276 region_sync_changed ();
277 region_resized (ARDOUR::bounds_change);
282 _enable_display = true;
285 display_model (_model);
289 reset_width_dependent_items (_pixel_width);
291 group->raise_to_top();
293 midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
294 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
297 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
298 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
300 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
301 boost::bind (&MidiRegionView::snap_changed, this),
304 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
305 boost::bind (&MidiRegionView::mouse_mode_changed, this),
308 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
309 connect_to_diskstream ();
311 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
313 PublicEditor& editor (trackview.editor());
314 editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
318 MidiRegionView::instrument_info () const
320 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
321 return route_ui->route()->instrument_info();
324 const boost::shared_ptr<ARDOUR::MidiRegion>
325 MidiRegionView::midi_region() const
327 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
331 MidiRegionView::connect_to_diskstream ()
333 midi_view()->midi_track()->DataRecorded.connect(
334 *this, invalidator(*this),
335 boost::bind (&MidiRegionView::data_recorded, this, _1),
340 MidiRegionView::canvas_group_event(GdkEvent* ev)
342 if (in_destructor || _recregion) {
346 if (!trackview.editor().internal_editing()) {
347 // not in internal edit mode, so just act like a normal region
348 return RegionView::canvas_group_event (ev);
354 case GDK_ENTER_NOTIFY:
355 _last_event_x = ev->crossing.x;
356 _last_event_y = ev->crossing.y;
357 enter_notify(&ev->crossing);
358 // set entered_regionview (among other things)
359 return RegionView::canvas_group_event (ev);
361 case GDK_LEAVE_NOTIFY:
362 _last_event_x = ev->crossing.x;
363 _last_event_y = ev->crossing.y;
364 leave_notify(&ev->crossing);
365 // reset entered_regionview (among other things)
366 return RegionView::canvas_group_event (ev);
369 if (scroll (&ev->scroll)) {
375 return key_press (&ev->key);
377 case GDK_KEY_RELEASE:
378 return key_release (&ev->key);
380 case GDK_BUTTON_PRESS:
381 return button_press (&ev->button);
383 case GDK_BUTTON_RELEASE:
384 r = button_release (&ev->button);
385 _note_player.reset();
388 case GDK_MOTION_NOTIFY:
389 _last_event_x = ev->motion.x;
390 _last_event_y = ev->motion.y;
391 return motion (&ev->motion);
397 return RegionView::canvas_group_event (ev);
401 MidiRegionView::enter_notify (GdkEventCrossing* ev)
410 MidiRegionView::leave_notify (GdkEventCrossing*)
419 MidiRegionView::mouse_mode_changed ()
421 // Adjust frame colour (become more transparent for internal tools)
425 if (trackview.editor().internal_editing()) {
426 // Switched in to internal editing mode while entered
429 // Switched out of internal editing mode while entered
436 MidiRegionView::enter_internal()
438 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
439 // Show ghost note under pencil
440 create_ghost_note(_last_event_x, _last_event_y);
443 if (!_selection.empty()) {
444 // Grab keyboard for moving selected notes with arrow keys
445 Keyboard::magic_widget_grab_focus();
446 _grabbed_keyboard = true;
449 // Lower frame handles below notes so they don't steal events
450 if (frame_handle_start) {
451 frame_handle_start->lower_to_bottom();
453 if (frame_handle_end) {
454 frame_handle_end->lower_to_bottom();
459 MidiRegionView::leave_internal()
461 trackview.editor().verbose_cursor()->hide ();
462 remove_ghost_note ();
464 if (_grabbed_keyboard) {
465 Keyboard::magic_widget_drop_focus();
466 _grabbed_keyboard = false;
469 // Raise frame handles above notes so they catch events
470 if (frame_handle_start) {
471 frame_handle_start->raise_to_top();
473 if (frame_handle_end) {
474 frame_handle_end->raise_to_top();
479 MidiRegionView::button_press (GdkEventButton* ev)
481 if (ev->button != 1) {
485 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
486 MouseMode m = editor->current_mouse_mode();
488 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
489 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
492 if (_mouse_state != SelectTouchDragging) {
494 _pressed_button = ev->button;
495 _mouse_state = Pressed;
500 _pressed_button = ev->button;
506 MidiRegionView::button_release (GdkEventButton* ev)
508 double event_x, event_y;
510 if (ev->button != 1) {
517 group->canvas_to_item (event_x, event_y);
520 PublicEditor& editor = trackview.editor ();
522 _press_cursor_ctx.reset();
524 switch (_mouse_state) {
525 case Pressed: // Clicked
527 switch (editor.current_mouse_mode()) {
529 /* no motion occured - simple click */
538 if (Keyboard::is_insert_note_event(ev)) {
540 double event_x, event_y;
544 group->canvas_to_item (event_x, event_y);
546 Evoral::MusicalTime beats = get_grid_beats(editor.pixel_to_sample(event_x));
548 /* Shorten the length by 1 tick so that we can add a new note at the next
549 grid snap without it overlapping this one.
551 beats -= Evoral::MusicalTime::tick();
553 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
560 Evoral::MusicalTime beats = get_grid_beats(editor.pixel_to_sample(event_x));
562 /* Shorten the length by 1 tick so that we can add a new note at the next
563 grid snap without it overlapping this one.
565 beats -= Evoral::MusicalTime::tick();
567 create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
578 case SelectRectDragging:
580 editor.drags()->end_grab ((GdkEvent *) ev);
582 create_ghost_note (ev->x, ev->y);
594 MidiRegionView::motion (GdkEventMotion* ev)
596 PublicEditor& editor = trackview.editor ();
598 if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
599 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
600 _mouse_state != AddDragging) {
602 create_ghost_note (ev->x, ev->y);
604 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
605 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
607 update_ghost_note (ev->x, ev->y);
609 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
611 remove_ghost_note ();
612 editor.verbose_cursor()->hide ();
614 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
616 update_ghost_note (ev->x, ev->y);
619 /* any motion immediately hides velocity text that may have been visible */
621 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
622 (*i)->hide_velocity ();
625 switch (_mouse_state) {
628 if (_pressed_button == 1) {
630 MouseMode m = editor.current_mouse_mode();
632 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
633 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
634 _mouse_state = AddDragging;
635 remove_ghost_note ();
636 editor.verbose_cursor()->hide ();
638 } else if (m == MouseContent) {
639 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
640 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
643 _mouse_state = SelectRectDragging;
645 } else if (m == MouseRange) {
646 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
647 _mouse_state = SelectVerticalDragging;
654 case SelectRectDragging:
655 case SelectVerticalDragging:
657 editor.drags()->motion_handler ((GdkEvent *) ev, false);
660 case SelectTouchDragging:
668 /* we may be dragging some non-note object (eg. patch-change, sysex)
671 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
676 MidiRegionView::scroll (GdkEventScroll* ev)
678 if (_selection.empty()) {
682 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
683 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
684 it still works for zoom.
689 trackview.editor().verbose_cursor()->hide ();
691 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
692 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
694 if (ev->direction == GDK_SCROLL_UP) {
695 change_velocities (true, fine, false, together);
696 } else if (ev->direction == GDK_SCROLL_DOWN) {
697 change_velocities (false, fine, false, together);
699 /* left, right: we don't use them */
707 MidiRegionView::key_press (GdkEventKey* ev)
709 /* since GTK bindings are generally activated on press, and since
710 detectable auto-repeat is the name of the game and only sends
711 repeated presses, carry out key actions at key press, not release.
714 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
716 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
717 _mouse_state = SelectTouchDragging;
720 } else if (ev->keyval == GDK_Escape && unmodified) {
724 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
726 bool start = (ev->keyval == GDK_comma);
727 bool end = (ev->keyval == GDK_period);
728 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
729 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
731 change_note_lengths (fine, shorter, Evoral::MusicalTime(), start, end);
735 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
737 if (_selection.empty()) {
744 } else if (ev->keyval == GDK_Tab) {
746 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
747 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
749 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
753 } else if (ev->keyval == GDK_ISO_Left_Tab) {
755 /* Shift-TAB generates ISO Left Tab, for some reason */
757 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
758 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
760 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
766 } else if (ev->keyval == GDK_Up) {
768 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
769 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
770 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
772 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
773 change_velocities (true, fine, allow_smush, together);
775 transpose (true, fine, allow_smush);
779 } else if (ev->keyval == GDK_Down) {
781 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
782 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
783 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
785 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
786 change_velocities (false, fine, allow_smush, together);
788 transpose (false, fine, allow_smush);
792 } else if (ev->keyval == GDK_Left) {
794 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
795 nudge_notes (false, fine);
798 } else if (ev->keyval == GDK_Right) {
800 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
801 nudge_notes (true, fine);
804 } else if (ev->keyval == GDK_c && unmodified) {
808 } else if (ev->keyval == GDK_v && unmodified) {
817 MidiRegionView::key_release (GdkEventKey* ev)
819 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
827 MidiRegionView::channel_edit ()
829 if (_selection.empty()) {
833 /* pick a note somewhat at random (since Selection is a set<>) to
834 * provide the "current" channel for the dialog.
837 uint8_t current_channel = (*_selection.begin())->note()->channel ();
838 MidiChannelDialog channel_dialog (current_channel);
839 int ret = channel_dialog.run ();
842 case Gtk::RESPONSE_OK:
848 uint8_t new_channel = channel_dialog.active_channel ();
850 start_note_diff_command (_("channel edit"));
852 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
853 Selection::iterator next = i;
855 change_note_channel (*i, new_channel);
863 MidiRegionView::velocity_edit ()
865 if (_selection.empty()) {
869 /* pick a note somewhat at random (since Selection is a set<>) to
870 * provide the "current" velocity for the dialog.
873 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
874 MidiVelocityDialog velocity_dialog (current_velocity);
875 int ret = velocity_dialog.run ();
878 case Gtk::RESPONSE_OK:
884 uint8_t new_velocity = velocity_dialog.velocity ();
886 start_note_diff_command (_("velocity edit"));
888 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
889 Selection::iterator next = i;
891 change_note_velocity (*i, new_velocity, false);
899 MidiRegionView::show_list_editor ()
902 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
904 _list_editor->present ();
907 /** Add a note to the model, and the view, at a canvas (click) coordinate.
908 * \param t time in frames relative to the position of the region
909 * \param y vertical position in pixels
910 * \param length duration of the note in beats
911 * \param snap_t true to snap t to the grid, otherwise false.
914 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::MusicalTime length, bool snap_t)
916 if (length < 2 * DBL_EPSILON) {
920 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
921 MidiStreamView* const view = mtv->midi_view();
923 // Start of note in frames relative to region start
925 framecnt_t grid_frames;
926 t = snap_frame_to_grid_underneath (t, grid_frames);
929 const MidiModel::TimeType beat_time = region_frames_to_region_beats(
930 t + _region->start());
932 const double note = view->y_to_note(y);
933 const uint8_t chan = mtv->get_channel_for_add();
934 const uint8_t velocity = get_velocity_for_add(beat_time);
936 const boost::shared_ptr<NoteType> new_note(
937 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
939 if (_model->contains (new_note)) {
943 view->update_note_range(new_note->note());
945 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
947 _model->apply_command(*trackview.session(), cmd);
949 play_midi_note (new_note);
953 MidiRegionView::clear_events (bool with_selection_signal)
955 clear_selection (with_selection_signal);
958 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
959 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
964 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
969 _patch_changes.clear();
971 _optimization_iterator = _events.end();
975 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
979 content_connection.disconnect ();
980 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
984 if (_enable_display) {
990 MidiRegionView::start_note_diff_command (string name)
992 if (!_note_diff_command) {
993 _note_diff_command = _model->new_note_diff_command (name);
998 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1000 if (_note_diff_command) {
1001 _note_diff_command->add (note);
1004 _marked_for_selection.insert(note);
1006 if (show_velocity) {
1007 _marked_for_velocity.insert(note);
1012 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1014 if (_note_diff_command && ev->note()) {
1015 _note_diff_command->remove(ev->note());
1020 MidiRegionView::note_diff_add_change (NoteBase* ev,
1021 MidiModel::NoteDiffCommand::Property property,
1024 if (_note_diff_command) {
1025 _note_diff_command->change (ev->note(), property, val);
1030 MidiRegionView::note_diff_add_change (NoteBase* ev,
1031 MidiModel::NoteDiffCommand::Property property,
1032 Evoral::MusicalTime val)
1034 if (_note_diff_command) {
1035 _note_diff_command->change (ev->note(), property, val);
1040 MidiRegionView::apply_diff (bool as_subcommand)
1044 if (!_note_diff_command) {
1048 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1049 // Mark all selected notes for selection when model reloads
1050 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1051 _marked_for_selection.insert((*i)->note());
1055 if (as_subcommand) {
1056 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1058 _model->apply_command (*trackview.session(), _note_diff_command);
1061 _note_diff_command = 0;
1062 midi_view()->midi_track()->playlist_modified();
1064 if (add_or_remove) {
1065 _marked_for_selection.clear();
1068 _marked_for_velocity.clear();
1072 MidiRegionView::abort_command()
1074 delete _note_diff_command;
1075 _note_diff_command = 0;
1080 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1082 if (_optimization_iterator != _events.end()) {
1083 ++_optimization_iterator;
1086 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1087 return *_optimization_iterator;
1090 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1091 if ((*_optimization_iterator)->note() == note) {
1092 return *_optimization_iterator;
1100 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1102 MidiModel::Notes notes;
1103 _model->get_notes (notes, op, val, chan_mask);
1105 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1106 NoteBase* cne = find_canvas_note (*n);
1114 MidiRegionView::redisplay_model()
1116 if (_active_notes) {
1117 /* Recording, so just update canvas events to reflect changes
1118 in zoom or whatever without touching model. */
1119 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1129 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1130 (*i)->invalidate ();
1133 MidiModel::ReadLock lock(_model->read_lock());
1135 MidiModel::Notes& notes (_model->notes());
1136 _optimization_iterator = _events.begin();
1138 bool empty_when_starting = _events.empty();
1140 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1142 boost::shared_ptr<NoteType> note (*n);
1146 if (note_in_region_range (note, visible)) {
1148 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1161 add_note (note, visible);
1166 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1174 /* remove note items that are no longer valid */
1176 if (!empty_when_starting) {
1177 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1178 if (!(*i)->valid ()) {
1180 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1181 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1183 gr->remove_note (*i);
1188 i = _events.erase (i);
1196 _patch_changes.clear();
1200 display_patch_changes ();
1202 _marked_for_selection.clear ();
1203 _marked_for_velocity.clear ();
1205 /* we may have caused _events to contain things out of order (e.g. if a note
1206 moved earlier or later). we don't generally need them in time order, but
1207 make a note that a sort is required for those cases that require it.
1210 _sort_needed = true;
1214 MidiRegionView::display_patch_changes ()
1216 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1217 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1219 for (uint8_t i = 0; i < 16; ++i) {
1220 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1224 /** @param active_channel true to display patch changes fully, false to display
1225 * them `greyed-out' (as on an inactive channel)
1228 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1230 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1232 if ((*i)->channel() != channel) {
1236 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1237 add_canvas_patch_change (*i, patch_name, active_channel);
1242 MidiRegionView::display_sysexes()
1244 bool have_periodic_system_messages = false;
1245 bool display_periodic_messages = true;
1247 if (!ARDOUR_UI::config()->get_never_display_periodic_midi()) {
1249 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1250 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1251 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1254 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1255 have_periodic_system_messages = true;
1261 if (have_periodic_system_messages) {
1262 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1264 /* get an approximate value for the number of samples per video frame */
1266 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1268 /* if we are zoomed out beyond than the cutoff (i.e. more
1269 * frames per pixel than frames per 4 video frames), don't
1270 * show periodic sysex messages.
1273 if (zoom > (video_frame*4)) {
1274 display_periodic_messages = false;
1278 display_periodic_messages = false;
1281 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1283 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1284 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1286 Evoral::MusicalTime time = (*i)->time();
1289 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1290 if (!display_periodic_messages) {
1298 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1299 str << int((*i)->buffer()[b]);
1300 if (b != (*i)->size() -1) {
1304 string text = str.str();
1306 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1308 double height = midi_stream_view()->contents_height();
1310 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1311 // SysEx canvas object!!!
1313 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1314 new SysEx (*this, _note_group, text, height, x, 1.0));
1316 // Show unless message is beyond the region bounds
1317 if (time - _region->start() >= _region->length() || time < _region->start()) {
1323 _sys_exes.push_back(sysex);
1327 MidiRegionView::~MidiRegionView ()
1329 in_destructor = true;
1331 trackview.editor().verbose_cursor()->hide ();
1333 note_delete_connection.disconnect ();
1335 delete _list_editor;
1337 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1339 if (_active_notes) {
1343 _selection_cleared_connection.disconnect ();
1346 clear_events (false);
1349 delete _note_diff_command;
1350 delete _step_edit_cursor;
1351 delete _temporary_note_group;
1355 MidiRegionView::region_resized (const PropertyChange& what_changed)
1357 RegionView::region_resized(what_changed);
1359 if (what_changed.contains (ARDOUR::Properties::position)) {
1360 _region_relative_time_converter.set_origin_b(_region->position());
1361 set_duration(_region->length(), 0);
1362 if (_enable_display) {
1367 if (what_changed.contains (ARDOUR::Properties::start) ||
1368 what_changed.contains (ARDOUR::Properties::position)) {
1369 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1374 MidiRegionView::reset_width_dependent_items (double pixel_width)
1376 RegionView::reset_width_dependent_items(pixel_width);
1378 if (_enable_display) {
1382 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1383 if ((*x)->canvas_item()->width() >= _pixel_width) {
1390 move_step_edit_cursor (_step_edit_cursor_position);
1391 set_step_edit_cursor_width (_step_edit_cursor_width);
1395 MidiRegionView::set_height (double height)
1397 double old_height = _height;
1398 RegionView::set_height(height);
1400 apply_note_range (midi_stream_view()->lowest_note(),
1401 midi_stream_view()->highest_note(),
1402 height != old_height);
1405 name_text->raise_to_top();
1408 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1409 (*x)->set_height (midi_stream_view()->contents_height());
1412 if (_step_edit_cursor) {
1413 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1418 /** Apply the current note range from the stream view
1419 * by repositioning/hiding notes as necessary
1422 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1424 if (!_enable_display) {
1428 if (!force && _current_range_min == min && _current_range_max == max) {
1432 _current_range_min = min;
1433 _current_range_max = max;
1435 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1436 NoteBase* event = *i;
1437 boost::shared_ptr<NoteType> note (event->note());
1439 if (note->note() < _current_range_min ||
1440 note->note() > _current_range_max) {
1446 if (Note* cnote = dynamic_cast<Note*>(event)) {
1448 const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
1449 const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
1454 } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
1461 MidiRegionView::add_ghost (TimeAxisView& tv)
1463 double unit_position = _region->position () / samples_per_pixel;
1464 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1465 MidiGhostRegion* ghost;
1467 if (mtv && mtv->midi_view()) {
1468 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1469 to allow having midi notes on top of note lines and waveforms.
1471 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1473 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1476 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1477 ghost->add_note(*i);
1480 ghost->set_height ();
1481 ghost->set_duration (_region->length() / samples_per_pixel);
1482 ghosts.push_back (ghost);
1484 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1490 /** Begin tracking note state for successive calls to add_event
1493 MidiRegionView::begin_write()
1495 if (_active_notes) {
1496 delete[] _active_notes;
1498 _active_notes = new Note*[128];
1499 for (unsigned i = 0; i < 128; ++i) {
1500 _active_notes[i] = 0;
1505 /** Destroy note state for add_event
1508 MidiRegionView::end_write()
1510 delete[] _active_notes;
1512 _marked_for_selection.clear();
1513 _marked_for_velocity.clear();
1517 /** Resolve an active MIDI note (while recording).
1520 MidiRegionView::resolve_note(uint8_t note, Evoral::MusicalTime end_time)
1522 if (midi_view()->note_mode() != Sustained) {
1526 if (_active_notes && _active_notes[note]) {
1528 /* XXX is end_time really region-centric? I think so, because
1529 this is a new region that we're recording, so source zero is
1530 the same as region zero
1532 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1534 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1535 _active_notes[note]->set_outline_all ();
1536 _active_notes[note] = 0;
1542 /** Extend active notes to rightmost edge of region (if length is changed)
1545 MidiRegionView::extend_active_notes()
1547 if (!_active_notes) {
1551 for (unsigned i=0; i < 128; ++i) {
1552 if (_active_notes[i]) {
1553 _active_notes[i]->set_x1(
1554 trackview.editor().sample_to_pixel(_region->position() + _region->length()));
1561 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1563 if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1567 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1569 if (!route_ui || !route_ui->midi_track()) {
1573 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1577 /* NotePlayer deletes itself */
1581 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1583 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1584 start_playing_midi_chord(notes);
1588 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1590 if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
1594 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1596 if (!route_ui || !route_ui->midi_track()) {
1600 _note_player = boost::shared_ptr<NotePlayer>(new NotePlayer(route_ui->midi_track()));
1602 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1603 _note_player->add (*n);
1606 _note_player->on ();
1611 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1613 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1614 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1616 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1617 (note->note() <= midi_stream_view()->highest_note());
1623 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1627 if ((sus = dynamic_cast<Note*>(note))) {
1628 update_sustained(sus, update_ghost_regions);
1629 } else if ((hit = dynamic_cast<Hit*>(note))) {
1630 update_hit(hit, update_ghost_regions);
1634 /** Update a canvas note's size from its model note.
1635 * @param ev Canvas note to update.
1636 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1639 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1641 boost::shared_ptr<NoteType> note = ev->note();
1642 const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
1643 const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
1648 /* trim note display to not overlap the end of its region */
1650 if (note->length() > 0) {
1651 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1652 ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames));
1654 ev->set_x1 (trackview.editor().sample_to_pixel (_region->length()));
1657 ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
1659 if (!note->length()) {
1660 if (_active_notes && note->note() < 128) {
1661 // If this note is already active there's a stuck note,
1662 // finish the old note rectangle
1663 if (_active_notes[note->note()]) {
1664 Note* const old_rect = _active_notes[note->note()];
1665 boost::shared_ptr<NoteType> old_note = old_rect->note();
1666 old_rect->set_x1 (x);
1667 old_rect->set_outline_all ();
1669 _active_notes[note->note()] = ev;
1671 /* outline all but right edge */
1672 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1673 ArdourCanvas::Rectangle::TOP|
1674 ArdourCanvas::Rectangle::LEFT|
1675 ArdourCanvas::Rectangle::BOTTOM));
1677 /* outline all edges */
1678 ev->set_outline_all ();
1681 // Update color in case velocity has changed
1682 ev->set_fill_color(ev->base_color());
1683 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1685 if (update_ghost_regions) {
1686 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1687 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1689 gr->update_note (ev);
1696 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1698 boost::shared_ptr<NoteType> note = ev->note();
1700 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1701 const double x = trackview.editor().sample_to_pixel(note_start_frames);
1702 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1703 const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
1705 ev->set_position (ArdourCanvas::Duple (x, y));
1706 ev->set_height (diamond_size);
1708 // Update color in case velocity has changed
1709 ev->set_fill_color(ev->base_color());
1710 ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
1712 if (update_ghost_regions) {
1713 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1714 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1716 gr->update_note (ev);
1722 /** Add a MIDI note to the view (with length).
1724 * If in sustained mode, notes with length 0 will be considered active
1725 * notes, and resolve_note should be called when the corresponding note off
1726 * event arrives, to properly display the note.
1729 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1731 NoteBase* event = 0;
1733 if (midi_view()->note_mode() == Sustained) {
1735 Note* ev_rect = new Note (*this, _note_group, note);
1737 update_note (ev_rect);
1741 } else if (midi_view()->note_mode() == Percussive) {
1743 const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
1745 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1747 update_hit (ev_diamond);
1756 MidiGhostRegion* gr;
1758 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1759 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1760 gr->add_note(event);
1764 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1765 note_selected(event, true);
1768 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1769 event->show_velocity();
1772 event->on_channel_selection_change (get_selected_channels());
1773 _events.push_back(event);
1782 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1783 MidiStreamView* const view = mtv->midi_view();
1785 view->update_note_range (note->note());
1789 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1790 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1792 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1794 /* potentially extend region to hold new note */
1796 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1797 framepos_t region_end = _region->last_frame();
1799 if (end_frame > region_end) {
1800 _region->set_length (end_frame - _region->position());
1803 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1804 MidiStreamView* const view = mtv->midi_view();
1806 view->update_note_range(new_note->note());
1808 _marked_for_selection.clear ();
1811 start_note_diff_command (_("step add"));
1812 note_diff_add_note (new_note, true, false);
1815 // last_step_edit_note = new_note;
1819 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1821 change_note_lengths (false, false, beats, false, true);
1824 /** Add a new patch change flag to the canvas.
1825 * @param patch the patch change to add
1826 * @param the text to display in the flag
1827 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1830 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1832 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1833 const double x = trackview.editor().sample_to_pixel (region_frames);
1835 double const height = midi_stream_view()->contents_height();
1837 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1838 // so we need to do something more sophisticated to keep its color
1839 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1842 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1843 new PatchChange(*this, group,
1850 if (patch_change->item().width() < _pixel_width) {
1851 // Show unless patch change is beyond the region bounds
1852 if (region_frames < 0 || region_frames >= _region->length()) {
1853 patch_change->hide();
1855 patch_change->show();
1858 patch_change->hide ();
1861 _patch_changes.push_back (patch_change);
1864 MIDI::Name::PatchPrimaryKey
1865 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1867 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1870 /// Return true iff @p pc applies to the given time on the given channel.
1872 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::MusicalTime time, uint8_t channel)
1874 return pc->time() <= time && pc->channel() == channel;
1878 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1880 // The earliest event not before time
1881 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1883 // Go backwards until we find the latest PC for this channel, or the start
1884 while (i != _model->patch_changes().begin() &&
1885 (i == _model->patch_changes().end() ||
1886 !patch_applies(*i, time, channel))) {
1890 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1891 key.set_bank((*i)->bank());
1892 key.set_program((*i)->program ());
1900 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1902 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1904 if (pc.patch()->program() != new_patch.program()) {
1905 c->change_program (pc.patch (), new_patch.program());
1908 int const new_bank = new_patch.bank();
1909 if (pc.patch()->bank() != new_bank) {
1910 c->change_bank (pc.patch (), new_bank);
1913 _model->apply_command (*trackview.session(), c);
1915 _patch_changes.clear ();
1916 display_patch_changes ();
1920 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1922 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1924 if (old_change->time() != new_change.time()) {
1925 c->change_time (old_change, new_change.time());
1928 if (old_change->channel() != new_change.channel()) {
1929 c->change_channel (old_change, new_change.channel());
1932 if (old_change->program() != new_change.program()) {
1933 c->change_program (old_change, new_change.program());
1936 if (old_change->bank() != new_change.bank()) {
1937 c->change_bank (old_change, new_change.bank());
1940 _model->apply_command (*trackview.session(), c);
1942 _patch_changes.clear ();
1943 display_patch_changes ();
1946 /** Add a patch change to the region.
1947 * @param t Time in frames relative to region position
1948 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1949 * MidiTimeAxisView::get_channel_for_add())
1952 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1954 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1956 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1957 c->add (MidiModel::PatchChangePtr (
1958 new Evoral::PatchChange<Evoral::MusicalTime> (
1959 absolute_frames_to_source_beats (_region->position() + t),
1960 mtv->get_channel_for_add(), patch.program(), patch.bank()
1965 _model->apply_command (*trackview.session(), c);
1967 _patch_changes.clear ();
1968 display_patch_changes ();
1972 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::MusicalTime t)
1974 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1975 c->change_time (pc.patch (), t);
1976 _model->apply_command (*trackview.session(), c);
1978 _patch_changes.clear ();
1979 display_patch_changes ();
1983 MidiRegionView::delete_patch_change (PatchChange* pc)
1985 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1986 c->remove (pc->patch ());
1987 _model->apply_command (*trackview.session(), c);
1989 _patch_changes.clear ();
1990 display_patch_changes ();
1994 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
1996 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
1998 key.set_bank(key.bank() + delta);
2000 key.set_program(key.program() + delta);
2002 change_patch_change(patch, key);
2006 MidiRegionView::maybe_remove_deleted_note_from_selection (NoteBase* cne)
2008 if (_selection.empty()) {
2012 _selection.erase (cne);
2016 MidiRegionView::delete_selection()
2018 if (_selection.empty()) {
2022 start_note_diff_command (_("delete selection"));
2024 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2025 if ((*i)->selected()) {
2026 _note_diff_command->remove((*i)->note());
2036 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2038 start_note_diff_command (_("delete note"));
2039 _note_diff_command->remove (n);
2042 trackview.editor().verbose_cursor()->hide ();
2046 MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
2048 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2050 Selection::iterator tmp = i;
2053 (*i)->set_selected (false);
2054 (*i)->hide_velocity ();
2055 _selection.erase (i);
2063 if (!ev && _entered) {
2064 // Clearing selection entirely, ungrab keyboard
2065 Keyboard::magic_widget_drop_focus();
2066 _grabbed_keyboard = false;
2069 /* this does not change the status of this regionview w.r.t the editor
2074 SelectionCleared (this); /* EMIT SIGNAL */
2079 MidiRegionView::unique_select(NoteBase* ev)
2081 const bool selection_was_empty = _selection.empty();
2083 clear_selection_except (ev);
2085 /* don't bother with checking to see if we should remove this
2086 regionview from the editor selection, since we're about to add
2087 another note, and thus put/keep this regionview in the editor
2091 if (!ev->selected()) {
2092 add_to_selection (ev);
2093 if (selection_was_empty && _entered) {
2094 // Grab keyboard for moving notes with arrow keys
2095 Keyboard::magic_widget_grab_focus();
2096 _grabbed_keyboard = true;
2102 MidiRegionView::select_all_notes ()
2106 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2107 add_to_selection (*i);
2112 MidiRegionView::select_range (framepos_t start, framepos_t end)
2116 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2117 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2118 if (t >= start && t <= end) {
2119 add_to_selection (*i);
2125 MidiRegionView::invert_selection ()
2127 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2128 if ((*i)->selected()) {
2129 remove_from_selection(*i);
2131 add_to_selection (*i);
2137 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2139 bool have_selection = !_selection.empty();
2140 uint8_t low_note = 127;
2141 uint8_t high_note = 0;
2142 MidiModel::Notes& notes (_model->notes());
2143 _optimization_iterator = _events.begin();
2145 if (extend && !have_selection) {
2149 /* scan existing selection to get note range */
2151 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2152 if ((*i)->note()->note() < low_note) {
2153 low_note = (*i)->note()->note();
2155 if ((*i)->note()->note() > high_note) {
2156 high_note = (*i)->note()->note();
2163 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2164 /* only note previously selected is the one we are
2165 * reselecting. treat this as cancelling the selection.
2172 low_note = min (low_note, notenum);
2173 high_note = max (high_note, notenum);
2176 _no_sound_notes = true;
2178 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2180 boost::shared_ptr<NoteType> note (*n);
2182 bool select = false;
2184 if (((1 << note->channel()) & channel_mask) != 0) {
2186 if ((note->note() >= low_note && note->note() <= high_note)) {
2189 } else if (note->note() == notenum) {
2195 if ((cne = find_canvas_note (note)) != 0) {
2196 // extend is false because we've taken care of it,
2197 // since it extends by time range, not pitch.
2198 note_selected (cne, add, false);
2202 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2206 _no_sound_notes = false;
2210 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2212 MidiModel::Notes& notes (_model->notes());
2213 _optimization_iterator = _events.begin();
2215 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2217 boost::shared_ptr<NoteType> note (*n);
2220 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2221 if ((cne = find_canvas_note (note)) != 0) {
2222 if (cne->selected()) {
2223 note_deselected (cne);
2225 note_selected (cne, true, false);
2233 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2236 clear_selection_except (ev);
2237 if (!_selection.empty()) {
2238 PublicEditor& editor (trackview.editor());
2239 editor.get_selection().add (this);
2245 if (!ev->selected()) {
2246 add_to_selection (ev);
2250 /* find end of latest note selected, select all between that and the start of "ev" */
2252 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2253 Evoral::MusicalTime latest = Evoral::MusicalTime();
2255 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2256 if ((*i)->note()->end_time() > latest) {
2257 latest = (*i)->note()->end_time();
2259 if ((*i)->note()->time() < earliest) {
2260 earliest = (*i)->note()->time();
2264 if (ev->note()->end_time() > latest) {
2265 latest = ev->note()->end_time();
2268 if (ev->note()->time() < earliest) {
2269 earliest = ev->note()->time();
2272 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2274 /* find notes entirely within OR spanning the earliest..latest range */
2276 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2277 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2278 add_to_selection (*i);
2286 MidiRegionView::note_deselected(NoteBase* ev)
2288 remove_from_selection (ev);
2292 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2294 PublicEditor& editor = trackview.editor();
2296 // Convert to local coordinates
2297 const framepos_t p = _region->position();
2298 const double y = midi_view()->y_position();
2299 const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2300 const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2301 const double y0 = max(0.0, gy0 - y);
2302 const double y1 = max(0.0, gy1 - y);
2304 // TODO: Make this faster by storing the last updated selection rect, and only
2305 // adjusting things that are in the area that appears/disappeared.
2306 // We probably need a tree to be able to find events in O(log(n)) time.
2308 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2309 if ((*i)->x0() < x1 && (*i)->x1() > x0 && (*i)->y0() < y1 && (*i)->y1() > y0) {
2310 // Rectangles intersect
2311 if (!(*i)->selected()) {
2312 add_to_selection (*i);
2314 } else if ((*i)->selected() && !extend) {
2315 // Rectangles do not intersect
2316 remove_from_selection (*i);
2320 typedef RouteTimeAxisView::AutomationTracks ATracks;
2321 typedef std::list<Selectable*> Selectables;
2323 /* Add control points to selection. */
2324 const ATracks& atracks = midi_view()->automation_tracks();
2325 Selectables selectables;
2326 editor.get_selection().clear_points();
2327 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2328 a->second->get_selectables(start, end, gy0, gy1, selectables);
2329 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2330 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2332 editor.get_selection().add(cp);
2335 a->second->set_selected_points(editor.get_selection().points);
2340 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2346 // TODO: Make this faster by storing the last updated selection rect, and only
2347 // adjusting things that are in the area that appears/disappeared.
2348 // We probably need a tree to be able to find events in O(log(n)) time.
2350 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2351 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2352 // within y- (note-) range
2353 if (!(*i)->selected()) {
2354 add_to_selection (*i);
2356 } else if ((*i)->selected() && !extend) {
2357 remove_from_selection (*i);
2363 MidiRegionView::remove_from_selection (NoteBase* ev)
2365 Selection::iterator i = _selection.find (ev);
2367 if (i != _selection.end()) {
2368 _selection.erase (i);
2369 if (_selection.empty() && _grabbed_keyboard) {
2371 Keyboard::magic_widget_drop_focus();
2372 _grabbed_keyboard = false;
2376 ev->set_selected (false);
2377 ev->hide_velocity ();
2379 if (_selection.empty()) {
2380 PublicEditor& editor (trackview.editor());
2381 editor.get_selection().remove (this);
2386 MidiRegionView::add_to_selection (NoteBase* ev)
2388 const bool selection_was_empty = _selection.empty();
2390 if (_selection.insert (ev).second) {
2391 ev->set_selected (true);
2392 start_playing_midi_note ((ev)->note());
2393 if (selection_was_empty && _entered) {
2394 // Grab keyboard for moving notes with arrow keys
2395 Keyboard::magic_widget_grab_focus();
2396 _grabbed_keyboard = true;
2400 if (selection_was_empty) {
2401 PublicEditor& editor (trackview.editor());
2402 editor.get_selection().add (this);
2407 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2409 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2410 PossibleChord to_play;
2411 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2413 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2414 if ((*i)->note()->time() < earliest) {
2415 earliest = (*i)->note()->time();
2419 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2420 if ((*i)->note()->time() == earliest) {
2421 to_play.push_back ((*i)->note());
2423 (*i)->move_event(dx, dy);
2426 if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) {
2428 if (to_play.size() > 1) {
2430 PossibleChord shifted;
2432 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2433 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2434 moved_note->set_note (moved_note->note() + cumulative_dy);
2435 shifted.push_back (moved_note);
2438 start_playing_midi_chord (shifted);
2440 } else if (!to_play.empty()) {
2442 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2443 moved_note->set_note (moved_note->note() + cumulative_dy);
2444 start_playing_midi_note (moved_note);
2450 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2452 uint8_t lowest_note_in_selection = 127;
2453 uint8_t highest_note_in_selection = 0;
2454 uint8_t highest_note_difference = 0;
2456 // find highest and lowest notes first
2458 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2459 uint8_t pitch = (*i)->note()->note();
2460 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2461 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2465 cerr << "dnote: " << (int) dnote << endl;
2466 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2467 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2468 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2469 << int(highest_note_in_selection) << endl;
2470 cerr << "selection size: " << _selection.size() << endl;
2471 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2474 // Make sure the note pitch does not exceed the MIDI standard range
2475 if (highest_note_in_selection + dnote > 127) {
2476 highest_note_difference = highest_note_in_selection - 127;
2479 start_note_diff_command (_("move notes"));
2481 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2483 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2484 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2490 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2492 uint8_t original_pitch = (*i)->note()->note();
2493 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2495 // keep notes in standard midi range
2496 clamp_to_0_127(new_pitch);
2498 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2499 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2501 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2506 // care about notes being moved beyond the upper/lower bounds on the canvas
2507 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2508 highest_note_in_selection > midi_stream_view()->highest_note()) {
2509 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2513 /** @param x Pixel relative to the region position.
2514 * @return Snapped frame relative to the region position.
2517 MidiRegionView::snap_pixel_to_sample(double x)
2519 PublicEditor& editor (trackview.editor());
2520 return snap_frame_to_frame (editor.pixel_to_sample (x));
2523 /** @param x Pixel relative to the region position.
2524 * @return Snapped pixel relative to the region position.
2527 MidiRegionView::snap_to_pixel(double x)
2529 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x));
2533 MidiRegionView::get_position_pixels()
2535 framepos_t region_frame = get_position();
2536 return trackview.editor().sample_to_pixel(region_frame);
2540 MidiRegionView::get_end_position_pixels()
2542 framepos_t frame = get_position() + get_duration ();
2543 return trackview.editor().sample_to_pixel(frame);
2547 MidiRegionView::source_beats_to_absolute_frames(Evoral::MusicalTime beats) const
2549 /* the time converter will return the frame corresponding to `beats'
2550 relative to the start of the source. The start of the source
2551 is an implied position given by region->position - region->start
2553 const framepos_t source_start = _region->position() - _region->start();
2554 return source_start + _source_relative_time_converter.to (beats);
2558 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2560 /* the `frames' argument needs to be converted into a frame count
2561 relative to the start of the source before being passed in to the
2564 const framepos_t source_start = _region->position() - _region->start();
2565 return _source_relative_time_converter.from (frames - source_start);
2569 MidiRegionView::region_beats_to_region_frames(Evoral::MusicalTime beats) const
2571 return _region_relative_time_converter.to(beats);
2575 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2577 return _region_relative_time_converter.from(frames);
2581 MidiRegionView::begin_resizing (bool /*at_front*/)
2583 _resize_data.clear();
2585 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2586 Note *note = dynamic_cast<Note*> (*i);
2588 // only insert CanvasNotes into the map
2590 NoteResizeData *resize_data = new NoteResizeData();
2591 resize_data->note = note;
2593 // create a new SimpleRect from the note which will be the resize preview
2594 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2595 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2597 // calculate the colors: get the color settings
2598 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2599 ARDOUR_UI::config()->color ("midi note selected"),
2602 // make the resize preview notes more transparent and bright
2603 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2605 // calculate color based on note velocity
2606 resize_rect->set_fill_color (UINT_INTERPOLATE(
2607 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2611 resize_rect->set_outline_color (NoteBase::calculate_outline (
2612 ARDOUR_UI::config()->color ("midi note selected")));
2614 resize_data->resize_rect = resize_rect;
2615 _resize_data.push_back(resize_data);
2620 /** Update resizing notes while user drags.
2621 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2622 * @param at_front which end of the note (true == note on, false == note off)
2623 * @param delta_x change in mouse position since the start of the drag
2624 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2625 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2626 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2627 * as the \a primary note.
2630 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2632 bool cursor_set = false;
2634 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2635 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2636 Note* canvas_note = (*i)->note;
2641 current_x = canvas_note->x0() + delta_x;
2643 current_x = primary->x0() + delta_x;
2647 current_x = canvas_note->x1() + delta_x;
2649 current_x = primary->x1() + delta_x;
2653 if (current_x < 0) {
2654 // This works even with snapping because RegionView::snap_frame_to_frame()
2655 // snaps forward if the snapped sample is before the beginning of the region
2658 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2659 current_x = trackview.editor().sample_to_pixel(_region->length());
2663 resize_rect->set_x0 (snap_to_pixel(current_x));
2664 resize_rect->set_x1 (canvas_note->x1());
2666 resize_rect->set_x1 (snap_to_pixel(current_x));
2667 resize_rect->set_x0 (canvas_note->x0());
2671 const double snapped_x = snap_pixel_to_sample (current_x);
2672 Evoral::MusicalTime beats = region_frames_to_region_beats (snapped_x);
2673 Evoral::MusicalTime len = Evoral::MusicalTime();
2676 if (beats < canvas_note->note()->end_time()) {
2677 len = canvas_note->note()->time() - beats;
2678 len += canvas_note->note()->length();
2681 if (beats >= canvas_note->note()->time()) {
2682 len = beats - canvas_note->note()->time();
2687 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2688 show_verbose_cursor (buf, 0, 0);
2697 /** Finish resizing notes when the user releases the mouse button.
2698 * Parameters the same as for \a update_resizing().
2701 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
2703 start_note_diff_command (_("resize notes"));
2705 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2706 Note* canvas_note = (*i)->note;
2707 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2709 /* Get the new x position for this resize, which is in pixels relative
2710 * to the region position.
2717 current_x = canvas_note->x0() + delta_x;
2719 current_x = primary->x0() + delta_x;
2723 current_x = canvas_note->x1() + delta_x;
2725 current_x = primary->x1() + delta_x;
2729 if (current_x < 0) {
2732 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2733 current_x = trackview.editor().sample_to_pixel(_region->length());
2736 /* Convert that to a frame within the source */
2737 current_x = snap_pixel_to_sample (current_x) + _region->start ();
2739 /* and then to beats */
2740 const Evoral::MusicalTime x_beats = region_frames_to_region_beats (current_x);
2742 if (at_front && x_beats < canvas_note->note()->end_time()) {
2743 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats);
2745 Evoral::MusicalTime len = canvas_note->note()->time() - x_beats;
2746 len += canvas_note->note()->length();
2749 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2754 const Evoral::MusicalTime len = x_beats - canvas_note->note()->time();
2757 /* XXX convert to beats */
2758 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2766 _resize_data.clear();
2771 MidiRegionView::abort_resizing ()
2773 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2774 delete (*i)->resize_rect;
2778 _resize_data.clear ();
2782 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2784 uint8_t new_velocity;
2787 new_velocity = event->note()->velocity() + velocity;
2788 clamp_to_0_127(new_velocity);
2790 new_velocity = velocity;
2793 event->set_selected (event->selected()); // change color
2795 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2799 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
2804 new_note = event->note()->note() + note;
2809 clamp_to_0_127 (new_note);
2810 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2814 MidiRegionView::trim_note (NoteBase* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2816 bool change_start = false;
2817 bool change_length = false;
2818 Evoral::MusicalTime new_start;
2819 Evoral::MusicalTime new_length;
2821 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2823 front_delta: if positive - move the start of the note later in time (shortening it)
2824 if negative - move the start of the note earlier in time (lengthening it)
2826 end_delta: if positive - move the end of the note later in time (lengthening it)
2827 if negative - move the end of the note earlier in time (shortening it)
2830 if (!!front_delta) {
2831 if (front_delta < 0) {
2833 if (event->note()->time() < -front_delta) {
2834 new_start = Evoral::MusicalTime();
2836 new_start = event->note()->time() + front_delta; // moves earlier
2839 /* start moved toward zero, so move the end point out to where it used to be.
2840 Note that front_delta is negative, so this increases the length.
2843 new_length = event->note()->length() - front_delta;
2844 change_start = true;
2845 change_length = true;
2849 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2851 if (new_pos < event->note()->end_time()) {
2852 new_start = event->note()->time() + front_delta;
2853 /* start moved toward the end, so move the end point back to where it used to be */
2854 new_length = event->note()->length() - front_delta;
2855 change_start = true;
2856 change_length = true;
2863 bool can_change = true;
2864 if (end_delta < 0) {
2865 if (event->note()->length() < -end_delta) {
2871 new_length = event->note()->length() + end_delta;
2872 change_length = true;
2877 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2880 if (change_length) {
2881 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2886 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
2888 uint8_t new_channel;
2892 if (event->note()->channel() < -chn) {
2895 new_channel = event->note()->channel() + chn;
2898 new_channel = event->note()->channel() + chn;
2901 new_channel = (uint8_t) chn;
2904 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2908 MidiRegionView::change_note_time (NoteBase* event, Evoral::MusicalTime delta, bool relative)
2910 Evoral::MusicalTime new_time;
2914 if (event->note()->time() < -delta) {
2915 new_time = Evoral::MusicalTime();
2917 new_time = event->note()->time() + delta;
2920 new_time = event->note()->time() + delta;
2926 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2930 MidiRegionView::change_note_length (NoteBase* event, Evoral::MusicalTime t)
2932 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2936 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2941 if (_selection.empty()) {
2956 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2957 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
2963 start_note_diff_command (_("change velocities"));
2965 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2966 Selection::iterator next = i;
2970 if (i == _selection.begin()) {
2971 change_note_velocity (*i, delta, true);
2972 value = (*i)->note()->velocity() + delta;
2974 change_note_velocity (*i, value, false);
2978 change_note_velocity (*i, delta, true);
2987 if (!_selection.empty()) {
2989 snprintf (buf, sizeof (buf), "Vel %d",
2990 (int) (*_selection.begin())->note()->velocity());
2991 show_verbose_cursor (buf, 10, 10);
2997 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2999 if (_selection.empty()) {
3016 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3018 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3022 if ((int8_t) (*i)->note()->note() + delta > 127) {
3029 start_note_diff_command (_("transpose"));
3031 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3032 Selection::iterator next = i;
3034 change_note_note (*i, delta, true);
3042 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
3046 delta = Evoral::MusicalTime(1.0/128.0);
3048 /* grab the current grid distance */
3049 delta = get_grid_beats(_region->position());
3057 start_note_diff_command (_("change note lengths"));
3059 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3060 Selection::iterator next = i;
3063 /* note the negation of the delta for start */
3066 (start ? -delta : Evoral::MusicalTime()),
3067 (end ? delta : Evoral::MusicalTime()));
3076 MidiRegionView::nudge_notes (bool forward, bool fine)
3078 if (_selection.empty()) {
3082 /* pick a note as the point along the timeline to get the nudge distance.
3083 its not necessarily the earliest note, so we may want to pull the notes out
3084 into a vector and sort before using the first one.
3087 const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3088 Evoral::MusicalTime delta;
3092 /* non-fine, move by 1 bar regardless of snap */
3093 delta = Evoral::MusicalTime(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
3095 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3097 /* grid is off - use nudge distance */
3100 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3101 delta = region_frames_to_region_beats (fabs ((double)distance));
3107 framepos_t next_pos = ref_point;
3110 if (max_framepos - 1 < next_pos) {
3114 if (next_pos == 0) {
3120 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3121 const framecnt_t distance = ref_point - next_pos;
3122 delta = region_frames_to_region_beats (fabs ((double)distance));
3133 start_note_diff_command (_("nudge"));
3135 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3136 Selection::iterator next = i;
3138 change_note_time (*i, delta, true);
3146 MidiRegionView::change_channel(uint8_t channel)
3148 start_note_diff_command(_("change channel"));
3149 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3150 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3158 MidiRegionView::note_entered(NoteBase* ev)
3160 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3162 if (_mouse_state == SelectTouchDragging) {
3163 note_selected (ev, true);
3164 } else if (editor->current_mouse_mode() == MouseContent) {
3165 show_verbose_cursor (ev->note ());
3166 } else if (editor->current_mouse_mode() == MouseDraw) {
3167 show_verbose_cursor (ev->note ());
3172 MidiRegionView::note_left (NoteBase*)
3174 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3176 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3177 (*i)->hide_velocity ();
3180 editor->verbose_cursor()->hide ();
3184 MidiRegionView::patch_entered (PatchChange* p)
3187 /* XXX should get patch name if we can */
3188 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3189 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3190 << _("Channel ") << ((int) p->patch()->channel() + 1);
3191 show_verbose_cursor (s.str(), 10, 20);
3192 p->item().grab_focus();
3196 MidiRegionView::patch_left (PatchChange *)
3198 trackview.editor().verbose_cursor()->hide ();
3199 /* focus will transfer back via the enter-notify event sent to this
3205 MidiRegionView::sysex_entered (SysEx* p)
3209 // need a way to extract text from p->_flag->_text
3211 // show_verbose_cursor (s.str(), 10, 20);
3212 p->item().grab_focus();
3216 MidiRegionView::sysex_left (SysEx *)
3218 trackview.editor().verbose_cursor()->hide ();
3219 /* focus will transfer back via the enter-notify event sent to this
3225 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3227 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3228 Editing::MouseMode mm = editor->current_mouse_mode();
3229 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3231 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3232 if (can_set_cursor && ctx) {
3233 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3234 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3235 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3236 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3238 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3244 MidiRegionView::get_fill_color() const
3246 const std::string mod_name = (_dragging ? "dragging region" :
3247 trackview.editor().internal_editing() ? "editable region" :
3250 return ARDOUR_UI::config()->color_mod ("selected region base", mod_name);
3251 } else if (high_enough_for_name || !ARDOUR_UI::config()->get_color_regions_using_track_color()) {
3252 return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name);
3254 return ARDOUR_UI::config()->color_mod (fill_color, mod_name);
3258 MidiRegionView::midi_channel_mode_changed ()
3260 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3261 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3262 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3264 if (mode == ForceChannel) {
3265 mask = 0xFFFF; // Show all notes as active (below)
3268 // Update notes for selection
3269 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3270 (*i)->on_channel_selection_change (mask);
3273 _patch_changes.clear ();
3274 display_patch_changes ();
3278 MidiRegionView::instrument_settings_changed ()
3284 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3286 if (_selection.empty()) {
3290 PublicEditor& editor (trackview.editor());
3294 /* XXX what to do ? */
3298 editor.get_cut_buffer().add (selection_as_cut_buffer());
3306 start_note_diff_command();
3308 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3315 note_diff_remove_note (*i);
3325 MidiRegionView::selection_as_cut_buffer () const
3329 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3330 NoteType* n = (*i)->note().get();
3331 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3334 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3340 /** This method handles undo */
3342 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
3344 // Paste notes, if available
3345 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3346 if (m != selection.midi_notes.end()) {
3347 ctx.counts.increase_n_notes();
3348 paste_internal(pos, ctx.count, ctx.times, **m);
3351 // Paste control points to automation children, if available
3352 typedef RouteTimeAxisView::AutomationTracks ATracks;
3353 const ATracks& atracks = midi_view()->automation_tracks();
3354 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3355 a->second->paste(pos, selection, ctx);
3361 /** This method handles undo */
3363 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3369 start_note_diff_command (_("paste"));
3371 const Evoral::MusicalTime snap_beats = get_grid_beats(pos);
3372 const Evoral::MusicalTime first_time = (*mcb.notes().begin())->time();
3373 const Evoral::MusicalTime last_time = (*mcb.notes().rbegin())->end_time();
3374 const Evoral::MusicalTime duration = last_time - first_time;
3375 const Evoral::MusicalTime snap_duration = duration.snap_to(snap_beats);
3376 const Evoral::MusicalTime paste_offset = snap_duration * paste_count;
3377 const Evoral::MusicalTime pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
3378 Evoral::MusicalTime end_point = Evoral::MusicalTime();
3380 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3383 duration, pos, _region->position(),
3388 for (int n = 0; n < (int) times; ++n) {
3390 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3392 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3393 copied_note->set_time (pos_beats + copied_note->time() - first_time);
3395 /* make all newly added notes selected */
3397 note_diff_add_note (copied_note, true);
3398 end_point = copied_note->end_time();
3402 /* if we pasted past the current end of the region, extend the region */
3404 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3405 framepos_t region_end = _region->position() + _region->length() - 1;
3407 if (end_frame > region_end) {
3409 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3411 _region->clear_changes ();
3412 _region->set_length (end_frame - _region->position());
3413 trackview.session()->add_command (new StatefulDiffCommand (_region));
3419 struct EventNoteTimeEarlyFirstComparator {
3420 bool operator() (NoteBase* a, NoteBase* b) {
3421 return a->note()->time() < b->note()->time();
3426 MidiRegionView::time_sort_events ()
3428 if (!_sort_needed) {
3432 EventNoteTimeEarlyFirstComparator cmp;
3435 _sort_needed = false;
3439 MidiRegionView::goto_next_note (bool add_to_selection)
3441 bool use_next = false;
3443 if (_events.back()->selected()) {
3447 time_sort_events ();
3449 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3450 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3452 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3453 if ((*i)->selected()) {
3456 } else if (use_next) {
3457 if (channel_mask & (1 << (*i)->note()->channel())) {
3458 if (!add_to_selection) {
3461 note_selected (*i, true, false);
3468 /* use the first one */
3470 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3471 unique_select (_events.front());
3476 MidiRegionView::goto_previous_note (bool add_to_selection)
3478 bool use_next = false;
3480 if (_events.front()->selected()) {
3484 time_sort_events ();
3486 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3487 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3489 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3490 if ((*i)->selected()) {
3493 } else if (use_next) {
3494 if (channel_mask & (1 << (*i)->note()->channel())) {
3495 if (!add_to_selection) {
3498 note_selected (*i, true, false);
3505 /* use the last one */
3507 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3508 unique_select (*(_events.rbegin()));
3513 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3515 bool had_selected = false;
3517 time_sort_events ();
3519 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3520 if ((*i)->selected()) {
3521 selected.insert ((*i)->note());
3522 had_selected = true;
3526 if (allow_all_if_none_selected && !had_selected) {
3527 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3528 selected.insert ((*i)->note());
3534 MidiRegionView::update_ghost_note (double x, double y)
3536 x = std::max(0.0, x);
3538 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3543 _note_group->canvas_to_item (x, y);
3545 PublicEditor& editor = trackview.editor ();
3547 framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3548 framecnt_t grid_frames;
3549 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3551 /* calculate time in beats relative to start of source */
3552 const Evoral::MusicalTime length = get_grid_beats(unsnapped_frame);
3553 const Evoral::MusicalTime time = std::max(
3554 Evoral::MusicalTime(),
3555 absolute_frames_to_source_beats (f + _region->position ()));
3557 _ghost_note->note()->set_time (time);
3558 _ghost_note->note()->set_length (length);
3559 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3560 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3561 _ghost_note->note()->set_velocity (get_velocity_for_add (time));
3563 /* the ghost note does not appear in ghost regions, so pass false in here */
3564 update_note (_ghost_note, false);
3566 show_verbose_cursor (_ghost_note->note ());
3570 MidiRegionView::create_ghost_note (double x, double y)
3572 remove_ghost_note ();
3574 boost::shared_ptr<NoteType> g (new NoteType);
3575 if (midi_view()->note_mode() == Sustained) {
3576 _ghost_note = new Note (*this, _note_group, g);
3578 _ghost_note = new Hit (*this, _note_group, 10, g);
3580 _ghost_note->set_ignore_events (true);
3581 _ghost_note->set_outline_color (0x000000aa);
3582 update_ghost_note (x, y);
3583 _ghost_note->show ();
3585 show_verbose_cursor (_ghost_note->note ());
3589 MidiRegionView::remove_ghost_note ()
3596 MidiRegionView::snap_changed ()
3602 create_ghost_note (_last_ghost_x, _last_ghost_y);
3606 MidiRegionView::drop_down_keys ()
3608 _mouse_state = None;
3612 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3614 /* XXX: This is dead code. What was it for? */
3616 double note = midi_stream_view()->y_to_note(y);
3618 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3620 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3622 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3623 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3624 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3625 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3630 bool add_mrv_selection = false;
3632 if (_selection.empty()) {
3633 add_mrv_selection = true;
3636 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3637 if (_selection.insert (*i).second) {
3638 (*i)->set_selected (true);
3642 if (add_mrv_selection) {
3643 PublicEditor& editor (trackview.editor());
3644 editor.get_selection().add (this);
3649 MidiRegionView::color_handler ()
3651 RegionView::color_handler ();
3653 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3654 (*i)->set_selected ((*i)->selected()); // will change color
3657 /* XXX probably more to do here */
3661 MidiRegionView::enable_display (bool yn)
3663 RegionView::enable_display (yn);
3670 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3672 if (_step_edit_cursor == 0) {
3673 ArdourCanvas::Item* const group = get_canvas_group();
3675 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3676 _step_edit_cursor->set_y0 (0);
3677 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3678 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3679 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3682 move_step_edit_cursor (pos);
3683 _step_edit_cursor->show ();
3687 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3689 _step_edit_cursor_position = pos;
3691 if (_step_edit_cursor) {
3692 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3693 _step_edit_cursor->set_x0 (pixel);
3694 set_step_edit_cursor_width (_step_edit_cursor_width);
3699 MidiRegionView::hide_step_edit_cursor ()
3701 if (_step_edit_cursor) {
3702 _step_edit_cursor->hide ();
3707 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3709 _step_edit_cursor_width = beats;
3711 if (_step_edit_cursor) {
3712 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
3716 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3717 * @param w Source that the data will end up in.
3720 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3722 if (!_active_notes) {
3723 /* we aren't actively being recorded to */
3727 boost::shared_ptr<MidiSource> src = w.lock ();
3728 if (!src || src != midi_region()->midi_source()) {
3729 /* recorded data was not destined for our source */
3733 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3735 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3737 framepos_t back = max_framepos;
3739 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3740 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3742 if (ev.is_channel_event()) {
3743 if (get_channel_mode() == FilterChannels) {
3744 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3750 /* convert from session frames to source beats */
3751 Evoral::MusicalTime const time_beats = _source_relative_time_converter.from(ev.time());
3753 if (ev.type() == MIDI_CMD_NOTE_ON) {
3754 boost::shared_ptr<NoteType> note (
3755 new NoteType (ev.channel(), time_beats, Evoral::MusicalTime(), ev.note(), ev.velocity()));
3757 add_note (note, true);
3759 /* fix up our note range */
3760 if (ev.note() < _current_range_min) {
3761 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3762 } else if (ev.note() > _current_range_max) {
3763 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3766 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3767 resolve_note (ev.note (), time_beats);
3773 midi_stream_view()->check_record_layers (region(), back);
3777 MidiRegionView::trim_front_starting ()
3779 /* Reparent the note group to the region view's parent, so that it doesn't change
3780 when the region view is trimmed.
3782 _temporary_note_group = new ArdourCanvas::Container (group->parent ());
3783 _temporary_note_group->move (group->position ());
3784 _note_group->reparent (_temporary_note_group);
3788 MidiRegionView::trim_front_ending ()
3790 _note_group->reparent (group);
3791 delete _temporary_note_group;
3792 _temporary_note_group = 0;
3794 if (_region->start() < 0) {
3795 /* Trim drag made start time -ve; fix this */
3796 midi_region()->fix_negative_start ();
3801 MidiRegionView::edit_patch_change (PatchChange* pc)
3803 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3805 int response = d.run();
3808 case Gtk::RESPONSE_ACCEPT:
3810 case Gtk::RESPONSE_REJECT:
3811 delete_patch_change (pc);
3817 change_patch_change (pc->patch(), d.patch ());
3821 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
3824 // sysyex object doesn't have a pointer to a sysex event
3825 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3826 // c->remove (sysex->sysex());
3827 // _model->apply_command (*trackview.session(), c);
3829 //_sys_exes.clear ();
3830 // display_sysexes();
3834 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3836 using namespace MIDI::Name;
3840 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3842 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
3844 MIDI::Name::PatchPrimaryKey patch_key;
3845 get_patch_key_at(n->time(), n->channel(), patch_key);
3846 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
3849 patch_key.program(),
3855 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
3857 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
3858 (int) n->channel() + 1,
3859 (int) n->velocity());
3861 show_verbose_cursor(buf, 10, 20);
3865 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3867 trackview.editor().verbose_cursor()->set (text);
3868 trackview.editor().verbose_cursor()->show ();
3869 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
3873 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
3875 if (_model->notes().empty()) {
3876 return 0x40; // No notes, use default
3879 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
3880 if (m == _model->notes().begin()) {
3881 // Before the start, use the velocity of the first note
3882 return (*m)->velocity();
3883 } else if (m == _model->notes().end()) {
3884 // Past the end, use the velocity of the last note
3886 return (*m)->velocity();
3889 // Interpolate velocity of surrounding notes
3890 MidiModel::Notes::const_iterator n = m;
3893 const double frac = ((time - (*n)->time()).to_double() /
3894 ((*m)->time() - (*n)->time()).to_double());
3896 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
3899 /** @param p A session framepos.
3900 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3901 * @return p snapped to the grid subdivision underneath it.
3904 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3906 PublicEditor& editor = trackview.editor ();
3908 const Evoral::MusicalTime grid_beats = get_grid_beats(p);
3910 grid_frames = region_beats_to_region_frames (grid_beats);
3912 /* Hack so that we always snap to the note that we are over, instead of snapping
3913 to the next one if we're more than halfway through the one we're over.
3915 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3916 p -= grid_frames / 2;
3919 return snap_frame_to_frame (p);
3922 /** Called when the selection has been cleared in any MidiRegionView.
3923 * @param rv MidiRegionView that the selection was cleared in.
3926 MidiRegionView::selection_cleared (MidiRegionView* rv)
3932 /* Clear our selection in sympathy; but don't signal the fact */
3933 clear_selection (false);
3937 MidiRegionView::note_button_release ()
3939 _note_player.reset();
3943 MidiRegionView::get_channel_mode () const
3945 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
3946 return rtav->midi_track()->get_playback_channel_mode();
3950 MidiRegionView::get_selected_channels () const
3952 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
3953 return rtav->midi_track()->get_playback_channel_mask();
3958 MidiRegionView::get_grid_beats(framepos_t pos) const
3960 PublicEditor& editor = trackview.editor();
3961 bool success = false;
3962 Evoral::MusicalTime beats = editor.get_grid_type_as_beats(success, pos);
3964 beats = Evoral::MusicalTime(1);