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.
27 #include "gtkmm2ext/gtk_ui.h"
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/midi_region.h"
35 #include "ardour/midi_source.h"
36 #include "ardour/midi_model.h"
37 #include "ardour/midi_patch_manager.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 "rgb_macros.h"
70 #include "selection.h"
71 #include "simpleline.h"
72 #include "streamview.h"
74 #include "patch_change_dialog.h"
75 #include "verbose_cursor.h"
79 using namespace ARDOUR;
81 using namespace Editing;
82 using namespace ArdourCanvas;
83 using Gtkmm2ext::Keyboard;
85 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
87 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
89 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
90 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
91 : RegionView (parent, tv, r, spu, basic_color)
92 , _last_channel_selection(0xFFFF)
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 , _last_channel_selection(0xFFFF)
130 , _current_range_min(0)
131 , _current_range_max(0)
133 , _note_group(new ArdourCanvas::Group(*parent))
134 , _note_diff_command (0)
136 , _step_edit_cursor (0)
137 , _step_edit_cursor_width (1.0)
138 , _step_edit_cursor_position (0.0)
139 , _channel_selection_scoped_note (0)
140 , _temporary_note_group (0)
143 , _sort_needed (true)
144 , _optimization_iterator (_events.end())
146 , _no_sound_notes (false)
149 , pre_enter_cursor (0)
150 , pre_press_cursor (0)
153 _note_group->raise_to_top();
154 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
156 connect_to_diskstream ();
158 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
162 MidiRegionView::parameter_changed (std::string const & p)
164 if (p == "diplay-first-midi-bank-as-zero") {
165 if (_enable_display) {
171 MidiRegionView::MidiRegionView (const MidiRegionView& other)
172 : sigc::trackable(other)
174 , _last_channel_selection(0xFFFF)
175 , _current_range_min(0)
176 , _current_range_max(0)
178 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
179 , _note_diff_command (0)
181 , _step_edit_cursor (0)
182 , _step_edit_cursor_width (1.0)
183 , _step_edit_cursor_position (0.0)
184 , _channel_selection_scoped_note (0)
185 , _temporary_note_group (0)
188 , _sort_needed (true)
189 , _optimization_iterator (_events.end())
191 , _no_sound_notes (false)
194 , pre_enter_cursor (0)
195 , pre_press_cursor (0)
201 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
202 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
207 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
208 : RegionView (other, boost::shared_ptr<Region> (region))
209 , _last_channel_selection(0xFFFF)
210 , _current_range_min(0)
211 , _current_range_max(0)
213 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
214 , _note_diff_command (0)
216 , _step_edit_cursor (0)
217 , _step_edit_cursor_width (1.0)
218 , _step_edit_cursor_position (0.0)
219 , _channel_selection_scoped_note (0)
220 , _temporary_note_group (0)
223 , _sort_needed (true)
224 , _optimization_iterator (_events.end())
226 , _no_sound_notes (false)
229 , pre_enter_cursor (0)
230 , pre_press_cursor (0)
236 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
237 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
243 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
245 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
247 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
248 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
252 midi_region()->midi_source(0)->load_model();
255 _model = midi_region()->midi_source(0)->model();
256 _enable_display = false;
258 RegionView::init (basic_color, false);
260 compute_colors (basic_color);
262 set_height (trackview.current_height());
265 region_sync_changed ();
266 region_resized (ARDOUR::bounds_change);
271 _enable_display = true;
274 display_model (_model);
278 reset_width_dependent_items (_pixel_width);
280 group->raise_to_top();
281 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
283 midi_view()->signal_channel_mode_changed().connect(
284 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
286 instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
287 boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
289 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
290 boost::bind (&MidiRegionView::snap_changed, this),
293 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
294 connect_to_diskstream ();
296 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
300 MidiRegionView::instrument_info () const
302 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
303 return route_ui->route()->instrument_info();
306 const boost::shared_ptr<ARDOUR::MidiRegion>
307 MidiRegionView::midi_region() const
309 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
313 MidiRegionView::connect_to_diskstream ()
315 midi_view()->midi_track()->DataRecorded.connect(
316 *this, invalidator(*this),
317 boost::bind (&MidiRegionView::data_recorded, this, _1),
322 MidiRegionView::canvas_event(GdkEvent* ev)
327 case GDK_ENTER_NOTIFY:
328 case GDK_LEAVE_NOTIFY:
329 _last_event_x = ev->crossing.x;
330 _last_event_y = ev->crossing.y;
332 case GDK_MOTION_NOTIFY:
333 _last_event_x = ev->motion.x;
334 _last_event_y = ev->motion.y;
340 if (ev->type == GDK_2BUTTON_PRESS) {
341 // cannot use double-click to exit internal mode if single-click is being used
342 MouseMode m = trackview.editor().current_mouse_mode();
344 if ((m != MouseObject || !Keyboard::modifier_state_contains (ev->button.state, Keyboard::insert_note_modifier())) && (m != MouseDraw)) {
345 return trackview.editor().toggle_internal_editing_from_double_click (ev);
349 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
350 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
351 (trackview.editor().current_mouse_mode() == MouseZoom)) {
352 // handle non-draw modes elsewhere
358 return scroll (&ev->scroll);
361 return key_press (&ev->key);
363 case GDK_KEY_RELEASE:
364 return key_release (&ev->key);
366 case GDK_BUTTON_PRESS:
367 return button_press (&ev->button);
369 case GDK_BUTTON_RELEASE:
370 r = button_release (&ev->button);
375 case GDK_ENTER_NOTIFY:
376 return enter_notify (&ev->crossing);
378 case GDK_LEAVE_NOTIFY:
379 return leave_notify (&ev->crossing);
381 case GDK_MOTION_NOTIFY:
382 return motion (&ev->motion);
392 MidiRegionView::remove_ghost_note ()
399 MidiRegionView::enter_notify (GdkEventCrossing* ev)
401 trackview.editor().MouseModeChanged.connect (
402 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
405 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
406 create_ghost_note (ev->x, ev->y);
409 if (!trackview.editor().internal_editing()) {
410 Keyboard::magic_widget_drop_focus();
412 Keyboard::magic_widget_grab_focus();
416 // if current operation is non-operational in a midi region, change the cursor to so indicate
417 if (trackview.editor().current_mouse_mode() == MouseGain) {
418 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
419 pre_enter_cursor = editor->get_canvas_cursor();
420 editor->set_canvas_cursor(editor->cursors()->timebar);
427 MidiRegionView::leave_notify (GdkEventCrossing*)
429 _mouse_mode_connection.disconnect ();
431 trackview.editor().verbose_cursor()->hide ();
432 remove_ghost_note ();
434 if (pre_enter_cursor) {
435 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
436 editor->set_canvas_cursor(pre_enter_cursor);
443 MidiRegionView::mouse_mode_changed ()
445 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
446 create_ghost_note (_last_event_x, _last_event_y);
448 remove_ghost_note ();
449 trackview.editor().verbose_cursor()->hide ();
452 if (!trackview.editor().internal_editing()) {
453 Keyboard::magic_widget_drop_focus();
455 Keyboard::magic_widget_grab_focus();
461 MidiRegionView::button_press (GdkEventButton* ev)
463 if (ev->button != 1) {
467 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
468 MouseMode m = editor->current_mouse_mode();
470 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
471 pre_press_cursor = editor->get_canvas_cursor ();
472 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
475 if (_mouse_state != SelectTouchDragging) {
477 _pressed_button = ev->button;
478 _mouse_state = Pressed;
483 _pressed_button = ev->button;
489 MidiRegionView::button_release (GdkEventButton* ev)
491 double event_x, event_y;
493 if (ev->button != 1) {
500 group->w2i(event_x, event_y);
501 group->ungrab(ev->time);
503 PublicEditor& editor = trackview.editor ();
505 if (pre_press_cursor) {
506 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
507 pre_press_cursor = 0;
510 switch (_mouse_state) {
511 case Pressed: // Clicked
513 switch (editor.current_mouse_mode()) {
515 /* no motion occured - simple click */
524 if (Keyboard::is_insert_note_event(ev)) {
526 double event_x, event_y;
530 group->w2i(event_x, event_y);
533 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
539 /* Shorten the length by 1 tick so that we can add a new note at the next
540 grid snap without it overlapping this one.
542 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
544 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
552 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
558 /* Shorten the length by 1 tick so that we can add a new note at the next
559 grid snap without it overlapping this one.
561 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
563 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
574 case SelectRectDragging:
576 editor.drags()->end_grab ((GdkEvent *) ev);
578 create_ghost_note (ev->x, ev->y);
590 MidiRegionView::motion (GdkEventMotion* ev)
592 PublicEditor& editor = trackview.editor ();
594 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
595 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
596 _mouse_state != AddDragging) {
598 create_ghost_note (ev->x, ev->y);
600 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
601 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
603 update_ghost_note (ev->x, ev->y);
605 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
607 remove_ghost_note ();
608 editor.verbose_cursor()->hide ();
610 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
612 update_ghost_note (ev->x, ev->y);
615 /* any motion immediately hides velocity text that may have been visible */
617 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
618 (*i)->hide_velocity ();
621 switch (_mouse_state) {
624 if (_pressed_button == 1) {
626 MouseMode m = editor.current_mouse_mode();
628 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
630 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
631 _mouse_state = AddDragging;
632 remove_ghost_note ();
633 editor.verbose_cursor()->hide ();
635 } else if (m == MouseObject) {
636 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
638 _mouse_state = SelectRectDragging;
640 } else if (m == MouseRange) {
641 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
642 _mouse_state = SelectVerticalDragging;
649 case SelectRectDragging:
650 case SelectVerticalDragging:
652 editor.drags()->motion_handler ((GdkEvent *) ev, false);
655 case SelectTouchDragging:
667 MidiRegionView::scroll (GdkEventScroll* ev)
669 if (_selection.empty()) {
673 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
674 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
675 it still works for zoom.
680 trackview.editor().verbose_cursor()->hide ();
682 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
683 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
685 if (ev->direction == GDK_SCROLL_UP) {
686 change_velocities (true, fine, false, together);
687 } else if (ev->direction == GDK_SCROLL_DOWN) {
688 change_velocities (false, fine, false, together);
690 /* left, right: we don't use them */
698 MidiRegionView::key_press (GdkEventKey* ev)
700 /* since GTK bindings are generally activated on press, and since
701 detectable auto-repeat is the name of the game and only sends
702 repeated presses, carry out key actions at key press, not release.
705 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
707 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
708 _mouse_state = SelectTouchDragging;
711 } else if (ev->keyval == GDK_Escape && unmodified) {
715 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
717 bool start = (ev->keyval == GDK_comma);
718 bool end = (ev->keyval == GDK_period);
719 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
720 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
722 change_note_lengths (fine, shorter, 0.0, start, end);
726 } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
728 if (_selection.empty()) {
735 } else if (ev->keyval == GDK_Tab) {
737 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
738 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
740 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
744 } else if (ev->keyval == GDK_ISO_Left_Tab) {
746 /* Shift-TAB generates ISO Left Tab, for some reason */
748 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
749 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
751 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
757 } else if (ev->keyval == GDK_Up) {
759 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
760 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
761 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
763 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
764 change_velocities (true, fine, allow_smush, together);
766 transpose (true, fine, allow_smush);
770 } else if (ev->keyval == GDK_Down) {
772 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
773 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
774 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
776 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
777 change_velocities (false, fine, allow_smush, together);
779 transpose (false, fine, allow_smush);
783 } else if (ev->keyval == GDK_Left && unmodified) {
788 } else if (ev->keyval == GDK_Right && unmodified) {
793 } else if (ev->keyval == GDK_c && unmodified) {
797 } else if (ev->keyval == GDK_v && unmodified) {
806 MidiRegionView::key_release (GdkEventKey* ev)
808 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
816 MidiRegionView::channel_edit ()
818 if (_selection.empty()) {
822 /* pick a note somewhat at random (since Selection is a set<>) to
823 * provide the "current" channel for the dialog.
826 uint8_t current_channel = (*_selection.begin())->note()->channel ();
827 MidiChannelDialog channel_dialog (current_channel);
828 int ret = channel_dialog.run ();
831 case Gtk::RESPONSE_OK:
837 uint8_t new_channel = channel_dialog.active_channel ();
839 start_note_diff_command (_("channel edit"));
841 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
842 Selection::iterator next = i;
844 change_note_channel (*i, new_channel);
852 MidiRegionView::velocity_edit ()
854 if (_selection.empty()) {
858 /* pick a note somewhat at random (since Selection is a set<>) to
859 * provide the "current" velocity for the dialog.
862 uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
863 MidiVelocityDialog velocity_dialog (current_velocity);
864 int ret = velocity_dialog.run ();
867 case Gtk::RESPONSE_OK:
873 uint8_t new_velocity = velocity_dialog.velocity ();
875 start_note_diff_command (_("velocity edit"));
877 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
878 Selection::iterator next = i;
880 change_note_velocity (*i, new_velocity, false);
888 MidiRegionView::show_list_editor ()
891 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
893 _list_editor->present ();
896 /** Add a note to the model, and the view, at a canvas (click) coordinate.
897 * \param t time in frames relative to the position of the region
898 * \param y vertical position in pixels
899 * \param length duration of the note in beats
900 * \param snap_t true to snap t to the grid, otherwise false.
903 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
905 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
906 MidiStreamView* const view = mtv->midi_view();
908 double note = view->y_to_note(y);
911 assert(note <= 127.0);
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);
920 assert (length != 0);
922 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
923 region_frames_to_region_beats(t + _region->start()),
925 (uint8_t)note, 0x40));
927 if (_model->contains (new_note)) {
931 view->update_note_range(new_note->note());
933 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
935 _model->apply_command(*trackview.session(), cmd);
937 play_midi_note (new_note);
941 MidiRegionView::clear_events (bool with_selection_signal)
943 clear_selection (with_selection_signal);
946 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
947 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
952 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
957 _patch_changes.clear();
959 _optimization_iterator = _events.end();
963 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
967 content_connection.disconnect ();
968 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
972 if (_enable_display) {
978 MidiRegionView::start_note_diff_command (string name)
980 if (!_note_diff_command) {
981 _note_diff_command = _model->new_note_diff_command (name);
986 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
988 if (_note_diff_command) {
989 _note_diff_command->add (note);
992 _marked_for_selection.insert(note);
995 _marked_for_velocity.insert(note);
1000 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
1002 if (_note_diff_command && ev->note()) {
1003 _note_diff_command->remove(ev->note());
1008 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1009 MidiModel::NoteDiffCommand::Property property,
1012 if (_note_diff_command) {
1013 _note_diff_command->change (ev->note(), property, val);
1018 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1019 MidiModel::NoteDiffCommand::Property property,
1020 Evoral::MusicalTime val)
1022 if (_note_diff_command) {
1023 _note_diff_command->change (ev->note(), property, val);
1028 MidiRegionView::apply_diff (bool as_subcommand)
1032 if (!_note_diff_command) {
1036 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1037 // Mark all selected notes for selection when model reloads
1038 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1039 _marked_for_selection.insert((*i)->note());
1043 if (as_subcommand) {
1044 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1046 _model->apply_command (*trackview.session(), _note_diff_command);
1049 _note_diff_command = 0;
1050 midi_view()->midi_track()->playlist_modified();
1052 if (add_or_remove) {
1053 _marked_for_selection.clear();
1056 _marked_for_velocity.clear();
1060 MidiRegionView::abort_command()
1062 delete _note_diff_command;
1063 _note_diff_command = 0;
1068 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1070 if (_optimization_iterator != _events.end()) {
1071 ++_optimization_iterator;
1074 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1075 return *_optimization_iterator;
1078 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1079 if ((*_optimization_iterator)->note() == note) {
1080 return *_optimization_iterator;
1088 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1090 MidiModel::Notes notes;
1091 _model->get_notes (notes, op, val, chan_mask);
1093 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1094 CanvasNoteEvent* cne = find_canvas_note (*n);
1102 MidiRegionView::redisplay_model()
1104 // Don't redisplay the model if we're currently recording and displaying that
1105 if (_active_notes) {
1113 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1114 (*i)->invalidate ();
1117 MidiModel::ReadLock lock(_model->read_lock());
1119 MidiModel::Notes& notes (_model->notes());
1120 _optimization_iterator = _events.begin();
1122 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1124 boost::shared_ptr<NoteType> note (*n);
1125 CanvasNoteEvent* cne;
1128 if (note_in_region_range (note, visible)) {
1130 if ((cne = find_canvas_note (note)) != 0) {
1137 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1139 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1151 add_note (note, visible);
1156 if ((cne = find_canvas_note (note)) != 0) {
1164 /* remove note items that are no longer valid */
1166 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1167 if (!(*i)->valid ()) {
1169 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1170 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1172 gr->remove_note (*i);
1177 i = _events.erase (i);
1184 _patch_changes.clear();
1188 display_patch_changes ();
1190 _marked_for_selection.clear ();
1191 _marked_for_velocity.clear ();
1193 /* we may have caused _events to contain things out of order (e.g. if a note
1194 moved earlier or later). we don't generally need them in time order, but
1195 make a note that a sort is required for those cases that require it.
1198 _sort_needed = true;
1202 MidiRegionView::display_patch_changes ()
1204 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1205 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1207 for (uint8_t i = 0; i < 16; ++i) {
1208 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1212 /** @param active_channel true to display patch changes fully, false to display
1213 * them `greyed-out' (as on an inactive channel)
1216 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1218 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1220 if ((*i)->channel() != channel) {
1224 string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1225 add_canvas_patch_change (*i, patch_name, active_channel);
1230 MidiRegionView::display_sysexes()
1232 bool have_periodic_system_messages = false;
1233 bool display_periodic_messages = true;
1235 if (!Config->get_never_display_periodic_midi()) {
1237 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1238 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1239 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1242 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1243 have_periodic_system_messages = true;
1249 if (have_periodic_system_messages) {
1250 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1252 /* get an approximate value for the number of samples per video frame */
1254 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1256 /* if we are zoomed out beyond than the cutoff (i.e. more
1257 * frames per pixel than frames per 4 video frames), don't
1258 * show periodic sysex messages.
1261 if (zoom > (video_frame*4)) {
1262 display_periodic_messages = false;
1266 display_periodic_messages = false;
1269 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1271 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1272 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1274 Evoral::MusicalTime time = (*i)->time();
1278 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1279 if (!display_periodic_messages) {
1287 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1288 str << int((*i)->buffer()[b]);
1289 if (b != (*i)->size() -1) {
1293 string text = str.str();
1295 const double x = trackview.editor().frame_to_pixel(source_beats_to_region_frames(time));
1297 double height = midi_stream_view()->contents_height();
1299 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1300 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0, (*i)));
1302 // Show unless message is beyond the region bounds
1303 if (time - _region->start() >= _region->length() || time < _region->start()) {
1309 _sys_exes.push_back(sysex);
1313 MidiRegionView::~MidiRegionView ()
1315 in_destructor = true;
1317 trackview.editor().verbose_cursor()->hide ();
1319 note_delete_connection.disconnect ();
1321 delete _list_editor;
1323 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1325 if (_active_notes) {
1329 _selection_cleared_connection.disconnect ();
1332 clear_events (false);
1335 delete _note_diff_command;
1336 delete _step_edit_cursor;
1337 delete _temporary_note_group;
1341 MidiRegionView::region_resized (const PropertyChange& what_changed)
1343 RegionView::region_resized(what_changed);
1345 if (what_changed.contains (ARDOUR::Properties::position)) {
1346 set_duration(_region->length(), 0);
1347 if (_enable_display) {
1354 MidiRegionView::reset_width_dependent_items (double pixel_width)
1356 RegionView::reset_width_dependent_items(pixel_width);
1357 assert(_pixel_width == pixel_width);
1359 if (_enable_display) {
1363 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1364 if ((*x)->width() >= _pixel_width) {
1371 move_step_edit_cursor (_step_edit_cursor_position);
1372 set_step_edit_cursor_width (_step_edit_cursor_width);
1376 MidiRegionView::set_height (double height)
1378 static const double FUDGE = 2.0;
1379 const double old_height = _height;
1380 RegionView::set_height(height);
1381 _height = height - FUDGE;
1383 apply_note_range(midi_stream_view()->lowest_note(),
1384 midi_stream_view()->highest_note(),
1385 height != old_height + FUDGE);
1388 name_pixbuf->raise_to_top();
1391 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1392 (*x)->set_height (midi_stream_view()->contents_height());
1395 if (_step_edit_cursor) {
1396 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1401 /** Apply the current note range from the stream view
1402 * by repositioning/hiding notes as necessary
1405 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1407 if (!_enable_display) {
1411 if (!force && _current_range_min == min && _current_range_max == max) {
1415 _current_range_min = min;
1416 _current_range_max = max;
1418 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1419 CanvasNoteEvent* event = *i;
1420 boost::shared_ptr<NoteType> note (event->note());
1422 if (note->note() < _current_range_min ||
1423 note->note() > _current_range_max) {
1429 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1431 const double y1 = midi_stream_view()->note_to_y(note->note());
1432 const double y2 = y1 + floor(midi_stream_view()->note_height());
1434 cnote->property_y1() = y1;
1435 cnote->property_y2() = y2;
1437 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1439 const double diamond_size = update_hit (chit);
1441 chit->set_height (diamond_size);
1447 MidiRegionView::add_ghost (TimeAxisView& tv)
1451 double unit_position = _region->position () / samples_per_unit;
1452 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1453 MidiGhostRegion* ghost;
1455 if (mtv && mtv->midi_view()) {
1456 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1457 to allow having midi notes on top of note lines and waveforms.
1459 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1461 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1464 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1465 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1466 ghost->add_note(note);
1470 ghost->set_height ();
1471 ghost->set_duration (_region->length() / samples_per_unit);
1472 ghosts.push_back (ghost);
1474 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1480 /** Begin tracking note state for successive calls to add_event
1483 MidiRegionView::begin_write()
1485 assert(!_active_notes);
1486 _active_notes = new CanvasNote*[128];
1487 for (unsigned i=0; i < 128; ++i) {
1488 _active_notes[i] = 0;
1493 /** Destroy note state for add_event
1496 MidiRegionView::end_write()
1498 delete[] _active_notes;
1500 _marked_for_selection.clear();
1501 _marked_for_velocity.clear();
1505 /** Resolve an active MIDI note (while recording).
1508 MidiRegionView::resolve_note(uint8_t note, double end_time)
1510 if (midi_view()->note_mode() != Sustained) {
1514 if (_active_notes && _active_notes[note]) {
1516 /* XXX is end_time really region-centric? I think so, because
1517 this is a new region that we're recording, so source zero is
1518 the same as region zero
1520 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1522 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1523 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1524 _active_notes[note] = 0;
1529 /** Extend active notes to rightmost edge of region (if length is changed)
1532 MidiRegionView::extend_active_notes()
1534 if (!_active_notes) {
1538 for (unsigned i=0; i < 128; ++i) {
1539 if (_active_notes[i]) {
1540 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1547 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1549 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1553 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1555 if (!route_ui || !route_ui->midi_track()) {
1559 NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1563 /* NotePlayer deletes itself */
1567 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1569 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1573 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1575 if (!route_ui || !route_ui->midi_track()) {
1579 delete _note_player;
1580 _note_player = new NotePlayer (route_ui->midi_track ());
1581 _note_player->add (note);
1582 _note_player->on ();
1586 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1588 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1592 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1594 if (!route_ui || !route_ui->midi_track()) {
1598 delete _note_player;
1599 _note_player = new NotePlayer (route_ui->midi_track());
1601 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1602 _note_player->add (*n);
1605 _note_player->on ();
1610 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1612 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1613 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1615 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1616 (note->note() <= midi_stream_view()->highest_note());
1621 /** Update a canvas note's size from its model note.
1622 * @param ev Canvas note to update.
1623 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1626 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1628 boost::shared_ptr<NoteType> note = ev->note();
1629 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1630 const double y1 = midi_stream_view()->note_to_y(note->note());
1632 ev->property_x1() = x;
1633 ev->property_y1() = y1;
1635 /* trim note display to not overlap the end of its region */
1637 if (note->length() > 0) {
1638 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1639 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1641 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1644 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1646 if (note->length() == 0) {
1647 if (_active_notes) {
1648 assert(note->note() < 128);
1649 // If this note is already active there's a stuck note,
1650 // finish the old note rectangle
1651 if (_active_notes[note->note()]) {
1652 CanvasNote* const old_rect = _active_notes[note->note()];
1653 boost::shared_ptr<NoteType> old_note = old_rect->note();
1654 old_rect->property_x2() = x;
1655 old_rect->property_outline_what() = (guint32) 0xF;
1657 _active_notes[note->note()] = ev;
1659 /* outline all but right edge */
1660 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1662 /* outline all edges */
1663 ev->property_outline_what() = (guint32) 0xF;
1666 if (update_ghost_regions) {
1667 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1668 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1670 gr->update_note (ev);
1677 MidiRegionView::update_hit (CanvasHit* ev)
1679 boost::shared_ptr<NoteType> note = ev->note();
1681 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1682 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1683 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1684 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1688 return diamond_size;
1691 /** Add a MIDI note to the view (with length).
1693 * If in sustained mode, notes with length 0 will be considered active
1694 * notes, and resolve_note should be called when the corresponding note off
1695 * event arrives, to properly display the note.
1698 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1700 CanvasNoteEvent* event = 0;
1702 assert(note->time() >= 0);
1703 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1705 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1707 if (midi_view()->note_mode() == Sustained) {
1709 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1711 update_note (ev_rect);
1715 MidiGhostRegion* gr;
1717 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1718 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1719 gr->add_note(ev_rect);
1723 } else if (midi_view()->note_mode() == Percussive) {
1725 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1727 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1729 update_hit (ev_diamond);
1738 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1739 note_selected(event, true);
1742 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1743 event->show_velocity();
1746 event->on_channel_selection_change(_last_channel_selection);
1747 _events.push_back(event);
1756 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1757 MidiStreamView* const view = mtv->midi_view();
1759 view->update_note_range (note->note());
1763 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1764 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1766 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1768 /* potentially extend region to hold new note */
1770 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1771 framepos_t region_end = _region->last_frame();
1773 if (end_frame > region_end) {
1774 _region->set_length (end_frame - _region->position());
1777 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1778 MidiStreamView* const view = mtv->midi_view();
1780 view->update_note_range(new_note->note());
1782 _marked_for_selection.clear ();
1785 start_note_diff_command (_("step add"));
1786 note_diff_add_note (new_note, true, false);
1789 // last_step_edit_note = new_note;
1793 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1795 change_note_lengths (false, false, beats, false, true);
1798 /** Add a new patch change flag to the canvas.
1799 * @param patch the patch change to add
1800 * @param the text to display in the flag
1801 * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1804 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel)
1806 assert (patch->time() >= 0);
1808 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1809 const double x = trackview.editor().frame_to_pixel (region_frames);
1811 double const height = midi_stream_view()->contents_height();
1813 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1814 new CanvasPatchChange(*this, *group,
1823 if (patch_change->width() < _pixel_width) {
1824 // Show unless patch change is beyond the region bounds
1825 if (region_frames < 0 || region_frames >= _region->length()) {
1826 patch_change->hide();
1828 patch_change->show();
1831 patch_change->hide ();
1834 _patch_changes.push_back (patch_change);
1837 MIDI::Name::PatchPrimaryKey
1838 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1840 return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1844 MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1846 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1847 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1851 if (i != _model->patch_changes().end()) {
1852 key.bank_number = (*i)->bank();
1853 key.program_number = (*i)->program ();
1855 key.bank_number = key.program_number = 0;
1858 assert (key.is_sane());
1862 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1864 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1866 if (pc.patch()->program() != new_patch.program_number) {
1867 c->change_program (pc.patch (), new_patch.program_number);
1870 int const new_bank = new_patch.bank_number;
1871 if (pc.patch()->bank() != new_bank) {
1872 c->change_bank (pc.patch (), new_bank);
1875 _model->apply_command (*trackview.session(), c);
1877 _patch_changes.clear ();
1878 display_patch_changes ();
1882 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1884 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1886 if (old_change->time() != new_change.time()) {
1887 c->change_time (old_change, new_change.time());
1890 if (old_change->channel() != new_change.channel()) {
1891 c->change_channel (old_change, new_change.channel());
1894 if (old_change->program() != new_change.program()) {
1895 c->change_program (old_change, new_change.program());
1898 if (old_change->bank() != new_change.bank()) {
1899 c->change_bank (old_change, new_change.bank());
1902 _model->apply_command (*trackview.session(), c);
1904 _patch_changes.clear ();
1905 display_patch_changes ();
1908 /** Add a patch change to the region.
1909 * @param t Time in frames relative to region position
1910 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1911 * MidiTimeAxisView::get_channel_for_add())
1914 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1916 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1918 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1919 c->add (MidiModel::PatchChangePtr (
1920 new Evoral::PatchChange<Evoral::MusicalTime> (
1921 absolute_frames_to_source_beats (_region->position() + t),
1922 mtv->get_channel_for_add(), patch.program(), patch.bank()
1927 _model->apply_command (*trackview.session(), c);
1929 _patch_changes.clear ();
1930 display_patch_changes ();
1934 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1936 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1937 c->change_time (pc.patch (), t);
1938 _model->apply_command (*trackview.session(), c);
1940 _patch_changes.clear ();
1941 display_patch_changes ();
1945 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1947 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1948 c->remove (pc->patch ());
1949 _model->apply_command (*trackview.session(), c);
1951 _patch_changes.clear ();
1952 display_patch_changes ();
1956 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1958 if (patch.patch()->program() < 127) {
1959 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1960 key.program_number++;
1961 change_patch_change (patch, key);
1966 MidiRegionView::next_patch (CanvasPatchChange& patch)
1968 if (patch.patch()->program() > 0) {
1969 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1970 key.program_number--;
1971 change_patch_change (patch, key);
1976 MidiRegionView::next_bank (CanvasPatchChange& patch)
1978 if (patch.patch()->program() < 127) {
1979 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1980 if (key.bank_number > 0) {
1982 change_patch_change (patch, key);
1988 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1990 if (patch.patch()->program() > 0) {
1991 MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
1992 if (key.bank_number < 127) {
1994 change_patch_change (patch, key);
2000 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
2002 if (_selection.empty()) {
2006 _selection.erase (cne);
2010 MidiRegionView::delete_selection()
2012 if (_selection.empty()) {
2016 start_note_diff_command (_("delete selection"));
2018 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2019 if ((*i)->selected()) {
2020 _note_diff_command->remove((*i)->note());
2030 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2032 start_note_diff_command (_("delete note"));
2033 _note_diff_command->remove (n);
2036 trackview.editor().verbose_cursor()->hide ();
2040 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2042 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2044 Selection::iterator tmp = i;
2047 (*i)->set_selected (false);
2048 (*i)->hide_velocity ();
2049 _selection.erase (i);
2057 /* this does not change the status of this regionview w.r.t the editor
2062 SelectionCleared (this); /* EMIT SIGNAL */
2067 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2069 clear_selection_except (ev);
2071 /* don't bother with checking to see if we should remove this
2072 regionview from the editor selection, since we're about to add
2073 another note, and thus put/keep this regionview in the editor
2077 if (!ev->selected()) {
2078 add_to_selection (ev);
2083 MidiRegionView::select_all_notes ()
2087 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2088 add_to_selection (*i);
2093 MidiRegionView::select_range (framepos_t start, framepos_t end)
2097 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2098 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2099 if (t >= start && t <= end) {
2100 add_to_selection (*i);
2106 MidiRegionView::invert_selection ()
2108 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2109 if ((*i)->selected()) {
2110 remove_from_selection(*i);
2112 add_to_selection (*i);
2118 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2120 uint8_t low_note = 127;
2121 uint8_t high_note = 0;
2122 MidiModel::Notes& notes (_model->notes());
2123 _optimization_iterator = _events.begin();
2129 if (extend && _selection.empty()) {
2135 /* scan existing selection to get note range */
2137 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2138 if ((*i)->note()->note() < low_note) {
2139 low_note = (*i)->note()->note();
2141 if ((*i)->note()->note() > high_note) {
2142 high_note = (*i)->note()->note();
2146 low_note = min (low_note, notenum);
2147 high_note = max (high_note, notenum);
2150 _no_sound_notes = true;
2152 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2154 boost::shared_ptr<NoteType> note (*n);
2155 CanvasNoteEvent* cne;
2156 bool select = false;
2158 if (((1 << note->channel()) & channel_mask) != 0) {
2160 if ((note->note() >= low_note && note->note() <= high_note)) {
2163 } else if (note->note() == notenum) {
2169 if ((cne = find_canvas_note (note)) != 0) {
2170 // extend is false because we've taken care of it,
2171 // since it extends by time range, not pitch.
2172 note_selected (cne, add, false);
2176 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2180 _no_sound_notes = false;
2184 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2186 MidiModel::Notes& notes (_model->notes());
2187 _optimization_iterator = _events.begin();
2189 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2191 boost::shared_ptr<NoteType> note (*n);
2192 CanvasNoteEvent* cne;
2194 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2195 if ((cne = find_canvas_note (note)) != 0) {
2196 if (cne->selected()) {
2197 note_deselected (cne);
2199 note_selected (cne, true, false);
2207 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2210 clear_selection_except (ev);
2211 if (!_selection.empty()) {
2212 PublicEditor& editor (trackview.editor());
2213 editor.get_selection().add (this);
2219 if (!ev->selected()) {
2220 add_to_selection (ev);
2224 /* find end of latest note selected, select all between that and the start of "ev" */
2226 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2227 Evoral::MusicalTime latest = 0;
2229 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2230 if ((*i)->note()->end_time() > latest) {
2231 latest = (*i)->note()->end_time();
2233 if ((*i)->note()->time() < earliest) {
2234 earliest = (*i)->note()->time();
2238 if (ev->note()->end_time() > latest) {
2239 latest = ev->note()->end_time();
2242 if (ev->note()->time() < earliest) {
2243 earliest = ev->note()->time();
2246 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2248 /* find notes entirely within OR spanning the earliest..latest range */
2250 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2251 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2252 add_to_selection (*i);
2260 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2262 remove_from_selection (ev);
2266 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2276 // TODO: Make this faster by storing the last updated selection rect, and only
2277 // adjusting things that are in the area that appears/disappeared.
2278 // We probably need a tree to be able to find events in O(log(n)) time.
2280 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2282 /* check if any corner of the note is inside the rect
2285 1) this is computing "touched by", not "contained by" the rect.
2286 2) this does not require that events be sorted in time.
2289 const double ix1 = (*i)->x1();
2290 const double ix2 = (*i)->x2();
2291 const double iy1 = (*i)->y1();
2292 const double iy2 = (*i)->y2();
2294 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2295 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2296 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2297 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2300 if (!(*i)->selected()) {
2301 add_to_selection (*i);
2303 } else if ((*i)->selected() && !extend) {
2304 // Not inside rectangle
2305 remove_from_selection (*i);
2311 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2317 // TODO: Make this faster by storing the last updated selection rect, and only
2318 // adjusting things that are in the area that appears/disappeared.
2319 // We probably need a tree to be able to find events in O(log(n)) time.
2321 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2323 /* check if any corner of the note is inside the rect
2326 1) this is computing "touched by", not "contained by" the rect.
2327 2) this does not require that events be sorted in time.
2330 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2331 // within y- (note-) range
2332 if (!(*i)->selected()) {
2333 add_to_selection (*i);
2335 } else if ((*i)->selected() && !extend) {
2336 // Not inside rectangle
2337 remove_from_selection (*i);
2343 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2345 Selection::iterator i = _selection.find (ev);
2347 if (i != _selection.end()) {
2348 _selection.erase (i);
2351 ev->set_selected (false);
2352 ev->hide_velocity ();
2354 if (_selection.empty()) {
2355 PublicEditor& editor (trackview.editor());
2356 editor.get_selection().remove (this);
2361 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2363 bool add_mrv_selection = false;
2365 if (_selection.empty()) {
2366 add_mrv_selection = true;
2369 if (_selection.insert (ev).second) {
2370 ev->set_selected (true);
2371 start_playing_midi_note ((ev)->note());
2374 if (add_mrv_selection) {
2375 PublicEditor& editor (trackview.editor());
2376 editor.get_selection().add (this);
2381 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2383 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2384 PossibleChord to_play;
2385 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2387 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2388 if ((*i)->note()->time() < earliest) {
2389 earliest = (*i)->note()->time();
2393 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2394 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2395 to_play.push_back ((*i)->note());
2397 (*i)->move_event(dx, dy);
2400 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2402 if (to_play.size() > 1) {
2404 PossibleChord shifted;
2406 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2407 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2408 moved_note->set_note (moved_note->note() + cumulative_dy);
2409 shifted.push_back (moved_note);
2412 start_playing_midi_chord (shifted);
2414 } else if (!to_play.empty()) {
2416 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2417 moved_note->set_note (moved_note->note() + cumulative_dy);
2418 start_playing_midi_note (moved_note);
2424 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2426 assert (!_selection.empty());
2428 uint8_t lowest_note_in_selection = 127;
2429 uint8_t highest_note_in_selection = 0;
2430 uint8_t highest_note_difference = 0;
2432 // find highest and lowest notes first
2434 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2435 uint8_t pitch = (*i)->note()->note();
2436 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2437 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2441 cerr << "dnote: " << (int) dnote << endl;
2442 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2443 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2444 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2445 << int(highest_note_in_selection) << endl;
2446 cerr << "selection size: " << _selection.size() << endl;
2447 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2450 // Make sure the note pitch does not exceed the MIDI standard range
2451 if (highest_note_in_selection + dnote > 127) {
2452 highest_note_difference = highest_note_in_selection - 127;
2455 start_note_diff_command (_("move notes"));
2457 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2459 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2460 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2466 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2468 uint8_t original_pitch = (*i)->note()->note();
2469 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2471 // keep notes in standard midi range
2472 clamp_to_0_127(new_pitch);
2474 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2475 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2477 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2482 // care about notes being moved beyond the upper/lower bounds on the canvas
2483 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2484 highest_note_in_selection > midi_stream_view()->highest_note()) {
2485 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2489 /** @param x Pixel relative to the region position.
2490 * @return Snapped frame relative to the region position.
2493 MidiRegionView::snap_pixel_to_frame(double x)
2495 PublicEditor& editor (trackview.editor());
2496 return snap_frame_to_frame (editor.pixel_to_frame (x));
2499 /** @param x Pixel relative to the region position.
2500 * @return Snapped pixel relative to the region position.
2503 MidiRegionView::snap_to_pixel(double x)
2505 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2509 MidiRegionView::get_position_pixels()
2511 framepos_t region_frame = get_position();
2512 return trackview.editor().frame_to_pixel(region_frame);
2516 MidiRegionView::get_end_position_pixels()
2518 framepos_t frame = get_position() + get_duration ();
2519 return trackview.editor().frame_to_pixel(frame);
2523 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2525 /* the time converter will return the frame corresponding to `beats'
2526 relative to the start of the source. The start of the source
2527 is an implied position given by region->position - region->start
2529 const framepos_t source_start = _region->position() - _region->start();
2530 return source_start + _source_relative_time_converter.to (beats);
2534 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2536 /* the `frames' argument needs to be converted into a frame count
2537 relative to the start of the source before being passed in to the
2540 const framepos_t source_start = _region->position() - _region->start();
2541 return _source_relative_time_converter.from (frames - source_start);
2545 MidiRegionView::region_beats_to_region_frames(double beats) const
2547 return _region_relative_time_converter.to(beats);
2551 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2553 return _region_relative_time_converter.from(frames);
2557 MidiRegionView::begin_resizing (bool /*at_front*/)
2559 _resize_data.clear();
2561 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2562 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2564 // only insert CanvasNotes into the map
2566 NoteResizeData *resize_data = new NoteResizeData();
2567 resize_data->canvas_note = note;
2569 // create a new SimpleRect from the note which will be the resize preview
2570 SimpleRect *resize_rect = new SimpleRect(
2571 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2573 // calculate the colors: get the color settings
2574 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2575 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2578 // make the resize preview notes more transparent and bright
2579 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2581 // calculate color based on note velocity
2582 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2583 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2587 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2588 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2590 resize_data->resize_rect = resize_rect;
2591 _resize_data.push_back(resize_data);
2596 /** Update resizing notes while user drags.
2597 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2598 * @param at_front which end of the note (true == note on, false == note off)
2599 * @param delta_x change in mouse position since the start of the drag
2600 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2601 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2602 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2603 * as the \a primary note.
2606 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2608 bool cursor_set = false;
2610 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2611 SimpleRect* resize_rect = (*i)->resize_rect;
2612 CanvasNote* canvas_note = (*i)->canvas_note;
2617 current_x = canvas_note->x1() + delta_x;
2619 current_x = primary->x1() + delta_x;
2623 current_x = canvas_note->x2() + delta_x;
2625 current_x = primary->x2() + delta_x;
2630 resize_rect->property_x1() = snap_to_pixel(current_x);
2631 resize_rect->property_x2() = canvas_note->x2();
2633 resize_rect->property_x2() = snap_to_pixel(current_x);
2634 resize_rect->property_x1() = canvas_note->x1();
2640 beats = snap_pixel_to_frame (current_x);
2641 beats = region_frames_to_region_beats (beats);
2646 if (beats < canvas_note->note()->end_time()) {
2647 len = canvas_note->note()->time() - beats;
2648 len += canvas_note->note()->length();
2653 if (beats >= canvas_note->note()->time()) {
2654 len = beats - canvas_note->note()->time();
2661 snprintf (buf, sizeof (buf), "%.3g beats", len);
2662 show_verbose_cursor (buf, 0, 0);
2671 /** Finish resizing notes when the user releases the mouse button.
2672 * Parameters the same as for \a update_resizing().
2675 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2677 start_note_diff_command (_("resize notes"));
2679 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2680 CanvasNote* canvas_note = (*i)->canvas_note;
2681 SimpleRect* resize_rect = (*i)->resize_rect;
2683 /* Get the new x position for this resize, which is in pixels relative
2684 * to the region position.
2691 current_x = canvas_note->x1() + delta_x;
2693 current_x = primary->x1() + delta_x;
2697 current_x = canvas_note->x2() + delta_x;
2699 current_x = primary->x2() + delta_x;
2703 /* Convert that to a frame within the source */
2704 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2706 /* and then to beats */
2707 current_x = region_frames_to_region_beats (current_x);
2709 if (at_front && current_x < canvas_note->note()->end_time()) {
2710 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2712 double len = canvas_note->note()->time() - current_x;
2713 len += canvas_note->note()->length();
2716 /* XXX convert to beats */
2717 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2722 double len = current_x - canvas_note->note()->time();
2725 /* XXX convert to beats */
2726 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2734 _resize_data.clear();
2739 MidiRegionView::abort_resizing ()
2741 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2742 delete (*i)->resize_rect;
2746 _resize_data.clear ();
2750 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2752 uint8_t new_velocity;
2755 new_velocity = event->note()->velocity() + velocity;
2756 clamp_to_0_127(new_velocity);
2758 new_velocity = velocity;
2761 event->set_selected (event->selected()); // change color
2763 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2767 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2772 new_note = event->note()->note() + note;
2777 clamp_to_0_127 (new_note);
2778 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2782 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2784 bool change_start = false;
2785 bool change_length = false;
2786 Evoral::MusicalTime new_start = 0;
2787 Evoral::MusicalTime new_length = 0;
2789 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2791 front_delta: if positive - move the start of the note later in time (shortening it)
2792 if negative - move the start of the note earlier in time (lengthening it)
2794 end_delta: if positive - move the end of the note later in time (lengthening it)
2795 if negative - move the end of the note earlier in time (shortening it)
2799 if (front_delta < 0) {
2801 if (event->note()->time() < -front_delta) {
2804 new_start = event->note()->time() + front_delta; // moves earlier
2807 /* start moved toward zero, so move the end point out to where it used to be.
2808 Note that front_delta is negative, so this increases the length.
2811 new_length = event->note()->length() - front_delta;
2812 change_start = true;
2813 change_length = true;
2817 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2819 if (new_pos < event->note()->end_time()) {
2820 new_start = event->note()->time() + front_delta;
2821 /* start moved toward the end, so move the end point back to where it used to be */
2822 new_length = event->note()->length() - front_delta;
2823 change_start = true;
2824 change_length = true;
2831 bool can_change = true;
2832 if (end_delta < 0) {
2833 if (event->note()->length() < -end_delta) {
2839 new_length = event->note()->length() + end_delta;
2840 change_length = true;
2845 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2848 if (change_length) {
2849 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2854 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2856 uint8_t new_channel;
2860 if (event->note()->channel() < -chn) {
2863 new_channel = event->note()->channel() + chn;
2866 new_channel = event->note()->channel() + chn;
2869 new_channel = (uint8_t) chn;
2872 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2876 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2878 Evoral::MusicalTime new_time;
2882 if (event->note()->time() < -delta) {
2885 new_time = event->note()->time() + delta;
2888 new_time = event->note()->time() + delta;
2894 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2898 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2900 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2904 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2909 if (_selection.empty()) {
2924 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2925 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2931 start_note_diff_command (_("change velocities"));
2933 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2934 Selection::iterator next = i;
2938 if (i == _selection.begin()) {
2939 change_note_velocity (*i, delta, true);
2940 value = (*i)->note()->velocity() + delta;
2942 change_note_velocity (*i, value, false);
2946 change_note_velocity (*i, delta, true);
2954 if (!_selection.empty()) {
2956 snprintf (buf, sizeof (buf), "Vel %d",
2957 (int) (*_selection.begin())->note()->velocity());
2958 show_verbose_cursor (buf, 10, 10);
2964 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2966 if (_selection.empty()) {
2983 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2985 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2989 if ((int8_t) (*i)->note()->note() + delta > 127) {
2996 start_note_diff_command (_("transpose"));
2998 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2999 Selection::iterator next = i;
3001 change_note_note (*i, delta, true);
3009 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
3015 /* grab the current grid distance */
3017 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
3019 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
3020 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
3030 start_note_diff_command (_("change note lengths"));
3032 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3033 Selection::iterator next = i;
3036 /* note the negation of the delta for start */
3038 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
3047 MidiRegionView::nudge_notes (bool forward)
3049 if (_selection.empty()) {
3053 /* pick a note as the point along the timeline to get the nudge distance.
3054 its not necessarily the earliest note, so we may want to pull the notes out
3055 into a vector and sort before using the first one.
3058 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3060 framecnt_t distance;
3062 if (trackview.editor().snap_mode() == Editing::SnapOff) {
3064 /* grid is off - use nudge distance */
3066 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3072 framepos_t next_pos = ref_point;
3075 if (max_framepos - 1 < next_pos) {
3079 if (next_pos == 0) {
3085 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3086 distance = ref_point - next_pos;
3089 if (distance == 0) {
3093 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3099 start_note_diff_command (_("nudge"));
3101 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3102 Selection::iterator next = i;
3104 change_note_time (*i, delta, true);
3112 MidiRegionView::change_channel(uint8_t channel)
3114 start_note_diff_command(_("change channel"));
3115 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3116 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3124 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3126 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3128 pre_enter_cursor = editor->get_canvas_cursor ();
3130 if (_mouse_state == SelectTouchDragging) {
3131 note_selected (ev, true);
3134 show_verbose_cursor (ev->note ());
3138 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3140 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3142 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3143 (*i)->hide_velocity ();
3146 editor->verbose_cursor()->hide ();
3148 if (pre_enter_cursor) {
3149 editor->set_canvas_cursor (pre_enter_cursor);
3150 pre_enter_cursor = 0;
3155 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* p)
3158 /* XXX should get patch name if we can */
3159 s << _("Bank:") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3160 << _("Program:") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
3161 << _("Channel:") << ((int) p->patch()->channel() + 1);
3162 show_verbose_cursor (s.str(), 10, 20);
3167 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3169 trackview.editor().verbose_cursor()->hide ();
3170 /* focus will transfer back via the enter-notify event sent to this
3176 MidiRegionView::sysex_entered (ArdourCanvas::CanvasSysEx* p)
3180 show_verbose_cursor (s.str(), 10, 20);
3185 MidiRegionView::sysex_left (ArdourCanvas::CanvasSysEx *)
3187 trackview.editor().verbose_cursor()->hide ();
3188 /* focus will transfer back via the enter-notify event sent to this
3194 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3196 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3197 Editing::MouseMode mm = editor->current_mouse_mode();
3198 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3200 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3201 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3202 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3203 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3205 if (pre_enter_cursor && can_set_cursor) {
3206 editor->set_canvas_cursor (pre_enter_cursor);
3212 MidiRegionView::set_frame_color()
3216 TimeAxisViewItem::set_frame_color ();
3223 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3224 } else if (high_enough_for_name) {
3225 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3230 if (!rect_visible) {
3231 f = UINT_RGBA_CHANGE_A (f, 0);
3234 frame->property_fill_color_rgba() = f;
3238 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3240 if (mode == ForceChannel) {
3241 mask = 0xFFFF; // Show all notes as active (below)
3244 // Update notes for selection
3245 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3246 (*i)->on_channel_selection_change(mask);
3249 _last_channel_selection = mask;
3251 _patch_changes.clear ();
3252 display_patch_changes ();
3256 MidiRegionView::instrument_settings_changed ()
3262 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3264 if (_selection.empty()) {
3268 PublicEditor& editor (trackview.editor());
3272 /* XXX what to do ? */
3276 editor.get_cut_buffer().add (selection_as_cut_buffer());
3284 start_note_diff_command();
3286 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3293 note_diff_remove_note (*i);
3303 MidiRegionView::selection_as_cut_buffer () const
3307 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3308 NoteType* n = (*i)->note().get();
3309 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3312 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3318 /** This method handles undo */
3320 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3326 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3328 trackview.session()->begin_reversible_command (_("paste"));
3330 start_note_diff_command (_("paste"));
3332 Evoral::MusicalTime beat_delta;
3333 Evoral::MusicalTime paste_pos_beats;
3334 Evoral::MusicalTime duration;
3335 Evoral::MusicalTime end_point = 0;
3337 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3338 paste_pos_beats = absolute_frames_to_source_beats (pos);
3339 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3340 paste_pos_beats = 0;
3342 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",
3343 (*mcb.notes().begin())->time(),
3344 (*mcb.notes().rbegin())->end_time(),
3345 duration, pos, _region->position(),
3346 paste_pos_beats, beat_delta));
3350 for (int n = 0; n < (int) times; ++n) {
3352 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3354 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3355 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3357 /* make all newly added notes selected */
3359 note_diff_add_note (copied_note, true);
3360 end_point = copied_note->end_time();
3363 paste_pos_beats += duration;
3366 /* if we pasted past the current end of the region, extend the region */
3368 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3369 framepos_t region_end = _region->position() + _region->length() - 1;
3371 if (end_frame > region_end) {
3373 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3375 _region->clear_changes ();
3376 _region->set_length (end_frame - _region->position());
3377 trackview.session()->add_command (new StatefulDiffCommand (_region));
3382 trackview.session()->commit_reversible_command ();
3385 struct EventNoteTimeEarlyFirstComparator {
3386 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3387 return a->note()->time() < b->note()->time();
3392 MidiRegionView::time_sort_events ()
3394 if (!_sort_needed) {
3398 EventNoteTimeEarlyFirstComparator cmp;
3401 _sort_needed = false;
3405 MidiRegionView::goto_next_note (bool add_to_selection)
3407 bool use_next = false;
3409 if (_events.back()->selected()) {
3413 time_sort_events ();
3415 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3416 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3418 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3419 if ((*i)->selected()) {
3422 } else if (use_next) {
3423 if (channel_mask & (1 << (*i)->note()->channel())) {
3424 if (!add_to_selection) {
3427 note_selected (*i, true, false);
3434 /* use the first one */
3436 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3437 unique_select (_events.front());
3442 MidiRegionView::goto_previous_note (bool add_to_selection)
3444 bool use_next = false;
3446 if (_events.front()->selected()) {
3450 time_sort_events ();
3452 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3453 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3455 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3456 if ((*i)->selected()) {
3459 } else if (use_next) {
3460 if (channel_mask & (1 << (*i)->note()->channel())) {
3461 if (!add_to_selection) {
3464 note_selected (*i, true, false);
3471 /* use the last one */
3473 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3474 unique_select (*(_events.rbegin()));
3479 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3481 bool had_selected = false;
3483 time_sort_events ();
3485 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3486 if ((*i)->selected()) {
3487 selected.insert ((*i)->note());
3488 had_selected = true;
3492 if (allow_all_if_none_selected && !had_selected) {
3493 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3494 selected.insert ((*i)->note());
3500 MidiRegionView::update_ghost_note (double x, double y)
3502 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3507 _note_group->w2i (x, y);
3509 PublicEditor& editor = trackview.editor ();
3511 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3512 framecnt_t grid_frames;
3513 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3515 /* use region_frames... because we are converting a delta within the region
3519 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3525 /* note that this sets the time of the ghost note in beats relative to
3526 the start of the source; that is how all note times are stored.
3528 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3529 _ghost_note->note()->set_length (length);
3530 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3531 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3533 /* the ghost note does not appear in ghost regions, so pass false in here */
3534 update_note (_ghost_note, false);
3536 show_verbose_cursor (_ghost_note->note ());
3540 MidiRegionView::create_ghost_note (double x, double y)
3542 remove_ghost_note ();
3544 boost::shared_ptr<NoteType> g (new NoteType);
3545 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3546 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3547 update_ghost_note (x, y);
3548 _ghost_note->show ();
3553 show_verbose_cursor (_ghost_note->note ());
3557 MidiRegionView::snap_changed ()
3563 create_ghost_note (_last_ghost_x, _last_ghost_y);
3567 MidiRegionView::drop_down_keys ()
3569 _mouse_state = None;
3573 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3575 double note = midi_stream_view()->y_to_note(y);
3577 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3579 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3581 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3582 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3583 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3584 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3589 bool add_mrv_selection = false;
3591 if (_selection.empty()) {
3592 add_mrv_selection = true;
3595 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3596 if (_selection.insert (*i).second) {
3597 (*i)->set_selected (true);
3601 if (add_mrv_selection) {
3602 PublicEditor& editor (trackview.editor());
3603 editor.get_selection().add (this);
3608 MidiRegionView::color_handler ()
3610 RegionView::color_handler ();
3612 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3613 (*i)->set_selected ((*i)->selected()); // will change color
3616 /* XXX probably more to do here */
3620 MidiRegionView::enable_display (bool yn)
3622 RegionView::enable_display (yn);
3629 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3631 if (_step_edit_cursor == 0) {
3632 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3634 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3635 _step_edit_cursor->property_y1() = 0;
3636 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3637 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3638 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3641 move_step_edit_cursor (pos);
3642 _step_edit_cursor->show ();
3646 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3648 _step_edit_cursor_position = pos;
3650 if (_step_edit_cursor) {
3651 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3652 _step_edit_cursor->property_x1() = pixel;
3653 set_step_edit_cursor_width (_step_edit_cursor_width);
3658 MidiRegionView::hide_step_edit_cursor ()
3660 if (_step_edit_cursor) {
3661 _step_edit_cursor->hide ();
3666 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3668 _step_edit_cursor_width = beats;
3670 if (_step_edit_cursor) {
3671 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3675 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3676 * @param w Source that the data will end up in.
3679 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3681 if (!_active_notes) {
3682 /* we aren't actively being recorded to */
3686 boost::shared_ptr<MidiSource> src = w.lock ();
3687 if (!src || src != midi_region()->midi_source()) {
3688 /* recorded data was not destined for our source */
3692 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3694 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3696 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3698 framepos_t back = max_framepos;
3700 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3701 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3702 assert (ev.buffer ());
3704 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3705 frames from the start of the source, and so time_beats is in terms of the
3709 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3711 if (ev.type() == MIDI_CMD_NOTE_ON) {
3713 boost::shared_ptr<NoteType> note (
3714 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3717 add_note (note, true);
3719 /* fix up our note range */
3720 if (ev.note() < _current_range_min) {
3721 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3722 } else if (ev.note() > _current_range_max) {
3723 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3726 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3727 resolve_note (ev.note (), time_beats);
3733 midi_stream_view()->check_record_layers (region(), back);
3737 MidiRegionView::trim_front_starting ()
3739 /* Reparent the note group to the region view's parent, so that it doesn't change
3740 when the region view is trimmed.
3742 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3743 _temporary_note_group->move (group->property_x(), group->property_y());
3744 _note_group->reparent (*_temporary_note_group);
3748 MidiRegionView::trim_front_ending ()
3750 _note_group->reparent (*group);
3751 delete _temporary_note_group;
3752 _temporary_note_group = 0;
3754 if (_region->start() < 0) {
3755 /* Trim drag made start time -ve; fix this */
3756 midi_region()->fix_negative_start ();
3761 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3763 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY);
3765 d.set_position (Gtk::WIN_POS_MOUSE);
3767 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3771 change_patch_change (pc->patch(), d.patch ());
3775 MidiRegionView::delete_sysex (CanvasSysEx* sysex)
3777 MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
3778 c->remove (sysex->sysex());
3779 _model->apply_command (*trackview.session(), c);
3786 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3789 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3790 Evoral::midi_note_name (n->note()).c_str(),
3792 (int) n->channel() + 1,
3793 (int) n->velocity());
3795 show_verbose_cursor (buf, 10, 20);
3799 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3803 trackview.editor().get_pointer_position (wx, wy);
3808 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3810 double x1, y1, x2, y2;
3811 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3813 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3814 wy -= (y2 - y1) + 2 * yoffset;
3817 trackview.editor().verbose_cursor()->set (text, wx, wy);
3818 trackview.editor().verbose_cursor()->show ();
3821 /** @param p A session framepos.
3822 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3823 * @return p snapped to the grid subdivision underneath it.
3826 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3828 PublicEditor& editor = trackview.editor ();
3831 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3837 grid_frames = region_beats_to_region_frames (grid_beats);
3839 /* Hack so that we always snap to the note that we are over, instead of snapping
3840 to the next one if we're more than halfway through the one we're over.
3842 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3843 p -= grid_frames / 2;
3846 return snap_frame_to_frame (p);
3849 /** Called when the selection has been cleared in any MidiRegionView.
3850 * @param rv MidiRegionView that the selection was cleared in.
3853 MidiRegionView::selection_cleared (MidiRegionView* rv)
3859 /* Clear our selection in sympathy; but don't signal the fact */
3860 clear_selection (false);
3864 MidiRegionView::note_button_release ()
3866 delete _note_player;