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 "pbd/memento_command.h"
31 #include "pbd/stateful_diff_command.h"
33 #include "ardour/midi_model.h"
34 #include "ardour/midi_patch_manager.h"
35 #include "ardour/midi_region.h"
36 #include "ardour/midi_source.h"
37 #include "ardour/midi_track.h"
38 #include "ardour/session.h"
40 #include "evoral/Parameter.hpp"
41 #include "evoral/MIDIParameters.hpp"
42 #include "evoral/MIDIEvent.hpp"
43 #include "evoral/Control.hpp"
44 #include "evoral/midi_util.h"
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas_patch_change.h"
51 #include "canvas-sysex.h"
54 #include "editor_drag.h"
55 #include "ghostregion.h"
56 #include "gui_thread.h"
58 #include "midi_channel_dialog.h"
59 #include "midi_cut_buffer.h"
60 #include "midi_list_editor.h"
61 #include "midi_region_view.h"
62 #include "midi_streamview.h"
63 #include "midi_time_axis.h"
64 #include "midi_util.h"
65 #include "midi_velocity_dialog.h"
66 #include "mouse_cursors.h"
67 #include "note_player.h"
68 #include "public_editor.h"
69 #include "route_time_axis.h"
70 #include "rgb_macros.h"
71 #include "selection.h"
72 #include "simpleline.h"
73 #include "streamview.h"
75 #include "patch_change_dialog.h"
76 #include "verbose_cursor.h"
80 using namespace ARDOUR;
82 using namespace Editing;
83 using namespace ArdourCanvas;
84 using Gtkmm2ext::Keyboard;
86 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
88 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
90 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
91 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
92 : RegionView (parent, tv, r, spu, basic_color)
93 , _current_range_min(0)
94 , _current_range_max(0)
96 , _note_group(new ArdourCanvas::Group(*group))
97 , _note_diff_command (0)
99 , _step_edit_cursor (0)
100 , _step_edit_cursor_width (1.0)
101 , _step_edit_cursor_position (0.0)
102 , _channel_selection_scoped_note (0)
103 , _temporary_note_group (0)
106 , _sort_needed (true)
107 , _optimization_iterator (_events.end())
109 , _no_sound_notes (false)
112 , pre_enter_cursor (0)
113 , pre_press_cursor (0)
116 _note_group->raise_to_top();
117 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
119 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
120 connect_to_diskstream ();
122 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
125 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
126 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
127 TimeAxisViewItem::Visibility visibility)
128 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
129 , _current_range_min(0)
130 , _current_range_max(0)
132 , _note_group(new ArdourCanvas::Group(*parent))
133 , _note_diff_command (0)
135 , _step_edit_cursor (0)
136 , _step_edit_cursor_width (1.0)
137 , _step_edit_cursor_position (0.0)
138 , _channel_selection_scoped_note (0)
139 , _temporary_note_group (0)
142 , _sort_needed (true)
143 , _optimization_iterator (_events.end())
145 , _no_sound_notes (false)
148 , pre_enter_cursor (0)
149 , pre_press_cursor (0)
152 _note_group->raise_to_top();
153 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
155 connect_to_diskstream ();
157 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
161 MidiRegionView::parameter_changed (std::string const & p)
163 if (p == "diplay-first-midi-bank-as-zero") {
164 if (_enable_display) {
170 MidiRegionView::MidiRegionView (const MidiRegionView& other)
171 : sigc::trackable(other)
173 , _current_range_min(0)
174 , _current_range_max(0)
176 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
177 , _note_diff_command (0)
179 , _step_edit_cursor (0)
180 , _step_edit_cursor_width (1.0)
181 , _step_edit_cursor_position (0.0)
182 , _channel_selection_scoped_note (0)
183 , _temporary_note_group (0)
186 , _sort_needed (true)
187 , _optimization_iterator (_events.end())
189 , _no_sound_notes (false)
192 , pre_enter_cursor (0)
193 , pre_press_cursor (0)
199 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
200 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
205 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
206 : RegionView (other, boost::shared_ptr<Region> (region))
207 , _current_range_min(0)
208 , _current_range_max(0)
210 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
211 , _note_diff_command (0)
213 , _step_edit_cursor (0)
214 , _step_edit_cursor_width (1.0)
215 , _step_edit_cursor_position (0.0)
216 , _channel_selection_scoped_note (0)
217 , _temporary_note_group (0)
220 , _sort_needed (true)
221 , _optimization_iterator (_events.end())
223 , _no_sound_notes (false)
226 , pre_enter_cursor (0)
227 , pre_press_cursor (0)
233 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
234 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
240 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
242 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
244 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
245 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
249 midi_region()->midi_source(0)->load_model();
252 _model = midi_region()->midi_source(0)->model();
253 _enable_display = false;
255 RegionView::init (basic_color, false);
257 compute_colors (basic_color);
259 set_height (trackview.current_height());
262 region_sync_changed ();
263 region_resized (ARDOUR::bounds_change);
268 _enable_display = true;
271 display_model (_model);
275 reset_width_dependent_items (_pixel_width);
277 group->raise_to_top();
278 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
281 midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
282 boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
285 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
286 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
288 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
289 boost::bind (&MidiRegionView::snap_changed, this),
292 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
293 connect_to_diskstream ();
295 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
299 MidiRegionView::instrument_info () const
301 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
302 return route_ui->route()->instrument_info();
305 const boost::shared_ptr<ARDOUR::MidiRegion>
306 MidiRegionView::midi_region() const
308 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
312 MidiRegionView::connect_to_diskstream ()
314 midi_view()->midi_track()->DataRecorded.connect(
315 *this, invalidator(*this),
316 boost::bind (&MidiRegionView::data_recorded, this, _1),
321 MidiRegionView::canvas_event(GdkEvent* ev)
326 case GDK_ENTER_NOTIFY:
327 case GDK_LEAVE_NOTIFY:
328 _last_event_x = ev->crossing.x;
329 _last_event_y = ev->crossing.y;
331 case GDK_MOTION_NOTIFY:
332 _last_event_x = ev->motion.x;
333 _last_event_y = ev->motion.y;
339 if (ev->type == GDK_2BUTTON_PRESS) {
340 // cannot use double-click to exit internal mode if single-click is being used
341 MouseMode m = trackview.editor().current_mouse_mode();
343 if ((m != MouseObject || !Keyboard::modifier_state_contains (ev->button.state, Keyboard::insert_note_modifier())) && (m != MouseDraw)) {
344 return trackview.editor().toggle_internal_editing_from_double_click (ev);
348 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
349 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
350 (trackview.editor().current_mouse_mode() == MouseZoom)) {
351 // handle non-draw modes elsewhere
357 return scroll (&ev->scroll);
360 return key_press (&ev->key);
362 case GDK_KEY_RELEASE:
363 return key_release (&ev->key);
365 case GDK_BUTTON_PRESS:
366 return button_press (&ev->button);
368 case GDK_BUTTON_RELEASE:
369 r = button_release (&ev->button);
374 case GDK_ENTER_NOTIFY:
375 return enter_notify (&ev->crossing);
377 case GDK_LEAVE_NOTIFY:
378 return leave_notify (&ev->crossing);
380 case GDK_MOTION_NOTIFY:
381 return motion (&ev->motion);
391 MidiRegionView::remove_ghost_note ()
398 MidiRegionView::enter_notify (GdkEventCrossing* ev)
400 trackview.editor().MouseModeChanged.connect (
401 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
404 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
405 create_ghost_note (ev->x, ev->y);
408 if (!trackview.editor().internal_editing()) {
409 Keyboard::magic_widget_drop_focus();
411 Keyboard::magic_widget_grab_focus();
415 // if current operation is non-operational in a midi region, change the cursor to so indicate
416 if (trackview.editor().current_mouse_mode() == MouseGain) {
417 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
418 pre_enter_cursor = editor->get_canvas_cursor();
419 editor->set_canvas_cursor(editor->cursors()->timebar);
426 MidiRegionView::leave_notify (GdkEventCrossing*)
428 _mouse_mode_connection.disconnect ();
430 trackview.editor().verbose_cursor()->hide ();
431 remove_ghost_note ();
433 if (pre_enter_cursor) {
434 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
435 editor->set_canvas_cursor(pre_enter_cursor);
442 MidiRegionView::mouse_mode_changed ()
444 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
445 create_ghost_note (_last_event_x, _last_event_y);
447 remove_ghost_note ();
448 trackview.editor().verbose_cursor()->hide ();
451 if (!trackview.editor().internal_editing()) {
452 Keyboard::magic_widget_drop_focus();
454 Keyboard::magic_widget_grab_focus();
460 MidiRegionView::button_press (GdkEventButton* ev)
462 if (ev->button != 1) {
466 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
467 MouseMode m = editor->current_mouse_mode();
469 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
470 pre_press_cursor = editor->get_canvas_cursor ();
471 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
474 if (_mouse_state != SelectTouchDragging) {
476 _pressed_button = ev->button;
477 _mouse_state = Pressed;
482 _pressed_button = ev->button;
488 MidiRegionView::button_release (GdkEventButton* ev)
490 double event_x, event_y;
492 if (ev->button != 1) {
499 group->w2i(event_x, event_y);
500 group->ungrab(ev->time);
502 PublicEditor& editor = trackview.editor ();
504 if (pre_press_cursor) {
505 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
506 pre_press_cursor = 0;
509 switch (_mouse_state) {
510 case Pressed: // Clicked
512 switch (editor.current_mouse_mode()) {
514 /* no motion occured - simple click */
523 if (Keyboard::is_insert_note_event(ev)) {
525 double event_x, event_y;
529 group->w2i(event_x, event_y);
532 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
538 /* Shorten the length by 1 tick so that we can add a new note at the next
539 grid snap without it overlapping this one.
541 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
543 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
551 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
557 /* Shorten the length by 1 tick so that we can add a new note at the next
558 grid snap without it overlapping this one.
560 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
562 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
573 case SelectRectDragging:
575 editor.drags()->end_grab ((GdkEvent *) ev);
577 create_ghost_note (ev->x, ev->y);
589 MidiRegionView::motion (GdkEventMotion* ev)
591 PublicEditor& editor = trackview.editor ();
593 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
594 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
595 _mouse_state != AddDragging) {
597 create_ghost_note (ev->x, ev->y);
599 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
600 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
602 update_ghost_note (ev->x, ev->y);
604 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
606 remove_ghost_note ();
607 editor.verbose_cursor()->hide ();
609 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
611 update_ghost_note (ev->x, ev->y);
614 /* any motion immediately hides velocity text that may have been visible */
616 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
617 (*i)->hide_velocity ();
620 switch (_mouse_state) {
623 if (_pressed_button == 1) {
625 MouseMode m = editor.current_mouse_mode();
627 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
629 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
630 _mouse_state = AddDragging;
631 remove_ghost_note ();
632 editor.verbose_cursor()->hide ();
634 } else if (m == MouseObject) {
635 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
637 _mouse_state = SelectRectDragging;
639 } else if (m == MouseRange) {
640 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
641 _mouse_state = SelectVerticalDragging;
648 case SelectRectDragging:
649 case SelectVerticalDragging:
651 editor.drags()->motion_handler ((GdkEvent *) ev, false);
654 case SelectTouchDragging:
666 MidiRegionView::scroll (GdkEventScroll* ev)
668 if (_selection.empty()) {
672 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
673 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
674 it still works for zoom.
679 trackview.editor().verbose_cursor()->hide ();
681 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
682 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
684 if (ev->direction == GDK_SCROLL_UP) {
685 change_velocities (true, fine, false, together);
686 } else if (ev->direction == GDK_SCROLL_DOWN) {
687 change_velocities (false, fine, false, together);
689 /* left, right: we don't use them */
697 MidiRegionView::key_press (GdkEventKey* ev)
699 /* since GTK bindings are generally activated on press, and since
700 detectable auto-repeat is the name of the game and only sends
701 repeated presses, carry out key actions at key press, not release.
704 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
706 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
707 _mouse_state = SelectTouchDragging;
710 } else if (ev->keyval == GDK_Escape && unmodified) {
714 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
716 bool start = (ev->keyval == GDK_comma);
717 bool end = (ev->keyval == GDK_period);
718 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
719 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
721 change_note_lengths (fine, shorter, 0.0, start, end);
725 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
727 if (_selection.empty()) {
734 } else if (ev->keyval == GDK_Tab) {
736 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
737 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
739 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
743 } else if (ev->keyval == GDK_ISO_Left_Tab) {
745 /* Shift-TAB generates ISO Left Tab, for some reason */
747 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
748 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
750 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
756 } else if (ev->keyval == GDK_Up) {
758 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
759 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
760 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
762 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
763 change_velocities (true, fine, allow_smush, together);
765 transpose (true, fine, allow_smush);
769 } else if (ev->keyval == GDK_Down) {
771 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
772 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
773 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
775 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
776 change_velocities (false, fine, allow_smush, together);
778 transpose (false, fine, allow_smush);
782 } else if (ev->keyval == GDK_Left && unmodified) {
787 } else if (ev->keyval == GDK_Right && unmodified) {
792 } else if (ev->keyval == GDK_c && unmodified) {
796 } else if (ev->keyval == GDK_v && unmodified) {
805 MidiRegionView::key_release (GdkEventKey* ev)
807 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
815 MidiRegionView::channel_edit ()
817 if (_selection.empty()) {
821 /* pick a note somewhat at random (since Selection is a set<>) to
822 * provide the "current" channel for the dialog.
825 uint8_t current_channel = (*_selection.begin())->note()->channel ();
826 MidiChannelDialog channel_dialog (current_channel);
827 int ret = channel_dialog.run ();
830 case Gtk::RESPONSE_OK:
836 uint8_t new_channel = channel_dialog.active_channel ();
838 start_note_diff_command (_("channel edit"));
840 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
841 Selection::iterator next = i;
843 change_note_channel (*i, new_channel);
851 MidiRegionView::velocity_edit ()
853 if (_selection.empty()) {
857 /* pick a note somewhat at random (since Selection is a set<>) to
858 * provide the "current" velocity for the dialog.
861 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
862 MidiVelocityDialog velocity_dialog (current_velocity);
863 int ret = velocity_dialog.run ();
866 case Gtk::RESPONSE_OK:
872 uint8_t new_velocity = velocity_dialog.velocity ();
874 start_note_diff_command (_("velocity edit"));
876 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
877 Selection::iterator next = i;
879 change_note_velocity (*i, new_velocity, false);
887 MidiRegionView::show_list_editor ()
890 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
892 _list_editor->present ();
895 /** Add a note to the model, and the view, at a canvas (click) coordinate.
896 * \param t time in frames relative to the position of the region
897 * \param y vertical position in pixels
898 * \param length duration of the note in beats
899 * \param snap_t true to snap t to the grid, otherwise false.
902 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
904 if (length < 2 * DBL_EPSILON) {
908 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
909 MidiStreamView* const view = mtv->midi_view();
911 const double note = view->y_to_note(y);
913 // Start of note in frames relative to region start
915 framecnt_t grid_frames;
916 t = snap_frame_to_grid_underneath (t, grid_frames);
919 const boost::shared_ptr<NoteType> new_note (
920 new NoteType (mtv->get_channel_for_add (),
921 region_frames_to_region_beats(t + _region->start()),
923 (uint8_t)note, 0x40));
925 if (_model->contains (new_note)) {
929 view->update_note_range(new_note->note());
931 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
933 _model->apply_command(*trackview.session(), cmd);
935 play_midi_note (new_note);
939 MidiRegionView::clear_events (bool with_selection_signal)
941 clear_selection (with_selection_signal);
944 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
945 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
950 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
955 _patch_changes.clear();
957 _optimization_iterator = _events.end();
961 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
965 content_connection.disconnect ();
966 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
970 if (_enable_display) {
976 MidiRegionView::start_note_diff_command (string name)
978 if (!_note_diff_command) {
979 _note_diff_command = _model->new_note_diff_command (name);
984 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
986 if (_note_diff_command) {
987 _note_diff_command->add (note);
990 _marked_for_selection.insert(note);
993 _marked_for_velocity.insert(note);
998 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
1000 if (_note_diff_command && ev->note()) {
1001 _note_diff_command->remove(ev->note());
1006 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1007 MidiModel::NoteDiffCommand::Property property,
1010 if (_note_diff_command) {
1011 _note_diff_command->change (ev->note(), property, val);
1016 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1017 MidiModel::NoteDiffCommand::Property property,
1018 Evoral::MusicalTime val)
1020 if (_note_diff_command) {
1021 _note_diff_command->change (ev->note(), property, val);
1026 MidiRegionView::apply_diff (bool as_subcommand)
1030 if (!_note_diff_command) {
1034 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1035 // Mark all selected notes for selection when model reloads
1036 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1037 _marked_for_selection.insert((*i)->note());
1041 if (as_subcommand) {
1042 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1044 _model->apply_command (*trackview.session(), _note_diff_command);
1047 _note_diff_command = 0;
1048 midi_view()->midi_track()->playlist_modified();
1050 if (add_or_remove) {
1051 _marked_for_selection.clear();
1054 _marked_for_velocity.clear();
1058 MidiRegionView::abort_command()
1060 delete _note_diff_command;
1061 _note_diff_command = 0;
1066 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1068 if (_optimization_iterator != _events.end()) {
1069 ++_optimization_iterator;
1072 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1073 return *_optimization_iterator;
1076 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1077 if ((*_optimization_iterator)->note() == note) {
1078 return *_optimization_iterator;
1086 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1088 MidiModel::Notes notes;
1089 _model->get_notes (notes, op, val, chan_mask);
1091 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1092 CanvasNoteEvent* cne = find_canvas_note (*n);
1100 MidiRegionView::redisplay_model()
1102 // Don't redisplay the model if we're currently recording and displaying that
1103 if (_active_notes) {
1111 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1112 (*i)->invalidate ();
1115 MidiModel::ReadLock lock(_model->read_lock());
1117 MidiModel::Notes& notes (_model->notes());
1118 _optimization_iterator = _events.begin();
1120 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1122 boost::shared_ptr<NoteType> note (*n);
1123 CanvasNoteEvent* cne;
1126 if (note_in_region_range (note, visible)) {
1128 if ((cne = find_canvas_note (note)) != 0) {
1135 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1137 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1149 add_note (note, visible);
1154 if ((cne = find_canvas_note (note)) != 0) {
1162 /* remove note items that are no longer valid */
1164 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1165 if (!(*i)->valid ()) {
1167 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1168 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1170 gr->remove_note (*i);
1175 i = _events.erase (i);
1182 _patch_changes.clear();
1186 display_patch_changes ();
1188 _marked_for_selection.clear ();
1189 _marked_for_velocity.clear ();
1191 /* we may have caused _events to contain things out of order (e.g. if a note
1192 moved earlier or later). we don't generally need them in time order, but
1193 make a note that a sort is required for those cases that require it.
1196 _sort_needed = true;
1200 MidiRegionView::display_patch_changes ()
1202 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1203 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1205 for (uint8_t i = 0; i < 16; ++i) {
1206 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1210 /** @param active_channel true to display patch changes fully, false to display
1211 * them `greyed-out' (as on an inactive channel)
1214 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1216 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1218 if ((*i)->channel() != channel) {
1222 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1223 add_canvas_patch_change (*i, patch_name, active_channel);
1228 MidiRegionView::display_sysexes()
1230 bool have_periodic_system_messages = false;
1231 bool display_periodic_messages = true;
1233 if (!Config->get_never_display_periodic_midi()) {
1235 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1236 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1237 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1240 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1241 have_periodic_system_messages = true;
1247 if (have_periodic_system_messages) {
1248 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1250 /* get an approximate value for the number of samples per video frame */
1252 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1254 /* if we are zoomed out beyond than the cutoff (i.e. more
1255 * frames per pixel than frames per 4 video frames), don't
1256 * show periodic sysex messages.
1259 if (zoom > (video_frame*4)) {
1260 display_periodic_messages = false;
1264 display_periodic_messages = false;
1267 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1269 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1270 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1272 Evoral::MusicalTime time = (*i)->time();
1275 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1276 if (!display_periodic_messages) {
1284 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1285 str << int((*i)->buffer()[b]);
1286 if (b != (*i)->size() -1) {
1290 string text = str.str();
1292 const double x = trackview.editor().frame_to_pixel(source_beats_to_region_frames(time));
1294 double height = midi_stream_view()->contents_height();
1296 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1297 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0, (*i)));
1299 // Show unless message is beyond the region bounds
1300 if (time - _region->start() >= _region->length() || time < _region->start()) {
1306 _sys_exes.push_back(sysex);
1310 MidiRegionView::~MidiRegionView ()
1312 in_destructor = true;
1314 trackview.editor().verbose_cursor()->hide ();
1316 note_delete_connection.disconnect ();
1318 delete _list_editor;
1320 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1322 if (_active_notes) {
1326 _selection_cleared_connection.disconnect ();
1329 clear_events (false);
1332 delete _note_diff_command;
1333 delete _step_edit_cursor;
1334 delete _temporary_note_group;
1338 MidiRegionView::region_resized (const PropertyChange& what_changed)
1340 RegionView::region_resized(what_changed);
1342 if (what_changed.contains (ARDOUR::Properties::position)) {
1343 set_duration(_region->length(), 0);
1344 if (_enable_display) {
1351 MidiRegionView::reset_width_dependent_items (double pixel_width)
1353 RegionView::reset_width_dependent_items(pixel_width);
1355 if (_enable_display) {
1359 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1360 if ((*x)->width() >= _pixel_width) {
1367 move_step_edit_cursor (_step_edit_cursor_position);
1368 set_step_edit_cursor_width (_step_edit_cursor_width);
1372 MidiRegionView::set_height (double height)
1374 static const double FUDGE = 2.0;
1375 const double old_height = _height;
1376 RegionView::set_height(height);
1377 _height = height - FUDGE;
1379 apply_note_range(midi_stream_view()->lowest_note(),
1380 midi_stream_view()->highest_note(),
1381 height != old_height + FUDGE);
1384 name_pixbuf->raise_to_top();
1387 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1388 (*x)->set_height (midi_stream_view()->contents_height());
1391 if (_step_edit_cursor) {
1392 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1397 /** Apply the current note range from the stream view
1398 * by repositioning/hiding notes as necessary
1401 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1403 if (!_enable_display) {
1407 if (!force && _current_range_min == min && _current_range_max == max) {
1411 _current_range_min = min;
1412 _current_range_max = max;
1414 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1415 CanvasNoteEvent* event = *i;
1416 boost::shared_ptr<NoteType> note (event->note());
1418 if (note->note() < _current_range_min ||
1419 note->note() > _current_range_max) {
1425 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1427 const double y1 = midi_stream_view()->note_to_y(note->note());
1428 const double y2 = y1 + floor(midi_stream_view()->note_height());
1430 cnote->property_y1() = y1;
1431 cnote->property_y2() = y2;
1433 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1435 const double diamond_size = update_hit (chit);
1437 chit->set_height (diamond_size);
1443 MidiRegionView::add_ghost (TimeAxisView& tv)
1447 double unit_position = _region->position () / samples_per_unit;
1448 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1449 MidiGhostRegion* ghost;
1451 if (mtv && mtv->midi_view()) {
1452 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1453 to allow having midi notes on top of note lines and waveforms.
1455 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1457 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1460 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1461 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1462 ghost->add_note(note);
1466 ghost->set_height ();
1467 ghost->set_duration (_region->length() / samples_per_unit);
1468 ghosts.push_back (ghost);
1470 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1476 /** Begin tracking note state for successive calls to add_event
1479 MidiRegionView::begin_write()
1481 if (_active_notes) {
1482 delete[] _active_notes;
1484 _active_notes = new CanvasNote*[128];
1485 for (unsigned i = 0; i < 128; ++i) {
1486 _active_notes[i] = 0;
1491 /** Destroy note state for add_event
1494 MidiRegionView::end_write()
1496 delete[] _active_notes;
1498 _marked_for_selection.clear();
1499 _marked_for_velocity.clear();
1503 /** Resolve an active MIDI note (while recording).
1506 MidiRegionView::resolve_note(uint8_t note, double end_time)
1508 if (midi_view()->note_mode() != Sustained) {
1512 if (_active_notes && _active_notes[note]) {
1514 /* XXX is end_time really region-centric? I think so, because
1515 this is a new region that we're recording, so source zero is
1516 the same as region zero
1518 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1520 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1521 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1522 _active_notes[note] = 0;
1527 /** Extend active notes to rightmost edge of region (if length is changed)
1530 MidiRegionView::extend_active_notes()
1532 if (!_active_notes) {
1536 for (unsigned i=0; i < 128; ++i) {
1537 if (_active_notes[i]) {
1538 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1545 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1547 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1551 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1553 if (!route_ui || !route_ui->midi_track()) {
1557 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1561 /* NotePlayer deletes itself */
1565 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1567 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1571 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1573 if (!route_ui || !route_ui->midi_track()) {
1577 delete _note_player;
1578 _note_player = new NotePlayer (route_ui->midi_track ());
1579 _note_player->add (note);
1580 _note_player->on ();
1584 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1586 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1590 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1592 if (!route_ui || !route_ui->midi_track()) {
1596 delete _note_player;
1597 _note_player = new NotePlayer (route_ui->midi_track());
1599 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1600 _note_player->add (*n);
1603 _note_player->on ();
1608 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1610 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1611 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1613 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1614 (note->note() <= midi_stream_view()->highest_note());
1619 /** Update a canvas note's size from its model note.
1620 * @param ev Canvas note to update.
1621 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1624 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1626 boost::shared_ptr<NoteType> note = ev->note();
1627 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1628 const double y1 = midi_stream_view()->note_to_y(note->note());
1630 ev->property_x1() = x;
1631 ev->property_y1() = y1;
1633 /* trim note display to not overlap the end of its region */
1635 if (note->length() > 0) {
1636 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1637 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1639 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1642 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1644 if (note->length() == 0) {
1645 if (_active_notes && note->note() < 128) {
1646 // If this note is already active there's a stuck note,
1647 // finish the old note rectangle
1648 if (_active_notes[note->note()]) {
1649 CanvasNote* const old_rect = _active_notes[note->note()];
1650 boost::shared_ptr<NoteType> old_note = old_rect->note();
1651 old_rect->property_x2() = x;
1652 old_rect->property_outline_what() = (guint32) 0xF;
1654 _active_notes[note->note()] = ev;
1656 /* outline all but right edge */
1657 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1659 /* outline all edges */
1660 ev->property_outline_what() = (guint32) 0xF;
1663 if (update_ghost_regions) {
1664 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1665 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1667 gr->update_note (ev);
1674 MidiRegionView::update_hit (CanvasHit* ev)
1676 boost::shared_ptr<NoteType> note = ev->note();
1678 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1679 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1680 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1681 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1685 return diamond_size;
1688 /** Add a MIDI note to the view (with length).
1690 * If in sustained mode, notes with length 0 will be considered active
1691 * notes, and resolve_note should be called when the corresponding note off
1692 * event arrives, to properly display the note.
1695 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1697 CanvasNoteEvent* event = 0;
1699 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1701 if (midi_view()->note_mode() == Sustained) {
1703 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1705 update_note (ev_rect);
1709 MidiGhostRegion* gr;
1711 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1712 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1713 gr->add_note(ev_rect);
1717 } else if (midi_view()->note_mode() == Percussive) {
1719 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1721 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1723 update_hit (ev_diamond);
1732 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1733 note_selected(event, true);
1736 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1737 event->show_velocity();
1740 event->on_channel_selection_change (get_selected_channels());
1741 _events.push_back(event);
1750 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1751 MidiStreamView* const view = mtv->midi_view();
1753 view->update_note_range (note->note());
1757 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1758 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1760 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1762 /* potentially extend region to hold new note */
1764 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1765 framepos_t region_end = _region->last_frame();
1767 if (end_frame > region_end) {
1768 _region->set_length (end_frame - _region->position());
1771 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1772 MidiStreamView* const view = mtv->midi_view();
1774 view->update_note_range(new_note->note());
1776 _marked_for_selection.clear ();
1779 start_note_diff_command (_("step add"));
1780 note_diff_add_note (new_note, true, false);
1783 // last_step_edit_note = new_note;
1787 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1789 change_note_lengths (false, false, beats, false, true);
1792 /** Add a new patch change flag to the canvas.
1793 * @param patch the patch change to add
1794 * @param the text to display in the flag
1795 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1798 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1800 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1801 const double x = trackview.editor().frame_to_pixel (region_frames);
1803 double const height = midi_stream_view()->contents_height();
1805 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1806 new CanvasPatchChange(*this, *group,
1814 if (patch_change->width() < _pixel_width) {
1815 // Show unless patch change is beyond the region bounds
1816 if (region_frames < 0 || region_frames >= _region->length()) {
1817 patch_change->hide();
1819 patch_change->show();
1822 patch_change->hide ();
1825 _patch_changes.push_back (patch_change);
1828 MIDI::Name::PatchPrimaryKey
1829 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1831 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1834 /// Return true iff @p pc applies to the given time on the given channel.
1836 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, double time, uint8_t channel)
1838 return pc->time() <= time && pc->channel() == channel;
1842 MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1844 // The earliest event not before time
1845 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1847 // Go backwards until we find the latest PC for this channel, or the start
1848 while (i != _model->patch_changes().begin() &&
1849 (i == _model->patch_changes().end() ||
1850 !patch_applies(*i, time, channel))) {
1854 if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
1855 key.bank_number = (*i)->bank();
1856 key.program_number = (*i)->program ();
1858 key.bank_number = key.program_number = 0;
1861 if (!key.is_sane()) {
1862 error << string_compose(_("insane MIDI patch key %1:%2"),
1863 key.bank_number, key.program_number) << endmsg;
1868 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1870 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1872 if (pc.patch()->program() != new_patch.program_number) {
1873 c->change_program (pc.patch (), new_patch.program_number);
1876 int const new_bank = new_patch.bank_number;
1877 if (pc.patch()->bank() != new_bank) {
1878 c->change_bank (pc.patch (), new_bank);
1881 _model->apply_command (*trackview.session(), c);
1883 _patch_changes.clear ();
1884 display_patch_changes ();
1888 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1890 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1892 if (old_change->time() != new_change.time()) {
1893 c->change_time (old_change, new_change.time());
1896 if (old_change->channel() != new_change.channel()) {
1897 c->change_channel (old_change, new_change.channel());
1900 if (old_change->program() != new_change.program()) {
1901 c->change_program (old_change, new_change.program());
1904 if (old_change->bank() != new_change.bank()) {
1905 c->change_bank (old_change, new_change.bank());
1908 _model->apply_command (*trackview.session(), c);
1910 _patch_changes.clear ();
1911 display_patch_changes ();
1914 /** Add a patch change to the region.
1915 * @param t Time in frames relative to region position
1916 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1917 * MidiTimeAxisView::get_channel_for_add())
1920 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1922 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1924 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1925 c->add (MidiModel::PatchChangePtr (
1926 new Evoral::PatchChange<Evoral::MusicalTime> (
1927 absolute_frames_to_source_beats (_region->position() + t),
1928 mtv->get_channel_for_add(), patch.program(), patch.bank()
1933 _model->apply_command (*trackview.session(), c);
1935 _patch_changes.clear ();
1936 display_patch_changes ();
1940 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1942 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1943 c->change_time (pc.patch (), t);
1944 _model->apply_command (*trackview.session(), c);
1946 _patch_changes.clear ();
1947 display_patch_changes ();
1951 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1953 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1954 c->remove (pc->patch ());
1955 _model->apply_command (*trackview.session(), c);
1957 _patch_changes.clear ();
1958 display_patch_changes ();
1962 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1964 if (patch.patch()->program() < 127) {
1965 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1966 key.program_number++;
1967 change_patch_change (patch, key);
1972 MidiRegionView::next_patch (CanvasPatchChange& patch)
1974 if (patch.patch()->program() > 0) {
1975 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1976 key.program_number--;
1977 change_patch_change (patch, key);
1982 MidiRegionView::next_bank (CanvasPatchChange& patch)
1984 if (patch.patch()->program() < 127) {
1985 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1986 if (key.bank_number > 0) {
1988 change_patch_change (patch, key);
1994 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1996 if (patch.patch()->program() > 0) {
1997 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1998 if (key.bank_number < 127) {
2000 change_patch_change (patch, key);
2006 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
2008 if (_selection.empty()) {
2012 _selection.erase (cne);
2016 MidiRegionView::delete_selection()
2018 if (_selection.empty()) {
2022 start_note_diff_command (_("delete selection"));
2024 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2025 if ((*i)->selected()) {
2026 _note_diff_command->remove((*i)->note());
2036 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2038 start_note_diff_command (_("delete note"));
2039 _note_diff_command->remove (n);
2042 trackview.editor().verbose_cursor()->hide ();
2046 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2048 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2050 Selection::iterator tmp = i;
2053 (*i)->set_selected (false);
2054 (*i)->hide_velocity ();
2055 _selection.erase (i);
2063 /* this does not change the status of this regionview w.r.t the editor
2068 SelectionCleared (this); /* EMIT SIGNAL */
2073 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2075 clear_selection_except (ev);
2077 /* don't bother with checking to see if we should remove this
2078 regionview from the editor selection, since we're about to add
2079 another note, and thus put/keep this regionview in the editor
2083 if (!ev->selected()) {
2084 add_to_selection (ev);
2089 MidiRegionView::select_all_notes ()
2093 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2094 add_to_selection (*i);
2099 MidiRegionView::select_range (framepos_t start, framepos_t end)
2103 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2104 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2105 if (t >= start && t <= end) {
2106 add_to_selection (*i);
2112 MidiRegionView::invert_selection ()
2114 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2115 if ((*i)->selected()) {
2116 remove_from_selection(*i);
2118 add_to_selection (*i);
2124 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2126 uint8_t low_note = 127;
2127 uint8_t high_note = 0;
2128 MidiModel::Notes& notes (_model->notes());
2129 _optimization_iterator = _events.begin();
2135 if (extend && _selection.empty()) {
2141 /* scan existing selection to get note range */
2143 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2144 if ((*i)->note()->note() < low_note) {
2145 low_note = (*i)->note()->note();
2147 if ((*i)->note()->note() > high_note) {
2148 high_note = (*i)->note()->note();
2152 low_note = min (low_note, notenum);
2153 high_note = max (high_note, notenum);
2156 _no_sound_notes = true;
2158 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2160 boost::shared_ptr<NoteType> note (*n);
2161 CanvasNoteEvent* cne;
2162 bool select = false;
2164 if (((1 << note->channel()) & channel_mask) != 0) {
2166 if ((note->note() >= low_note && note->note() <= high_note)) {
2169 } else if (note->note() == notenum) {
2175 if ((cne = find_canvas_note (note)) != 0) {
2176 // extend is false because we've taken care of it,
2177 // since it extends by time range, not pitch.
2178 note_selected (cne, add, false);
2182 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2186 _no_sound_notes = false;
2190 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2192 MidiModel::Notes& notes (_model->notes());
2193 _optimization_iterator = _events.begin();
2195 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2197 boost::shared_ptr<NoteType> note (*n);
2198 CanvasNoteEvent* cne;
2200 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2201 if ((cne = find_canvas_note (note)) != 0) {
2202 if (cne->selected()) {
2203 note_deselected (cne);
2205 note_selected (cne, true, false);
2213 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2216 clear_selection_except (ev);
2217 if (!_selection.empty()) {
2218 PublicEditor& editor (trackview.editor());
2219 editor.get_selection().add (this);
2225 if (!ev->selected()) {
2226 add_to_selection (ev);
2230 /* find end of latest note selected, select all between that and the start of "ev" */
2232 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2233 Evoral::MusicalTime latest = 0;
2235 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2236 if ((*i)->note()->end_time() > latest) {
2237 latest = (*i)->note()->end_time();
2239 if ((*i)->note()->time() < earliest) {
2240 earliest = (*i)->note()->time();
2244 if (ev->note()->end_time() > latest) {
2245 latest = ev->note()->end_time();
2248 if (ev->note()->time() < earliest) {
2249 earliest = ev->note()->time();
2252 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2254 /* find notes entirely within OR spanning the earliest..latest range */
2256 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2257 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2258 add_to_selection (*i);
2266 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2268 remove_from_selection (ev);
2272 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2274 // TODO: Make this faster by storing the last updated selection rect, and only
2275 // adjusting things that are in the area that appears/disappeared.
2276 // We probably need a tree to be able to find events in O(log(n)) time.
2278 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2279 if ((*i)->x1() < x2 && (*i)->x2() > x1 && (*i)->y1() < y2 && (*i)->y2() > y1) {
2280 // Rectangles intersect
2281 if (!(*i)->selected()) {
2282 add_to_selection (*i);
2284 } else if ((*i)->selected() && !extend) {
2285 // Rectangles do not intersect
2286 remove_from_selection (*i);
2292 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2298 // TODO: Make this faster by storing the last updated selection rect, and only
2299 // adjusting things that are in the area that appears/disappeared.
2300 // We probably need a tree to be able to find events in O(log(n)) time.
2302 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2303 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2304 // within y- (note-) range
2305 if (!(*i)->selected()) {
2306 add_to_selection (*i);
2308 } else if ((*i)->selected() && !extend) {
2309 remove_from_selection (*i);
2315 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2317 Selection::iterator i = _selection.find (ev);
2319 if (i != _selection.end()) {
2320 _selection.erase (i);
2323 ev->set_selected (false);
2324 ev->hide_velocity ();
2326 if (_selection.empty()) {
2327 PublicEditor& editor (trackview.editor());
2328 editor.get_selection().remove (this);
2333 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2335 bool add_mrv_selection = false;
2337 if (_selection.empty()) {
2338 add_mrv_selection = true;
2341 if (_selection.insert (ev).second) {
2342 ev->set_selected (true);
2343 start_playing_midi_note ((ev)->note());
2346 if (add_mrv_selection) {
2347 PublicEditor& editor (trackview.editor());
2348 editor.get_selection().add (this);
2353 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2355 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2356 PossibleChord to_play;
2357 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2359 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2360 if ((*i)->note()->time() < earliest) {
2361 earliest = (*i)->note()->time();
2365 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2366 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2367 to_play.push_back ((*i)->note());
2369 (*i)->move_event(dx, dy);
2372 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2374 if (to_play.size() > 1) {
2376 PossibleChord shifted;
2378 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2379 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2380 moved_note->set_note (moved_note->note() + cumulative_dy);
2381 shifted.push_back (moved_note);
2384 start_playing_midi_chord (shifted);
2386 } else if (!to_play.empty()) {
2388 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2389 moved_note->set_note (moved_note->note() + cumulative_dy);
2390 start_playing_midi_note (moved_note);
2396 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2398 uint8_t lowest_note_in_selection = 127;
2399 uint8_t highest_note_in_selection = 0;
2400 uint8_t highest_note_difference = 0;
2402 // find highest and lowest notes first
2404 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2405 uint8_t pitch = (*i)->note()->note();
2406 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2407 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2411 cerr << "dnote: " << (int) dnote << endl;
2412 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2413 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2414 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2415 << int(highest_note_in_selection) << endl;
2416 cerr << "selection size: " << _selection.size() << endl;
2417 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2420 // Make sure the note pitch does not exceed the MIDI standard range
2421 if (highest_note_in_selection + dnote > 127) {
2422 highest_note_difference = highest_note_in_selection - 127;
2425 start_note_diff_command (_("move notes"));
2427 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2429 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2430 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2436 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2438 uint8_t original_pitch = (*i)->note()->note();
2439 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2441 // keep notes in standard midi range
2442 clamp_to_0_127(new_pitch);
2444 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2445 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2447 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2452 // care about notes being moved beyond the upper/lower bounds on the canvas
2453 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2454 highest_note_in_selection > midi_stream_view()->highest_note()) {
2455 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2459 /** @param x Pixel relative to the region position.
2460 * @return Snapped frame relative to the region position.
2463 MidiRegionView::snap_pixel_to_frame(double x)
2465 PublicEditor& editor (trackview.editor());
2466 return snap_frame_to_frame (editor.pixel_to_frame (x));
2469 /** @param x Pixel relative to the region position.
2470 * @return Snapped pixel relative to the region position.
2473 MidiRegionView::snap_to_pixel(double x)
2475 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2479 MidiRegionView::get_position_pixels()
2481 framepos_t region_frame = get_position();
2482 return trackview.editor().frame_to_pixel(region_frame);
2486 MidiRegionView::get_end_position_pixels()
2488 framepos_t frame = get_position() + get_duration ();
2489 return trackview.editor().frame_to_pixel(frame);
2493 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2495 /* the time converter will return the frame corresponding to `beats'
2496 relative to the start of the source. The start of the source
2497 is an implied position given by region->position - region->start
2499 const framepos_t source_start = _region->position() - _region->start();
2500 return source_start + _source_relative_time_converter.to (beats);
2504 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2506 /* the `frames' argument needs to be converted into a frame count
2507 relative to the start of the source before being passed in to the
2510 const framepos_t source_start = _region->position() - _region->start();
2511 return _source_relative_time_converter.from (frames - source_start);
2515 MidiRegionView::region_beats_to_region_frames(double beats) const
2517 return _region_relative_time_converter.to(beats);
2521 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2523 return _region_relative_time_converter.from(frames);
2527 MidiRegionView::begin_resizing (bool /*at_front*/)
2529 _resize_data.clear();
2531 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2532 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2534 // only insert CanvasNotes into the map
2536 NoteResizeData *resize_data = new NoteResizeData();
2537 resize_data->canvas_note = note;
2539 // create a new SimpleRect from the note which will be the resize preview
2540 SimpleRect *resize_rect = new SimpleRect(
2541 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2543 // calculate the colors: get the color settings
2544 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2545 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2548 // make the resize preview notes more transparent and bright
2549 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2551 // calculate color based on note velocity
2552 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2553 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2557 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2558 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2560 resize_data->resize_rect = resize_rect;
2561 _resize_data.push_back(resize_data);
2566 /** Update resizing notes while user drags.
2567 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2568 * @param at_front which end of the note (true == note on, false == note off)
2569 * @param delta_x change in mouse position since the start of the drag
2570 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2571 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2572 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2573 * as the \a primary note.
2576 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2578 bool cursor_set = false;
2580 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2581 SimpleRect* resize_rect = (*i)->resize_rect;
2582 CanvasNote* canvas_note = (*i)->canvas_note;
2587 current_x = canvas_note->x1() + delta_x;
2589 current_x = primary->x1() + delta_x;
2593 current_x = canvas_note->x2() + delta_x;
2595 current_x = primary->x2() + delta_x;
2600 resize_rect->property_x1() = snap_to_pixel(current_x);
2601 resize_rect->property_x2() = canvas_note->x2();
2603 resize_rect->property_x2() = snap_to_pixel(current_x);
2604 resize_rect->property_x1() = canvas_note->x1();
2610 beats = snap_pixel_to_frame (current_x);
2611 beats = region_frames_to_region_beats (beats);
2616 if (beats < canvas_note->note()->end_time()) {
2617 len = canvas_note->note()->time() - beats;
2618 len += canvas_note->note()->length();
2623 if (beats >= canvas_note->note()->time()) {
2624 len = beats - canvas_note->note()->time();
2631 snprintf (buf, sizeof (buf), "%.3g beats", len);
2632 show_verbose_cursor (buf, 0, 0);
2641 /** Finish resizing notes when the user releases the mouse button.
2642 * Parameters the same as for \a update_resizing().
2645 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2647 start_note_diff_command (_("resize notes"));
2649 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2650 CanvasNote* canvas_note = (*i)->canvas_note;
2651 SimpleRect* resize_rect = (*i)->resize_rect;
2653 /* Get the new x position for this resize, which is in pixels relative
2654 * to the region position.
2661 current_x = canvas_note->x1() + delta_x;
2663 current_x = primary->x1() + delta_x;
2667 current_x = canvas_note->x2() + delta_x;
2669 current_x = primary->x2() + delta_x;
2673 /* Convert that to a frame within the source */
2674 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2676 /* and then to beats */
2677 current_x = region_frames_to_region_beats (current_x);
2679 if (at_front && current_x < canvas_note->note()->end_time()) {
2680 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2682 double len = canvas_note->note()->time() - current_x;
2683 len += canvas_note->note()->length();
2686 /* XXX convert to beats */
2687 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2692 double len = current_x - canvas_note->note()->time();
2695 /* XXX convert to beats */
2696 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2704 _resize_data.clear();
2709 MidiRegionView::abort_resizing ()
2711 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2712 delete (*i)->resize_rect;
2716 _resize_data.clear ();
2720 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2722 uint8_t new_velocity;
2725 new_velocity = event->note()->velocity() + velocity;
2726 clamp_to_0_127(new_velocity);
2728 new_velocity = velocity;
2731 event->set_selected (event->selected()); // change color
2733 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2737 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2742 new_note = event->note()->note() + note;
2747 clamp_to_0_127 (new_note);
2748 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2752 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2754 bool change_start = false;
2755 bool change_length = false;
2756 Evoral::MusicalTime new_start = 0;
2757 Evoral::MusicalTime new_length = 0;
2759 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2761 front_delta: if positive - move the start of the note later in time (shortening it)
2762 if negative - move the start of the note earlier in time (lengthening it)
2764 end_delta: if positive - move the end of the note later in time (lengthening it)
2765 if negative - move the end of the note earlier in time (shortening it)
2769 if (front_delta < 0) {
2771 if (event->note()->time() < -front_delta) {
2774 new_start = event->note()->time() + front_delta; // moves earlier
2777 /* start moved toward zero, so move the end point out to where it used to be.
2778 Note that front_delta is negative, so this increases the length.
2781 new_length = event->note()->length() - front_delta;
2782 change_start = true;
2783 change_length = true;
2787 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2789 if (new_pos < event->note()->end_time()) {
2790 new_start = event->note()->time() + front_delta;
2791 /* start moved toward the end, so move the end point back to where it used to be */
2792 new_length = event->note()->length() - front_delta;
2793 change_start = true;
2794 change_length = true;
2801 bool can_change = true;
2802 if (end_delta < 0) {
2803 if (event->note()->length() < -end_delta) {
2809 new_length = event->note()->length() + end_delta;
2810 change_length = true;
2815 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2818 if (change_length) {
2819 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2824 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2826 uint8_t new_channel;
2830 if (event->note()->channel() < -chn) {
2833 new_channel = event->note()->channel() + chn;
2836 new_channel = event->note()->channel() + chn;
2839 new_channel = (uint8_t) chn;
2842 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2846 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2848 Evoral::MusicalTime new_time;
2852 if (event->note()->time() < -delta) {
2855 new_time = event->note()->time() + delta;
2858 new_time = event->note()->time() + delta;
2864 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2868 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2870 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2874 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2879 if (_selection.empty()) {
2894 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2895 if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
2901 start_note_diff_command (_("change velocities"));
2903 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2904 Selection::iterator next = i;
2908 if (i == _selection.begin()) {
2909 change_note_velocity (*i, delta, true);
2910 value = (*i)->note()->velocity() + delta;
2912 change_note_velocity (*i, value, false);
2916 change_note_velocity (*i, delta, true);
2925 if (!_selection.empty()) {
2927 snprintf (buf, sizeof (buf), "Vel %d",
2928 (int) (*_selection.begin())->note()->velocity());
2929 show_verbose_cursor (buf, 10, 10);
2935 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2937 if (_selection.empty()) {
2954 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2956 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2960 if ((int8_t) (*i)->note()->note() + delta > 127) {
2967 start_note_diff_command (_("transpose"));
2969 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2970 Selection::iterator next = i;
2972 change_note_note (*i, delta, true);
2980 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2986 /* grab the current grid distance */
2988 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2990 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2991 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3001 start_note_diff_command (_("change note lengths"));
3003 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3004 Selection::iterator next = i;
3007 /* note the negation of the delta for start */
3009 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3018 MidiRegionView::nudge_notes (bool forward)
3020 if (_selection.empty()) {
3024 /* pick a note as the point along the timeline to get the nudge distance.
3025 its not necessarily the earliest note, so we may want to pull the notes out
3026 into a vector and sort before using the first one.
3029 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3031 framecnt_t distance;
3033 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3035 /* grid is off - use nudge distance */
3037 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3043 framepos_t next_pos = ref_point;
3046 if (max_framepos - 1 < next_pos) {
3050 if (next_pos == 0) {
3056 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3057 distance = ref_point - next_pos;
3060 if (distance == 0) {
3064 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3070 start_note_diff_command (_("nudge"));
3072 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3073 Selection::iterator next = i;
3075 change_note_time (*i, delta, true);
3083 MidiRegionView::change_channel(uint8_t channel)
3085 start_note_diff_command(_("change channel"));
3086 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3087 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3095 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3097 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3099 pre_enter_cursor = editor->get_canvas_cursor ();
3101 if (_mouse_state == SelectTouchDragging) {
3102 note_selected (ev, true);
3105 show_verbose_cursor (ev->note ());
3109 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3111 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3113 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3114 (*i)->hide_velocity ();
3117 editor->verbose_cursor()->hide ();
3119 if (pre_enter_cursor) {
3120 editor->set_canvas_cursor (pre_enter_cursor);
3121 pre_enter_cursor = 0;
3126 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* p)
3129 /* XXX should get patch name if we can */
3130 s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3131 << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3132 << _("Channel ") << ((int) p->patch()->channel() + 1);
3133 show_verbose_cursor (s.str(), 10, 20);
3138 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3140 trackview.editor().verbose_cursor()->hide ();
3141 /* focus will transfer back via the enter-notify event sent to this
3147 MidiRegionView::sysex_entered (ArdourCanvas::CanvasSysEx* p)
3151 show_verbose_cursor (s.str(), 10, 20);
3156 MidiRegionView::sysex_left (ArdourCanvas::CanvasSysEx *)
3158 trackview.editor().verbose_cursor()->hide ();
3159 /* focus will transfer back via the enter-notify event sent to this
3165 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3167 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3168 Editing::MouseMode mm = editor->current_mouse_mode();
3169 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3171 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3172 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3173 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3174 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3176 if (pre_enter_cursor && can_set_cursor) {
3177 editor->set_canvas_cursor (pre_enter_cursor);
3183 MidiRegionView::set_frame_color()
3187 TimeAxisViewItem::set_frame_color ();
3194 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3195 } else if (high_enough_for_name) {
3196 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3201 if (!rect_visible) {
3202 f = UINT_RGBA_CHANGE_A (f, 0);
3205 frame->property_fill_color_rgba() = f;
3209 MidiRegionView::midi_channel_mode_changed ()
3211 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3212 uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3213 ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3215 if (mode == ForceChannel) {
3216 mask = 0xFFFF; // Show all notes as active (below)
3219 // Update notes for selection
3220 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3221 (*i)->on_channel_selection_change (mask);
3224 _patch_changes.clear ();
3225 display_patch_changes ();
3229 MidiRegionView::instrument_settings_changed ()
3235 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3237 if (_selection.empty()) {
3241 PublicEditor& editor (trackview.editor());
3245 /* XXX what to do ? */
3249 editor.get_cut_buffer().add (selection_as_cut_buffer());
3257 start_note_diff_command();
3259 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3266 note_diff_remove_note (*i);
3276 MidiRegionView::selection_as_cut_buffer () const
3280 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3281 NoteType* n = (*i)->note().get();
3282 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3285 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3291 /** This method handles undo */
3293 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3299 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3301 trackview.session()->begin_reversible_command (_("paste"));
3303 start_note_diff_command (_("paste"));
3305 Evoral::MusicalTime beat_delta;
3306 Evoral::MusicalTime paste_pos_beats;
3307 Evoral::MusicalTime duration;
3308 Evoral::MusicalTime end_point = 0;
3310 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3311 paste_pos_beats = absolute_frames_to_source_beats (pos);
3312 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3313 paste_pos_beats = 0;
3315 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n",
3316 (*mcb.notes().begin())->time(),
3317 (*mcb.notes().rbegin())->end_time(),
3318 duration, pos, _region->position(),
3319 paste_pos_beats, beat_delta));
3323 for (int n = 0; n < (int) times; ++n) {
3325 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3327 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3328 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3330 /* make all newly added notes selected */
3332 note_diff_add_note (copied_note, true);
3333 end_point = copied_note->end_time();
3336 paste_pos_beats += duration;
3339 /* if we pasted past the current end of the region, extend the region */
3341 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3342 framepos_t region_end = _region->position() + _region->length() - 1;
3344 if (end_frame > region_end) {
3346 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3348 _region->clear_changes ();
3349 _region->set_length (end_frame - _region->position());
3350 trackview.session()->add_command (new StatefulDiffCommand (_region));
3355 trackview.session()->commit_reversible_command ();
3358 struct EventNoteTimeEarlyFirstComparator {
3359 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3360 return a->note()->time() < b->note()->time();
3365 MidiRegionView::time_sort_events ()
3367 if (!_sort_needed) {
3371 EventNoteTimeEarlyFirstComparator cmp;
3374 _sort_needed = false;
3378 MidiRegionView::goto_next_note (bool add_to_selection)
3380 bool use_next = false;
3382 if (_events.back()->selected()) {
3386 time_sort_events ();
3388 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3389 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3391 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3392 if ((*i)->selected()) {
3395 } else if (use_next) {
3396 if (channel_mask & (1 << (*i)->note()->channel())) {
3397 if (!add_to_selection) {
3400 note_selected (*i, true, false);
3407 /* use the first one */
3409 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3410 unique_select (_events.front());
3415 MidiRegionView::goto_previous_note (bool add_to_selection)
3417 bool use_next = false;
3419 if (_events.front()->selected()) {
3423 time_sort_events ();
3425 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3426 uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3428 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3429 if ((*i)->selected()) {
3432 } else if (use_next) {
3433 if (channel_mask & (1 << (*i)->note()->channel())) {
3434 if (!add_to_selection) {
3437 note_selected (*i, true, false);
3444 /* use the last one */
3446 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3447 unique_select (*(_events.rbegin()));
3452 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3454 bool had_selected = false;
3456 time_sort_events ();
3458 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3459 if ((*i)->selected()) {
3460 selected.insert ((*i)->note());
3461 had_selected = true;
3465 if (allow_all_if_none_selected && !had_selected) {
3466 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3467 selected.insert ((*i)->note());
3473 MidiRegionView::update_ghost_note (double x, double y)
3475 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3480 _note_group->w2i (x, y);
3482 PublicEditor& editor = trackview.editor ();
3484 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3485 framecnt_t grid_frames;
3486 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3488 /* use region_frames... because we are converting a delta within the region
3492 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3498 /* note that this sets the time of the ghost note in beats relative to
3499 the start of the source; that is how all note times are stored.
3501 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3502 _ghost_note->note()->set_length (length);
3503 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3504 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3506 /* the ghost note does not appear in ghost regions, so pass false in here */
3507 update_note (_ghost_note, false);
3509 show_verbose_cursor (_ghost_note->note ());
3513 MidiRegionView::create_ghost_note (double x, double y)
3515 remove_ghost_note ();
3517 boost::shared_ptr<NoteType> g (new NoteType);
3518 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3519 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3520 update_ghost_note (x, y);
3521 _ghost_note->show ();
3526 show_verbose_cursor (_ghost_note->note ());
3530 MidiRegionView::snap_changed ()
3536 create_ghost_note (_last_ghost_x, _last_ghost_y);
3540 MidiRegionView::drop_down_keys ()
3542 _mouse_state = None;
3546 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3548 double note = midi_stream_view()->y_to_note(y);
3550 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3552 uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3554 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3555 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3556 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3557 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3562 bool add_mrv_selection = false;
3564 if (_selection.empty()) {
3565 add_mrv_selection = true;
3568 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3569 if (_selection.insert (*i).second) {
3570 (*i)->set_selected (true);
3574 if (add_mrv_selection) {
3575 PublicEditor& editor (trackview.editor());
3576 editor.get_selection().add (this);
3581 MidiRegionView::color_handler ()
3583 RegionView::color_handler ();
3585 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3586 (*i)->set_selected ((*i)->selected()); // will change color
3589 /* XXX probably more to do here */
3593 MidiRegionView::enable_display (bool yn)
3595 RegionView::enable_display (yn);
3602 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3604 if (_step_edit_cursor == 0) {
3605 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3607 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3608 _step_edit_cursor->property_y1() = 0;
3609 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3610 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3611 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3614 move_step_edit_cursor (pos);
3615 _step_edit_cursor->show ();
3619 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3621 _step_edit_cursor_position = pos;
3623 if (_step_edit_cursor) {
3624 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3625 _step_edit_cursor->property_x1() = pixel;
3626 set_step_edit_cursor_width (_step_edit_cursor_width);
3631 MidiRegionView::hide_step_edit_cursor ()
3633 if (_step_edit_cursor) {
3634 _step_edit_cursor->hide ();
3639 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3641 _step_edit_cursor_width = beats;
3643 if (_step_edit_cursor) {
3644 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3648 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3649 * @param w Source that the data will end up in.
3652 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3654 if (!_active_notes) {
3655 /* we aren't actively being recorded to */
3659 boost::shared_ptr<MidiSource> src = w.lock ();
3660 if (!src || src != midi_region()->midi_source()) {
3661 /* recorded data was not destined for our source */
3665 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3667 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3669 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3671 framepos_t back = max_framepos;
3673 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3674 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3676 if (ev.is_channel_event()) {
3677 if (get_channel_mode() == FilterChannels) {
3678 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
3684 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3685 frames from the start of the source, and so time_beats is in terms of the
3689 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3691 if (ev.type() == MIDI_CMD_NOTE_ON) {
3692 boost::shared_ptr<NoteType> note (
3693 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity()));
3695 add_note (note, true);
3697 /* fix up our note range */
3698 if (ev.note() < _current_range_min) {
3699 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3700 } else if (ev.note() > _current_range_max) {
3701 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3704 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3705 resolve_note (ev.note (), time_beats);
3711 midi_stream_view()->check_record_layers (region(), back);
3715 MidiRegionView::trim_front_starting ()
3717 /* Reparent the note group to the region view's parent, so that it doesn't change
3718 when the region view is trimmed.
3720 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3721 _temporary_note_group->move (group->property_x(), group->property_y());
3722 _note_group->reparent (*_temporary_note_group);
3726 MidiRegionView::trim_front_ending ()
3728 _note_group->reparent (*group);
3729 delete _temporary_note_group;
3730 _temporary_note_group = 0;
3732 if (_region->start() < 0) {
3733 /* Trim drag made start time -ve; fix this */
3734 midi_region()->fix_negative_start ();
3739 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3741 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
3743 d.set_position (Gtk::WIN_POS_MOUSE);
3745 int response = d.run();
3748 case Gtk::RESPONSE_ACCEPT:
3750 case Gtk::RESPONSE_REJECT:
3751 delete_patch_change (pc);
3757 change_patch_change (pc->patch(), d.patch ());
3761 MidiRegionView::delete_sysex (CanvasSysEx* sysex)
3763 MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3764 c->remove (sysex->sysex());
3765 _model->apply_command (*trackview.session(), c);
3772 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3774 using namespace MIDI::Name;
3778 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3780 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
3782 MIDI::Name::PatchPrimaryKey patch_key;
3783 get_patch_key_at(n->time(), n->channel(), patch_key);
3784 name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
3786 patch_key.bank_number,
3787 patch_key.program_number,
3793 snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
3795 name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
3796 (int) n->channel() + 1,
3797 (int) n->velocity());
3799 show_verbose_cursor(buf, 10, 20);
3803 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3807 trackview.editor().get_pointer_position (wx, wy);
3812 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3814 double x1, y1, x2, y2;
3815 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3817 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3818 wy -= (y2 - y1) + 2 * yoffset;
3821 trackview.editor().verbose_cursor()->set (text, wx, wy);
3822 trackview.editor().verbose_cursor()->show ();
3825 /** @param p A session framepos.
3826 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3827 * @return p snapped to the grid subdivision underneath it.
3830 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3832 PublicEditor& editor = trackview.editor ();
3835 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3841 grid_frames = region_beats_to_region_frames (grid_beats);
3843 /* Hack so that we always snap to the note that we are over, instead of snapping
3844 to the next one if we're more than halfway through the one we're over.
3846 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3847 p -= grid_frames / 2;
3850 return snap_frame_to_frame (p);
3853 /** Called when the selection has been cleared in any MidiRegionView.
3854 * @param rv MidiRegionView that the selection was cleared in.
3857 MidiRegionView::selection_cleared (MidiRegionView* rv)
3863 /* Clear our selection in sympathy; but don't signal the fact */
3864 clear_selection (false);
3868 MidiRegionView::note_button_release ()
3870 delete _note_player;
3875 MidiRegionView::get_channel_mode () const
3877 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
3878 return rtav->midi_track()->get_playback_channel_mode();
3882 MidiRegionView::get_selected_channels () const
3884 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
3885 return rtav->midi_track()->get_playback_channel_mask();