2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "gtkmm2ext/gtk_ui.h"
28 #include <sigc++/signal.h>
30 #include "midi++/midnam_patch.h"
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
43 #include "evoral/Parameter.hpp"
44 #include "evoral/Event.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
81 #include "patch_change.h"
83 #include "ui_config.h"
87 using namespace ARDOUR;
89 using namespace Editing;
91 using Gtkmm2ext::Keyboard;
93 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
95 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
96 RouteTimeAxisView& tv,
97 boost::shared_ptr<MidiRegion> r,
100 : RegionView (parent, tv, r, spu, basic_color)
101 , _current_range_min(0)
102 , _current_range_max(0)
103 , _region_relative_time_converter(r->session().tempo_map(), r->position())
104 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
105 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
107 , _note_group (new ArdourCanvas::Container (group))
108 , _note_diff_command (0)
110 , _step_edit_cursor (0)
111 , _step_edit_cursor_width (1.0)
112 , _step_edit_cursor_position (0.0)
113 , _channel_selection_scoped_note (0)
116 , _optimization_iterator (_events.end())
118 , _no_sound_notes (false)
119 , _last_display_zoom (0)
122 , _grabbed_keyboard (false)
125 , _mouse_changed_selection (false)
127 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
129 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
130 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
132 _note_group->raise_to_top();
133 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
135 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
136 connect_to_diskstream ();
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())
151 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
153 , _note_group (new ArdourCanvas::Container (group))
154 , _note_diff_command (0)
156 , _step_edit_cursor (0)
157 , _step_edit_cursor_width (1.0)
158 , _step_edit_cursor_position (0.0)
159 , _channel_selection_scoped_note (0)
162 , _optimization_iterator (_events.end())
164 , _no_sound_notes (false)
165 , _last_display_zoom (0)
168 , _grabbed_keyboard (false)
171 , _mouse_changed_selection (false)
173 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
175 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
176 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
178 _note_group->raise_to_top();
180 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
182 connect_to_diskstream ();
186 MidiRegionView::parameter_changed (std::string const & p)
188 if (p == "display-first-midi-bank-as-zero") {
189 if (_enable_display) {
192 } else if (p == "color-regions-using-track-color") {
197 MidiRegionView::MidiRegionView (const MidiRegionView& other)
198 : sigc::trackable(other)
200 , _current_range_min(0)
201 , _current_range_max(0)
202 , _region_relative_time_converter(other.region_relative_time_converter())
203 , _source_relative_time_converter(other.source_relative_time_converter())
204 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
206 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
207 , _note_diff_command (0)
209 , _step_edit_cursor (0)
210 , _step_edit_cursor_width (1.0)
211 , _step_edit_cursor_position (0.0)
212 , _channel_selection_scoped_note (0)
215 , _optimization_iterator (_events.end())
217 , _no_sound_notes (false)
218 , _last_display_zoom (0)
221 , _grabbed_keyboard (false)
224 , _mouse_changed_selection (false)
229 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
230 : RegionView (other, boost::shared_ptr<Region> (region))
231 , _current_range_min(0)
232 , _current_range_max(0)
233 , _region_relative_time_converter(other.region_relative_time_converter())
234 , _source_relative_time_converter(other.source_relative_time_converter())
235 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
237 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
238 , _note_diff_command (0)
240 , _step_edit_cursor (0)
241 , _step_edit_cursor_width (1.0)
242 , _step_edit_cursor_position (0.0)
243 , _channel_selection_scoped_note (0)
246 , _optimization_iterator (_events.end())
248 , _no_sound_notes (false)
249 , _last_display_zoom (0)
252 , _grabbed_keyboard (false)
255 , _mouse_changed_selection (false)
261 MidiRegionView::init (bool wfd)
263 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
266 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
267 midi_region()->midi_source(0)->load_model(lm);
270 _model = midi_region()->midi_source(0)->model();
271 _enable_display = false;
272 fill_color_name = "midi frame base";
274 RegionView::init (false);
276 //set_height (trackview.current_height());
279 region_sync_changed ();
280 region_resized (ARDOUR::bounds_change);
285 _enable_display = true;
288 display_model (_model);
292 reset_width_dependent_items (_pixel_width);
294 group->raise_to_top();
296 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
297 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
300 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
301 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
303 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
304 boost::bind (&MidiRegionView::snap_changed, this),
307 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
308 boost::bind (&MidiRegionView::mouse_mode_changed, this),
311 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
312 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
313 connect_to_diskstream ();
317 MidiRegionView::instrument_info () const
319 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
320 return route_ui->route()->instrument_info();
323 const boost::shared_ptr<ARDOUR::MidiRegion>
324 MidiRegionView::midi_region() const
326 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
330 MidiRegionView::connect_to_diskstream ()
332 midi_view()->midi_track()->DataRecorded.connect(
333 *this, invalidator(*this),
334 boost::bind (&MidiRegionView::data_recorded, this, _1),
339 MidiRegionView::canvas_group_event(GdkEvent* ev)
341 if (in_destructor || _recregion) {
345 if (!trackview.editor().internal_editing()) {
346 // not in internal edit mode, so just act like a normal region
347 return RegionView::canvas_group_event (ev);
350 //For now, move the snapped cursor aside so it doesn't bother you during internal editing
351 //trackview.editor().set_snapped_cursor_position(_region->position());
356 case GDK_ENTER_NOTIFY:
357 _last_event_x = ev->crossing.x;
358 _last_event_y = ev->crossing.y;
359 enter_notify(&ev->crossing);
360 // set entered_regionview (among other things)
361 return RegionView::canvas_group_event (ev);
363 case GDK_LEAVE_NOTIFY:
364 _last_event_x = ev->crossing.x;
365 _last_event_y = ev->crossing.y;
366 leave_notify(&ev->crossing);
367 // reset entered_regionview (among other things)
368 return RegionView::canvas_group_event (ev);
371 if (scroll (&ev->scroll)) {
377 return key_press (&ev->key);
379 case GDK_KEY_RELEASE:
380 return key_release (&ev->key);
382 case GDK_BUTTON_PRESS:
383 return button_press (&ev->button);
385 case GDK_BUTTON_RELEASE:
386 r = button_release (&ev->button);
389 case GDK_MOTION_NOTIFY:
390 _last_event_x = ev->motion.x;
391 _last_event_y = ev->motion.y;
392 return motion (&ev->motion);
398 return RegionView::canvas_group_event (ev);
402 MidiRegionView::enter_notify (GdkEventCrossing* ev)
404 enter_internal (ev->state);
411 MidiRegionView::leave_notify (GdkEventCrossing*)
420 MidiRegionView::mouse_mode_changed ()
422 // Adjust sample colour (become more transparent for internal tools)
426 if (!trackview.editor().internal_editing()) {
427 /* Switched out of internal editing mode while entered.
428 Only necessary for leave as a mouse_mode_change over a region
429 automatically triggers an enter event. */
432 else if (trackview.editor().current_mouse_mode() == MouseContent) {
433 // hide cursor and ghost note after changing to internal edit mode
434 remove_ghost_note ();
436 /* XXX This is problematic as the function is executed for every region
437 and only for one region _entered_note can be true. Still it's
438 necessary as to hide the verbose cursor when we're changing from
439 draw mode to internal edit mode. These lines are the reason why
440 in some situations no verbose cursor is shown when we enter internal
441 edit mode over a note. */
442 if (!_entered_note) {
443 hide_verbose_cursor ();
450 MidiRegionView::enter_internal (uint32_t state)
452 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
453 // Show ghost note under pencil
454 create_ghost_note(_last_event_x, _last_event_y, state);
457 if (!_selection.empty()) {
458 // Grab keyboard for moving selected notes with arrow keys
459 Keyboard::magic_widget_grab_focus();
460 _grabbed_keyboard = true;
463 // Lower sample handles below notes so they don't steal events
464 if (sample_handle_start) {
465 sample_handle_start->lower_to_bottom();
467 if (sample_handle_end) {
468 sample_handle_end->lower_to_bottom();
473 MidiRegionView::leave_internal()
475 hide_verbose_cursor ();
476 remove_ghost_note ();
479 if (_grabbed_keyboard) {
480 Keyboard::magic_widget_drop_focus();
481 _grabbed_keyboard = false;
484 // Raise sample handles above notes so they catch events
485 if (sample_handle_start) {
486 sample_handle_start->raise_to_top();
488 if (sample_handle_end) {
489 sample_handle_end->raise_to_top();
494 MidiRegionView::button_press (GdkEventButton* ev)
496 if (ev->button != 1) {
500 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
501 MouseMode m = editor->current_mouse_mode();
503 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
504 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
507 if (_mouse_state != SelectTouchDragging) {
509 _pressed_button = ev->button;
511 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
513 if (midi_view()->note_mode() == Percussive) {
514 editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
516 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
519 _mouse_state = AddDragging;
520 remove_ghost_note ();
521 hide_verbose_cursor ();
523 _mouse_state = Pressed;
529 _pressed_button = ev->button;
530 _mouse_changed_selection = false;
536 MidiRegionView::button_release (GdkEventButton* ev)
538 double event_x, event_y;
540 if (ev->button != 1) {
547 group->canvas_to_item (event_x, event_y);
550 PublicEditor& editor = trackview.editor ();
552 _press_cursor_ctx.reset();
554 switch (_mouse_state) {
555 case Pressed: // Clicked
557 switch (editor.current_mouse_mode()) {
559 /* no motion occurred - simple click */
560 clear_editor_note_selection ();
561 _mouse_changed_selection = true;
567 _mouse_changed_selection = true;
568 clear_editor_note_selection ();
583 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
584 we don't want one when we were drag-selecting either. */
585 case SelectRectDragging:
586 editor.drags()->end_grab ((GdkEvent *) ev);
595 if (_mouse_changed_selection) {
596 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
597 trackview.editor().commit_reversible_selection_op ();
604 MidiRegionView::motion (GdkEventMotion* ev)
606 PublicEditor& editor = trackview.editor ();
608 if (!_entered_note) {
610 if (_mouse_state == AddDragging) {
612 remove_ghost_note ();
615 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
616 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
617 _mouse_state != AddDragging) {
619 create_ghost_note (ev->x, ev->y, ev->state);
621 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
622 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
624 update_ghost_note (ev->x, ev->y, ev->state);
626 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
628 remove_ghost_note ();
629 hide_verbose_cursor ();
631 } else if (editor.current_mouse_mode() == MouseDraw) {
634 update_ghost_note (ev->x, ev->y, ev->state);
637 create_ghost_note (ev->x, ev->y, ev->state);
642 /* any motion immediately hides velocity text that may have been visible */
644 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
645 (*i)->hide_velocity ();
648 switch (_mouse_state) {
651 if (_pressed_button == 1) {
653 MouseMode m = editor.current_mouse_mode();
655 if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
656 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
657 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
658 clear_editor_note_selection ();
659 _mouse_changed_selection = true;
661 _mouse_state = SelectRectDragging;
663 } else if (m == MouseRange) {
664 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
665 _mouse_state = SelectVerticalDragging;
672 case SelectRectDragging:
673 case SelectVerticalDragging:
675 editor.drags()->motion_handler ((GdkEvent *) ev, false);
678 case SelectTouchDragging:
686 /* we may be dragging some non-note object (eg. patch-change, sysex)
689 return editor.drags()->motion_handler ((GdkEvent *) ev, false);
694 MidiRegionView::scroll (GdkEventScroll* ev)
696 if (_selection.empty()) {
700 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
701 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
702 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
703 * through so that it still works for navigation.
708 hide_verbose_cursor ();
710 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
711 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
712 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
714 if (ev->direction == GDK_SCROLL_UP) {
715 change_velocities (true, fine, false, together);
716 } else if (ev->direction == GDK_SCROLL_DOWN) {
717 change_velocities (false, fine, false, together);
719 /* left, right: we don't use them */
727 MidiRegionView::key_press (GdkEventKey* ev)
729 /* since GTK bindings are generally activated on press, and since
730 detectable auto-repeat is the name of the game and only sends
731 repeated presses, carry out key actions at key press, not release.
733 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
735 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
737 if (_mouse_state != AddDragging) {
738 _mouse_state = SelectTouchDragging;
743 } else if (ev->keyval == GDK_Escape && unmodified) {
744 clear_editor_note_selection ();
747 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
749 bool start = (ev->keyval == GDK_comma);
750 bool end = (ev->keyval == GDK_period);
751 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
752 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
754 change_note_lengths (fine, shorter, Temporal::Beats(), start, end);
758 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
760 if (_selection.empty()) {
767 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
769 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
771 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
772 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
774 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
777 trackview.editor().commit_reversible_selection_op();
781 } else if (ev->keyval == GDK_Up) {
783 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
784 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
785 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
787 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
788 change_velocities (true, fine, allow_smush, together);
790 transpose (true, fine, allow_smush);
794 } else if (ev->keyval == GDK_Down) {
796 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
797 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
798 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
800 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
801 change_velocities (false, fine, allow_smush, together);
803 transpose (false, fine, allow_smush);
807 } else if (ev->keyval == GDK_Left) {
809 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
810 nudge_notes (false, fine);
813 } else if (ev->keyval == GDK_Right) {
815 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
816 nudge_notes (true, fine);
819 } else if (ev->keyval == GDK_c && unmodified) {
823 } else if (ev->keyval == GDK_v && unmodified) {
832 MidiRegionView::key_release (GdkEventKey* ev)
834 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
842 MidiRegionView::channel_edit ()
844 if (_selection.empty()) {
848 /* pick a note somewhat at random (since Selection is a set<>) to
849 * provide the "current" channel for the dialog.
852 uint8_t current_channel = (*_selection.begin())->note()->channel ();
853 MidiChannelDialog channel_dialog (current_channel);
854 int ret = channel_dialog.run ();
857 case Gtk::RESPONSE_OK:
863 uint8_t new_channel = channel_dialog.active_channel ();
865 start_note_diff_command (_("channel edit"));
867 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
868 Selection::iterator next = i;
870 change_note_channel (*i, new_channel);
878 MidiRegionView::velocity_edit ()
880 if (_selection.empty()) {
884 /* pick a note somewhat at random (since Selection is a set<>) to
885 * provide the "current" velocity for the dialog.
888 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
889 MidiVelocityDialog velocity_dialog (current_velocity);
890 int ret = velocity_dialog.run ();
893 case Gtk::RESPONSE_OK:
899 uint8_t new_velocity = velocity_dialog.velocity ();
901 start_note_diff_command (_("velocity edit"));
903 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
904 Selection::iterator next = i;
906 change_note_velocity (*i, new_velocity, false);
914 MidiRegionView::show_list_editor ()
917 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
919 _list_editor->present ();
922 /** Add a note to the model, and the view, at a canvas (click) coordinate.
923 * \param t time in samples relative to the position of the region
924 * \param y vertical position in pixels
925 * \param length duration of the note in beats
926 * \param snap_t true to snap t to the grid, otherwise false.
929 MidiRegionView::create_note_at (samplepos_t t, double y, Temporal::Beats length, uint32_t state, bool shift_snap)
931 if (length < 2 * DBL_EPSILON) {
935 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
936 MidiStreamView* const view = mtv->midi_view();
937 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
943 // Start of note in samples relative to region start
944 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
945 Temporal::Beats beat_time = snap_sample_to_grid_underneath (t, divisions, shift_snap);
947 const double note = view->y_to_note(y);
948 const uint8_t chan = mtv->get_channel_for_add();
949 const uint8_t velocity = get_velocity_for_add(beat_time);
951 const boost::shared_ptr<NoteType> new_note(
952 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
954 if (_model->contains (new_note)) {
958 view->update_note_range(new_note->note());
960 start_note_diff_command(_("add note"));
962 note_diff_add_note (new_note, true, false);
966 play_midi_note (new_note);
970 MidiRegionView::clear_events ()
972 // clear selection without signaling
973 clear_selection_internal ();
976 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
977 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
983 _note_group->clear (true);
985 _patch_changes.clear();
987 _optimization_iterator = _events.end();
991 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
995 content_connection.disconnect ();
996 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
997 /* Don't signal as nobody else needs to know until selection has been altered. */
1000 if (_enable_display) {
1006 MidiRegionView::start_note_diff_command (string name)
1008 if (!_note_diff_command) {
1009 trackview.editor().begin_reversible_command (name);
1010 _note_diff_command = _model->new_note_diff_command (name);
1015 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1017 if (_note_diff_command) {
1018 _note_diff_command->add (note);
1021 _marked_for_selection.insert(note);
1023 if (show_velocity) {
1024 _marked_for_velocity.insert(note);
1029 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1031 if (_note_diff_command && ev->note()) {
1032 _note_diff_command->remove(ev->note());
1037 MidiRegionView::note_diff_add_change (NoteBase* ev,
1038 MidiModel::NoteDiffCommand::Property property,
1041 if (_note_diff_command) {
1042 _note_diff_command->change (ev->note(), property, val);
1047 MidiRegionView::note_diff_add_change (NoteBase* ev,
1048 MidiModel::NoteDiffCommand::Property property,
1049 Temporal::Beats val)
1051 if (_note_diff_command) {
1052 _note_diff_command->change (ev->note(), property, val);
1057 MidiRegionView::apply_diff (bool as_subcommand, bool was_copy)
1060 bool commit = false;
1062 if (!_note_diff_command) {
1066 if (!was_copy && (add_or_remove = _note_diff_command->adds_or_removes())) {
1067 // Mark all selected notes for selection when model reloads
1068 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1069 _marked_for_selection.insert((*i)->note());
1073 midi_view()->midi_track()->midi_playlist()->region_edited (_region, _note_diff_command);
1075 if (as_subcommand) {
1076 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1078 _model->apply_command (*trackview.session(), _note_diff_command);
1082 _note_diff_command = 0;
1084 if (add_or_remove) {
1085 _marked_for_selection.clear();
1088 _marked_for_velocity.clear();
1090 trackview.editor().commit_reversible_command ();
1095 MidiRegionView::abort_command()
1097 delete _note_diff_command;
1098 _note_diff_command = 0;
1099 trackview.editor().abort_reversible_command();
1100 clear_editor_note_selection();
1104 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1107 if (_optimization_iterator != _events.end()) {
1108 ++_optimization_iterator;
1111 if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) {
1112 return _optimization_iterator->second;
1115 _optimization_iterator = _events.find (note);
1116 if (_optimization_iterator != _events.end()) {
1117 return _optimization_iterator->second;
1123 /** This version finds any canvas note matching the supplied note. */
1125 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1127 Events::iterator it;
1129 for (it = _events.begin(); it != _events.end(); ++it) {
1130 if (it->first->id() == id) {
1138 boost::shared_ptr<PatchChange>
1139 MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
1141 PatchChanges::const_iterator f = _patch_changes.find (p);
1143 if (f != _patch_changes.end()) {
1147 return boost::shared_ptr<PatchChange>();
1150 boost::shared_ptr<SysEx>
1151 MidiRegionView::find_canvas_sys_ex (MidiModel::SysExPtr s)
1153 SysExes::const_iterator f = _sys_exes.find (s);
1155 if (f != _sys_exes.end()) {
1159 return boost::shared_ptr<SysEx>();
1163 MidiRegionView::get_events (Events& e, Evoral::Sequence<Temporal::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1165 MidiModel::Notes notes;
1166 _model->get_notes (notes, op, val, chan_mask);
1168 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1169 NoteBase* cne = find_canvas_note (*n);
1171 e.insert (make_pair (*n, cne));
1177 MidiRegionView::redisplay_model()
1179 if (_active_notes) {
1180 // Currently recording
1181 const samplecnt_t zoom = trackview.editor().get_current_zoom();
1182 if (zoom != _last_display_zoom) {
1183 /* Update resolved canvas notes to reflect changes in zoom without
1184 touching model. Leave active notes (with length 0) alone since
1185 they are being extended. */
1186 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1187 if (i->second->note()->length() > 0) {
1188 update_note(i->second);
1191 _last_display_zoom = zoom;
1200 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1201 _optimization_iterator->second->invalidate();
1204 bool empty_when_starting = _events.empty();
1205 _optimization_iterator = _events.begin();
1206 MidiModel::Notes missing_notes;
1210 MidiModel::ReadLock lock(_model->read_lock());
1211 MidiModel::Notes& notes (_model->notes());
1214 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1216 boost::shared_ptr<NoteType> note (*n);
1219 if (note_in_region_range (note, visible)) {
1220 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1228 missing_notes.insert (note);
1233 if (!empty_when_starting) {
1234 MidiModel::Notes::iterator f;
1235 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1237 NoteBase* cne = i->second;
1239 /* remove note items that are no longer valid */
1240 if (!cne->valid()) {
1242 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1243 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1245 gr->remove_note (cne);
1250 i = _events.erase (i);
1253 bool visible = cne->item()->visible();
1255 if ((sus = dynamic_cast<Note*>(cne))) {
1258 update_sustained (sus);
1261 } else if ((hit = dynamic_cast<Hit*>(cne))) {
1273 for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
1274 boost::shared_ptr<NoteType> note (*n);
1278 if (note_in_region_range (note, visible)) {
1280 cne = add_note (note, true);
1282 cne = add_note (note, false);
1285 cne = add_note (note, false);
1288 for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1289 if ((*it) == note->id()) {
1290 add_to_selection (cne);
1295 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1296 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1297 if (gr && !gr->trackview.hidden()) {
1298 gr->redisplay_model ();
1303 display_patch_changes ();
1305 _marked_for_selection.clear ();
1306 _marked_for_velocity.clear ();
1307 _pending_note_selection.clear ();
1312 MidiRegionView::display_patch_changes ()
1314 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1315 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1317 for (uint8_t i = 0; i < 16; ++i) {
1318 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1322 /** @param active_channel true to display patch changes fully, false to display
1323 * them `greyed-out' (as on an inactive channel)
1326 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1328 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1329 boost::shared_ptr<PatchChange> p;
1331 if ((*i)->channel() != channel) {
1335 if ((p = find_canvas_patch_change (*i)) != 0) {
1337 const samplecnt_t region_samples = source_beats_to_region_samples ((*i)->time());
1339 if (region_samples < 0 || region_samples >= _region->length()) {
1342 const double x = trackview.editor().sample_to_pixel (region_samples);
1343 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1344 p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
1345 p->set_text (patch_name);
1351 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1352 add_canvas_patch_change (*i, patch_name, active_channel);
1358 MidiRegionView::display_sysexes()
1360 bool have_periodic_system_messages = false;
1361 bool display_periodic_messages = true;
1363 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1365 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1366 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1367 have_periodic_system_messages = true;
1372 if (have_periodic_system_messages) {
1373 double zoom = trackview.editor().get_current_zoom (); // samples per pixel
1375 /* get an approximate value for the number of samples per video frame */
1377 double video_frame = trackview.session()->sample_rate() * (1.0/30);
1379 /* if we are zoomed out beyond than the cutoff (i.e. more
1380 * samples per pixel than samples per 4 video frames), don't
1381 * show periodic sysex messages.
1384 if (zoom > (video_frame*4)) {
1385 display_periodic_messages = false;
1389 display_periodic_messages = false;
1392 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1393 MidiModel::SysExPtr sysex_ptr = *i;
1394 Temporal::Beats time = sysex_ptr->time();
1396 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1397 if (!display_periodic_messages) {
1404 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1405 str << int((*i)->buffer()[b]);
1406 if (b != (*i)->size() -1) {
1410 string text = str.str();
1412 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_samples(time));
1414 double height = midi_stream_view()->contents_height();
1416 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1417 // SysEx canvas object!!!
1418 boost::shared_ptr<SysEx> sysex = find_canvas_sys_ex (sysex_ptr);
1421 sysex = boost::shared_ptr<SysEx>(
1422 new SysEx (*this, _note_group, text, height, x, 1.0, sysex_ptr));
1423 _sys_exes.insert (make_pair (sysex_ptr, sysex));
1425 sysex->set_height (height);
1426 sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
1429 // Show unless message is beyond the region bounds
1430 // XXX REQUIRES APPROPRIATE OPERATORS FOR Temporal::Beats and samplepos? say what?
1431 #warning paul fix this
1432 // if (time - _region->start() >= _region->length() || time < _region->start()) {
1440 MidiRegionView::~MidiRegionView ()
1442 in_destructor = true;
1444 hide_verbose_cursor ();
1446 delete _list_editor;
1448 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1450 if (_active_notes) {
1457 delete _note_diff_command;
1458 delete _step_edit_cursor;
1462 MidiRegionView::region_resized (const PropertyChange& what_changed)
1464 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1466 if (what_changed.contains (ARDOUR::Properties::position)) {
1467 _region_relative_time_converter.set_origin_b(_region->position());
1468 _region_relative_time_converter_double.set_origin_b(_region->position());
1469 /* reset_width dependent_items() redisplays model */
1473 if (what_changed.contains (ARDOUR::Properties::start) ||
1474 what_changed.contains (ARDOUR::Properties::position)) {
1475 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1477 /* catch end and start trim so we can update the view*/
1478 if (!what_changed.contains (ARDOUR::Properties::start) &&
1479 what_changed.contains (ARDOUR::Properties::length)) {
1480 enable_display (true);
1481 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1482 what_changed.contains (ARDOUR::Properties::length)) {
1483 enable_display (true);
1488 MidiRegionView::reset_width_dependent_items (double pixel_width)
1490 RegionView::reset_width_dependent_items(pixel_width);
1492 if (_enable_display) {
1496 bool hide_all = false;
1497 PatchChanges::iterator x = _patch_changes.begin();
1498 if (x != _patch_changes.end()) {
1499 hide_all = x->second->width() >= _pixel_width;
1503 for (; x != _patch_changes.end(); ++x) {
1508 move_step_edit_cursor (_step_edit_cursor_position);
1509 set_step_edit_cursor_width (_step_edit_cursor_width);
1513 MidiRegionView::set_height (double height)
1515 double old_height = _height;
1516 RegionView::set_height(height);
1518 apply_note_range (midi_stream_view()->lowest_note(),
1519 midi_stream_view()->highest_note(),
1520 height != old_height);
1523 name_text->raise_to_top();
1526 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1527 (*x).second->set_height (midi_stream_view()->contents_height());
1530 if (_step_edit_cursor) {
1531 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1536 /** Apply the current note range from the stream view
1537 * by repositioning/hiding notes as necessary
1540 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1542 if (!_enable_display) {
1546 if (!force && _current_range_min == min && _current_range_max == max) {
1550 _current_range_min = min;
1551 _current_range_max = max;
1557 MidiRegionView::add_ghost (TimeAxisView& tv)
1559 double unit_position = _region->position () / samples_per_pixel;
1560 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1561 MidiGhostRegion* ghost;
1563 if (mtv && mtv->midi_view()) {
1564 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1565 to allow having midi notes on top of note lines and waveforms.
1567 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1569 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1572 ghost->set_colors ();
1573 ghost->set_height ();
1574 ghost->set_duration (_region->length() / samples_per_pixel);
1576 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1577 ghost->add_note(i->second);
1580 ghosts.push_back (ghost);
1581 enable_display (true);
1586 /** Begin tracking note state for successive calls to add_event
1589 MidiRegionView::begin_write()
1591 if (_active_notes) {
1592 delete[] _active_notes;
1594 _active_notes = new Note*[128];
1595 for (unsigned i = 0; i < 128; ++i) {
1596 _active_notes[i] = 0;
1601 /** Destroy note state for add_event
1604 MidiRegionView::end_write()
1606 delete[] _active_notes;
1608 _marked_for_selection.clear();
1609 _marked_for_velocity.clear();
1613 /** Resolve an active MIDI note (while recording).
1616 MidiRegionView::resolve_note(uint8_t note, Temporal::Beats end_time)
1618 if (midi_view()->note_mode() != Sustained) {
1622 if (_active_notes && _active_notes[note]) {
1623 /* Set note length so update_note() works. Note this is a local note
1624 for recording, not from a model, so we can safely mess with it. */
1625 _active_notes[note]->note()->set_length(
1626 end_time - _active_notes[note]->note()->time());
1628 /* End time is relative to the region being recorded. */
1629 const samplepos_t end_time_samples = region_beats_to_region_samples(end_time);
1631 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_samples));
1632 _active_notes[note]->set_outline_all ();
1633 _active_notes[note] = 0;
1638 /** Extend active notes to rightmost edge of region (if length is changed)
1641 MidiRegionView::extend_active_notes()
1643 if (!_active_notes) {
1647 for (unsigned i = 0; i < 128; ++i) {
1648 if (_active_notes[i]) {
1649 _active_notes[i]->set_x1(
1650 trackview.editor().sample_to_pixel(_region->length()));
1656 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1658 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1662 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1664 if (!route_ui || !route_ui->midi_track()) {
1668 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1672 /* NotePlayer deletes itself */
1676 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1678 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1679 start_playing_midi_chord(notes);
1683 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1685 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1689 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1691 if (!route_ui || !route_ui->midi_track()) {
1695 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1697 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1706 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1708 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1710 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1711 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1712 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1714 visible = (note->note() >= _current_range_min) &&
1715 (note->note() <= _current_range_max);
1721 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1725 if ((sus = dynamic_cast<Note*>(note))) {
1726 update_sustained(sus, update_ghost_regions);
1727 } else if ((hit = dynamic_cast<Hit*>(note))) {
1728 update_hit(hit, update_ghost_regions);
1732 /** Update a canvas note's size from its model note.
1733 * @param ev Canvas note to update.
1734 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1737 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1739 TempoMap& map (trackview.session()->tempo_map());
1740 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1741 boost::shared_ptr<NoteType> note = ev->note();
1743 const double session_source_start = _region->quarter_note() - mr->start_beats();
1744 const samplepos_t note_start_samples = map.sample_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1746 const double x0 = trackview.editor().sample_to_pixel (note_start_samples);
1748 const double y0 = 1 + floor(note_to_y(note->note()));
1751 /* trim note display to not overlap the end of its region */
1752 if (note->length().to_double() > 0.0) {
1753 double note_end_time = note->end_time().to_double();
1755 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1756 note_end_time = mr->start_beats() + mr->length_beats();
1759 const samplepos_t note_end_samples = map.sample_at_quarter_note (session_source_start + note_end_time) - _region->position();
1761 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_samples)) - 1;
1763 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1766 y1 = y0 + std::max(1., floor(note_height()) - 1);
1768 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1770 if (!note->length()) {
1771 if (_active_notes && note->note() < 128) {
1772 Note* const old_rect = _active_notes[note->note()];
1774 /* There is an active note on this key, so we have a stuck
1775 note. Finish the old rectangle here. */
1776 old_rect->set_x1 (x1);
1777 old_rect->set_outline_all ();
1779 _active_notes[note->note()] = ev;
1781 /* outline all but right edge */
1782 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1783 ArdourCanvas::Rectangle::TOP|
1784 ArdourCanvas::Rectangle::LEFT|
1785 ArdourCanvas::Rectangle::BOTTOM));
1787 /* outline all edges */
1788 ev->set_outline_all ();
1791 // Update color in case velocity has changed
1792 const uint32_t base_col = ev->base_color();
1793 ev->set_fill_color(base_col);
1794 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1799 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1801 boost::shared_ptr<NoteType> note = ev->note();
1803 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1804 const samplepos_t note_start_samples = trackview.session()->tempo_map().sample_at_quarter_note (note_time_qn) - _region->position();
1806 const double x = trackview.editor().sample_to_pixel(note_start_samples);
1807 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1808 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1810 // see DnD note in MidiRegionView::apply_note_range() above
1811 if (y <= 0 || y >= _height) {
1817 ev->set_position (ArdourCanvas::Duple (x, y));
1818 ev->set_height (diamond_size);
1820 // Update color in case velocity has changed
1821 const uint32_t base_col = ev->base_color();
1822 ev->set_fill_color(base_col);
1823 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1827 /** Add a MIDI note to the view (with length).
1829 * If in sustained mode, notes with length 0 will be considered active
1830 * notes, and resolve_note should be called when the corresponding note off
1831 * event arrives, to properly display the note.
1834 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1836 NoteBase* event = 0;
1838 if (midi_view()->note_mode() == Sustained) {
1840 Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak
1842 update_sustained (ev_rect);
1846 } else if (midi_view()->note_mode() == Percussive) {
1848 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1850 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak
1852 update_hit (ev_diamond);
1861 MidiGhostRegion* gr;
1863 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1864 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1865 gr->add_note(event);
1869 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1870 note_selected(event, true);
1873 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1874 event->show_velocity();
1877 event->on_channel_selection_change (get_selected_channels());
1878 _events.insert (make_pair (event->note(), event));
1887 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1888 MidiStreamView* const view = mtv->midi_view();
1890 view->update_note_range (note->note());
1895 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1896 Temporal::Beats pos, Temporal::Beats len)
1898 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1900 /* potentially extend region to hold new note */
1902 samplepos_t end_sample = source_beats_to_absolute_samples (new_note->end_time());
1903 samplepos_t region_end = _region->last_sample();
1905 if (end_sample > region_end) {
1906 /* XX sets length in beats from audio space. make musical */
1907 _region->set_length (end_sample - _region->position(), 0);
1910 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1911 MidiStreamView* const view = mtv->midi_view();
1913 view->update_note_range(new_note->note());
1915 _marked_for_selection.clear ();
1917 start_note_diff_command (_("step add"));
1919 clear_editor_note_selection ();
1920 note_diff_add_note (new_note, true, false);
1924 // last_step_edit_note = new_note;
1928 MidiRegionView::step_sustain (Temporal::Beats beats)
1930 change_note_lengths (false, false, beats, false, true);
1933 /** Add a new patch change flag to the canvas.
1934 * @param patch the patch change to add
1935 * @param the text to display in the flag
1936 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1939 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1941 samplecnt_t region_samples = source_beats_to_region_samples (patch->time());
1942 const double x = trackview.editor().sample_to_pixel (region_samples);
1944 double const height = midi_stream_view()->contents_height();
1946 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1947 // so we need to do something more sophisticated to keep its color
1948 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1950 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1951 new PatchChange(*this, group,
1957 _patch_change_outline,
1961 if (patch_change->item().width() < _pixel_width) {
1962 // Show unless patch change is beyond the region bounds
1963 if (region_samples < 0 || region_samples >= _region->length()) {
1964 patch_change->hide();
1966 patch_change->show();
1969 patch_change->hide ();
1972 _patch_changes.insert (make_pair (patch, patch_change));
1976 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
1978 /* remove the canvas item */
1979 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1980 if (x->second->patch() == pc->patch()) {
1981 _patch_changes.erase (x);
1987 MIDI::Name::PatchPrimaryKey
1988 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1990 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1993 /// Return true iff @p pc applies to the given time on the given channel.
1995 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Temporal::Beats time, uint8_t channel)
1997 return pc->time() <= time && pc->channel() == channel;
2001 MidiRegionView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
2003 // The earliest event not before time
2004 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
2006 // Go backwards until we find the latest PC for this channel, or the start
2007 while (i != _model->patch_changes().begin() &&
2008 (i == _model->patch_changes().end() ||
2009 !patch_applies(*i, time, channel))) {
2013 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
2014 key.set_bank((*i)->bank());
2015 key.set_program((*i)->program ());
2023 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
2025 string name = _("alter patch change");
2026 trackview.editor().begin_reversible_command (name);
2028 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2030 if (pc.patch()->program() != new_patch.program()) {
2031 c->change_program (pc.patch (), new_patch.program());
2034 int const new_bank = new_patch.bank();
2035 if (pc.patch()->bank() != new_bank) {
2036 c->change_bank (pc.patch (), new_bank);
2039 _model->apply_command (*trackview.session(), c);
2040 trackview.editor().commit_reversible_command ();
2042 remove_canvas_patch_change (&pc);
2043 display_patch_changes ();
2047 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Temporal::Beats> & new_change)
2049 string name = _("alter patch change");
2050 trackview.editor().begin_reversible_command (name);
2051 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2053 if (old_change->time() != new_change.time()) {
2054 c->change_time (old_change, new_change.time());
2057 if (old_change->channel() != new_change.channel()) {
2058 c->change_channel (old_change, new_change.channel());
2061 if (old_change->program() != new_change.program()) {
2062 c->change_program (old_change, new_change.program());
2065 if (old_change->bank() != new_change.bank()) {
2066 c->change_bank (old_change, new_change.bank());
2069 _model->apply_command (*trackview.session(), c);
2070 trackview.editor().commit_reversible_command ();
2072 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2073 if (x->second->patch() == old_change) {
2074 _patch_changes.erase (x);
2079 display_patch_changes ();
2082 /** Add a patch change to the region.
2083 * @param t Time in samples relative to region position
2084 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2085 * MidiTimeAxisView::get_channel_for_add())
2088 MidiRegionView::add_patch_change (samplecnt_t t, Evoral::PatchChange<Temporal::Beats> const & patch)
2090 string name = _("add patch change");
2092 trackview.editor().begin_reversible_command (name);
2093 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2094 c->add (MidiModel::PatchChangePtr (
2095 new Evoral::PatchChange<Temporal::Beats> (
2096 absolute_samples_to_source_beats (_region->position() + t),
2097 patch.channel(), patch.program(), patch.bank()
2102 _model->apply_command (*trackview.session(), c);
2103 trackview.editor().commit_reversible_command ();
2105 display_patch_changes ();
2109 MidiRegionView::move_patch_change (PatchChange& pc, Temporal::Beats t)
2111 trackview.editor().begin_reversible_command (_("move patch change"));
2112 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2113 c->change_time (pc.patch (), t);
2114 _model->apply_command (*trackview.session(), c);
2115 trackview.editor().commit_reversible_command ();
2117 display_patch_changes ();
2121 MidiRegionView::delete_patch_change (PatchChange* pc)
2123 trackview.editor().begin_reversible_command (_("delete patch change"));
2125 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2126 c->remove (pc->patch ());
2127 _model->apply_command (*trackview.session(), c);
2128 trackview.editor().commit_reversible_command ();
2130 remove_canvas_patch_change (pc);
2131 display_patch_changes ();
2135 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2137 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2139 key.set_bank(key.bank() + delta);
2141 key.set_program(key.program() + delta);
2143 change_patch_change(patch, key);
2147 MidiRegionView::note_deleted (NoteBase* cne)
2149 if (_entered_note && cne == _entered_note) {
2153 if (_selection.empty()) {
2157 _selection.erase (cne);
2161 MidiRegionView::delete_selection()
2163 if (_selection.empty()) {
2167 if (trackview.editor().drags()->active()) {
2171 start_note_diff_command (_("delete selection"));
2173 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2174 if ((*i)->selected()) {
2175 _note_diff_command->remove((*i)->note());
2183 hide_verbose_cursor ();
2187 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2189 start_note_diff_command (_("delete note"));
2190 _note_diff_command->remove (n);
2193 hide_verbose_cursor ();
2197 MidiRegionView::clear_editor_note_selection ()
2199 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2200 PublicEditor& editor(trackview.editor());
2201 editor.get_selection().clear_midi_notes();
2205 MidiRegionView::clear_selection ()
2207 clear_selection_internal();
2208 PublicEditor& editor(trackview.editor());
2209 editor.get_selection().remove(this);
2213 MidiRegionView::clear_selection_internal ()
2215 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2217 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2218 (*i)->set_selected(false);
2219 (*i)->hide_velocity();
2224 // Clearing selection entirely, ungrab keyboard
2225 Keyboard::magic_widget_drop_focus();
2226 _grabbed_keyboard = false;
2231 MidiRegionView::unique_select(NoteBase* ev)
2233 clear_editor_note_selection();
2234 add_to_selection(ev);
2238 MidiRegionView::select_all_notes ()
2240 clear_editor_note_selection ();
2242 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2243 add_to_selection (i->second);
2248 MidiRegionView::select_range (samplepos_t start, samplepos_t end)
2250 clear_editor_note_selection ();
2252 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2253 samplepos_t t = source_beats_to_absolute_samples(i->first->time());
2254 if (t >= start && t <= end) {
2255 add_to_selection (i->second);
2261 MidiRegionView::invert_selection ()
2263 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2264 if (i->second->selected()) {
2265 remove_from_selection(i->second);
2267 add_to_selection (i->second);
2272 /** Used for selection undo/redo.
2273 The requested notes most likely won't exist in the view until the next model redisplay.
2276 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2279 list<Evoral::event_id_t>::iterator n;
2281 for (n = notes.begin(); n != notes.end(); ++n) {
2282 if ((cne = find_canvas_note(*n)) != 0) {
2283 add_to_selection (cne);
2285 _pending_note_selection.insert(*n);
2291 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2293 bool have_selection = !_selection.empty();
2294 uint8_t low_note = 127;
2295 uint8_t high_note = 0;
2296 MidiModel::Notes& notes (_model->notes());
2297 _optimization_iterator = _events.begin();
2299 if (extend && !have_selection) {
2303 /* scan existing selection to get note range */
2305 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2306 if ((*i)->note()->note() < low_note) {
2307 low_note = (*i)->note()->note();
2309 if ((*i)->note()->note() > high_note) {
2310 high_note = (*i)->note()->note();
2315 clear_editor_note_selection ();
2317 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2318 /* only note previously selected is the one we are
2319 * reselecting. treat this as cancelling the selection.
2326 low_note = min (low_note, notenum);
2327 high_note = max (high_note, notenum);
2330 _no_sound_notes = true;
2332 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2334 boost::shared_ptr<NoteType> note (*n);
2336 bool select = false;
2338 if (((1 << note->channel()) & channel_mask) != 0) {
2340 if ((note->note() >= low_note && note->note() <= high_note)) {
2343 } else if (note->note() == notenum) {
2349 if ((cne = find_canvas_note (note)) != 0) {
2350 // extend is false because we've taken care of it,
2351 // since it extends by time range, not pitch.
2352 note_selected (cne, add, false);
2356 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2360 _no_sound_notes = false;
2364 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2366 MidiModel::Notes& notes (_model->notes());
2367 _optimization_iterator = _events.begin();
2369 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2371 boost::shared_ptr<NoteType> note (*n);
2374 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2375 if ((cne = find_canvas_note (note)) != 0) {
2376 if (cne->selected()) {
2377 note_deselected (cne);
2379 note_selected (cne, true, false);
2387 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2390 clear_editor_note_selection();
2391 add_to_selection (ev);
2396 if (!ev->selected()) {
2397 add_to_selection (ev);
2401 /* find end of latest note selected, select all between that and the start of "ev" */
2403 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2404 Temporal::Beats latest = Temporal::Beats();
2406 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2407 if ((*i)->note()->end_time() > latest) {
2408 latest = (*i)->note()->end_time();
2410 if ((*i)->note()->time() < earliest) {
2411 earliest = (*i)->note()->time();
2415 if (ev->note()->end_time() > latest) {
2416 latest = ev->note()->end_time();
2419 if (ev->note()->time() < earliest) {
2420 earliest = ev->note()->time();
2423 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2425 /* find notes entirely within OR spanning the earliest..latest range */
2427 if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
2428 (i->first->time() <= earliest && i->first->end_time() >= latest)) {
2429 add_to_selection (i->second);
2436 MidiRegionView::note_deselected(NoteBase* ev)
2438 remove_from_selection (ev);
2442 MidiRegionView::update_drag_selection(samplepos_t start, samplepos_t end, double gy0, double gy1, bool extend)
2444 PublicEditor& editor = trackview.editor();
2446 // Convert to local coordinates
2447 const samplepos_t p = _region->position();
2448 const double y = midi_view()->y_position();
2449 const double x0 = editor.sample_to_pixel(max((samplepos_t)0, start - p));
2450 const double x1 = editor.sample_to_pixel(max((samplepos_t)0, end - p));
2451 const double y0 = max(0.0, gy0 - y);
2452 const double y1 = max(0.0, gy1 - y);
2454 // TODO: Make this faster by storing the last updated selection rect, and only
2455 // adjusting things that are in the area that appears/disappeared.
2456 // We probably need a tree to be able to find events in O(log(n)) time.
2458 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2459 if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
2460 // Rectangles intersect
2461 if (!i->second->selected()) {
2462 add_to_selection (i->second);
2464 } else if (i->second->selected() && !extend) {
2465 // Rectangles do not intersect
2466 remove_from_selection (i->second);
2470 typedef RouteTimeAxisView::AutomationTracks ATracks;
2471 typedef std::list<Selectable*> Selectables;
2473 /* Add control points to selection. */
2474 const ATracks& atracks = midi_view()->automation_tracks();
2475 Selectables selectables;
2476 editor.get_selection().clear_points();
2477 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2478 a->second->get_selectables(start, end, gy0, gy1, selectables);
2479 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2480 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2482 editor.get_selection().add(cp);
2485 a->second->set_selected_points(editor.get_selection().points);
2490 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2496 // TODO: Make this faster by storing the last updated selection rect, and only
2497 // adjusting things that are in the area that appears/disappeared.
2498 // We probably need a tree to be able to find events in O(log(n)) time.
2500 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2501 if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
2502 // within y- (note-) range
2503 if (!i->second->selected()) {
2504 add_to_selection (i->second);
2506 } else if (i->second->selected() && !extend) {
2507 remove_from_selection (i->second);
2513 MidiRegionView::remove_from_selection (NoteBase* ev)
2515 Selection::iterator i = _selection.find (ev);
2517 if (i != _selection.end()) {
2518 _selection.erase (i);
2519 if (_selection.empty() && _grabbed_keyboard) {
2521 Keyboard::magic_widget_drop_focus();
2522 _grabbed_keyboard = false;
2526 ev->set_selected (false);
2527 ev->hide_velocity ();
2529 if (_selection.empty()) {
2530 PublicEditor& editor (trackview.editor());
2531 editor.get_selection().remove (this);
2536 MidiRegionView::add_to_selection (NoteBase* ev)
2538 const bool selection_was_empty = _selection.empty();
2540 if (_selection.insert (ev).second) {
2541 ev->set_selected (true);
2542 start_playing_midi_note ((ev)->note());
2543 if (selection_was_empty && _entered) {
2544 // Grab keyboard for moving notes with arrow keys
2545 Keyboard::magic_widget_grab_focus();
2546 _grabbed_keyboard = true;
2550 if (selection_was_empty) {
2551 PublicEditor& editor (trackview.editor());
2552 editor.get_selection().add (this);
2557 MidiRegionView::earliest_in_selection ()
2559 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2561 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2562 if ((*i)->note()->time() < earliest) {
2563 earliest = (*i)->note()->time();
2571 MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
2573 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2574 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2575 TempoMap& tmap (editor->session()->tempo_map());
2576 PossibleChord to_play;
2577 Temporal::Beats earliest = earliest_in_selection();
2579 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2581 if (n->note()->time() == earliest) {
2582 to_play.push_back (n->note());
2584 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2586 if (midi_view()->note_mode() == Sustained) {
2587 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2588 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2590 /* Hit::x0() is offset by _position.x, unlike Note::x0() */
2591 Hit* hit = dynamic_cast<Hit*>(n);
2593 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2594 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2598 (*i)->move_event(dx, dy);
2601 if (midi_view()->note_mode() == Sustained) {
2602 Note* sus = dynamic_cast<Note*> (*i);
2603 double const len_dx = editor->sample_to_pixel_unrounded (
2604 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2606 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2610 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2612 if (to_play.size() > 1) {
2614 PossibleChord shifted;
2616 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2617 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2618 moved_note->set_note (moved_note->note() + cumulative_dy);
2619 shifted.push_back (moved_note);
2622 start_playing_midi_chord (shifted);
2624 } else if (!to_play.empty()) {
2626 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2627 moved_note->set_note (moved_note->note() + cumulative_dy);
2628 start_playing_midi_note (moved_note);
2634 MidiRegionView::copy_selection (NoteBase* primary)
2636 _copy_drag_events.clear ();
2638 if (_selection.empty()) {
2645 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2646 boost::shared_ptr<NoteType> g (new NoteType (*((*i)->note())));
2647 if (midi_view()->note_mode() == Sustained) {
2648 Note* n = new Note (*this, _note_group, g);
2649 update_sustained (n, false);
2652 Hit* h = new Hit (*this, _note_group, 10, g);
2653 update_hit (h, false);
2657 if ((*i) == primary) {
2661 _copy_drag_events.push_back (note);
2668 MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
2670 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2671 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2672 TempoMap& tmap (editor->session()->tempo_map());
2673 PossibleChord to_play;
2674 Temporal::Beats earliest = earliest_in_selection();
2676 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2678 if (n->note()->time() == earliest) {
2679 to_play.push_back (n->note());
2681 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2683 if (midi_view()->note_mode() == Sustained) {
2684 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2685 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2687 Hit* hit = dynamic_cast<Hit*>(n);
2689 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2690 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2694 (*i)->move_event(dx, dy);
2696 if (midi_view()->note_mode() == Sustained) {
2697 Note* sus = dynamic_cast<Note*> (*i);
2698 double const len_dx = editor->sample_to_pixel_unrounded (
2699 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2701 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2705 if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2707 if (to_play.size() > 1) {
2709 PossibleChord shifted;
2711 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2712 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2713 moved_note->set_note (moved_note->note() + cumulative_dy);
2714 shifted.push_back (moved_note);
2717 start_playing_midi_chord (shifted);
2719 } else if (!to_play.empty()) {
2721 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2722 moved_note->set_note (moved_note->note() + cumulative_dy);
2723 start_playing_midi_note (moved_note);
2729 MidiRegionView::note_dropped(NoteBase *, double d_qn, int8_t dnote, bool copy)
2731 uint8_t lowest_note_in_selection = 127;
2732 uint8_t highest_note_in_selection = 0;
2733 uint8_t highest_note_difference = 0;
2736 // find highest and lowest notes first
2738 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2739 uint8_t pitch = (*i)->note()->note();
2740 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2741 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2745 cerr << "dnote: " << (int) dnote << endl;
2746 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2747 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2748 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2749 << int(highest_note_in_selection) << endl;
2750 cerr << "selection size: " << _selection.size() << endl;
2751 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2754 // Make sure the note pitch does not exceed the MIDI standard range
2755 if (highest_note_in_selection + dnote > 127) {
2756 highest_note_difference = highest_note_in_selection - 127;
2759 start_note_diff_command (_("move notes"));
2761 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2763 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2769 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2771 uint8_t original_pitch = (*i)->note()->note();
2772 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2774 // keep notes in standard midi range
2775 clamp_to_0_127(new_pitch);
2777 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2778 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2780 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2784 clear_editor_note_selection ();
2786 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2787 uint8_t pitch = (*i)->note()->note();
2788 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2789 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2792 // Make sure the note pitch does not exceed the MIDI standard range
2793 if (highest_note_in_selection + dnote > 127) {
2794 highest_note_difference = highest_note_in_selection - 127;
2797 start_note_diff_command (_("copy notes"));
2799 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
2802 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2808 (*i)->note()->set_time (new_time);
2812 uint8_t original_pitch = (*i)->note()->note();
2813 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2815 (*i)->note()->set_note (new_pitch);
2817 // keep notes in standard midi range
2818 clamp_to_0_127(new_pitch);
2820 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2821 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2823 note_diff_add_note ((*i)->note(), true);
2828 _copy_drag_events.clear ();
2831 apply_diff (false, copy);
2833 // care about notes being moved beyond the upper/lower bounds on the canvas
2834 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2835 highest_note_in_selection > midi_stream_view()->highest_note()) {
2836 midi_stream_view()->set_note_range (MidiStreamView::ContentsRange);
2840 /** @param x Pixel relative to the region position.
2841 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2842 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2843 * @return Snapped sample relative to the region position.
2846 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2848 PublicEditor& editor (trackview.editor());
2849 return snap_sample_to_sample (editor.pixel_to_sample (x), ensure_snap).sample;
2852 /** @param x Pixel relative to the region position.
2853 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2854 * @return Snapped pixel relative to the region position.
2857 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2859 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2863 MidiRegionView::get_position_pixels()
2865 samplepos_t region_sample = get_position();
2866 return trackview.editor().sample_to_pixel(region_sample);
2870 MidiRegionView::get_end_position_pixels()
2872 samplepos_t sample = get_position() + get_duration ();
2873 return trackview.editor().sample_to_pixel(sample);
2877 MidiRegionView::source_beats_to_absolute_samples(Temporal::Beats beats) const
2879 /* the time converter will return the sample corresponding to `beats'
2880 relative to the start of the source. The start of the source
2881 is an implied position given by region->position - region->start
2883 const samplepos_t source_start = _region->position() - _region->start();
2884 return source_start + _source_relative_time_converter.to (beats);
2888 MidiRegionView::absolute_samples_to_source_beats(samplepos_t samples) const
2890 /* the `samples' argument needs to be converted into a sample count
2891 relative to the start of the source before being passed in to the
2894 const samplepos_t source_start = _region->position() - _region->start();
2895 return _source_relative_time_converter.from (samples - source_start);
2899 MidiRegionView::region_beats_to_region_samples(Temporal::Beats beats) const
2901 return _region_relative_time_converter.to(beats);
2905 MidiRegionView::region_samples_to_region_beats(samplepos_t samples) const
2907 return _region_relative_time_converter.from(samples);
2911 MidiRegionView::region_samples_to_region_beats_double (samplepos_t samples) const
2913 return _region_relative_time_converter_double.from(samples);
2917 MidiRegionView::begin_resizing (bool /*at_front*/)
2919 _resize_data.clear();
2921 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2922 Note *note = dynamic_cast<Note*> (*i);
2924 // only insert CanvasNotes into the map
2926 NoteResizeData *resize_data = new NoteResizeData();
2927 resize_data->note = note;
2929 // create a new SimpleRect from the note which will be the resize preview
2930 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2931 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2933 // calculate the colors: get the color settings
2934 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2935 UIConfiguration::instance().color ("midi note selected"),
2938 // make the resize preview notes more transparent and bright
2939 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2941 // calculate color based on note velocity
2942 resize_rect->set_fill_color (UINT_INTERPOLATE(
2943 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2947 resize_rect->set_outline_color (NoteBase::calculate_outline (
2948 UIConfiguration::instance().color ("midi note selected")));
2950 resize_data->resize_rect = resize_rect;
2951 _resize_data.push_back(resize_data);
2956 /** Update resizing notes while user drags.
2957 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2958 * @param at_front which end of the note (true == note on, false == note off)
2959 * @param delta_x change in mouse position since the start of the drag
2960 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2961 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2962 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2963 * as the \a primary note.
2964 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2965 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2968 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2970 TempoMap& tmap (trackview.session()->tempo_map());
2971 bool cursor_set = false;
2972 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2974 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2975 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2976 Note* canvas_note = (*i)->note;
2981 current_x = canvas_note->x0() + delta_x + snap_delta;
2983 current_x = primary->x0() + delta_x + snap_delta;
2987 current_x = canvas_note->x1() + delta_x + snap_delta;
2989 current_x = primary->x1() + delta_x + snap_delta;
2993 if (current_x < 0) {
2994 // This works even with snapping because RegionView::snap_sample_to_sample()
2995 // snaps forward if the snapped sample is before the beginning of the region
2998 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2999 current_x = trackview.editor().sample_to_pixel(_region->length());
3004 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3006 resize_rect->set_x0 (current_x - snap_delta);
3008 resize_rect->set_x1 (canvas_note->x1());
3011 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3013 resize_rect->set_x1 (current_x - snap_delta);
3015 resize_rect->set_x0 (canvas_note->x0());
3019 /* Convert snap delta from pixels to beats. */
3020 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3021 double snap_delta_beats = 0.0;
3024 /* negative beat offsets aren't allowed */
3025 if (snap_delta_samps > 0) {
3026 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3027 } else if (snap_delta_samps < 0) {
3028 snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
3033 int32_t divisions = 0;
3036 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
3037 divisions = trackview.editor().get_grid_music_divisions (0);
3039 snapped_x = trackview.editor ().pixel_to_sample (current_x);
3042 const Temporal::Beats beats = Temporal::Beats (tmap.exact_beat_at_sample (snapped_x + midi_region()->position(), divisions)
3043 - midi_region()->beat()) + midi_region()->start_beats();
3045 Temporal::Beats len = Temporal::Beats();
3048 if (beats < canvas_note->note()->end_time()) {
3049 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
3050 len += canvas_note->note()->length();
3053 if (beats >= canvas_note->note()->time()) {
3054 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
3058 len = std::max(Temporal::Beats(1 / 512.0), len);
3061 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
3062 show_verbose_cursor (buf, 0, 0);
3066 trackview.editor().set_snapped_cursor_position ( snapped_x + midi_region()->position() );
3073 /** Finish resizing notes when the user releases the mouse button.
3074 * Parameters the same as for \a update_resizing().
3077 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
3079 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
3080 TempoMap& tmap (trackview.session()->tempo_map());
3082 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
3083 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
3085 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3086 Note* canvas_note = (*i)->note;
3087 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
3089 /* Get the new x position for this resize, which is in pixels relative
3090 * to the region position.
3097 current_x = canvas_note->x0() + delta_x + snap_delta;
3099 current_x = primary->x0() + delta_x + snap_delta;
3103 current_x = canvas_note->x1() + delta_x + snap_delta;
3105 current_x = primary->x1() + delta_x + snap_delta;
3109 if (current_x < 0) {
3112 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3113 current_x = trackview.editor().sample_to_pixel(_region->length());
3116 /* Convert snap delta from pixels to beats with sign. */
3117 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3118 double snap_delta_beats = 0.0;
3121 if (snap_delta_samps > 0) {
3122 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3123 } else if (snap_delta_samps < 0) {
3124 snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
3128 uint32_t divisions = 0;
3129 /* Convert the new x position to a sample within the source */
3130 samplepos_t current_fr;
3132 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
3133 divisions = trackview.editor().get_grid_music_divisions (0);
3135 current_fr = trackview.editor().pixel_to_sample (current_x);
3138 /* and then to beats */
3139 const double e_qaf = tmap.exact_qn_at_sample (current_fr + midi_region()->position(), divisions);
3140 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
3141 const Temporal::Beats x_beats = Temporal::Beats (e_qaf - quarter_note_start);
3143 if (at_front && x_beats < canvas_note->note()->end_time()) {
3144 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
3145 Temporal::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
3146 len += canvas_note->note()->length();
3149 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3154 Temporal::Beats len = std::max(Temporal::Beats(1 / 512.0),
3155 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
3156 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3163 _resize_data.clear();
3168 MidiRegionView::abort_resizing ()
3170 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3171 delete (*i)->resize_rect;
3175 _resize_data.clear ();
3179 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
3181 uint8_t new_velocity;
3184 new_velocity = event->note()->velocity() + velocity;
3185 clamp_to_0_127(new_velocity);
3187 new_velocity = velocity;
3190 event->set_selected (event->selected()); // change color
3192 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
3196 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
3201 new_note = event->note()->note() + note;
3206 clamp_to_0_127 (new_note);
3207 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3211 MidiRegionView::trim_note (NoteBase* event, Temporal::Beats front_delta, Temporal::Beats end_delta)
3213 bool change_start = false;
3214 bool change_length = false;
3215 Temporal::Beats new_start;
3216 Temporal::Beats new_length;
3218 /* NOTE: the semantics of the two delta arguments are slightly subtle:
3220 front_delta: if positive - move the start of the note later in time (shortening it)
3221 if negative - move the start of the note earlier in time (lengthening it)
3223 end_delta: if positive - move the end of the note later in time (lengthening it)
3224 if negative - move the end of the note earlier in time (shortening it)
3227 if (!!front_delta) {
3228 if (front_delta < 0) {
3230 if (event->note()->time() < -front_delta) {
3231 new_start = Temporal::Beats();
3233 new_start = event->note()->time() + front_delta; // moves earlier
3236 /* start moved toward zero, so move the end point out to where it used to be.
3237 Note that front_delta is negative, so this increases the length.
3240 new_length = event->note()->length() - front_delta;
3241 change_start = true;
3242 change_length = true;
3246 Temporal::Beats new_pos = event->note()->time() + front_delta;
3248 if (new_pos < event->note()->end_time()) {
3249 new_start = event->note()->time() + front_delta;
3250 /* start moved toward the end, so move the end point back to where it used to be */
3251 new_length = event->note()->length() - front_delta;
3252 change_start = true;
3253 change_length = true;
3260 bool can_change = true;
3261 if (end_delta < 0) {
3262 if (event->note()->length() < -end_delta) {
3268 new_length = event->note()->length() + end_delta;
3269 change_length = true;
3274 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3277 if (change_length) {
3278 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3283 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3285 uint8_t new_channel;
3289 if (event->note()->channel() < -chn) {
3292 new_channel = event->note()->channel() + chn;
3295 new_channel = event->note()->channel() + chn;
3298 new_channel = (uint8_t) chn;
3301 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3305 MidiRegionView::change_note_time (NoteBase* event, Temporal::Beats delta, bool relative)
3307 Temporal::Beats new_time;
3311 if (event->note()->time() < -delta) {
3312 new_time = Temporal::Beats();
3314 new_time = event->note()->time() + delta;
3317 new_time = event->note()->time() + delta;
3323 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3327 MidiRegionView::change_note_length (NoteBase* event, Temporal::Beats t)
3329 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3333 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3338 if (_selection.empty()) {
3353 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3354 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3360 start_note_diff_command (_("change velocities"));
3362 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3363 Selection::iterator next = i;
3367 if (i == _selection.begin()) {
3368 change_note_velocity (*i, delta, true);
3369 value = (*i)->note()->velocity() + delta;
3371 change_note_velocity (*i, value, false);
3375 change_note_velocity (*i, delta, true);
3384 if (!_selection.empty()) {
3386 snprintf (buf, sizeof (buf), "Vel %d",
3387 (int) (*_selection.begin())->note()->velocity());
3388 show_verbose_cursor (buf, 10, 10);
3394 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3396 if (_selection.empty()) {
3413 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3415 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3419 if ((int8_t) (*i)->note()->note() + delta > 127) {
3426 start_note_diff_command (_("transpose"));
3428 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3429 Selection::iterator next = i;
3431 change_note_note (*i, delta, true);
3439 MidiRegionView::change_note_lengths (bool fine, bool shorter, Temporal::Beats delta, bool start, bool end)
3443 delta = Temporal::Beats(1.0/128.0);
3445 /* grab the current grid distance */
3446 delta = get_grid_beats(_region->position());
3454 start_note_diff_command (_("change note lengths"));
3456 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3457 Selection::iterator next = i;
3460 /* note the negation of the delta for start */
3463 (start ? -delta : Temporal::Beats()),
3464 (end ? delta : Temporal::Beats()));
3473 MidiRegionView::nudge_notes (bool forward, bool fine)
3475 if (_selection.empty()) {
3479 /* pick a note as the point along the timeline to get the nudge distance.
3480 its not necessarily the earliest note, so we may want to pull the notes out
3481 into a vector and sort before using the first one.
3484 const samplepos_t ref_point = source_beats_to_absolute_samples ((*(_selection.begin()))->note()->time());
3485 Temporal::Beats delta;
3489 /* non-fine, move by 1 bar regardless of snap */
3490 delta = Temporal::Beats(trackview.session()->tempo_map().meter_at_sample (ref_point).divisions_per_bar());
3492 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3494 /* grid is off - use nudge distance */
3497 const samplecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3498 delta = region_samples_to_region_beats (fabs ((double)distance));
3504 MusicSample next_pos (ref_point, 0);
3506 if (max_samplepos - 1 < next_pos.sample) {
3507 next_pos.sample += 1;
3510 if (next_pos.sample == 0) {
3513 next_pos.sample -= 1;
3516 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3517 const samplecnt_t distance = ref_point - next_pos.sample;
3518 delta = region_samples_to_region_beats (fabs ((double)distance));
3529 start_note_diff_command (_("nudge"));
3531 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3532 Selection::iterator next = i;
3534 change_note_time (*i, delta, true);
3542 MidiRegionView::change_channel(uint8_t channel)
3544 start_note_diff_command(_("change channel"));
3545 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3546 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3554 MidiRegionView::note_entered(NoteBase* ev)
3558 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3560 if (_mouse_state == SelectTouchDragging) {
3562 note_selected (ev, true);
3564 } else if (editor->current_mouse_mode() == MouseContent) {
3566 remove_ghost_note ();
3567 show_verbose_cursor (ev->note ());
3569 } else if (editor->current_mouse_mode() == MouseDraw) {
3571 remove_ghost_note ();
3572 show_verbose_cursor (ev->note ());
3577 MidiRegionView::note_left (NoteBase*)
3581 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3582 (*i)->hide_velocity ();
3585 hide_verbose_cursor ();
3589 MidiRegionView::patch_entered (PatchChange* p)
3592 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3593 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3594 << _("Channel ") << ((int) p->patch()->channel() + 1);
3595 show_verbose_cursor (s.str(), 10, 20);
3596 p->item().grab_focus();
3600 MidiRegionView::patch_left (PatchChange *)
3602 hide_verbose_cursor ();
3603 /* focus will transfer back via the enter-notify event sent to this
3609 MidiRegionView::sysex_entered (SysEx* p)
3613 // need a way to extract text from p->_flag->_text
3615 // show_verbose_cursor (s.str(), 10, 20);
3616 p->item().grab_focus();
3620 MidiRegionView::sysex_left (SysEx *)
3622 hide_verbose_cursor ();
3623 /* focus will transfer back via the enter-notify event sent to this
3629 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3631 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3632 Editing::MouseMode mm = editor->current_mouse_mode();
3633 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3635 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3636 if (can_set_cursor && ctx) {
3637 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3638 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3639 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3640 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3642 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3648 MidiRegionView::get_fill_color() const
3650 const std::string mod_name = (_dragging ? "dragging region" :
3651 trackview.editor().internal_editing() ? "editable region" :
3654 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3655 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3656 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3657 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3659 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3663 MidiRegionView::midi_channel_mode_changed ()
3665 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3666 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3667 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3669 if (mode == ForceChannel) {
3670 mask = 0xFFFF; // Show all notes as active (below)
3673 // Update notes for selection
3674 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3675 i->second->on_channel_selection_change (mask);
3678 _patch_changes.clear ();
3679 display_patch_changes ();
3683 MidiRegionView::instrument_settings_changed ()
3689 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3691 if (_selection.empty()) {
3695 PublicEditor& editor (trackview.editor());
3699 /* XXX what to do ? */
3703 editor.get_cut_buffer().add (selection_as_cut_buffer());
3711 start_note_diff_command();
3713 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3720 note_diff_remove_note (*i);
3730 MidiRegionView::selection_as_cut_buffer () const
3734 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3735 NoteType* n = (*i)->note().get();
3736 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3739 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3745 /** This method handles undo */
3747 MidiRegionView::paste (samplepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3749 bool commit = false;
3750 // Paste notes, if available
3751 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3752 if (m != selection.midi_notes.end()) {
3753 ctx.counts.increase_n_notes();
3754 if (!(*m)->empty()) {
3757 paste_internal(pos, ctx.count, ctx.times, **m);
3760 // Paste control points to automation children, if available
3761 typedef RouteTimeAxisView::AutomationTracks ATracks;
3762 const ATracks& atracks = midi_view()->automation_tracks();
3763 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3764 if (a->second->paste(pos, selection, ctx, sub_num)) {
3766 trackview.editor().begin_reversible_command (Operations::paste);
3773 trackview.editor().commit_reversible_command ();
3778 /** This method handles undo */
3780 MidiRegionView::paste_internal (samplepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3786 start_note_diff_command (_("paste"));
3788 const Temporal::Beats snap_beats = get_grid_beats(pos);
3789 const Temporal::Beats first_time = (*mcb.notes().begin())->time();
3790 const Temporal::Beats last_time = (*mcb.notes().rbegin())->end_time();
3791 const Temporal::Beats duration = last_time - first_time;
3792 const Temporal::Beats snap_duration = duration.snap_to(snap_beats);
3793 const Temporal::Beats paste_offset = snap_duration * paste_count;
3794 const Temporal::Beats quarter_note = absolute_samples_to_source_beats(pos) + paste_offset;
3795 Temporal::Beats end_point = Temporal::Beats();
3797 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3800 duration, pos, _region->position(),
3803 clear_editor_note_selection ();
3805 for (int n = 0; n < (int) times; ++n) {
3807 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3809 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3810 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3811 copied_note->set_id (Evoral::next_event_id());
3813 /* make all newly added notes selected */
3815 note_diff_add_note (copied_note, true);
3816 end_point = copied_note->end_time();
3820 /* if we pasted past the current end of the region, extend the region */
3822 samplepos_t end_sample = source_beats_to_absolute_samples (end_point);
3823 samplepos_t region_end = _region->position() + _region->length() - 1;
3825 if (end_sample > region_end) {
3827 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_sample));
3829 _region->clear_changes ();
3830 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3831 _region->set_length (end_sample - _region->position(), trackview.editor().get_grid_music_divisions (0));
3832 trackview.session()->add_command (new StatefulDiffCommand (_region));
3838 struct EventNoteTimeEarlyFirstComparator {
3839 bool operator() (NoteBase* a, NoteBase* b) {
3840 return a->note()->time() < b->note()->time();
3845 MidiRegionView::goto_next_note (bool add_to_selection)
3847 bool use_next = false;
3849 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3850 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3851 NoteBase* first_note = 0;
3853 MidiModel::ReadLock lock(_model->read_lock());
3854 MidiModel::Notes& notes (_model->notes());
3856 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
3858 if ((cne = find_canvas_note (*n))) {
3860 if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
3864 if (cne->selected()) {
3867 } else if (use_next) {
3868 if (channel_mask & (1 << (*n)->channel())) {
3869 if (!add_to_selection) {
3870 unique_select (cne);
3872 note_selected (cne, true, false);
3881 /* use the first one */
3883 if (!_events.empty() && first_note) {
3884 unique_select (first_note);
3889 MidiRegionView::goto_previous_note (bool add_to_selection)
3891 bool use_next = false;
3893 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3894 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3895 NoteBase* last_note = 0;
3897 MidiModel::ReadLock lock(_model->read_lock());
3898 MidiModel::Notes& notes (_model->notes());
3900 for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
3902 if ((cne = find_canvas_note (*n))) {
3904 if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
3908 if (cne->selected()) {
3912 } else if (use_next) {
3913 if (channel_mask & (1 << (*n)->channel())) {
3914 if (!add_to_selection) {
3915 unique_select (cne);
3917 note_selected (cne, true, false);
3926 /* use the last one */
3928 if (!_events.empty() && last_note) {
3929 unique_select (last_note);
3934 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3936 bool had_selected = false;
3938 /* we previously time sorted events here, but Notes is a multiset sorted by time */
3940 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3941 if (i->second->selected()) {
3942 selected.insert (i->first);
3943 had_selected = true;
3947 if (allow_all_if_none_selected && !had_selected) {
3948 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3949 selected.insert (i->first);
3955 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3957 x = std::max(0.0, x);
3959 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3964 _note_group->canvas_to_item (x, y);
3966 PublicEditor& editor = trackview.editor ();
3968 samplepos_t const unsnapped_sample = editor.pixel_to_sample (x);
3970 const int32_t divisions = editor.get_grid_music_divisions (state);
3971 const bool shift_snap = midi_view()->note_mode() != Percussive;
3972 const Temporal::Beats snapped_beats = snap_sample_to_grid_underneath (unsnapped_sample, divisions, shift_snap);
3974 /* prevent Percussive mode from displaying a ghost hit at region end */
3975 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3976 _ghost_note->hide();
3977 hide_verbose_cursor ();
3981 /* ghost note may have been snapped before region */
3982 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3983 _ghost_note->hide();
3986 } else if (_ghost_note) {
3987 _ghost_note->show();
3990 /* calculate time in beats relative to start of source */
3991 const Temporal::Beats length = get_grid_beats(unsnapped_sample + _region->position());
3993 _ghost_note->note()->set_time (snapped_beats);
3994 _ghost_note->note()->set_length (length);
3995 _ghost_note->note()->set_note (y_to_note (y));
3996 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3997 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3998 /* the ghost note does not appear in ghost regions, so pass false in here */
3999 update_note (_ghost_note, false);
4001 show_verbose_cursor (_ghost_note->note ());
4005 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
4007 remove_ghost_note ();
4009 boost::shared_ptr<NoteType> g (new NoteType);
4010 if (midi_view()->note_mode() == Sustained) {
4011 _ghost_note = new Note (*this, _note_group, g);
4013 _ghost_note = new Hit (*this, _note_group, 10, g);
4015 _ghost_note->set_ignore_events (true);
4016 _ghost_note->set_outline_color (0x000000aa);
4017 update_ghost_note (x, y, state);
4018 _ghost_note->show ();
4020 show_verbose_cursor (_ghost_note->note ());
4024 MidiRegionView::remove_ghost_note ()
4031 MidiRegionView::hide_verbose_cursor ()
4033 trackview.editor().verbose_cursor()->hide ();
4034 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4036 mtv->set_note_highlight (NO_MIDI_NOTE);
4041 MidiRegionView::snap_changed ()
4047 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
4051 MidiRegionView::drop_down_keys ()
4053 _mouse_state = None;
4057 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
4059 /* XXX: This is dead code. What was it for? */
4061 double note = y_to_note(y);
4063 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4065 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
4067 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
4068 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
4069 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
4070 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
4075 bool add_mrv_selection = false;
4077 if (_selection.empty()) {
4078 add_mrv_selection = true;
4081 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
4082 if (_selection.insert (i->second).second) {
4083 i->second->set_selected (true);
4087 if (add_mrv_selection) {
4088 PublicEditor& editor (trackview.editor());
4089 editor.get_selection().add (this);
4094 MidiRegionView::color_handler ()
4096 RegionView::color_handler ();
4098 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
4099 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
4101 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
4102 i->second->set_selected (i->second->selected()); // will change color
4105 /* XXX probably more to do here */
4109 MidiRegionView::enable_display (bool yn)
4111 RegionView::enable_display (yn);
4115 MidiRegionView::show_step_edit_cursor (Temporal::Beats pos)
4117 if (_step_edit_cursor == 0) {
4118 ArdourCanvas::Item* const group = get_canvas_group();
4120 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
4121 _step_edit_cursor->set_y0 (0);
4122 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
4123 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
4124 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
4127 move_step_edit_cursor (pos);
4128 _step_edit_cursor->show ();
4132 MidiRegionView::move_step_edit_cursor (Temporal::Beats pos)
4134 _step_edit_cursor_position = pos;
4136 if (_step_edit_cursor) {
4137 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_samples (pos));
4138 _step_edit_cursor->set_x0 (pixel);
4139 set_step_edit_cursor_width (_step_edit_cursor_width);
4144 MidiRegionView::hide_step_edit_cursor ()
4146 if (_step_edit_cursor) {
4147 _step_edit_cursor->hide ();
4152 MidiRegionView::set_step_edit_cursor_width (Temporal::Beats beats)
4154 _step_edit_cursor_width = beats;
4156 if (_step_edit_cursor) {
4157 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
4158 region_beats_to_region_samples (_step_edit_cursor_position + beats)
4159 - region_beats_to_region_samples (_step_edit_cursor_position)));
4163 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
4164 * @param w Source that the data will end up in.
4167 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
4169 if (!_active_notes) {
4170 /* we aren't actively being recorded to */
4174 boost::shared_ptr<MidiSource> src = w.lock ();
4175 if (!src || src != midi_region()->midi_source()) {
4176 /* recorded data was not destined for our source */
4180 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
4182 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
4184 samplepos_t back = max_samplepos;
4186 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
4187 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
4189 if (ev.is_channel_event()) {
4190 if (get_channel_mode() == FilterChannels) {
4191 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
4197 /* convert from session samples to source beats */
4198 Temporal::Beats const time_beats = _source_relative_time_converter.from(
4199 ev.time() - src->timeline_position() + _region->start());
4201 if (ev.type() == MIDI_CMD_NOTE_ON) {
4202 boost::shared_ptr<NoteType> note (
4203 new NoteType (ev.channel(), time_beats, Temporal::Beats(), ev.note(), ev.velocity()));
4205 add_note (note, true);
4207 /* fix up our note range */
4208 if (ev.note() < _current_range_min) {
4209 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
4210 } else if (ev.note() > _current_range_max) {
4211 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
4214 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
4215 resolve_note (ev.note (), time_beats);
4221 midi_stream_view()->check_record_layers (region(), back);
4225 MidiRegionView::trim_front_starting ()
4227 /* We used to eparent the note group to the region view's parent, so that it didn't change.
4233 MidiRegionView::trim_front_ending ()
4235 if (_region->start() < 0) {
4236 /* Trim drag made start time -ve; fix this */
4237 midi_region()->fix_negative_start ();
4242 MidiRegionView::edit_patch_change (PatchChange* pc)
4244 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4246 int response = d.run();
4249 case Gtk::RESPONSE_ACCEPT:
4251 case Gtk::RESPONSE_REJECT:
4252 delete_patch_change (pc);
4258 change_patch_change (pc->patch(), d.patch ());
4262 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4265 // sysyex object doesn't have a pointer to a sysex event
4266 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4267 // c->remove (sysex->sysex());
4268 // _model->apply_command (*trackview.session(), c);
4270 //_sys_exes.clear ();
4271 // display_sysexes();
4275 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4277 using namespace MIDI::Name;
4280 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4282 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4284 MIDI::Name::PatchPrimaryKey patch_key;
4285 get_patch_key_at(n->time(), n->channel(), patch_key);
4286 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4289 patch_key.program(),
4295 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4297 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4298 (int) n->channel() + 1,
4299 (int) n->velocity());
4305 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4306 uint8_t new_value) const
4308 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4310 mtv->set_note_highlight (new_value);
4313 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4317 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4319 show_verbose_cursor_for_new_note_value(n, n->note());
4323 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4325 trackview.editor().verbose_cursor()->set (text);
4326 trackview.editor().verbose_cursor()->show ();
4327 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4331 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4333 if (_model->notes().empty()) {
4334 return 0x40; // No notes, use default
4337 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4338 if (m == _model->notes().begin()) {
4339 // Before the start, use the velocity of the first note
4340 return (*m)->velocity();
4341 } else if (m == _model->notes().end()) {
4342 // Past the end, use the velocity of the last note
4344 return (*m)->velocity();
4347 // Interpolate velocity of surrounding notes
4348 MidiModel::Notes::const_iterator n = m;
4351 const double frac = ((time - (*n)->time()).to_double() /
4352 ((*m)->time() - (*n)->time()).to_double());
4354 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4357 /** @param p A session samplepos.
4358 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4359 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4360 * @return beat duration of p snapped to the grid subdivision underneath it.
4363 MidiRegionView::snap_sample_to_grid_underneath (samplepos_t p, int32_t divisions, bool shift_snap) const
4365 TempoMap& map (trackview.session()->tempo_map());
4366 double eqaf = map.exact_qn_at_sample (p + _region->position(), divisions);
4368 if (divisions != 0 && shift_snap) {
4369 const double qaf = map.quarter_note_at_sample (p + _region->position());
4370 /* Hack so that we always snap to the note that we are over, instead of snapping
4371 to the next one if we're more than halfway through the one we're over.
4373 const Temporal::Beats grid_beats = get_grid_beats (p + _region->position());
4374 const double rem = eqaf - qaf;
4376 eqaf -= grid_beats.to_double();
4379 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4381 return Temporal::Beats (eqaf - session_start_off);
4385 MidiRegionView::get_channel_mode () const
4387 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4388 return rtav->midi_track()->get_playback_channel_mode();
4392 MidiRegionView::get_selected_channels () const
4394 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4395 return rtav->midi_track()->get_playback_channel_mask();
4400 MidiRegionView::get_grid_beats(samplepos_t pos) const
4402 PublicEditor& editor = trackview.editor();
4403 bool success = false;
4404 Temporal::Beats beats = editor.get_grid_type_as_beats (success, pos);
4406 beats = Temporal::Beats(1);
4411 MidiRegionView::y_to_note (double y) const
4413 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4414 + _current_range_min;
4418 } else if (n > 127) {
4422 /* min due to rounding and/or off-by-one errors */
4423 return min ((uint8_t) n, _current_range_max);
4427 MidiRegionView::note_to_y(uint8_t note) const
4429 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
4433 MidiRegionView::session_relative_qn (double qn) const
4435 return qn + (region()->quarter_note() - midi_region()->start_beats());