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 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
138 connect_to_diskstream ();
141 MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
142 RouteTimeAxisView& tv,
143 boost::shared_ptr<MidiRegion> r,
145 uint32_t basic_color,
147 TimeAxisViewItem::Visibility visibility)
148 : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
149 , _current_range_min(0)
150 , _current_range_max(0)
151 , _region_relative_time_converter(r->session().tempo_map(), r->position())
152 , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
153 , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
155 , _note_group (new ArdourCanvas::Container (group))
156 , _note_diff_command (0)
158 , _step_edit_cursor (0)
159 , _step_edit_cursor_width (1.0)
160 , _step_edit_cursor_position (0.0)
161 , _channel_selection_scoped_note (0)
164 , _optimization_iterator (_events.end())
166 , _no_sound_notes (false)
167 , _last_display_zoom (0)
170 , _grabbed_keyboard (false)
173 , _mouse_changed_selection (false)
175 CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
177 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
178 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
180 _note_group->raise_to_top();
182 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
184 connect_to_diskstream ();
188 MidiRegionView::parameter_changed (std::string const & p)
190 if (p == "display-first-midi-bank-as-zero") {
191 if (_enable_display) {
194 } else if (p == "color-regions-using-track-color") {
196 } else if (p == "use-note-color-for-velocity") {
201 MidiRegionView::MidiRegionView (const MidiRegionView& other)
202 : sigc::trackable(other)
204 , _current_range_min(0)
205 , _current_range_max(0)
206 , _region_relative_time_converter(other.region_relative_time_converter())
207 , _source_relative_time_converter(other.source_relative_time_converter())
208 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
210 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
211 , _note_diff_command (0)
213 , _step_edit_cursor (0)
214 , _step_edit_cursor_width (1.0)
215 , _step_edit_cursor_position (0.0)
216 , _channel_selection_scoped_note (0)
219 , _optimization_iterator (_events.end())
221 , _no_sound_notes (false)
222 , _last_display_zoom (0)
225 , _grabbed_keyboard (false)
228 , _mouse_changed_selection (false)
233 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
234 : RegionView (other, boost::shared_ptr<Region> (region))
235 , _current_range_min(0)
236 , _current_range_max(0)
237 , _region_relative_time_converter(other.region_relative_time_converter())
238 , _source_relative_time_converter(other.source_relative_time_converter())
239 , _region_relative_time_converter_double(other.region_relative_time_converter_double())
241 , _note_group (new ArdourCanvas::Container (get_canvas_group()))
242 , _note_diff_command (0)
244 , _step_edit_cursor (0)
245 , _step_edit_cursor_width (1.0)
246 , _step_edit_cursor_position (0.0)
247 , _channel_selection_scoped_note (0)
250 , _optimization_iterator (_events.end())
252 , _no_sound_notes (false)
253 , _last_display_zoom (0)
256 , _grabbed_keyboard (false)
259 , _mouse_changed_selection (false)
265 MidiRegionView::init (bool wfd)
267 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
270 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
271 midi_region()->midi_source(0)->load_model(lm);
274 _model = midi_region()->midi_source(0)->model();
275 _enable_display = false;
276 fill_color_name = "midi frame base";
278 RegionView::init (false);
280 //set_height (trackview.current_height());
283 region_sync_changed ();
284 region_resized (ARDOUR::bounds_change);
289 _enable_display = true;
292 display_model (_model);
296 reset_width_dependent_items (_pixel_width);
298 group->raise_to_top();
300 midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
301 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
304 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
305 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
307 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
308 boost::bind (&MidiRegionView::snap_changed, this),
311 trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
312 boost::bind (&MidiRegionView::mouse_mode_changed, this),
315 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
316 UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
317 connect_to_diskstream ();
321 MidiRegionView::instrument_info () const
323 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
324 return route_ui->route()->instrument_info();
327 const boost::shared_ptr<ARDOUR::MidiRegion>
328 MidiRegionView::midi_region() const
330 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
334 MidiRegionView::connect_to_diskstream ()
336 midi_view()->midi_track()->DataRecorded.connect(
337 *this, invalidator(*this),
338 boost::bind (&MidiRegionView::data_recorded, this, _1),
343 MidiRegionView::canvas_group_event(GdkEvent* ev)
345 if (in_destructor || _recregion) {
349 if (!trackview.editor().internal_editing()) {
350 // not in internal edit mode, so just act like a normal region
351 return RegionView::canvas_group_event (ev);
354 //For now, move the snapped cursor aside so it doesn't bother you during internal editing
355 //trackview.editor().set_snapped_cursor_position(_region->position());
360 case GDK_ENTER_NOTIFY:
361 _last_event_x = ev->crossing.x;
362 _last_event_y = ev->crossing.y;
363 enter_notify(&ev->crossing);
364 // set entered_regionview (among other things)
365 return RegionView::canvas_group_event (ev);
367 case GDK_LEAVE_NOTIFY:
368 _last_event_x = ev->crossing.x;
369 _last_event_y = ev->crossing.y;
370 leave_notify(&ev->crossing);
371 // reset entered_regionview (among other things)
372 return RegionView::canvas_group_event (ev);
375 if (scroll (&ev->scroll)) {
381 return key_press (&ev->key);
383 case GDK_KEY_RELEASE:
384 return key_release (&ev->key);
386 case GDK_BUTTON_PRESS:
387 return button_press (&ev->button);
389 case GDK_BUTTON_RELEASE:
390 r = button_release (&ev->button);
393 case GDK_MOTION_NOTIFY:
394 _last_event_x = ev->motion.x;
395 _last_event_y = ev->motion.y;
396 return motion (&ev->motion);
402 return RegionView::canvas_group_event (ev);
406 MidiRegionView::enter_notify (GdkEventCrossing* ev)
408 enter_internal (ev->state);
415 MidiRegionView::leave_notify (GdkEventCrossing*)
424 MidiRegionView::mouse_mode_changed ()
426 // Adjust sample colour (become more transparent for internal tools)
430 if (!trackview.editor().internal_editing()) {
431 /* Switched out of internal editing mode while entered.
432 Only necessary for leave as a mouse_mode_change over a region
433 automatically triggers an enter event. */
436 else if (trackview.editor().current_mouse_mode() == MouseContent) {
437 // hide cursor and ghost note after changing to internal edit mode
438 remove_ghost_note ();
440 /* XXX This is problematic as the function is executed for every region
441 and only for one region _entered_note can be true. Still it's
442 necessary as to hide the verbose cursor when we're changing from
443 draw mode to internal edit mode. These lines are the reason why
444 in some situations no verbose cursor is shown when we enter internal
445 edit mode over a note. */
446 if (!_entered_note) {
447 hide_verbose_cursor ();
454 MidiRegionView::enter_internal (uint32_t state)
456 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
457 // Show ghost note under pencil
458 create_ghost_note(_last_event_x, _last_event_y, state);
461 if (!_selection.empty()) {
462 // Grab keyboard for moving selected notes with arrow keys
463 Keyboard::magic_widget_grab_focus();
464 _grabbed_keyboard = true;
467 // Lower sample handles below notes so they don't steal events
468 if (sample_handle_start) {
469 sample_handle_start->lower_to_bottom();
471 if (sample_handle_end) {
472 sample_handle_end->lower_to_bottom();
477 MidiRegionView::leave_internal()
479 hide_verbose_cursor ();
480 remove_ghost_note ();
483 if (_grabbed_keyboard) {
484 Keyboard::magic_widget_drop_focus();
485 _grabbed_keyboard = false;
488 // Raise sample handles above notes so they catch events
489 if (sample_handle_start) {
490 sample_handle_start->raise_to_top();
492 if (sample_handle_end) {
493 sample_handle_end->raise_to_top();
498 MidiRegionView::button_press (GdkEventButton* ev)
500 if (ev->button != 1) {
504 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
505 MouseMode m = editor->current_mouse_mode();
507 if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
508 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
511 if (_mouse_state != SelectTouchDragging) {
513 _pressed_button = ev->button;
515 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
517 if (midi_view()->note_mode() == Percussive) {
518 editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
520 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
523 _mouse_state = AddDragging;
524 remove_ghost_note ();
525 hide_verbose_cursor ();
527 _mouse_state = Pressed;
533 _pressed_button = ev->button;
534 _mouse_changed_selection = false;
540 MidiRegionView::button_release (GdkEventButton* ev)
542 double event_x, event_y;
544 if (ev->button != 1) {
551 group->canvas_to_item (event_x, event_y);
554 PublicEditor& editor = trackview.editor ();
556 _press_cursor_ctx.reset();
558 switch (_mouse_state) {
559 case Pressed: // Clicked
561 switch (editor.current_mouse_mode()) {
563 /* no motion occurred - simple click */
564 clear_editor_note_selection ();
565 _mouse_changed_selection = true;
571 _mouse_changed_selection = true;
572 clear_editor_note_selection ();
587 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
588 we don't want one when we were drag-selecting either. */
589 case SelectRectDragging:
590 editor.drags()->end_grab ((GdkEvent *) ev);
599 if (_mouse_changed_selection) {
600 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
601 trackview.editor().commit_reversible_selection_op ();
608 MidiRegionView::motion (GdkEventMotion* ev)
610 PublicEditor& editor = trackview.editor ();
612 if (!_entered_note) {
614 if (_mouse_state == AddDragging) {
616 remove_ghost_note ();
619 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
620 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
621 _mouse_state != AddDragging) {
623 create_ghost_note (ev->x, ev->y, ev->state);
625 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
626 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
628 update_ghost_note (ev->x, ev->y, ev->state);
630 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
632 remove_ghost_note ();
633 hide_verbose_cursor ();
635 } else if (editor.current_mouse_mode() == MouseDraw) {
638 update_ghost_note (ev->x, ev->y, ev->state);
641 create_ghost_note (ev->x, ev->y, ev->state);
646 /* any motion immediately hides velocity text that may have been visible */
648 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
649 (*i)->hide_velocity ();
652 switch (_mouse_state) {
655 if (_pressed_button == 1) {
657 MouseMode m = editor.current_mouse_mode();
659 if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
660 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
661 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
662 clear_editor_note_selection ();
663 _mouse_changed_selection = true;
665 _mouse_state = SelectRectDragging;
667 } else if (m == MouseRange) {
668 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
669 _mouse_state = SelectVerticalDragging;
676 case SelectRectDragging:
677 case SelectVerticalDragging:
679 editor.drags()->motion_handler ((GdkEvent *) ev, false);
682 case SelectTouchDragging:
690 //let RegionView do it's thing. drags are handled in here
691 return RegionView::canvas_group_event ((GdkEvent *) ev);
696 MidiRegionView::scroll (GdkEventScroll* ev)
698 if (_selection.empty()) {
702 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
703 Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
704 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
705 * through so that it still works for navigation.
710 hide_verbose_cursor ();
712 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
713 Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
714 bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
716 if (ev->direction == GDK_SCROLL_UP) {
717 change_velocities (true, fine, false, together);
718 } else if (ev->direction == GDK_SCROLL_DOWN) {
719 change_velocities (false, fine, false, together);
721 /* left, right: we don't use them */
729 MidiRegionView::key_press (GdkEventKey* ev)
731 /* since GTK bindings are generally activated on press, and since
732 detectable auto-repeat is the name of the game and only sends
733 repeated presses, carry out key actions at key press, not release.
735 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
737 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
739 if (_mouse_state != AddDragging) {
740 _mouse_state = SelectTouchDragging;
745 } else if (ev->keyval == GDK_Escape && unmodified) {
746 clear_editor_note_selection ();
749 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
751 bool start = (ev->keyval == GDK_comma);
752 bool end = (ev->keyval == GDK_period);
753 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
754 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
756 change_note_lengths (fine, shorter, Temporal::Beats(), start, end);
760 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
762 if (_selection.empty()) {
769 } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
771 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
773 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
774 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
776 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
779 trackview.editor().commit_reversible_selection_op();
783 } else if (ev->keyval == GDK_Up) {
785 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
786 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
787 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
789 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
790 change_velocities (true, fine, allow_smush, together);
792 transpose (true, fine, allow_smush);
796 } else if (ev->keyval == GDK_Down) {
798 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
799 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
800 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
802 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
803 change_velocities (false, fine, allow_smush, together);
805 transpose (false, fine, allow_smush);
809 } else if (ev->keyval == GDK_Left) {
811 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
812 nudge_notes (false, fine);
815 } else if (ev->keyval == GDK_Right) {
817 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
818 nudge_notes (true, fine);
821 } else if (ev->keyval == GDK_c && unmodified) {
825 } else if (ev->keyval == GDK_v && unmodified) {
834 MidiRegionView::key_release (GdkEventKey* ev)
836 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
844 MidiRegionView::channel_edit ()
846 if (_selection.empty()) {
850 /* pick a note somewhat at random (since Selection is a set<>) to
851 * provide the "current" channel for the dialog.
854 uint8_t current_channel = (*_selection.begin())->note()->channel ();
855 MidiChannelDialog channel_dialog (current_channel);
856 int ret = channel_dialog.run ();
859 case Gtk::RESPONSE_OK:
865 uint8_t new_channel = channel_dialog.active_channel ();
867 start_note_diff_command (_("channel edit"));
869 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
870 Selection::iterator next = i;
872 change_note_channel (*i, new_channel);
880 MidiRegionView::velocity_edit ()
882 if (_selection.empty()) {
886 /* pick a note somewhat at random (since Selection is a set<>) to
887 * provide the "current" velocity for the dialog.
890 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
891 MidiVelocityDialog velocity_dialog (current_velocity);
892 int ret = velocity_dialog.run ();
895 case Gtk::RESPONSE_OK:
901 uint8_t new_velocity = velocity_dialog.velocity ();
903 start_note_diff_command (_("velocity edit"));
905 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
906 Selection::iterator next = i;
908 change_note_velocity (*i, new_velocity, false);
916 MidiRegionView::show_list_editor ()
919 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
921 _list_editor->present ();
924 /** Add a note to the model, and the view, at a canvas (click) coordinate.
925 * \param t time in samples relative to the position of the region
926 * \param y vertical position in pixels
927 * \param length duration of the note in beats
928 * \param snap_t true to snap t to the grid, otherwise false.
931 MidiRegionView::create_note_at (samplepos_t t, double y, Temporal::Beats length, uint32_t state, bool shift_snap)
933 if (length < 2 * DBL_EPSILON) {
937 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
938 MidiStreamView* const view = mtv->midi_view();
939 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
945 // Start of note in samples relative to region start
946 const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
947 Temporal::Beats beat_time = snap_sample_to_grid_underneath (t, divisions, shift_snap);
949 const double note = view->y_to_note(y);
950 const uint8_t chan = mtv->get_channel_for_add();
951 const uint8_t velocity = get_velocity_for_add(beat_time);
953 const boost::shared_ptr<NoteType> new_note(
954 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
956 if (_model->contains (new_note)) {
960 view->update_note_range(new_note->note());
962 start_note_diff_command(_("add note"));
964 note_diff_add_note (new_note, true, false);
968 play_midi_note (new_note);
972 MidiRegionView::clear_events ()
974 // clear selection without signaling
975 clear_selection_internal ();
978 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
979 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
985 _note_group->clear (true);
987 _patch_changes.clear();
989 _optimization_iterator = _events.end();
993 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
997 content_connection.disconnect ();
998 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
999 /* Don't signal as nobody else needs to know until selection has been altered. */
1002 if (_enable_display) {
1008 MidiRegionView::start_note_diff_command (string name)
1010 if (!_note_diff_command) {
1011 trackview.editor().begin_reversible_command (name);
1012 _note_diff_command = _model->new_note_diff_command (name);
1017 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1019 if (_note_diff_command) {
1020 _note_diff_command->add (note);
1023 _marked_for_selection.insert(note);
1025 if (show_velocity) {
1026 _marked_for_velocity.insert(note);
1031 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1033 if (_note_diff_command && ev->note()) {
1034 _note_diff_command->remove(ev->note());
1039 MidiRegionView::note_diff_add_change (NoteBase* ev,
1040 MidiModel::NoteDiffCommand::Property property,
1043 if (_note_diff_command) {
1044 _note_diff_command->change (ev->note(), property, val);
1049 MidiRegionView::note_diff_add_change (NoteBase* ev,
1050 MidiModel::NoteDiffCommand::Property property,
1051 Temporal::Beats val)
1053 if (_note_diff_command) {
1054 _note_diff_command->change (ev->note(), property, val);
1059 MidiRegionView::apply_diff (bool as_subcommand, bool was_copy)
1061 bool commit = false;
1063 if (!_note_diff_command) {
1067 bool add_or_remove = _note_diff_command->adds_or_removes();
1069 if (!was_copy && add_or_remove) {
1070 // Mark all selected notes for selection when model reloads
1071 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1072 _marked_for_selection.insert((*i)->note());
1076 midi_view()->midi_track()->midi_playlist()->region_edited (_region, _note_diff_command);
1078 if (as_subcommand) {
1079 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1081 _model->apply_command (*trackview.session(), _note_diff_command);
1085 _note_diff_command = 0;
1087 if (add_or_remove) {
1088 _marked_for_selection.clear();
1091 _marked_for_velocity.clear();
1093 trackview.editor().commit_reversible_command ();
1098 MidiRegionView::abort_command()
1100 delete _note_diff_command;
1101 _note_diff_command = 0;
1102 trackview.editor().abort_reversible_command();
1103 clear_editor_note_selection();
1107 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1110 if (_optimization_iterator != _events.end()) {
1111 ++_optimization_iterator;
1114 if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) {
1115 return _optimization_iterator->second;
1118 _optimization_iterator = _events.find (note);
1119 if (_optimization_iterator != _events.end()) {
1120 return _optimization_iterator->second;
1126 /** This version finds any canvas note matching the supplied note. */
1128 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1130 Events::iterator it;
1132 for (it = _events.begin(); it != _events.end(); ++it) {
1133 if (it->first->id() == id) {
1141 boost::shared_ptr<PatchChange>
1142 MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
1144 PatchChanges::const_iterator f = _patch_changes.find (p);
1146 if (f != _patch_changes.end()) {
1150 return boost::shared_ptr<PatchChange>();
1153 boost::shared_ptr<SysEx>
1154 MidiRegionView::find_canvas_sys_ex (MidiModel::SysExPtr s)
1156 SysExes::const_iterator f = _sys_exes.find (s);
1158 if (f != _sys_exes.end()) {
1162 return boost::shared_ptr<SysEx>();
1166 MidiRegionView::get_events (Events& e, Evoral::Sequence<Temporal::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1168 MidiModel::Notes notes;
1169 _model->get_notes (notes, op, val, chan_mask);
1171 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1172 NoteBase* cne = find_canvas_note (*n);
1174 e.insert (make_pair (*n, cne));
1180 MidiRegionView::redisplay_model()
1182 if (_active_notes) {
1183 // Currently recording
1184 const samplecnt_t zoom = trackview.editor().get_current_zoom();
1185 if (zoom != _last_display_zoom) {
1186 /* Update resolved canvas notes to reflect changes in zoom without
1187 touching model. Leave active notes (with length 0) alone since
1188 they are being extended. */
1189 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1190 if (i->second->note()->length() > 0) {
1191 update_note(i->second);
1194 _last_display_zoom = zoom;
1203 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1204 _optimization_iterator->second->invalidate();
1207 bool empty_when_starting = _events.empty();
1208 _optimization_iterator = _events.begin();
1209 MidiModel::Notes missing_notes;
1213 MidiModel::ReadLock lock(_model->read_lock());
1214 MidiModel::Notes& notes (_model->notes());
1217 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1219 boost::shared_ptr<NoteType> note (*n);
1222 if (note_in_region_range (note, visible)) {
1223 if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1231 missing_notes.insert (note);
1236 if (!empty_when_starting) {
1237 MidiModel::Notes::iterator f;
1238 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1240 NoteBase* cne = i->second;
1242 /* remove note items that are no longer valid */
1243 if (!cne->valid()) {
1245 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1246 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1248 gr->remove_note (cne);
1253 i = _events.erase (i);
1256 bool visible = cne->item()->visible();
1258 if ((sus = dynamic_cast<Note*>(cne))) {
1261 update_sustained (sus);
1264 } else if ((hit = dynamic_cast<Hit*>(cne))) {
1276 for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
1277 boost::shared_ptr<NoteType> note (*n);
1281 if (note_in_region_range (note, visible)) {
1283 cne = add_note (note, true);
1285 cne = add_note (note, false);
1288 cne = add_note (note, false);
1291 for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1292 if ((*it) == note->id()) {
1293 add_to_selection (cne);
1298 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1299 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1300 if (gr && !gr->trackview.hidden()) {
1301 gr->redisplay_model ();
1306 display_patch_changes ();
1308 _marked_for_selection.clear ();
1309 _marked_for_velocity.clear ();
1310 _pending_note_selection.clear ();
1315 MidiRegionView::display_patch_changes ()
1317 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1318 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1320 for (uint8_t i = 0; i < 16; ++i) {
1321 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1325 /** @param active_channel true to display patch changes fully, false to display
1326 * them `greyed-out' (as on an inactive channel)
1329 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1331 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1332 boost::shared_ptr<PatchChange> p;
1334 if ((*i)->channel() != channel) {
1338 if ((p = find_canvas_patch_change (*i)) != 0) {
1340 const samplecnt_t region_samples = source_beats_to_region_samples ((*i)->time());
1342 if (region_samples < 0 || region_samples >= _region->length()) {
1345 const double x = trackview.editor().sample_to_pixel (region_samples);
1346 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1347 p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
1348 p->set_text (patch_name);
1354 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1355 add_canvas_patch_change (*i, patch_name, active_channel);
1361 MidiRegionView::display_sysexes()
1363 bool have_periodic_system_messages = false;
1364 bool display_periodic_messages = true;
1366 if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1368 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1369 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1370 have_periodic_system_messages = true;
1375 if (have_periodic_system_messages) {
1376 double zoom = trackview.editor().get_current_zoom (); // samples per pixel
1378 /* get an approximate value for the number of samples per video frame */
1380 double video_frame = trackview.session()->sample_rate() * (1.0/30);
1382 /* if we are zoomed out beyond than the cutoff (i.e. more
1383 * samples per pixel than samples per 4 video frames), don't
1384 * show periodic sysex messages.
1387 if (zoom > (video_frame*4)) {
1388 display_periodic_messages = false;
1392 display_periodic_messages = false;
1395 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1396 MidiModel::SysExPtr sysex_ptr = *i;
1397 Temporal::Beats time = sysex_ptr->time();
1399 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1400 if (!display_periodic_messages) {
1407 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1408 str << int((*i)->buffer()[b]);
1409 if (b != (*i)->size() -1) {
1413 string text = str.str();
1415 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_samples(time));
1417 double height = midi_stream_view()->contents_height();
1419 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1420 // SysEx canvas object!!!
1421 boost::shared_ptr<SysEx> sysex = find_canvas_sys_ex (sysex_ptr);
1424 sysex = boost::shared_ptr<SysEx>(
1425 new SysEx (*this, _note_group, text, height, x, 1.0, sysex_ptr));
1426 _sys_exes.insert (make_pair (sysex_ptr, sysex));
1428 sysex->set_height (height);
1429 sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
1432 // Show unless message is beyond the region bounds
1433 // XXX REQUIRES APPROPRIATE OPERATORS FOR Temporal::Beats and samplepos? say what?
1434 #warning paul fix this
1435 // if (time - _region->start() >= _region->length() || time < _region->start()) {
1443 MidiRegionView::~MidiRegionView ()
1445 in_destructor = true;
1447 hide_verbose_cursor ();
1449 delete _list_editor;
1451 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1453 if (_active_notes) {
1460 delete _note_diff_command;
1461 delete _step_edit_cursor;
1465 MidiRegionView::region_resized (const PropertyChange& what_changed)
1467 RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1469 if (what_changed.contains (ARDOUR::Properties::position)) {
1470 _region_relative_time_converter.set_origin_b(_region->position());
1471 _region_relative_time_converter_double.set_origin_b(_region->position());
1472 /* reset_width dependent_items() redisplays model */
1476 if (what_changed.contains (ARDOUR::Properties::start) ||
1477 what_changed.contains (ARDOUR::Properties::position)) {
1478 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1480 /* catch end and start trim so we can update the view*/
1481 if (!what_changed.contains (ARDOUR::Properties::start) &&
1482 what_changed.contains (ARDOUR::Properties::length)) {
1483 enable_display (true);
1484 } else if (what_changed.contains (ARDOUR::Properties::start) &&
1485 what_changed.contains (ARDOUR::Properties::length)) {
1486 enable_display (true);
1491 MidiRegionView::reset_width_dependent_items (double pixel_width)
1493 RegionView::reset_width_dependent_items(pixel_width);
1495 if (_enable_display) {
1499 bool hide_all = false;
1500 PatchChanges::iterator x = _patch_changes.begin();
1501 if (x != _patch_changes.end()) {
1502 hide_all = x->second->width() >= _pixel_width;
1506 for (; x != _patch_changes.end(); ++x) {
1511 move_step_edit_cursor (_step_edit_cursor_position);
1512 set_step_edit_cursor_width (_step_edit_cursor_width);
1516 MidiRegionView::set_height (double height)
1518 double old_height = _height;
1519 RegionView::set_height(height);
1521 apply_note_range (midi_stream_view()->lowest_note(),
1522 midi_stream_view()->highest_note(),
1523 height != old_height);
1526 name_text->raise_to_top();
1529 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1530 (*x).second->set_height (midi_stream_view()->contents_height());
1533 if (_step_edit_cursor) {
1534 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1539 /** Apply the current note range from the stream view
1540 * by repositioning/hiding notes as necessary
1543 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1545 if (!_enable_display) {
1549 if (!force && _current_range_min == min && _current_range_max == max) {
1553 _current_range_min = min;
1554 _current_range_max = max;
1560 MidiRegionView::add_ghost (TimeAxisView& tv)
1562 double unit_position = _region->position () / samples_per_pixel;
1563 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1564 MidiGhostRegion* ghost;
1566 if (mtv && mtv->midi_view()) {
1567 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1568 to allow having midi notes on top of note lines and waveforms.
1570 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1572 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1575 ghost->set_colors ();
1576 ghost->set_height ();
1577 ghost->set_duration (_region->length() / samples_per_pixel);
1579 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1580 ghost->add_note(i->second);
1583 ghosts.push_back (ghost);
1584 enable_display (true);
1589 /** Begin tracking note state for successive calls to add_event
1592 MidiRegionView::begin_write()
1594 if (_active_notes) {
1595 delete[] _active_notes;
1597 _active_notes = new Note*[128];
1598 for (unsigned i = 0; i < 128; ++i) {
1599 _active_notes[i] = 0;
1604 /** Destroy note state for add_event
1607 MidiRegionView::end_write()
1609 delete[] _active_notes;
1611 _marked_for_selection.clear();
1612 _marked_for_velocity.clear();
1616 /** Resolve an active MIDI note (while recording).
1619 MidiRegionView::resolve_note(uint8_t note, Temporal::Beats end_time)
1621 if (midi_view()->note_mode() != Sustained) {
1625 if (_active_notes && _active_notes[note]) {
1626 /* Set note length so update_note() works. Note this is a local note
1627 for recording, not from a model, so we can safely mess with it. */
1628 _active_notes[note]->note()->set_length(
1629 end_time - _active_notes[note]->note()->time());
1631 /* End time is relative to the region being recorded. */
1632 const samplepos_t end_time_samples = region_beats_to_region_samples(end_time);
1634 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_samples));
1635 _active_notes[note]->set_outline_all ();
1636 _active_notes[note] = 0;
1641 /** Extend active notes to rightmost edge of region (if length is changed)
1644 MidiRegionView::extend_active_notes()
1646 if (!_active_notes) {
1650 for (unsigned i = 0; i < 128; ++i) {
1651 if (_active_notes[i]) {
1652 _active_notes[i]->set_x1(
1653 trackview.editor().sample_to_pixel(_region->length()));
1659 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1661 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1665 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1667 if (!route_ui || !route_ui->midi_track()) {
1671 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1675 /* NotePlayer deletes itself */
1679 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1681 const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1682 start_playing_midi_chord(notes);
1686 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1688 if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1692 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1694 if (!route_ui || !route_ui->midi_track()) {
1698 NotePlayer* player = new NotePlayer (route_ui->midi_track());
1700 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1709 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1711 const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1713 /* must compare double explicitly as Beats::operator< rounds to ppqn */
1714 const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1715 note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1717 visible = (note->note() >= _current_range_min) &&
1718 (note->note() <= _current_range_max);
1724 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1728 if ((sus = dynamic_cast<Note*>(note))) {
1729 update_sustained(sus, update_ghost_regions);
1730 } else if ((hit = dynamic_cast<Hit*>(note))) {
1731 update_hit(hit, update_ghost_regions);
1735 /** Update a canvas note's size from its model note.
1736 * @param ev Canvas note to update.
1737 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1740 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1742 TempoMap& map (trackview.session()->tempo_map());
1743 const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1744 boost::shared_ptr<NoteType> note = ev->note();
1746 const double session_source_start = _region->quarter_note() - mr->start_beats();
1747 const samplepos_t note_start_samples = map.sample_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1749 const double x0 = trackview.editor().sample_to_pixel (note_start_samples);
1751 const double y0 = 1 + floor(note_to_y(note->note()));
1754 /* trim note display to not overlap the end of its region */
1755 if (note->length().to_double() > 0.0) {
1756 double note_end_time = note->end_time().to_double();
1758 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1759 note_end_time = mr->start_beats() + mr->length_beats();
1762 const samplepos_t note_end_samples = map.sample_at_quarter_note (session_source_start + note_end_time) - _region->position();
1764 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_samples)) - 1;
1766 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1769 y1 = y0 + std::max(1., floor(note_height()) - 1);
1771 ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1772 ev->set_velocity (note->velocity()/127.0);
1774 if (!note->length()) {
1775 if (_active_notes && note->note() < 128) {
1776 Note* const old_rect = _active_notes[note->note()];
1778 /* There is an active note on this key, so we have a stuck
1779 note. Finish the old rectangle here. */
1780 old_rect->set_x1 (x1);
1781 old_rect->set_outline_all ();
1783 _active_notes[note->note()] = ev;
1785 /* outline all but right edge */
1786 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1787 ArdourCanvas::Rectangle::TOP|
1788 ArdourCanvas::Rectangle::LEFT|
1789 ArdourCanvas::Rectangle::BOTTOM));
1791 /* outline all edges */
1792 ev->set_outline_all ();
1795 // Update color in case velocity has changed
1796 const uint32_t base_col = ev->base_color();
1797 ev->set_fill_color(base_col);
1798 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1803 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1805 boost::shared_ptr<NoteType> note = ev->note();
1807 const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1808 const samplepos_t note_start_samples = trackview.session()->tempo_map().sample_at_quarter_note (note_time_qn) - _region->position();
1810 const double x = trackview.editor().sample_to_pixel(note_start_samples);
1811 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1812 const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1814 // see DnD note in MidiRegionView::apply_note_range() above
1815 if (y <= 0 || y >= _height) {
1821 ev->set_position (ArdourCanvas::Duple (x, y));
1822 ev->set_height (diamond_size);
1824 // Update color in case velocity has changed
1825 const uint32_t base_col = ev->base_color();
1826 ev->set_fill_color(base_col);
1827 ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1831 /** Add a MIDI note to the view (with length).
1833 * If in sustained mode, notes with length 0 will be considered active
1834 * notes, and resolve_note should be called when the corresponding note off
1835 * event arrives, to properly display the note.
1838 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1840 NoteBase* event = 0;
1842 if (midi_view()->note_mode() == Sustained) {
1844 Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak
1846 update_sustained (ev_rect);
1850 } else if (midi_view()->note_mode() == Percussive) {
1852 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1854 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak
1856 update_hit (ev_diamond);
1865 MidiGhostRegion* gr;
1867 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1868 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1869 gr->add_note(event);
1873 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1874 note_selected(event, true);
1877 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1878 event->show_velocity();
1881 event->on_channel_selection_change (get_selected_channels());
1882 _events.insert (make_pair (event->note(), event));
1891 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1892 MidiStreamView* const view = mtv->midi_view();
1894 view->update_note_range (note->note());
1899 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1900 Temporal::Beats pos, Temporal::Beats len)
1902 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1904 /* potentially extend region to hold new note */
1906 samplepos_t end_sample = source_beats_to_absolute_samples (new_note->end_time());
1907 samplepos_t region_end = _region->last_sample();
1909 if (end_sample > region_end) {
1910 /* XX sets length in beats from audio space. make musical */
1911 _region->set_length (end_sample - _region->position(), 0);
1914 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1915 MidiStreamView* const view = mtv->midi_view();
1917 view->update_note_range(new_note->note());
1919 _marked_for_selection.clear ();
1921 start_note_diff_command (_("step add"));
1923 clear_editor_note_selection ();
1924 note_diff_add_note (new_note, true, false);
1928 // last_step_edit_note = new_note;
1932 MidiRegionView::step_sustain (Temporal::Beats beats)
1934 change_note_lengths (false, false, beats, false, true);
1937 /** Add a new patch change flag to the canvas.
1938 * @param patch the patch change to add
1939 * @param the text to display in the flag
1940 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1943 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1945 samplecnt_t region_samples = source_beats_to_region_samples (patch->time());
1946 const double x = trackview.editor().sample_to_pixel (region_samples);
1948 double const height = midi_stream_view()->contents_height();
1950 // CAIROCANVAS: active_channel info removed from PatcChange constructor
1951 // so we need to do something more sophisticated to keep its color
1952 // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1954 boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1955 new PatchChange(*this, group,
1961 _patch_change_outline,
1965 if (patch_change->item().width() < _pixel_width) {
1966 // Show unless patch change is beyond the region bounds
1967 if (region_samples < 0 || region_samples >= _region->length()) {
1968 patch_change->hide();
1970 patch_change->show();
1973 patch_change->hide ();
1976 _patch_changes.insert (make_pair (patch, patch_change));
1980 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
1982 /* remove the canvas item */
1983 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1984 if (x->second->patch() == pc->patch()) {
1985 _patch_changes.erase (x);
1991 MIDI::Name::PatchPrimaryKey
1992 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1994 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1997 /// Return true iff @p pc applies to the given time on the given channel.
1999 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Temporal::Beats time, uint8_t channel)
2001 return pc->time() <= time && pc->channel() == channel;
2005 MidiRegionView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
2007 // The earliest event not before time
2008 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
2010 // Go backwards until we find the latest PC for this channel, or the start
2011 while (i != _model->patch_changes().begin() &&
2012 (i == _model->patch_changes().end() ||
2013 !patch_applies(*i, time, channel))) {
2017 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
2018 key.set_bank((*i)->bank());
2019 key.set_program((*i)->program ());
2027 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
2029 string name = _("alter patch change");
2030 trackview.editor().begin_reversible_command (name);
2032 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2034 if (pc.patch()->program() != new_patch.program()) {
2035 c->change_program (pc.patch (), new_patch.program());
2038 int const new_bank = new_patch.bank();
2039 if (pc.patch()->bank() != new_bank) {
2040 c->change_bank (pc.patch (), new_bank);
2043 _model->apply_command (*trackview.session(), c);
2044 trackview.editor().commit_reversible_command ();
2046 remove_canvas_patch_change (&pc);
2047 display_patch_changes ();
2051 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Temporal::Beats> & new_change)
2053 string name = _("alter patch change");
2054 trackview.editor().begin_reversible_command (name);
2055 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2057 if (old_change->time() != new_change.time()) {
2058 c->change_time (old_change, new_change.time());
2061 if (old_change->channel() != new_change.channel()) {
2062 c->change_channel (old_change, new_change.channel());
2065 if (old_change->program() != new_change.program()) {
2066 c->change_program (old_change, new_change.program());
2069 if (old_change->bank() != new_change.bank()) {
2070 c->change_bank (old_change, new_change.bank());
2073 _model->apply_command (*trackview.session(), c);
2074 trackview.editor().commit_reversible_command ();
2076 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2077 if (x->second->patch() == old_change) {
2078 _patch_changes.erase (x);
2083 display_patch_changes ();
2086 /** Add a patch change to the region.
2087 * @param t Time in samples relative to region position
2088 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2089 * MidiTimeAxisView::get_channel_for_add())
2092 MidiRegionView::add_patch_change (samplecnt_t t, Evoral::PatchChange<Temporal::Beats> const & patch)
2094 string name = _("add patch change");
2096 trackview.editor().begin_reversible_command (name);
2097 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2098 c->add (MidiModel::PatchChangePtr (
2099 new Evoral::PatchChange<Temporal::Beats> (
2100 absolute_samples_to_source_beats (_region->position() + t),
2101 patch.channel(), patch.program(), patch.bank()
2106 _model->apply_command (*trackview.session(), c);
2107 trackview.editor().commit_reversible_command ();
2109 display_patch_changes ();
2113 MidiRegionView::move_patch_change (PatchChange& pc, Temporal::Beats t)
2115 trackview.editor().begin_reversible_command (_("move patch change"));
2116 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2117 c->change_time (pc.patch (), t);
2118 _model->apply_command (*trackview.session(), c);
2119 trackview.editor().commit_reversible_command ();
2121 display_patch_changes ();
2125 MidiRegionView::delete_patch_change (PatchChange* pc)
2127 trackview.editor().begin_reversible_command (_("delete patch change"));
2129 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2130 c->remove (pc->patch ());
2131 _model->apply_command (*trackview.session(), c);
2132 trackview.editor().commit_reversible_command ();
2134 remove_canvas_patch_change (pc);
2135 display_patch_changes ();
2139 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2141 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2143 key.set_bank(key.bank() + delta);
2145 key.set_program(key.program() + delta);
2147 change_patch_change(patch, key);
2151 MidiRegionView::note_deleted (NoteBase* cne)
2153 if (_entered_note && cne == _entered_note) {
2157 if (_selection.empty()) {
2161 _selection.erase (cne);
2165 MidiRegionView::delete_selection()
2167 if (_selection.empty()) {
2171 if (trackview.editor().drags()->active()) {
2175 start_note_diff_command (_("delete selection"));
2177 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2178 if ((*i)->selected()) {
2179 _note_diff_command->remove((*i)->note());
2187 hide_verbose_cursor ();
2191 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2193 start_note_diff_command (_("delete note"));
2194 _note_diff_command->remove (n);
2197 hide_verbose_cursor ();
2201 MidiRegionView::clear_editor_note_selection ()
2203 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2204 PublicEditor& editor(trackview.editor());
2205 editor.get_selection().clear_midi_notes();
2209 MidiRegionView::clear_selection ()
2211 clear_selection_internal();
2212 PublicEditor& editor(trackview.editor());
2213 editor.get_selection().remove(this);
2217 MidiRegionView::clear_selection_internal ()
2219 DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2221 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2222 (*i)->set_selected(false);
2223 (*i)->hide_velocity();
2228 // Clearing selection entirely, ungrab keyboard
2229 Keyboard::magic_widget_drop_focus();
2230 _grabbed_keyboard = false;
2235 MidiRegionView::unique_select(NoteBase* ev)
2237 clear_editor_note_selection();
2238 add_to_selection(ev);
2242 MidiRegionView::select_all_notes ()
2244 clear_editor_note_selection ();
2246 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2247 add_to_selection (i->second);
2252 MidiRegionView::select_range (samplepos_t start, samplepos_t end)
2254 clear_editor_note_selection ();
2256 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2257 samplepos_t t = source_beats_to_absolute_samples(i->first->time());
2258 if (t >= start && t <= end) {
2259 add_to_selection (i->second);
2265 MidiRegionView::invert_selection ()
2267 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2268 if (i->second->selected()) {
2269 remove_from_selection(i->second);
2271 add_to_selection (i->second);
2276 /** Used for selection undo/redo.
2277 The requested notes most likely won't exist in the view until the next model redisplay.
2280 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2283 list<Evoral::event_id_t>::iterator n;
2285 for (n = notes.begin(); n != notes.end(); ++n) {
2286 if ((cne = find_canvas_note(*n)) != 0) {
2287 add_to_selection (cne);
2289 _pending_note_selection.insert(*n);
2295 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2297 bool have_selection = !_selection.empty();
2298 uint8_t low_note = 127;
2299 uint8_t high_note = 0;
2300 MidiModel::Notes& notes (_model->notes());
2301 _optimization_iterator = _events.begin();
2303 if (extend && !have_selection) {
2307 /* scan existing selection to get note range */
2309 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2310 if ((*i)->note()->note() < low_note) {
2311 low_note = (*i)->note()->note();
2313 if ((*i)->note()->note() > high_note) {
2314 high_note = (*i)->note()->note();
2319 clear_editor_note_selection ();
2321 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2322 /* only note previously selected is the one we are
2323 * reselecting. treat this as cancelling the selection.
2330 low_note = min (low_note, notenum);
2331 high_note = max (high_note, notenum);
2334 _no_sound_notes = true;
2336 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2338 boost::shared_ptr<NoteType> note (*n);
2340 bool select = false;
2342 if (((1 << note->channel()) & channel_mask) != 0) {
2344 if ((note->note() >= low_note && note->note() <= high_note)) {
2347 } else if (note->note() == notenum) {
2353 if ((cne = find_canvas_note (note)) != 0) {
2354 // extend is false because we've taken care of it,
2355 // since it extends by time range, not pitch.
2356 note_selected (cne, add, false);
2360 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2364 _no_sound_notes = false;
2368 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2370 MidiModel::Notes& notes (_model->notes());
2371 _optimization_iterator = _events.begin();
2373 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2375 boost::shared_ptr<NoteType> note (*n);
2378 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2379 if ((cne = find_canvas_note (note)) != 0) {
2380 if (cne->selected()) {
2381 note_deselected (cne);
2383 note_selected (cne, true, false);
2391 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2394 clear_editor_note_selection();
2395 add_to_selection (ev);
2400 if (!ev->selected()) {
2401 add_to_selection (ev);
2405 /* find end of latest note selected, select all between that and the start of "ev" */
2407 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2408 Temporal::Beats latest = Temporal::Beats();
2410 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2411 if ((*i)->note()->end_time() > latest) {
2412 latest = (*i)->note()->end_time();
2414 if ((*i)->note()->time() < earliest) {
2415 earliest = (*i)->note()->time();
2419 if (ev->note()->end_time() > latest) {
2420 latest = ev->note()->end_time();
2423 if (ev->note()->time() < earliest) {
2424 earliest = ev->note()->time();
2427 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2429 /* find notes entirely within OR spanning the earliest..latest range */
2431 if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
2432 (i->first->time() <= earliest && i->first->end_time() >= latest)) {
2433 add_to_selection (i->second);
2440 MidiRegionView::note_deselected(NoteBase* ev)
2442 remove_from_selection (ev);
2446 MidiRegionView::update_drag_selection(samplepos_t start, samplepos_t end, double gy0, double gy1, bool extend)
2448 PublicEditor& editor = trackview.editor();
2450 // Convert to local coordinates
2451 const samplepos_t p = _region->position();
2452 const double y = midi_view()->y_position();
2453 const double x0 = editor.sample_to_pixel(max((samplepos_t)0, start - p));
2454 const double x1 = editor.sample_to_pixel(max((samplepos_t)0, end - p));
2455 const double y0 = max(0.0, gy0 - y);
2456 const double y1 = max(0.0, gy1 - y);
2458 // TODO: Make this faster by storing the last updated selection rect, and only
2459 // adjusting things that are in the area that appears/disappeared.
2460 // We probably need a tree to be able to find events in O(log(n)) time.
2462 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2463 if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
2464 // Rectangles intersect
2465 if (!i->second->selected()) {
2466 add_to_selection (i->second);
2468 } else if (i->second->selected() && !extend) {
2469 // Rectangles do not intersect
2470 remove_from_selection (i->second);
2474 typedef RouteTimeAxisView::AutomationTracks ATracks;
2475 typedef std::list<Selectable*> Selectables;
2477 /* Add control points to selection. */
2478 const ATracks& atracks = midi_view()->automation_tracks();
2479 Selectables selectables;
2480 editor.get_selection().clear_points();
2481 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2482 a->second->get_selectables(start, end, gy0, gy1, selectables);
2483 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2484 ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2486 editor.get_selection().add(cp);
2489 a->second->set_selected_points(editor.get_selection().points);
2494 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2500 // TODO: Make this faster by storing the last updated selection rect, and only
2501 // adjusting things that are in the area that appears/disappeared.
2502 // We probably need a tree to be able to find events in O(log(n)) time.
2504 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2505 if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
2506 // within y- (note-) range
2507 if (!i->second->selected()) {
2508 add_to_selection (i->second);
2510 } else if (i->second->selected() && !extend) {
2511 remove_from_selection (i->second);
2517 MidiRegionView::remove_from_selection (NoteBase* ev)
2519 Selection::iterator i = _selection.find (ev);
2521 if (i != _selection.end()) {
2522 _selection.erase (i);
2523 if (_selection.empty() && _grabbed_keyboard) {
2525 Keyboard::magic_widget_drop_focus();
2526 _grabbed_keyboard = false;
2530 ev->set_selected (false);
2531 ev->hide_velocity ();
2533 if (_selection.empty()) {
2534 PublicEditor& editor (trackview.editor());
2535 editor.get_selection().remove (this);
2540 MidiRegionView::add_to_selection (NoteBase* ev)
2542 const bool selection_was_empty = _selection.empty();
2544 if (_selection.insert (ev).second) {
2545 ev->set_selected (true);
2546 start_playing_midi_note ((ev)->note());
2547 if (selection_was_empty && _entered) {
2548 // Grab keyboard for moving notes with arrow keys
2549 Keyboard::magic_widget_grab_focus();
2550 _grabbed_keyboard = true;
2554 if (selection_was_empty) {
2555 PublicEditor& editor (trackview.editor());
2556 editor.get_selection().add (this);
2561 MidiRegionView::earliest_in_selection ()
2563 Temporal::Beats earliest = std::numeric_limits<Temporal::Beats>::max();
2565 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2566 if ((*i)->note()->time() < earliest) {
2567 earliest = (*i)->note()->time();
2575 MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
2577 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2578 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2579 TempoMap& tmap (editor->session()->tempo_map());
2580 PossibleChord to_play;
2581 Temporal::Beats earliest = earliest_in_selection();
2583 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2585 if (n->note()->time() == earliest) {
2586 to_play.push_back (n->note());
2588 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2590 if (midi_view()->note_mode() == Sustained) {
2591 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2592 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2594 /* Hit::x0() is offset by _position.x, unlike Note::x0() */
2595 Hit* hit = dynamic_cast<Hit*>(n);
2597 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2598 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2602 (*i)->move_event(dx, dy);
2605 if (midi_view()->note_mode() == Sustained) {
2606 Note* sus = dynamic_cast<Note*> (*i);
2607 double const len_dx = editor->sample_to_pixel_unrounded (
2608 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2610 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2614 if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2616 if (to_play.size() > 1) {
2618 PossibleChord shifted;
2620 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2621 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2622 moved_note->set_note (moved_note->note() + cumulative_dy);
2623 shifted.push_back (moved_note);
2626 start_playing_midi_chord (shifted);
2628 } else if (!to_play.empty()) {
2630 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2631 moved_note->set_note (moved_note->note() + cumulative_dy);
2632 start_playing_midi_note (moved_note);
2638 MidiRegionView::copy_selection (NoteBase* primary)
2640 _copy_drag_events.clear ();
2642 if (_selection.empty()) {
2649 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2650 boost::shared_ptr<NoteType> g (new NoteType (*((*i)->note())));
2651 if (midi_view()->note_mode() == Sustained) {
2652 Note* n = new Note (*this, _note_group, g);
2653 update_sustained (n, false);
2656 Hit* h = new Hit (*this, _note_group, 10, g);
2657 update_hit (h, false);
2661 if ((*i) == primary) {
2665 _copy_drag_events.push_back (note);
2672 MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
2674 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2675 Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
2676 TempoMap& tmap (editor->session()->tempo_map());
2677 PossibleChord to_play;
2678 Temporal::Beats earliest = earliest_in_selection();
2680 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2682 if (n->note()->time() == earliest) {
2683 to_play.push_back (n->note());
2685 double const note_time_qn = session_relative_qn (n->note()->time().to_double());
2687 if (midi_view()->note_mode() == Sustained) {
2688 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2689 - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
2691 Hit* hit = dynamic_cast<Hit*>(n);
2693 dx = editor->sample_to_pixel_unrounded (tmap.sample_at_quarter_note (note_time_qn + dx_qn))
2694 - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
2698 (*i)->move_event(dx, dy);
2700 if (midi_view()->note_mode() == Sustained) {
2701 Note* sus = dynamic_cast<Note*> (*i);
2702 double const len_dx = editor->sample_to_pixel_unrounded (
2703 tmap.sample_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
2705 sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
2709 if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2711 if (to_play.size() > 1) {
2713 PossibleChord shifted;
2715 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2716 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2717 moved_note->set_note (moved_note->note() + cumulative_dy);
2718 shifted.push_back (moved_note);
2721 start_playing_midi_chord (shifted);
2723 } else if (!to_play.empty()) {
2725 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2726 moved_note->set_note (moved_note->note() + cumulative_dy);
2727 start_playing_midi_note (moved_note);
2733 MidiRegionView::note_dropped(NoteBase *, double d_qn, int8_t dnote, bool copy)
2735 uint8_t lowest_note_in_selection = 127;
2736 uint8_t highest_note_in_selection = 0;
2737 uint8_t highest_note_difference = 0;
2740 // find highest and lowest notes first
2742 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2743 uint8_t pitch = (*i)->note()->note();
2744 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2745 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2749 cerr << "dnote: " << (int) dnote << endl;
2750 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2751 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2752 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2753 << int(highest_note_in_selection) << endl;
2754 cerr << "selection size: " << _selection.size() << endl;
2755 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2758 // Make sure the note pitch does not exceed the MIDI standard range
2759 if (highest_note_in_selection + dnote > 127) {
2760 highest_note_difference = highest_note_in_selection - 127;
2763 start_note_diff_command (_("move notes"));
2765 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2767 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2773 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2775 uint8_t original_pitch = (*i)->note()->note();
2776 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2778 // keep notes in standard midi range
2779 clamp_to_0_127(new_pitch);
2781 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2782 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2784 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2788 clear_editor_note_selection ();
2790 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
2791 uint8_t pitch = (*i)->note()->note();
2792 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2793 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2796 // Make sure the note pitch does not exceed the MIDI standard range
2797 if (highest_note_in_selection + dnote > 127) {
2798 highest_note_difference = highest_note_in_selection - 127;
2801 start_note_diff_command (_("copy notes"));
2803 for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
2806 Temporal::Beats new_time = Temporal::Beats ((*i)->note()->time().to_double() + d_qn);
2812 (*i)->note()->set_time (new_time);
2816 uint8_t original_pitch = (*i)->note()->note();
2817 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2819 (*i)->note()->set_note (new_pitch);
2821 // keep notes in standard midi range
2822 clamp_to_0_127(new_pitch);
2824 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2825 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2827 note_diff_add_note ((*i)->note(), true);
2832 _copy_drag_events.clear ();
2835 apply_diff (false, copy);
2837 // care about notes being moved beyond the upper/lower bounds on the canvas
2838 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2839 highest_note_in_selection > midi_stream_view()->highest_note()) {
2840 midi_stream_view()->set_note_range (MidiStreamView::ContentsRange);
2844 /** @param x Pixel relative to the region position.
2845 * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2846 * Used for inverting the snap logic with key modifiers and snap delta calculation.
2847 * @return Snapped sample relative to the region position.
2850 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2852 PublicEditor& editor (trackview.editor());
2853 return snap_sample_to_sample (editor.pixel_to_sample (x), ensure_snap).sample;
2856 /** @param x Pixel relative to the region position.
2857 * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2858 * @return Snapped pixel relative to the region position.
2861 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2863 return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2867 MidiRegionView::get_position_pixels()
2869 samplepos_t region_sample = get_position();
2870 return trackview.editor().sample_to_pixel(region_sample);
2874 MidiRegionView::get_end_position_pixels()
2876 samplepos_t sample = get_position() + get_duration ();
2877 return trackview.editor().sample_to_pixel(sample);
2881 MidiRegionView::source_beats_to_absolute_samples(Temporal::Beats beats) const
2883 /* the time converter will return the sample corresponding to `beats'
2884 relative to the start of the source. The start of the source
2885 is an implied position given by region->position - region->start
2887 const samplepos_t source_start = _region->position() - _region->start();
2888 return source_start + _source_relative_time_converter.to (beats);
2892 MidiRegionView::absolute_samples_to_source_beats(samplepos_t samples) const
2894 /* the `samples' argument needs to be converted into a sample count
2895 relative to the start of the source before being passed in to the
2898 const samplepos_t source_start = _region->position() - _region->start();
2899 return _source_relative_time_converter.from (samples - source_start);
2903 MidiRegionView::region_beats_to_region_samples(Temporal::Beats beats) const
2905 return _region_relative_time_converter.to(beats);
2909 MidiRegionView::region_samples_to_region_beats(samplepos_t samples) const
2911 return _region_relative_time_converter.from(samples);
2915 MidiRegionView::region_samples_to_region_beats_double (samplepos_t samples) const
2917 return _region_relative_time_converter_double.from(samples);
2921 MidiRegionView::begin_resizing (bool /*at_front*/)
2923 _resize_data.clear();
2925 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2926 Note *note = dynamic_cast<Note*> (*i);
2928 // only insert CanvasNotes into the map
2930 NoteResizeData *resize_data = new NoteResizeData();
2931 resize_data->note = note;
2933 // create a new SimpleRect from the note which will be the resize preview
2934 ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2935 ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2937 // calculate the colors: get the color settings
2938 uint32_t fill_color = NoteBase::meter_style_fill_color (note->note()->velocity(), true);
2940 // make the resize preview notes more transparent and bright
2941 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2943 // calculate color based on note velocity
2944 resize_rect->set_fill_color (UINT_INTERPOLATE(
2945 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2949 resize_rect->set_outline_color (NoteBase::calculate_outline (
2950 UIConfiguration::instance().color ("midi note selected outline")));
2952 resize_data->resize_rect = resize_rect;
2953 _resize_data.push_back(resize_data);
2958 /** Update resizing notes while user drags.
2959 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2960 * @param at_front which end of the note (true == note on, false == note off)
2961 * @param delta_x change in mouse position since the start of the drag
2962 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2963 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2964 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2965 * as the \a primary note.
2966 * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2967 * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2970 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2972 TempoMap& tmap (trackview.session()->tempo_map());
2973 bool cursor_set = false;
2974 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2976 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2977 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2978 Note* canvas_note = (*i)->note;
2983 current_x = canvas_note->x0() + delta_x + snap_delta;
2985 current_x = primary->x0() + delta_x + snap_delta;
2989 current_x = canvas_note->x1() + delta_x + snap_delta;
2991 current_x = primary->x1() + delta_x + snap_delta;
2995 if (current_x < 0) {
2996 // This works even with snapping because RegionView::snap_sample_to_sample()
2997 // snaps forward if the snapped sample is before the beginning of the region
3000 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3001 current_x = trackview.editor().sample_to_pixel(_region->length());
3006 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3008 resize_rect->set_x0 (current_x - snap_delta);
3010 resize_rect->set_x1 (canvas_note->x1());
3013 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
3015 resize_rect->set_x1 (current_x - snap_delta);
3017 resize_rect->set_x0 (canvas_note->x0());
3021 /* Convert snap delta from pixels to beats. */
3022 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3023 double snap_delta_beats = 0.0;
3026 /* negative beat offsets aren't allowed */
3027 if (snap_delta_samps > 0) {
3028 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3029 } else if (snap_delta_samps < 0) {
3030 snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
3035 int32_t divisions = 0;
3038 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
3039 divisions = trackview.editor().get_grid_music_divisions (0);
3041 snapped_x = trackview.editor ().pixel_to_sample (current_x);
3044 const Temporal::Beats beats = Temporal::Beats (tmap.exact_beat_at_sample (snapped_x + midi_region()->position(), divisions)
3045 - midi_region()->beat()) + midi_region()->start_beats();
3047 Temporal::Beats len = Temporal::Beats();
3050 if (beats < canvas_note->note()->end_time()) {
3051 len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
3052 len += canvas_note->note()->length();
3055 if (beats >= canvas_note->note()->time()) {
3056 len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
3060 len = std::max(Temporal::Beats(1 / 512.0), len);
3063 snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
3064 show_verbose_cursor (buf, 0, 0);
3068 trackview.editor().set_snapped_cursor_position ( snapped_x + midi_region()->position() );
3075 /** Finish resizing notes when the user releases the mouse button.
3076 * Parameters the same as for \a update_resizing().
3079 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
3081 _note_diff_command = _model->new_note_diff_command (_("resize notes"));
3082 TempoMap& tmap (trackview.session()->tempo_map());
3084 /* XX why doesn't snap_pixel_to_sample() handle this properly? */
3085 bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
3087 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3088 Note* canvas_note = (*i)->note;
3089 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
3091 /* Get the new x position for this resize, which is in pixels relative
3092 * to the region position.
3099 current_x = canvas_note->x0() + delta_x + snap_delta;
3101 current_x = primary->x0() + delta_x + snap_delta;
3105 current_x = canvas_note->x1() + delta_x + snap_delta;
3107 current_x = primary->x1() + delta_x + snap_delta;
3111 if (current_x < 0) {
3114 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
3115 current_x = trackview.editor().sample_to_pixel(_region->length());
3118 /* Convert snap delta from pixels to beats with sign. */
3119 samplepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
3120 double snap_delta_beats = 0.0;
3123 if (snap_delta_samps > 0) {
3124 snap_delta_beats = region_samples_to_region_beats_double (snap_delta_samps);
3125 } else if (snap_delta_samps < 0) {
3126 snap_delta_beats = region_samples_to_region_beats_double ( - snap_delta_samps);
3130 uint32_t divisions = 0;
3131 /* Convert the new x position to a sample within the source */
3132 samplepos_t current_fr;
3134 current_fr = snap_pixel_to_sample (current_x, ensure_snap);
3135 divisions = trackview.editor().get_grid_music_divisions (0);
3137 current_fr = trackview.editor().pixel_to_sample (current_x);
3140 /* and then to beats */
3141 const double e_qaf = tmap.exact_qn_at_sample (current_fr + midi_region()->position(), divisions);
3142 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
3143 const Temporal::Beats x_beats = Temporal::Beats (e_qaf - quarter_note_start);
3145 if (at_front && x_beats < canvas_note->note()->end_time()) {
3146 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
3147 Temporal::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
3148 len += canvas_note->note()->length();
3151 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3156 Temporal::Beats len = std::max(Temporal::Beats(1 / 512.0),
3157 x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
3158 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
3165 _resize_data.clear();
3170 MidiRegionView::abort_resizing ()
3172 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
3173 delete (*i)->resize_rect;
3177 _resize_data.clear ();
3181 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
3183 uint8_t new_velocity;
3186 new_velocity = event->note()->velocity() + velocity;
3187 clamp_to_0_127(new_velocity);
3189 new_velocity = velocity;
3192 event->set_selected (event->selected()); // change color
3194 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
3198 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
3203 new_note = event->note()->note() + note;
3208 clamp_to_0_127 (new_note);
3209 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3213 MidiRegionView::trim_note (NoteBase* event, Temporal::Beats front_delta, Temporal::Beats end_delta)
3215 bool change_start = false;
3216 bool change_length = false;
3217 Temporal::Beats new_start;
3218 Temporal::Beats new_length;
3220 /* NOTE: the semantics of the two delta arguments are slightly subtle:
3222 front_delta: if positive - move the start of the note later in time (shortening it)
3223 if negative - move the start of the note earlier in time (lengthening it)
3225 end_delta: if positive - move the end of the note later in time (lengthening it)
3226 if negative - move the end of the note earlier in time (shortening it)
3229 if (!!front_delta) {
3230 if (front_delta < 0) {
3232 if (event->note()->time() < -front_delta) {
3233 new_start = Temporal::Beats();
3235 new_start = event->note()->time() + front_delta; // moves earlier
3238 /* start moved toward zero, so move the end point out to where it used to be.
3239 Note that front_delta is negative, so this increases the length.
3242 new_length = event->note()->length() - front_delta;
3243 change_start = true;
3244 change_length = true;
3248 Temporal::Beats new_pos = event->note()->time() + front_delta;
3250 if (new_pos < event->note()->end_time()) {
3251 new_start = event->note()->time() + front_delta;
3252 /* start moved toward the end, so move the end point back to where it used to be */
3253 new_length = event->note()->length() - front_delta;
3254 change_start = true;
3255 change_length = true;
3262 bool can_change = true;
3263 if (end_delta < 0) {
3264 if (event->note()->length() < -end_delta) {
3270 new_length = event->note()->length() + end_delta;
3271 change_length = true;
3276 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3279 if (change_length) {
3280 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3285 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3287 uint8_t new_channel;
3291 if (event->note()->channel() < -chn) {
3294 new_channel = event->note()->channel() + chn;
3297 new_channel = event->note()->channel() + chn;
3300 new_channel = (uint8_t) chn;
3303 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3307 MidiRegionView::change_note_time (NoteBase* event, Temporal::Beats delta, bool relative)
3309 Temporal::Beats new_time;
3313 if (event->note()->time() < -delta) {
3314 new_time = Temporal::Beats();
3316 new_time = event->note()->time() + delta;
3319 new_time = event->note()->time() + delta;
3325 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3329 MidiRegionView::change_note_length (NoteBase* event, Temporal::Beats t)
3331 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3335 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3340 if (_selection.empty()) {
3355 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3356 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3362 start_note_diff_command (_("change velocities"));
3364 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3365 Selection::iterator next = i;
3369 if (i == _selection.begin()) {
3370 change_note_velocity (*i, delta, true);
3371 value = (*i)->note()->velocity() + delta;
3373 change_note_velocity (*i, value, false);
3377 change_note_velocity (*i, delta, true);
3386 if (!_selection.empty()) {
3388 snprintf (buf, sizeof (buf), "Vel %d",
3389 (int) (*_selection.begin())->note()->velocity());
3390 show_verbose_cursor (buf, 10, 10);
3396 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3398 if (_selection.empty()) {
3415 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3417 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3421 if ((int8_t) (*i)->note()->note() + delta > 127) {
3428 start_note_diff_command (_("transpose"));
3430 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3431 Selection::iterator next = i;
3433 change_note_note (*i, delta, true);
3441 MidiRegionView::change_note_lengths (bool fine, bool shorter, Temporal::Beats delta, bool start, bool end)
3445 delta = Temporal::Beats(1.0/128.0);
3447 /* grab the current grid distance */
3448 delta = get_grid_beats(_region->position());
3456 start_note_diff_command (_("change note lengths"));
3458 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3459 Selection::iterator next = i;
3462 /* note the negation of the delta for start */
3465 (start ? -delta : Temporal::Beats()),
3466 (end ? delta : Temporal::Beats()));
3475 MidiRegionView::nudge_notes (bool forward, bool fine)
3477 if (_selection.empty()) {
3481 /* pick a note as the point along the timeline to get the nudge distance.
3482 its not necessarily the earliest note, so we may want to pull the notes out
3483 into a vector and sort before using the first one.
3486 const samplepos_t ref_point = source_beats_to_absolute_samples ((*(_selection.begin()))->note()->time());
3487 Temporal::Beats delta;
3491 /* non-fine, move by 1 bar regardless of snap */
3492 delta = Temporal::Beats(trackview.session()->tempo_map().meter_at_sample (ref_point).divisions_per_bar());
3494 } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3496 /* grid is off - use nudge distance */
3499 const samplecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3500 delta = region_samples_to_region_beats (fabs ((double)distance));
3506 MusicSample next_pos (ref_point, 0);
3508 if (max_samplepos - 1 < next_pos.sample) {
3509 next_pos.sample += 1;
3512 if (next_pos.sample == 0) {
3515 next_pos.sample -= 1;
3518 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), SnapToGrid, false);
3519 const samplecnt_t distance = ref_point - next_pos.sample;
3520 delta = region_samples_to_region_beats (fabs ((double)distance));
3531 start_note_diff_command (_("nudge"));
3533 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3534 Selection::iterator next = i;
3536 change_note_time (*i, delta, true);
3544 MidiRegionView::change_channel(uint8_t channel)
3546 start_note_diff_command(_("change channel"));
3547 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3548 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3556 MidiRegionView::note_entered(NoteBase* ev)
3560 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3562 if (_mouse_state == SelectTouchDragging) {
3564 note_selected (ev, true);
3566 } else if (editor->current_mouse_mode() == MouseContent) {
3568 remove_ghost_note ();
3569 show_verbose_cursor (ev->note ());
3571 } else if (editor->current_mouse_mode() == MouseDraw) {
3573 remove_ghost_note ();
3574 show_verbose_cursor (ev->note ());
3579 MidiRegionView::note_left (NoteBase*)
3583 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3584 (*i)->hide_velocity ();
3587 hide_verbose_cursor ();
3591 MidiRegionView::patch_entered (PatchChange* p)
3594 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3595 << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3596 << _("Channel ") << ((int) p->patch()->channel() + 1);
3597 show_verbose_cursor (s.str(), 10, 20);
3598 p->item().grab_focus();
3602 MidiRegionView::patch_left (PatchChange *)
3604 hide_verbose_cursor ();
3605 /* focus will transfer back via the enter-notify event sent to this
3611 MidiRegionView::sysex_entered (SysEx* p)
3615 // need a way to extract text from p->_flag->_text
3617 // show_verbose_cursor (s.str(), 10, 20);
3618 p->item().grab_focus();
3622 MidiRegionView::sysex_left (SysEx *)
3624 hide_verbose_cursor ();
3625 /* focus will transfer back via the enter-notify event sent to this
3631 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3633 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3634 Editing::MouseMode mm = editor->current_mouse_mode();
3635 bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3637 Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3638 if (can_set_cursor && ctx) {
3639 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3640 ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3641 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3642 ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3644 ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3650 MidiRegionView::get_fill_color() const
3652 const std::string mod_name = (_dragging ? "dragging region" :
3653 trackview.editor().internal_editing() ? "editable region" :
3656 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3657 } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3658 !UIConfiguration::instance().get_color_regions_using_track_color()) {
3659 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3661 return UIConfiguration::instance().color_mod (fill_color, mod_name);
3665 MidiRegionView::midi_channel_mode_changed ()
3667 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3668 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3669 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3671 if (mode == ForceChannel) {
3672 mask = 0xFFFF; // Show all notes as active (below)
3675 // Update notes for selection
3676 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3677 i->second->on_channel_selection_change (mask);
3680 _patch_changes.clear ();
3681 display_patch_changes ();
3685 MidiRegionView::instrument_settings_changed ()
3691 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3693 if (_selection.empty()) {
3697 PublicEditor& editor (trackview.editor());
3701 /* XXX what to do ? */
3705 editor.get_cut_buffer().add (selection_as_cut_buffer());
3713 start_note_diff_command();
3715 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3722 note_diff_remove_note (*i);
3732 MidiRegionView::selection_as_cut_buffer () const
3736 for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3737 NoteType* n = (*i)->note().get();
3738 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3741 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3747 /** This method handles undo */
3749 MidiRegionView::paste (samplepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3751 bool commit = false;
3752 // Paste notes, if available
3753 MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3754 if (m != selection.midi_notes.end()) {
3755 ctx.counts.increase_n_notes();
3756 if (!(*m)->empty()) {
3759 paste_internal(pos, ctx.count, ctx.times, **m);
3762 // Paste control points to automation children, if available
3763 typedef RouteTimeAxisView::AutomationTracks ATracks;
3764 const ATracks& atracks = midi_view()->automation_tracks();
3765 for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3766 if (a->second->paste(pos, selection, ctx, sub_num)) {
3768 trackview.editor().begin_reversible_command (Operations::paste);
3775 trackview.editor().commit_reversible_command ();
3780 /** This method handles undo */
3782 MidiRegionView::paste_internal (samplepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3788 start_note_diff_command (_("paste"));
3790 const Temporal::Beats snap_beats = get_grid_beats(pos);
3791 const Temporal::Beats first_time = (*mcb.notes().begin())->time();
3792 const Temporal::Beats last_time = (*mcb.notes().rbegin())->end_time();
3793 const Temporal::Beats duration = last_time - first_time;
3794 const Temporal::Beats snap_duration = duration.snap_to(snap_beats);
3795 const Temporal::Beats paste_offset = snap_duration * paste_count;
3796 const Temporal::Beats quarter_note = absolute_samples_to_source_beats(pos) + paste_offset;
3797 Temporal::Beats end_point = Temporal::Beats();
3799 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3802 duration, pos, _region->position(),
3805 clear_editor_note_selection ();
3807 for (int n = 0; n < (int) times; ++n) {
3809 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3811 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3812 copied_note->set_time (quarter_note + copied_note->time() - first_time);
3813 copied_note->set_id (Evoral::next_event_id());
3815 /* make all newly added notes selected */
3817 note_diff_add_note (copied_note, true);
3818 end_point = copied_note->end_time();
3822 /* if we pasted past the current end of the region, extend the region */
3824 samplepos_t end_sample = source_beats_to_absolute_samples (end_point);
3825 samplepos_t region_end = _region->position() + _region->length() - 1;
3827 if (end_sample > region_end) {
3829 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_sample));
3831 _region->clear_changes ();
3832 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3833 _region->set_length (end_sample - _region->position(), trackview.editor().get_grid_music_divisions (0));
3834 trackview.session()->add_command (new StatefulDiffCommand (_region));
3840 struct EventNoteTimeEarlyFirstComparator {
3841 bool operator() (NoteBase* a, NoteBase* b) {
3842 return a->note()->time() < b->note()->time();
3847 MidiRegionView::goto_next_note (bool add_to_selection)
3849 bool use_next = false;
3851 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3852 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3853 NoteBase* first_note = 0;
3855 MidiModel::ReadLock lock(_model->read_lock());
3856 MidiModel::Notes& notes (_model->notes());
3858 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
3860 if ((cne = find_canvas_note (*n))) {
3862 if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
3866 if (cne->selected()) {
3869 } else if (use_next) {
3870 if (channel_mask & (1 << (*n)->channel())) {
3871 if (!add_to_selection) {
3872 unique_select (cne);
3874 note_selected (cne, true, false);
3883 /* use the first one */
3885 if (!_events.empty() && first_note) {
3886 unique_select (first_note);
3891 MidiRegionView::goto_previous_note (bool add_to_selection)
3893 bool use_next = false;
3895 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3896 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3897 NoteBase* last_note = 0;
3899 MidiModel::ReadLock lock(_model->read_lock());
3900 MidiModel::Notes& notes (_model->notes());
3902 for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
3904 if ((cne = find_canvas_note (*n))) {
3906 if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
3910 if (cne->selected()) {
3914 } else if (use_next) {
3915 if (channel_mask & (1 << (*n)->channel())) {
3916 if (!add_to_selection) {
3917 unique_select (cne);
3919 note_selected (cne, true, false);
3928 /* use the last one */
3930 if (!_events.empty() && last_note) {
3931 unique_select (last_note);
3936 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3938 bool had_selected = false;
3940 /* we previously time sorted events here, but Notes is a multiset sorted by time */
3942 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3943 if (i->second->selected()) {
3944 selected.insert (i->first);
3945 had_selected = true;
3949 if (allow_all_if_none_selected && !had_selected) {
3950 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3951 selected.insert (i->first);
3957 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3959 x = std::max(0.0, x);
3961 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3966 _note_group->canvas_to_item (x, y);
3968 PublicEditor& editor = trackview.editor ();
3970 samplepos_t const unsnapped_sample = editor.pixel_to_sample (x);
3972 const int32_t divisions = editor.get_grid_music_divisions (state);
3973 const bool shift_snap = midi_view()->note_mode() != Percussive;
3974 const Temporal::Beats snapped_beats = snap_sample_to_grid_underneath (unsnapped_sample, divisions, shift_snap);
3976 /* prevent Percussive mode from displaying a ghost hit at region end */
3977 if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3978 _ghost_note->hide();
3979 hide_verbose_cursor ();
3983 /* ghost note may have been snapped before region */
3984 if (_ghost_note && snapped_beats.to_double() < 0.0) {
3985 _ghost_note->hide();
3988 } else if (_ghost_note) {
3989 _ghost_note->show();
3992 /* calculate time in beats relative to start of source */
3993 const Temporal::Beats length = get_grid_beats(unsnapped_sample + _region->position());
3995 _ghost_note->note()->set_time (snapped_beats);
3996 _ghost_note->note()->set_length (length);
3997 _ghost_note->note()->set_note (y_to_note (y));
3998 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3999 _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
4000 /* the ghost note does not appear in ghost regions, so pass false in here */
4001 update_note (_ghost_note, false);
4003 show_verbose_cursor (_ghost_note->note ());
4007 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
4009 remove_ghost_note ();
4011 boost::shared_ptr<NoteType> g (new NoteType);
4012 if (midi_view()->note_mode() == Sustained) {
4013 _ghost_note = new Note (*this, _note_group, g);
4015 _ghost_note = new Hit (*this, _note_group, 10, g);
4017 _ghost_note->set_ignore_events (true);
4018 _ghost_note->set_outline_color (0x000000aa);
4019 update_ghost_note (x, y, state);
4020 _ghost_note->show ();
4022 show_verbose_cursor (_ghost_note->note ());
4026 MidiRegionView::remove_ghost_note ()
4033 MidiRegionView::hide_verbose_cursor ()
4035 trackview.editor().verbose_cursor()->hide ();
4036 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4038 mtv->set_note_highlight (NO_MIDI_NOTE);
4043 MidiRegionView::snap_changed ()
4049 create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
4053 MidiRegionView::drop_down_keys ()
4055 _mouse_state = None;
4059 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
4061 /* XXX: This is dead code. What was it for? */
4063 double note = y_to_note(y);
4065 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4067 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
4069 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
4070 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
4071 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
4072 get_events (e, Evoral::Sequence<Temporal::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
4077 bool add_mrv_selection = false;
4079 if (_selection.empty()) {
4080 add_mrv_selection = true;
4083 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
4084 if (_selection.insert (i->second).second) {
4085 i->second->set_selected (true);
4089 if (add_mrv_selection) {
4090 PublicEditor& editor (trackview.editor());
4091 editor.get_selection().add (this);
4096 MidiRegionView::color_handler ()
4098 RegionView::color_handler ();
4100 _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
4101 _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
4103 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
4104 i->second->set_selected (i->second->selected()); // will change color
4107 /* XXX probably more to do here */
4111 MidiRegionView::enable_display (bool yn)
4113 RegionView::enable_display (yn);
4117 MidiRegionView::show_step_edit_cursor (Temporal::Beats pos)
4119 if (_step_edit_cursor == 0) {
4120 ArdourCanvas::Item* const group = get_canvas_group();
4122 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
4123 _step_edit_cursor->set_y0 (0);
4124 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
4125 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
4126 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
4129 move_step_edit_cursor (pos);
4130 _step_edit_cursor->show ();
4134 MidiRegionView::move_step_edit_cursor (Temporal::Beats pos)
4136 _step_edit_cursor_position = pos;
4138 if (_step_edit_cursor) {
4139 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_samples (pos));
4140 _step_edit_cursor->set_x0 (pixel);
4141 set_step_edit_cursor_width (_step_edit_cursor_width);
4146 MidiRegionView::hide_step_edit_cursor ()
4148 if (_step_edit_cursor) {
4149 _step_edit_cursor->hide ();
4154 MidiRegionView::set_step_edit_cursor_width (Temporal::Beats beats)
4156 _step_edit_cursor_width = beats;
4158 if (_step_edit_cursor) {
4159 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
4160 region_beats_to_region_samples (_step_edit_cursor_position + beats)
4161 - region_beats_to_region_samples (_step_edit_cursor_position)));
4165 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
4166 * @param w Source that the data will end up in.
4169 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
4171 if (!_active_notes) {
4172 /* we aren't actively being recorded to */
4176 boost::shared_ptr<MidiSource> src = w.lock ();
4177 if (!src || src != midi_region()->midi_source()) {
4178 /* recorded data was not destined for our source */
4182 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
4184 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
4186 samplepos_t back = max_samplepos;
4188 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
4189 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
4191 if (ev.is_channel_event()) {
4192 if (get_channel_mode() == FilterChannels) {
4193 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
4199 /* convert from session samples to source beats */
4200 Temporal::Beats const time_beats = _source_relative_time_converter.from(
4201 ev.time() - src->timeline_position() + _region->start());
4203 if (ev.type() == MIDI_CMD_NOTE_ON) {
4204 boost::shared_ptr<NoteType> note (
4205 new NoteType (ev.channel(), time_beats, Temporal::Beats(), ev.note(), ev.velocity()));
4207 add_note (note, true);
4209 /* fix up our note range */
4210 if (ev.note() < _current_range_min) {
4211 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
4212 } else if (ev.note() > _current_range_max) {
4213 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
4216 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
4217 resolve_note (ev.note (), time_beats);
4223 midi_stream_view()->check_record_layers (region(), back);
4227 MidiRegionView::trim_front_starting ()
4229 /* We used to eparent the note group to the region view's parent, so that it didn't change.
4235 MidiRegionView::trim_front_ending ()
4237 if (_region->start() < 0) {
4238 /* Trim drag made start time -ve; fix this */
4239 midi_region()->fix_negative_start ();
4244 MidiRegionView::edit_patch_change (PatchChange* pc)
4246 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4248 int response = d.run();
4251 case Gtk::RESPONSE_ACCEPT:
4253 case Gtk::RESPONSE_REJECT:
4254 delete_patch_change (pc);
4260 change_patch_change (pc->patch(), d.patch ());
4264 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4267 // sysyex object doesn't have a pointer to a sysex event
4268 // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4269 // c->remove (sysex->sysex());
4270 // _model->apply_command (*trackview.session(), c);
4272 //_sys_exes.clear ();
4273 // display_sysexes();
4277 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4279 using namespace MIDI::Name;
4282 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4284 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4286 MIDI::Name::PatchPrimaryKey patch_key;
4287 get_patch_key_at(n->time(), n->channel(), patch_key);
4288 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4291 patch_key.program(),
4297 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4299 name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4300 (int) n->channel() + 1,
4301 (int) n->velocity());
4307 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4308 uint8_t new_value) const
4310 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4312 mtv->set_note_highlight (new_value);
4315 show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4319 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4321 show_verbose_cursor_for_new_note_value(n, n->note());
4325 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4327 trackview.editor().verbose_cursor()->set (text);
4328 trackview.editor().verbose_cursor()->show ();
4329 trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4333 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4335 if (_model->notes().empty()) {
4336 return 0x40; // No notes, use default
4339 MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4340 if (m == _model->notes().begin()) {
4341 // Before the start, use the velocity of the first note
4342 return (*m)->velocity();
4343 } else if (m == _model->notes().end()) {
4344 // Past the end, use the velocity of the last note
4346 return (*m)->velocity();
4349 // Interpolate velocity of surrounding notes
4350 MidiModel::Notes::const_iterator n = m;
4353 const double frac = ((time - (*n)->time()).to_double() /
4354 ((*m)->time() - (*n)->time()).to_double());
4356 return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4359 /** @param p A session samplepos.
4360 * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4361 * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4362 * @return beat duration of p snapped to the grid subdivision underneath it.
4365 MidiRegionView::snap_sample_to_grid_underneath (samplepos_t p, int32_t divisions, bool shift_snap) const
4367 TempoMap& map (trackview.session()->tempo_map());
4368 double eqaf = map.exact_qn_at_sample (p + _region->position(), divisions);
4370 if (divisions != 0 && shift_snap) {
4371 const double qaf = map.quarter_note_at_sample (p + _region->position());
4372 /* Hack so that we always snap to the note that we are over, instead of snapping
4373 to the next one if we're more than halfway through the one we're over.
4375 const Temporal::Beats grid_beats = get_grid_beats (p + _region->position());
4376 const double rem = eqaf - qaf;
4378 eqaf -= grid_beats.to_double();
4381 const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4383 return Temporal::Beats (eqaf - session_start_off);
4387 MidiRegionView::get_channel_mode () const
4389 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4390 return rtav->midi_track()->get_playback_channel_mode();
4394 MidiRegionView::get_selected_channels () const
4396 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4397 return rtav->midi_track()->get_playback_channel_mask();
4402 MidiRegionView::get_grid_beats(samplepos_t pos) const
4404 PublicEditor& editor = trackview.editor();
4405 bool success = false;
4406 Temporal::Beats beats = editor.get_grid_type_as_beats (success, pos);
4408 beats = Temporal::Beats(1);
4413 MidiRegionView::y_to_note (double y) const
4415 int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4416 + _current_range_min;
4420 } else if (n > 127) {
4424 /* min due to rounding and/or off-by-one errors */
4425 return min ((uint8_t) n, _current_range_max);
4429 MidiRegionView::note_to_y(uint8_t note) const
4431 return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
4435 MidiRegionView::session_relative_qn (double qn) const
4437 return qn + (region()->quarter_note() - midi_region()->start_beats());