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/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas_patch_change.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
57 #include "midi_channel_dialog.h"
58 #include "midi_cut_buffer.h"
59 #include "midi_list_editor.h"
60 #include "midi_region_view.h"
61 #include "midi_streamview.h"
62 #include "midi_time_axis.h"
63 #include "midi_util.h"
64 #include "note_player.h"
65 #include "public_editor.h"
66 #include "rgb_macros.h"
67 #include "selection.h"
68 #include "simpleline.h"
69 #include "streamview.h"
71 #include "mouse_cursors.h"
72 #include "patch_change_dialog.h"
73 #include "verbose_cursor.h"
77 using namespace ARDOUR;
79 using namespace Editing;
80 using namespace ArdourCanvas;
81 using Gtkmm2ext::Keyboard;
83 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
84 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
85 : RegionView (parent, tv, r, spu, basic_color)
87 , _last_channel_selection(0xFFFF)
88 , _current_range_min(0)
89 , _current_range_max(0)
90 , _model_name(string())
91 , _custom_device_mode(string())
93 , _note_group(new ArdourCanvas::Group(*group))
94 , _note_diff_command (0)
97 , _step_edit_cursor (0)
98 , _step_edit_cursor_width (1.0)
99 , _step_edit_cursor_position (0.0)
100 , _channel_selection_scoped_note (0)
101 , _temporary_note_group (0)
104 , _sort_needed (true)
105 , _optimization_iterator (_events.end())
107 , _no_sound_notes (false)
110 , _pre_enter_cursor (0)
112 _note_group->raise_to_top();
113 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
115 connect_to_diskstream ();
118 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
119 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
120 TimeAxisViewItem::Visibility visibility)
121 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
123 , _last_channel_selection(0xFFFF)
124 , _model_name(string())
125 , _custom_device_mode(string())
127 , _note_group(new ArdourCanvas::Group(*parent))
128 , _note_diff_command (0)
131 , _step_edit_cursor (0)
132 , _step_edit_cursor_width (1.0)
133 , _step_edit_cursor_position (0.0)
134 , _channel_selection_scoped_note (0)
135 , _temporary_note_group (0)
138 , _sort_needed (true)
139 , _optimization_iterator (_events.end())
141 , _no_sound_notes (false)
145 _note_group->raise_to_top();
146 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
148 connect_to_diskstream ();
151 MidiRegionView::MidiRegionView (const MidiRegionView& other)
152 : sigc::trackable(other)
155 , _last_channel_selection(0xFFFF)
156 , _model_name(string())
157 , _custom_device_mode(string())
159 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
160 , _note_diff_command (0)
163 , _step_edit_cursor (0)
164 , _step_edit_cursor_width (1.0)
165 , _step_edit_cursor_position (0.0)
166 , _channel_selection_scoped_note (0)
167 , _temporary_note_group (0)
170 , _sort_needed (true)
171 , _optimization_iterator (_events.end())
173 , _no_sound_notes (false)
180 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
181 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
186 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
187 : RegionView (other, boost::shared_ptr<Region> (region))
189 , _last_channel_selection(0xFFFF)
190 , _model_name(string())
191 , _custom_device_mode(string())
193 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
194 , _note_diff_command (0)
197 , _step_edit_cursor (0)
198 , _step_edit_cursor_width (1.0)
199 , _step_edit_cursor_position (0.0)
200 , _channel_selection_scoped_note (0)
201 , _temporary_note_group (0)
204 , _sort_needed (true)
205 , _optimization_iterator (_events.end())
207 , _no_sound_notes (false)
214 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
215 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
221 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
223 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
225 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
226 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
230 midi_region()->midi_source(0)->load_model();
233 _model = midi_region()->midi_source(0)->model();
234 _enable_display = false;
236 RegionView::init (basic_color, false);
238 compute_colors (basic_color);
240 set_height (trackview.current_height());
243 region_sync_changed ();
244 region_resized (ARDOUR::bounds_change);
247 reset_width_dependent_items (_pixel_width);
251 _enable_display = true;
254 display_model (_model);
258 group->raise_to_top();
259 group->signal_event().connect(
260 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
262 midi_view()->signal_channel_mode_changed().connect(
263 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
265 midi_view()->signal_midi_patch_settings_changed().connect(
266 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
268 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
269 ui_bind(&MidiRegionView::snap_changed, this),
272 connect_to_diskstream ();
276 MidiRegionView::connect_to_diskstream ()
278 midi_view()->midi_track()->DataRecorded.connect(
279 *this, invalidator(*this),
280 ui_bind(&MidiRegionView::data_recorded, this, _1, _2),
285 MidiRegionView::canvas_event(GdkEvent* ev)
288 case GDK_ENTER_NOTIFY:
289 case GDK_LEAVE_NOTIFY:
290 _last_event_x = ev->crossing.x;
291 _last_event_y = ev->crossing.y;
293 case GDK_MOTION_NOTIFY:
294 _last_event_x = ev->motion.x;
295 _last_event_y = ev->motion.y;
301 if (!trackview.editor().internal_editing()) {
307 return scroll (&ev->scroll);
310 return key_press (&ev->key);
312 case GDK_KEY_RELEASE:
313 return key_release (&ev->key);
315 case GDK_BUTTON_PRESS:
316 return button_press (&ev->button);
318 case GDK_2BUTTON_PRESS:
321 case GDK_BUTTON_RELEASE:
322 return button_release (&ev->button);
324 case GDK_ENTER_NOTIFY:
325 return enter_notify (&ev->crossing);
327 case GDK_LEAVE_NOTIFY:
328 return leave_notify (&ev->crossing);
330 case GDK_MOTION_NOTIFY:
331 return motion (&ev->motion);
341 MidiRegionView::remove_ghost_note ()
348 MidiRegionView::enter_notify (GdkEventCrossing* ev)
350 trackview.editor().MouseModeChanged.connect (
351 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
354 Keyboard::magic_widget_grab_focus();
357 if (trackview.editor().current_mouse_mode() == MouseRange) {
358 create_ghost_note (ev->x, ev->y);
365 MidiRegionView::leave_notify (GdkEventCrossing*)
367 _mouse_mode_connection.disconnect ();
369 trackview.editor().verbose_cursor()->hide ();
370 remove_ghost_note ();
375 MidiRegionView::mouse_mode_changed ()
377 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
378 create_ghost_note (_last_event_x, _last_event_y);
380 remove_ghost_note ();
381 trackview.editor().verbose_cursor()->hide ();
386 MidiRegionView::button_press (GdkEventButton* ev)
388 if (ev->button != 1) {
395 group->w2i (_last_x, _last_y);
397 if (_mouse_state != SelectTouchDragging) {
399 _pressed_button = ev->button;
400 _mouse_state = Pressed;
405 _pressed_button = ev->button;
411 MidiRegionView::button_release (GdkEventButton* ev)
413 double event_x, event_y;
415 if (ev->button != 1) {
422 group->w2i(event_x, event_y);
423 group->ungrab(ev->time);
425 switch (_mouse_state) {
426 case Pressed: // Clicked
428 switch (trackview.editor().current_mouse_mode()) {
434 if (Keyboard::is_insert_note_event(ev)) {
436 double event_x, event_y;
440 group->w2i(event_x, event_y);
443 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
449 create_note_at (event_x, event_y, beats, true);
457 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
463 create_note_at (event_x, event_y, beats, true);
474 case SelectRectDragging: // Select drag done
481 case AddDragging: // Add drag done
485 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) {
487 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
489 const double x = _drag_rect->property_x1();
490 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
492 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), true);
499 create_ghost_note (ev->x, ev->y);
509 MidiRegionView::motion (GdkEventMotion* ev)
511 double event_x, event_y;
512 framepos_t event_frame = 0;
516 group->w2i(event_x, event_y);
518 // convert event_x to global frame
519 event_frame = snap_pixel_to_frame (event_x);
521 if (!_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
522 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
523 && _mouse_state != AddDragging) {
525 create_ghost_note (ev->x, ev->y);
526 } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
527 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
529 update_ghost_note (ev->x, ev->y);
530 } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange) {
535 trackview.editor().verbose_cursor()->hide ();
536 } else if (_ghost_note && trackview.editor().current_mouse_mode() == MouseRange) {
537 update_ghost_note (ev->x, ev->y);
540 /* any motion immediately hides velocity text that may have been visible */
542 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
543 (*i)->hide_velocity ();
546 switch (_mouse_state) {
547 case Pressed: // Maybe start a drag, if we've moved a bit
549 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
550 /* no appreciable movement since the button was pressed */
554 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject
555 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
558 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
559 Gdk::Cursor(Gdk::FLEUR), ev->time);
563 _drag_start_x = event_x;
564 _drag_start_y = event_y;
566 _drag_rect = new ArdourCanvas::SimpleRect(*group);
567 _drag_rect->property_x1() = event_x;
568 _drag_rect->property_y1() = event_y;
569 _drag_rect->property_x2() = event_x;
570 _drag_rect->property_y2() = event_y;
571 _drag_rect->property_outline_what() = 0xFF;
572 _drag_rect->property_outline_color_rgba()
573 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
574 _drag_rect->property_fill_color_rgba()
575 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
577 _mouse_state = SelectRectDragging;
580 } else if (trackview.editor().internal_editing()) {
581 // Add note drag start
586 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
587 Gdk::Cursor(Gdk::FLEUR), ev->time);
591 _drag_start_x = event_x;
592 _drag_start_y = event_y;
594 _drag_rect = new ArdourCanvas::SimpleRect(*group);
595 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
597 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
598 midi_stream_view()->y_to_note(event_y));
599 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
600 _drag_rect->property_y2() = _drag_rect->property_y1()
601 + floor(midi_stream_view()->note_height());
602 _drag_rect->property_outline_what() = 0xFF;
603 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
604 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
606 _mouse_state = AddDragging;
613 trackview.editor().verbose_cursor()->hide ();
621 case SelectRectDragging: // Select drag motion
622 case AddDragging: // Add note drag motion
627 GdkModifierType state;
628 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
633 if (_mouse_state == AddDragging) {
634 event_x = trackview.editor().frame_to_pixel(event_frame);
639 if (event_x > _drag_start_x) {
640 _drag_rect->property_x2() = event_x;
643 _drag_rect->property_x1() = event_x;
647 if (_drag_rect && _mouse_state == SelectRectDragging) {
649 if (event_y > _drag_start_y) {
650 _drag_rect->property_y2() = event_y;
653 _drag_rect->property_y1() = event_y;
656 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
662 case SelectTouchDragging:
674 MidiRegionView::scroll (GdkEventScroll* ev)
676 if (_selection.empty()) {
680 trackview.editor().verbose_cursor()->hide ();
682 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
684 if (ev->direction == GDK_SCROLL_UP) {
685 change_velocities (true, fine, false);
686 } else if (ev->direction == GDK_SCROLL_DOWN) {
687 change_velocities (false, fine, false);
693 MidiRegionView::key_press (GdkEventKey* ev)
695 /* since GTK bindings are generally activated on press, and since
696 detectable auto-repeat is the name of the game and only sends
697 repeated presses, carry out key actions at key press, not release.
700 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
701 _mouse_state = SelectTouchDragging;
704 } else if (ev->keyval == GDK_Escape) {
708 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
710 bool start = (ev->keyval == GDK_comma);
711 bool end = (ev->keyval == GDK_period);
712 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
713 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
715 change_note_lengths (fine, shorter, 0.0, start, end);
719 } else if (ev->keyval == GDK_Delete) {
724 } else if (ev->keyval == GDK_Tab) {
726 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
727 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
729 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
733 } else if (ev->keyval == GDK_ISO_Left_Tab) {
735 /* Shift-TAB generates ISO Left Tab, for some reason */
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));
746 } else if (ev->keyval == GDK_Up) {
748 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
749 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
751 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
752 change_velocities (true, fine, allow_smush);
754 transpose (true, fine, allow_smush);
758 } else if (ev->keyval == GDK_Down) {
760 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
761 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
763 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
764 change_velocities (false, fine, allow_smush);
766 transpose (false, fine, allow_smush);
770 } else if (ev->keyval == GDK_Left) {
775 } else if (ev->keyval == GDK_Right) {
780 } else if (ev->keyval == GDK_Control_L) {
783 } else if (ev->keyval == GDK_c) {
792 MidiRegionView::key_release (GdkEventKey* ev)
794 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
802 MidiRegionView::channel_edit ()
805 uint8_t current_channel;
807 if (_selection.empty()) {
811 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
813 current_channel = (*i)->note()->channel ();
818 MidiChannelDialog channel_dialog (current_channel);
819 int ret = channel_dialog.run ();
822 case Gtk::RESPONSE_OK:
828 uint8_t new_channel = channel_dialog.active_channel ();
830 start_note_diff_command (_("channel edit"));
832 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
833 Selection::iterator next = i;
835 change_note_channel (*i, new_channel);
843 MidiRegionView::show_list_editor ()
846 _list_editor = new MidiListEditor (trackview.session(), midi_region());
848 _list_editor->present ();
851 /** Add a note to the model, and the view, at a canvas (click) coordinate.
852 * \param x horizontal position in pixels
853 * \param y vertical position in pixels
854 * \param length duration of the note in beats, which will be snapped to the grid
855 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
858 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
860 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
861 MidiStreamView* const view = mtv->midi_view();
863 double note = view->y_to_note(y);
866 assert(note <= 127.0);
868 // Start of note in frames relative to region start
869 framepos_t const start_frames = snap_pixel_to_frame (x);
870 assert(start_frames >= 0);
873 length = frames_to_beats(
874 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
876 assert (length != 0);
879 length = frames_to_beats (beats_to_frames (length) - 1);
882 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
883 frames_to_beats(start_frames + _region->start()), length,
884 (uint8_t)note, 0x40));
886 if (_model->contains (new_note)) {
890 view->update_note_range(new_note->note());
892 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
894 _model->apply_command(*trackview.session(), cmd);
896 play_midi_note (new_note);
900 MidiRegionView::clear_events()
905 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
906 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
911 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
916 _patch_changes.clear();
918 _optimization_iterator = _events.end();
922 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
926 content_connection.disconnect ();
927 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
931 if (_enable_display) {
937 MidiRegionView::start_note_diff_command (string name)
939 if (!_note_diff_command) {
940 _note_diff_command = _model->new_note_diff_command (name);
945 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
947 if (_note_diff_command) {
948 _note_diff_command->add (note);
951 _marked_for_selection.insert(note);
954 _marked_for_velocity.insert(note);
959 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
961 if (_note_diff_command && ev->note()) {
962 _note_diff_command->remove(ev->note());
967 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
968 MidiModel::NoteDiffCommand::Property property,
971 if (_note_diff_command) {
972 _note_diff_command->change (ev->note(), property, val);
977 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
978 MidiModel::NoteDiffCommand::Property property,
979 Evoral::MusicalTime val)
981 if (_note_diff_command) {
982 _note_diff_command->change (ev->note(), property, val);
987 MidiRegionView::apply_diff (bool as_subcommand)
991 if (!_note_diff_command) {
995 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
996 // Mark all selected notes for selection when model reloads
997 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
998 _marked_for_selection.insert((*i)->note());
1002 if (as_subcommand) {
1003 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1005 _model->apply_command (*trackview.session(), _note_diff_command);
1008 _note_diff_command = 0;
1009 midi_view()->midi_track()->playlist_modified();
1011 if (add_or_remove) {
1012 _marked_for_selection.clear();
1015 _marked_for_velocity.clear();
1019 MidiRegionView::abort_command()
1021 delete _note_diff_command;
1022 _note_diff_command = 0;
1027 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1029 if (_optimization_iterator != _events.end()) {
1030 ++_optimization_iterator;
1033 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1034 return *_optimization_iterator;
1037 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1038 if ((*_optimization_iterator)->note() == note) {
1039 return *_optimization_iterator;
1047 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1049 MidiModel::Notes notes;
1050 _model->get_notes (notes, op, val, chan_mask);
1052 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1053 CanvasNoteEvent* cne = find_canvas_note (*n);
1061 MidiRegionView::redisplay_model()
1063 // Don't redisplay the model if we're currently recording and displaying that
1064 if (_active_notes) {
1072 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1073 (*i)->invalidate ();
1076 MidiModel::ReadLock lock(_model->read_lock());
1078 MidiModel::Notes& notes (_model->notes());
1079 _optimization_iterator = _events.begin();
1081 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1083 boost::shared_ptr<NoteType> note (*n);
1084 CanvasNoteEvent* cne;
1087 if (note_in_region_range (note, visible)) {
1089 if ((cne = find_canvas_note (note)) != 0) {
1096 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1098 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1110 add_note (note, visible);
1115 if ((cne = find_canvas_note (note)) != 0) {
1123 /* remove note items that are no longer valid */
1125 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1126 if (!(*i)->valid ()) {
1128 i = _events.erase (i);
1134 _patch_changes.clear();
1138 display_patch_changes ();
1140 _marked_for_selection.clear ();
1141 _marked_for_velocity.clear ();
1143 /* we may have caused _events to contain things out of order (e.g. if a note
1144 moved earlier or later). we don't generally need them in time order, but
1145 make a note that a sort is required for those cases that require it.
1148 _sort_needed = true;
1152 MidiRegionView::display_patch_changes ()
1154 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1155 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1157 for (uint8_t i = 0; i < 16; ++i) {
1158 if (chn_mask & (1<<i)) {
1159 display_patch_changes_on_channel (i);
1165 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1167 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1169 if ((*i)->channel() != channel) {
1173 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1175 boost::shared_ptr<MIDI::Name::Patch> patch =
1176 MIDI::Name::MidiPatchManager::instance().find_patch(
1177 _model_name, _custom_device_mode, channel, patch_key);
1180 add_canvas_patch_change (*i, patch->name());
1183 /* program and bank numbers are zero-based: convert to one-based */
1184 snprintf (buf, 16, "%d\n%d", (*i)->program() + 1, (*i)->bank() + 1);
1185 add_canvas_patch_change (*i, buf);
1191 MidiRegionView::display_sysexes()
1193 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1194 Evoral::MusicalTime time = (*i)->time();
1199 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1200 str << int((*i)->buffer()[b]);
1201 if (b != (*i)->size() -1) {
1205 string text = str.str();
1207 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1209 double height = midi_stream_view()->contents_height();
1211 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1212 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1214 // Show unless patch change is beyond the region bounds
1215 if (time - _region->start() >= _region->length() || time < _region->start()) {
1221 _sys_exes.push_back(sysex);
1226 MidiRegionView::~MidiRegionView ()
1228 in_destructor = true;
1230 trackview.editor().verbose_cursor()->hide ();
1232 note_delete_connection.disconnect ();
1234 delete _list_editor;
1236 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1238 if (_active_notes) {
1246 delete _note_diff_command;
1247 delete _step_edit_cursor;
1248 delete _temporary_note_group;
1252 MidiRegionView::region_resized (const PropertyChange& what_changed)
1254 RegionView::region_resized(what_changed);
1256 if (what_changed.contains (ARDOUR::Properties::position)) {
1257 set_duration(_region->length(), 0);
1258 if (_enable_display) {
1265 MidiRegionView::reset_width_dependent_items (double pixel_width)
1267 RegionView::reset_width_dependent_items(pixel_width);
1268 assert(_pixel_width == pixel_width);
1270 if (_enable_display) {
1274 move_step_edit_cursor (_step_edit_cursor_position);
1275 set_step_edit_cursor_width (_step_edit_cursor_width);
1279 MidiRegionView::set_height (double height)
1281 static const double FUDGE = 2.0;
1282 const double old_height = _height;
1283 RegionView::set_height(height);
1284 _height = height - FUDGE;
1286 apply_note_range(midi_stream_view()->lowest_note(),
1287 midi_stream_view()->highest_note(),
1288 height != old_height + FUDGE);
1291 name_pixbuf->raise_to_top();
1294 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1295 (*x)->set_height (midi_stream_view()->contents_height());
1298 if (_step_edit_cursor) {
1299 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1304 /** Apply the current note range from the stream view
1305 * by repositioning/hiding notes as necessary
1308 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1310 if (!_enable_display) {
1314 if (!force && _current_range_min == min && _current_range_max == max) {
1318 _current_range_min = min;
1319 _current_range_max = max;
1321 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1322 CanvasNoteEvent* event = *i;
1323 boost::shared_ptr<NoteType> note (event->note());
1325 if (note->note() < _current_range_min ||
1326 note->note() > _current_range_max) {
1332 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1334 const double y1 = midi_stream_view()->note_to_y(note->note());
1335 const double y2 = y1 + floor(midi_stream_view()->note_height());
1337 cnote->property_y1() = y1;
1338 cnote->property_y2() = y2;
1340 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1342 const double diamond_size = update_hit (chit);
1344 chit->set_height (diamond_size);
1350 MidiRegionView::add_ghost (TimeAxisView& tv)
1354 double unit_position = _region->position () / samples_per_unit;
1355 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1356 MidiGhostRegion* ghost;
1358 if (mtv && mtv->midi_view()) {
1359 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1360 to allow having midi notes on top of note lines and waveforms.
1362 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1364 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1367 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1368 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1369 ghost->add_note(note);
1373 ghost->set_height ();
1374 ghost->set_duration (_region->length() / samples_per_unit);
1375 ghosts.push_back (ghost);
1377 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1383 /** Begin tracking note state for successive calls to add_event
1386 MidiRegionView::begin_write()
1388 assert(!_active_notes);
1389 _active_notes = new CanvasNote*[128];
1390 for (unsigned i=0; i < 128; ++i) {
1391 _active_notes[i] = 0;
1396 /** Destroy note state for add_event
1399 MidiRegionView::end_write()
1401 delete[] _active_notes;
1403 _marked_for_selection.clear();
1404 _marked_for_velocity.clear();
1408 /** Resolve an active MIDI note (while recording).
1411 MidiRegionView::resolve_note(uint8_t note, double end_time)
1413 if (midi_view()->note_mode() != Sustained) {
1417 if (_active_notes && _active_notes[note]) {
1419 const framepos_t end_time_frames = beats_to_frames(end_time);
1421 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1422 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1423 _active_notes[note] = 0;
1428 /** Extend active notes to rightmost edge of region (if length is changed)
1431 MidiRegionView::extend_active_notes()
1433 if (!_active_notes) {
1437 for (unsigned i=0; i < 128; ++i) {
1438 if (_active_notes[i]) {
1439 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1446 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1448 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1452 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1454 if (!route_ui || !route_ui->midi_track()) {
1458 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1464 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1466 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1470 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1472 if (!route_ui || !route_ui->midi_track()) {
1476 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1478 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1487 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1489 const framepos_t note_start_frames = beats_to_frames(note->time());
1491 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1492 (note_start_frames < _region->start());
1494 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1495 (note->note() <= midi_stream_view()->highest_note());
1500 /** Update a canvas note's size from its model note.
1501 * @param ev Canvas note to update.
1502 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1505 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1507 boost::shared_ptr<NoteType> note = ev->note();
1509 const framepos_t note_start_frames = beats_to_frames(note->time());
1511 /* trim note display to not overlap the end of its region */
1512 const framepos_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length());
1514 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1515 const double y1 = midi_stream_view()->note_to_y(note->note());
1516 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1518 ev->property_x1() = x;
1519 ev->property_y1() = y1;
1521 if (note->length() > 0) {
1522 ev->property_x2() = note_endpixel;
1524 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1527 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1529 if (note->length() == 0) {
1530 if (_active_notes) {
1531 assert(note->note() < 128);
1532 // If this note is already active there's a stuck note,
1533 // finish the old note rectangle
1534 if (_active_notes[note->note()]) {
1535 CanvasNote* const old_rect = _active_notes[note->note()];
1536 boost::shared_ptr<NoteType> old_note = old_rect->note();
1537 old_rect->property_x2() = x;
1538 old_rect->property_outline_what() = (guint32) 0xF;
1540 _active_notes[note->note()] = ev;
1542 /* outline all but right edge */
1543 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1545 /* outline all edges */
1546 ev->property_outline_what() = (guint32) 0xF;
1549 if (update_ghost_regions) {
1550 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1551 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1553 gr->update_note (ev);
1560 MidiRegionView::update_hit (CanvasHit* ev)
1562 boost::shared_ptr<NoteType> note = ev->note();
1564 const framepos_t note_start_frames = beats_to_frames(note->time());
1565 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1566 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1567 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1571 return diamond_size;
1574 /** Add a MIDI note to the view (with length).
1576 * If in sustained mode, notes with length 0 will be considered active
1577 * notes, and resolve_note should be called when the corresponding note off
1578 * event arrives, to properly display the note.
1581 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1583 CanvasNoteEvent* event = 0;
1585 assert(note->time() >= 0);
1586 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1588 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1590 if (midi_view()->note_mode() == Sustained) {
1592 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1594 update_note (ev_rect);
1598 MidiGhostRegion* gr;
1600 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1601 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1602 gr->add_note(ev_rect);
1606 } else if (midi_view()->note_mode() == Percussive) {
1608 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1610 CanvasHit* ev_diamond = new CanvasHit(*this, *_note_group, diamond_size, note);
1612 update_hit (ev_diamond);
1621 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1622 note_selected(event, true);
1625 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1626 event->show_velocity();
1629 event->on_channel_selection_change(_last_channel_selection);
1630 _events.push_back(event);
1639 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1640 MidiStreamView* const view = mtv->midi_view();
1642 view->update_note_range(note->note());
1646 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1647 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1649 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1651 /* potentially extend region to hold new note */
1653 framepos_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1654 framepos_t region_end = _region->position() + _region->length() - 1;
1656 if (end_frame > region_end) {
1657 _region->set_length (end_frame - _region->position());
1660 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1661 MidiStreamView* const view = mtv->midi_view();
1663 view->update_note_range(new_note->note());
1665 _marked_for_selection.clear ();
1668 start_note_diff_command (_("step add"));
1669 note_diff_add_note (new_note, true, false);
1672 // last_step_edit_note = new_note;
1676 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1678 change_note_lengths (false, false, beats, false, true);
1682 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1684 assert (patch->time() >= 0);
1686 const double x = trackview.editor().frame_to_pixel (beats_to_frames (patch->time()));
1688 double const height = midi_stream_view()->contents_height();
1690 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1691 new CanvasPatchChange(*this, *_note_group,
1696 _custom_device_mode,
1700 // Show unless patch change is beyond the region bounds
1701 if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1702 patch_change->hide();
1704 patch_change->show();
1707 _patch_changes.push_back (patch_change);
1711 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1713 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1714 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1718 if (i != _model->patch_changes().end()) {
1719 key.msb = (*i)->bank_msb ();
1720 key.lsb = (*i)->bank_lsb ();
1721 key.program_number = (*i)->program ();
1723 key.msb = key.lsb = key.program_number = 0;
1726 assert (key.is_sane());
1731 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1733 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1735 if (pc.patch()->program() != new_patch.program_number) {
1736 c->change_program (pc.patch (), new_patch.program_number);
1739 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1740 if (pc.patch()->bank() != new_bank) {
1741 c->change_bank (pc.patch (), new_bank);
1744 _model->apply_command (*trackview.session(), c);
1746 _patch_changes.clear ();
1747 display_patch_changes ();
1751 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1753 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1755 if (old_change->time() != new_change.time()) {
1756 c->change_time (old_change, new_change.time());
1759 if (old_change->channel() != new_change.channel()) {
1760 c->change_channel (old_change, new_change.channel());
1763 if (old_change->program() != new_change.program()) {
1764 c->change_program (old_change, new_change.program());
1767 if (old_change->bank() != new_change.bank()) {
1768 c->change_bank (old_change, new_change.bank());
1771 _model->apply_command (*trackview.session(), c);
1773 _patch_changes.clear ();
1774 display_patch_changes ();
1777 /** Add a patch change to the region.
1778 * @param t Time in frames relative to region position
1779 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1780 * MidiTimeAxisView::get_channel_for_add())
1783 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1785 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1787 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1788 c->add (MidiModel::PatchChangePtr (
1789 new Evoral::PatchChange<Evoral::MusicalTime> (
1790 frames_to_beats (t + midi_region()->start()), mtv->get_channel_for_add(), patch.program(), patch.bank()
1794 _model->apply_command (*trackview.session(), c);
1796 _patch_changes.clear ();
1797 display_patch_changes ();
1801 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1803 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1804 c->change_time (pc.patch (), t);
1805 _model->apply_command (*trackview.session(), c);
1807 _patch_changes.clear ();
1808 display_patch_changes ();
1812 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1814 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1815 c->remove (pc->patch ());
1816 _model->apply_command (*trackview.session(), c);
1818 _patch_changes.clear ();
1819 display_patch_changes ();
1823 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1825 if (patch.patch()->program() < 127) {
1826 MIDI::Name::PatchPrimaryKey key;
1827 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1828 key.program_number++;
1829 change_patch_change (patch, key);
1834 MidiRegionView::next_patch (CanvasPatchChange& patch)
1836 if (patch.patch()->program() > 0) {
1837 MIDI::Name::PatchPrimaryKey key;
1838 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1839 key.program_number--;
1840 change_patch_change (patch, key);
1845 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1847 if (patch.patch()->program() < 127) {
1848 MIDI::Name::PatchPrimaryKey key;
1849 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1852 change_patch_change (patch, key);
1857 change_patch_change (patch, key);
1864 MidiRegionView::next_bank (CanvasPatchChange& patch)
1866 if (patch.patch()->program() > 0) {
1867 MIDI::Name::PatchPrimaryKey key;
1868 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1869 if (key.lsb < 127) {
1871 change_patch_change (patch, key);
1873 if (key.msb < 127) {
1876 change_patch_change (patch, key);
1883 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1885 if (_selection.empty()) {
1889 _selection.erase (cne);
1893 MidiRegionView::delete_selection()
1895 if (_selection.empty()) {
1899 start_note_diff_command (_("delete selection"));
1901 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1902 if ((*i)->selected()) {
1903 _note_diff_command->remove((*i)->note());
1913 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1915 start_note_diff_command (_("delete note"));
1916 _note_diff_command->remove (n);
1919 trackview.editor().verbose_cursor()->hide ();
1923 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1925 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1926 if ((*i)->selected() && (*i) != ev) {
1927 (*i)->set_selected(false);
1928 (*i)->hide_velocity();
1936 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1938 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1941 Selection::iterator tmp = i;
1944 (*i)->set_selected (false);
1945 _selection.erase (i);
1954 /* don't bother with removing this regionview from the editor selection,
1955 since we're about to add another note, and thus put/keep this
1956 regionview in the editor selection.
1959 if (!ev->selected()) {
1960 add_to_selection (ev);
1965 MidiRegionView::select_all_notes ()
1969 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1970 add_to_selection (*i);
1975 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1977 uint8_t low_note = 127;
1978 uint8_t high_note = 0;
1979 MidiModel::Notes& notes (_model->notes());
1980 _optimization_iterator = _events.begin();
1986 if (extend && _selection.empty()) {
1992 /* scan existing selection to get note range */
1994 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1995 if ((*i)->note()->note() < low_note) {
1996 low_note = (*i)->note()->note();
1998 if ((*i)->note()->note() > high_note) {
1999 high_note = (*i)->note()->note();
2003 low_note = min (low_note, notenum);
2004 high_note = max (high_note, notenum);
2007 _no_sound_notes = true;
2009 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2011 boost::shared_ptr<NoteType> note (*n);
2012 CanvasNoteEvent* cne;
2013 bool select = false;
2015 if (((1 << note->channel()) & channel_mask) != 0) {
2017 if ((note->note() >= low_note && note->note() <= high_note)) {
2020 } else if (note->note() == notenum) {
2026 if ((cne = find_canvas_note (note)) != 0) {
2027 // extend is false because we've taken care of it,
2028 // since it extends by time range, not pitch.
2029 note_selected (cne, add, false);
2033 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2037 _no_sound_notes = false;
2041 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2043 MidiModel::Notes& notes (_model->notes());
2044 _optimization_iterator = _events.begin();
2046 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2048 boost::shared_ptr<NoteType> note (*n);
2049 CanvasNoteEvent* cne;
2051 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2052 if ((cne = find_canvas_note (note)) != 0) {
2053 if (cne->selected()) {
2054 note_deselected (cne);
2056 note_selected (cne, true, false);
2064 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2067 clear_selection_except(ev);
2072 if (!ev->selected()) {
2073 add_to_selection (ev);
2077 /* find end of latest note selected, select all between that and the start of "ev" */
2079 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2080 Evoral::MusicalTime latest = 0;
2082 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2083 if ((*i)->note()->end_time() > latest) {
2084 latest = (*i)->note()->end_time();
2086 if ((*i)->note()->time() < earliest) {
2087 earliest = (*i)->note()->time();
2091 if (ev->note()->end_time() > latest) {
2092 latest = ev->note()->end_time();
2095 if (ev->note()->time() < earliest) {
2096 earliest = ev->note()->time();
2099 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2101 /* find notes entirely within OR spanning the earliest..latest range */
2103 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2104 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2105 add_to_selection (*i);
2113 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2115 remove_from_selection (ev);
2119 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
2129 // TODO: Make this faster by storing the last updated selection rect, and only
2130 // adjusting things that are in the area that appears/disappeared.
2131 // We probably need a tree to be able to find events in O(log(n)) time.
2133 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2135 /* check if any corner of the note is inside the rect
2138 1) this is computing "touched by", not "contained by" the rect.
2139 2) this does not require that events be sorted in time.
2142 const double ix1 = (*i)->x1();
2143 const double ix2 = (*i)->x2();
2144 const double iy1 = (*i)->y1();
2145 const double iy2 = (*i)->y2();
2147 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2148 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2149 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2150 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2153 if (!(*i)->selected()) {
2154 add_to_selection (*i);
2156 } else if ((*i)->selected()) {
2157 // Not inside rectangle
2158 remove_from_selection (*i);
2164 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2166 Selection::iterator i = _selection.find (ev);
2168 if (i != _selection.end()) {
2169 _selection.erase (i);
2172 ev->set_selected (false);
2173 ev->hide_velocity ();
2175 if (_selection.empty()) {
2176 PublicEditor& editor (trackview.editor());
2177 editor.get_selection().remove (this);
2182 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2184 bool add_mrv_selection = false;
2186 if (_selection.empty()) {
2187 add_mrv_selection = true;
2190 if (_selection.insert (ev).second) {
2191 ev->set_selected (true);
2192 play_midi_note ((ev)->note());
2195 if (add_mrv_selection) {
2196 PublicEditor& editor (trackview.editor());
2197 editor.get_selection().add (this);
2202 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2204 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2205 PossibleChord to_play;
2206 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2208 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2209 if ((*i)->note()->time() < earliest) {
2210 earliest = (*i)->note()->time();
2214 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2215 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2216 to_play.push_back ((*i)->note());
2218 (*i)->move_event(dx, dy);
2221 if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2223 if (to_play.size() > 1) {
2225 PossibleChord shifted;
2227 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2228 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2229 moved_note->set_note (moved_note->note() + cumulative_dy);
2230 shifted.push_back (moved_note);
2233 play_midi_chord (shifted);
2235 } else if (!to_play.empty()) {
2237 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2238 moved_note->set_note (moved_note->note() + cumulative_dy);
2239 play_midi_note (moved_note);
2245 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2247 assert (!_selection.empty());
2249 uint8_t lowest_note_in_selection = 127;
2250 uint8_t highest_note_in_selection = 0;
2251 uint8_t highest_note_difference = 0;
2253 // find highest and lowest notes first
2255 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2256 uint8_t pitch = (*i)->note()->note();
2257 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2258 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2262 cerr << "dnote: " << (int) dnote << endl;
2263 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2264 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2265 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2266 << int(highest_note_in_selection) << endl;
2267 cerr << "selection size: " << _selection.size() << endl;
2268 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2271 // Make sure the note pitch does not exceed the MIDI standard range
2272 if (highest_note_in_selection + dnote > 127) {
2273 highest_note_difference = highest_note_in_selection - 127;
2276 start_note_diff_command (_("move notes"));
2278 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2280 Evoral::MusicalTime new_time = frames_to_beats (beats_to_frames ((*i)->note()->time()) + dt);
2286 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2288 uint8_t original_pitch = (*i)->note()->note();
2289 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2291 // keep notes in standard midi range
2292 clamp_to_0_127(new_pitch);
2294 // keep original pitch if note is dragged outside valid midi range
2295 if ((original_pitch != 0 && new_pitch == 0)
2296 || (original_pitch != 127 && new_pitch == 127)) {
2297 new_pitch = original_pitch;
2300 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2301 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2303 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2308 // care about notes being moved beyond the upper/lower bounds on the canvas
2309 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2310 highest_note_in_selection > midi_stream_view()->highest_note()) {
2311 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2316 MidiRegionView::snap_pixel_to_frame(double x)
2318 PublicEditor& editor (trackview.editor());
2319 return snap_frame_to_frame (editor.pixel_to_frame (x));
2322 /** Snap a frame offset within our region using the current snap settings.
2323 * @param x Frame offset from this region's position.
2324 * @return Snapped frame offset from this region's position.
2327 MidiRegionView::snap_frame_to_frame (frameoffset_t x)
2329 PublicEditor& editor = trackview.editor();
2331 /* x is region relative, convert it to global absolute frames */
2332 framepos_t const session_frame = x + _region->position();
2334 /* try a snap in either direction */
2335 framepos_t frame = session_frame;
2336 editor.snap_to (frame, 0);
2338 /* if we went off the beginning of the region, snap forwards */
2339 if (frame < _region->position ()) {
2340 frame = session_frame;
2341 editor.snap_to (frame, 1);
2344 /* back to region relative */
2345 return frame - _region->position();
2349 MidiRegionView::snap_to_pixel(double x)
2351 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2355 MidiRegionView::get_position_pixels()
2357 framepos_t region_frame = get_position();
2358 return trackview.editor().frame_to_pixel(region_frame);
2362 MidiRegionView::get_end_position_pixels()
2364 framepos_t frame = get_position() + get_duration ();
2365 return trackview.editor().frame_to_pixel(frame);
2369 MidiRegionView::beats_to_frames(double beats) const
2371 return _time_converter.to(beats);
2375 MidiRegionView::frames_to_beats(framepos_t frames) const
2377 return _time_converter.from(frames);
2381 MidiRegionView::begin_resizing (bool /*at_front*/)
2383 _resize_data.clear();
2385 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2386 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2388 // only insert CanvasNotes into the map
2390 NoteResizeData *resize_data = new NoteResizeData();
2391 resize_data->canvas_note = note;
2393 // create a new SimpleRect from the note which will be the resize preview
2394 SimpleRect *resize_rect = new SimpleRect(
2395 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2397 // calculate the colors: get the color settings
2398 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2399 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2402 // make the resize preview notes more transparent and bright
2403 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2405 // calculate color based on note velocity
2406 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2407 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2411 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2412 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2414 resize_data->resize_rect = resize_rect;
2415 _resize_data.push_back(resize_data);
2420 /** Update resizing notes while user drags.
2421 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2422 * @param at_front which end of the note (true == note on, false == note off)
2423 * @param delta_x change in mouse position since the start of the drag
2424 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2425 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2426 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2427 * as the \a primary note.
2430 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2432 bool cursor_set = false;
2434 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2435 SimpleRect* resize_rect = (*i)->resize_rect;
2436 CanvasNote* canvas_note = (*i)->canvas_note;
2441 current_x = canvas_note->x1() + delta_x;
2443 current_x = primary->x1() + delta_x;
2447 current_x = canvas_note->x2() + delta_x;
2449 current_x = primary->x2() + delta_x;
2454 resize_rect->property_x1() = snap_to_pixel(current_x);
2455 resize_rect->property_x2() = canvas_note->x2();
2457 resize_rect->property_x2() = snap_to_pixel(current_x);
2458 resize_rect->property_x1() = canvas_note->x1();
2464 beats = snap_pixel_to_frame (current_x);
2465 beats = frames_to_beats (beats);
2470 if (beats < canvas_note->note()->end_time()) {
2471 len = canvas_note->note()->time() - beats;
2472 len += canvas_note->note()->length();
2477 if (beats >= canvas_note->note()->time()) {
2478 len = beats - canvas_note->note()->time();
2485 snprintf (buf, sizeof (buf), "%.3g beats", len);
2486 show_verbose_cursor (buf, 0, 0);
2495 /** Finish resizing notes when the user releases the mouse button.
2496 * Parameters the same as for \a update_resizing().
2499 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2501 start_note_diff_command (_("resize notes"));
2503 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2504 CanvasNote* canvas_note = (*i)->canvas_note;
2505 SimpleRect* resize_rect = (*i)->resize_rect;
2507 /* Get the new x position for this resize, which is in pixels relative
2508 * to the region position.
2515 current_x = canvas_note->x1() + delta_x;
2517 current_x = primary->x1() + delta_x;
2521 current_x = canvas_note->x2() + delta_x;
2523 current_x = primary->x2() + delta_x;
2527 /* Convert that to a frame within the region */
2528 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2530 /* and then to beats */
2531 current_x = frames_to_beats (current_x);
2533 if (at_front && current_x < canvas_note->note()->end_time()) {
2534 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2536 double len = canvas_note->note()->time() - current_x;
2537 len += canvas_note->note()->length();
2540 /* XXX convert to beats */
2541 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2546 double len = current_x - canvas_note->note()->time();
2549 /* XXX convert to beats */
2550 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2558 _resize_data.clear();
2563 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2565 uint8_t new_velocity;
2568 new_velocity = event->note()->velocity() + velocity;
2569 clamp_to_0_127(new_velocity);
2571 new_velocity = velocity;
2574 event->set_selected (event->selected()); // change color
2576 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2580 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2585 new_note = event->note()->note() + note;
2590 clamp_to_0_127 (new_note);
2591 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2595 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2597 bool change_start = false;
2598 bool change_length = false;
2599 Evoral::MusicalTime new_start = 0;
2600 Evoral::MusicalTime new_length = 0;
2602 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2604 front_delta: if positive - move the start of the note later in time (shortening it)
2605 if negative - move the start of the note earlier in time (lengthening it)
2607 end_delta: if positive - move the end of the note later in time (lengthening it)
2608 if negative - move the end of the note earlier in time (shortening it)
2612 if (front_delta < 0) {
2614 if (event->note()->time() < -front_delta) {
2617 new_start = event->note()->time() + front_delta; // moves earlier
2620 /* start moved toward zero, so move the end point out to where it used to be.
2621 Note that front_delta is negative, so this increases the length.
2624 new_length = event->note()->length() - front_delta;
2625 change_start = true;
2626 change_length = true;
2630 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2632 if (new_pos < event->note()->end_time()) {
2633 new_start = event->note()->time() + front_delta;
2634 /* start moved toward the end, so move the end point back to where it used to be */
2635 new_length = event->note()->length() - front_delta;
2636 change_start = true;
2637 change_length = true;
2644 bool can_change = true;
2645 if (end_delta < 0) {
2646 if (event->note()->length() < -end_delta) {
2652 new_length = event->note()->length() + end_delta;
2653 change_length = true;
2658 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2661 if (change_length) {
2662 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2667 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2669 uint8_t new_channel;
2673 if (event->note()->channel() < -chn) {
2676 new_channel = event->note()->channel() + chn;
2679 new_channel = event->note()->channel() + chn;
2682 new_channel = (uint8_t) chn;
2685 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2689 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2691 Evoral::MusicalTime new_time;
2695 if (event->note()->time() < -delta) {
2698 new_time = event->note()->time() + delta;
2701 new_time = event->note()->time() + delta;
2707 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2711 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2713 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2717 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2721 if (_selection.empty()) {
2736 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2737 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2743 start_note_diff_command (_("change velocities"));
2745 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2746 Selection::iterator next = i;
2748 change_note_velocity (*i, delta, true);
2754 if (!_selection.empty()) {
2756 snprintf (buf, sizeof (buf), "Vel %d",
2757 (int) (*_selection.begin())->note()->velocity());
2758 show_verbose_cursor (buf, 10, 10);
2764 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2766 if (_selection.empty()) {
2783 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2785 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2789 if ((int8_t) (*i)->note()->note() + delta > 127) {
2796 start_note_diff_command (_("transpose"));
2798 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2799 Selection::iterator next = i;
2801 change_note_note (*i, delta, true);
2809 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2815 /* grab the current grid distance */
2817 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2819 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2820 cerr << "Grid type not available as beats - TO BE FIXED\n";
2830 start_note_diff_command (_("change note lengths"));
2832 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2833 Selection::iterator next = i;
2836 /* note the negation of the delta for start */
2838 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2847 MidiRegionView::nudge_notes (bool forward)
2849 if (_selection.empty()) {
2853 /* pick a note as the point along the timeline to get the nudge distance.
2854 its not necessarily the earliest note, so we may want to pull the notes out
2855 into a vector and sort before using the first one.
2858 framepos_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2860 framepos_t distance;
2862 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2864 /* grid is off - use nudge distance */
2866 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2872 framepos_t next_pos = ref_point;
2875 if (max_framepos - 1 < next_pos) {
2879 if (next_pos == 0) {
2885 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2886 distance = ref_point - next_pos;
2889 if (distance == 0) {
2893 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2899 start_note_diff_command (_("nudge"));
2901 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2902 Selection::iterator next = i;
2904 change_note_time (*i, delta, true);
2912 MidiRegionView::change_channel(uint8_t channel)
2914 start_note_diff_command(_("change channel"));
2915 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2916 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2924 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2926 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2928 _pre_enter_cursor = editor->get_canvas_cursor ();
2930 if (_mouse_state == SelectTouchDragging) {
2931 note_selected (ev, true);
2934 show_verbose_cursor (ev->note ());
2938 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2940 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2942 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2943 (*i)->hide_velocity ();
2946 editor->verbose_cursor()->hide ();
2948 if (_pre_enter_cursor) {
2949 editor->set_canvas_cursor (_pre_enter_cursor);
2950 _pre_enter_cursor = 0;
2955 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2958 /* XXX should get patch name if we can */
2959 s << _("Bank:") << (ev->patch()->bank() + 1) << '\n' << _("Program:") << ((int) ev->patch()->program() + 1);
2960 show_verbose_cursor (s.str(), 10, 20);
2964 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
2966 trackview.editor().verbose_cursor()->hide ();
2970 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
2972 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2974 if (x_fraction > 0.0 && x_fraction < 0.25) {
2975 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
2976 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
2977 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
2979 if (_pre_enter_cursor && can_set_cursor) {
2980 editor->set_canvas_cursor (_pre_enter_cursor);
2986 MidiRegionView::set_frame_color()
2990 TimeAxisViewItem::set_frame_color ();
2997 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2998 } else if (high_enough_for_name) {
2999 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3004 if (!rect_visible) {
3005 f = UINT_RGBA_CHANGE_A (f, 0);
3008 frame->property_fill_color_rgba() = f;
3012 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3016 case FilterChannels:
3017 _force_channel = -1;
3020 _force_channel = mask;
3021 mask = 0xFFFF; // Show all notes as active (below)
3024 // Update notes for selection
3025 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3026 (*i)->on_channel_selection_change(mask);
3029 _last_channel_selection = mask;
3031 _patch_changes.clear ();
3032 display_patch_changes ();
3036 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3038 _model_name = model;
3039 _custom_device_mode = custom_device_mode;
3044 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3046 if (_selection.empty()) {
3050 PublicEditor& editor (trackview.editor());
3054 /* XXX what to do ? */
3058 editor.get_cut_buffer().add (selection_as_cut_buffer());
3066 start_note_diff_command();
3068 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3075 note_diff_remove_note (*i);
3085 MidiRegionView::selection_as_cut_buffer () const
3089 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3090 NoteType* n = (*i)->note().get();
3091 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3094 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3100 /** This method handles undo */
3102 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3108 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3110 trackview.session()->begin_reversible_command (_("paste"));
3112 start_note_diff_command (_("paste"));
3114 Evoral::MusicalTime beat_delta;
3115 Evoral::MusicalTime paste_pos_beats;
3116 Evoral::MusicalTime duration;
3117 Evoral::MusicalTime end_point = 0;
3119 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3120 paste_pos_beats = frames_to_beats (pos - _region->position());
3121 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3122 paste_pos_beats = 0;
3124 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",
3125 (*mcb.notes().begin())->time(),
3126 (*mcb.notes().rbegin())->end_time(),
3127 duration, pos, _region->position(),
3128 paste_pos_beats, beat_delta));
3132 for (int n = 0; n < (int) times; ++n) {
3134 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3136 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3137 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3139 /* make all newly added notes selected */
3141 note_diff_add_note (copied_note, true);
3142 end_point = copied_note->end_time();
3145 paste_pos_beats += duration;
3148 /* if we pasted past the current end of the region, extend the region */
3150 framepos_t end_frame = _region->position() + beats_to_frames (end_point);
3151 framepos_t region_end = _region->position() + _region->length() - 1;
3153 if (end_frame > region_end) {
3155 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3157 _region->clear_changes ();
3158 _region->set_length (end_frame);
3159 trackview.session()->add_command (new StatefulDiffCommand (_region));
3164 trackview.session()->commit_reversible_command ();
3167 struct EventNoteTimeEarlyFirstComparator {
3168 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3169 return a->note()->time() < b->note()->time();
3174 MidiRegionView::time_sort_events ()
3176 if (!_sort_needed) {
3180 EventNoteTimeEarlyFirstComparator cmp;
3183 _sort_needed = false;
3187 MidiRegionView::goto_next_note (bool add_to_selection)
3189 bool use_next = false;
3191 if (_events.back()->selected()) {
3195 time_sort_events ();
3197 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3198 if ((*i)->selected()) {
3201 } else if (use_next) {
3202 if (!add_to_selection) {
3205 note_selected (*i, true, false);
3211 /* use the first one */
3213 unique_select (_events.front());
3218 MidiRegionView::goto_previous_note (bool add_to_selection)
3220 bool use_next = false;
3222 if (_events.front()->selected()) {
3226 time_sort_events ();
3228 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3229 if ((*i)->selected()) {
3232 } else if (use_next) {
3233 if (!add_to_selection) {
3236 note_selected (*i, true, false);
3242 /* use the last one */
3244 unique_select (*(_events.rbegin()));
3248 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3250 bool had_selected = false;
3252 time_sort_events ();
3254 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3255 if ((*i)->selected()) {
3256 selected.insert ((*i)->note());
3257 had_selected = true;
3261 if (allow_all_if_none_selected && !had_selected) {
3262 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3263 selected.insert ((*i)->note());
3269 MidiRegionView::update_ghost_note (double x, double y)
3271 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3276 _note_group->w2i (x, y);
3278 PublicEditor& editor = trackview.editor ();
3280 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3283 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, unsnapped_frame);
3289 framecnt_t const grid_frames = beats_to_frames (grid_beats);
3293 if (unsnapped_frame < grid_frames / 2) {
3294 f = snap_frame_to_frame (unsnapped_frame);
3296 /* snap to half the grid spacing behind the mouse pointer;
3297 this makes the snapped note time more intuitive
3299 f = snap_frame_to_frame (unsnapped_frame - grid_frames / 2);
3302 double length = frames_to_beats (snap_frame_to_frame (f + grid_frames) - f);
3304 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
3305 _ghost_note->note()->set_length (length);
3306 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3307 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3309 /* the ghost note does not appear in ghost regions, so pass false in here */
3310 update_note (_ghost_note, false);
3312 show_verbose_cursor (_ghost_note->note ());
3316 MidiRegionView::create_ghost_note (double x, double y)
3321 boost::shared_ptr<NoteType> g (new NoteType);
3322 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3323 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3324 update_ghost_note (x, y);
3325 _ghost_note->show ();
3330 show_verbose_cursor (_ghost_note->note ());
3334 MidiRegionView::snap_changed ()
3340 create_ghost_note (_last_ghost_x, _last_ghost_y);
3344 MidiRegionView::drop_down_keys ()
3346 _mouse_state = None;
3350 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3352 double note = midi_stream_view()->y_to_note(y);
3354 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3356 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3358 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3359 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3360 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3361 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3366 bool add_mrv_selection = false;
3368 if (_selection.empty()) {
3369 add_mrv_selection = true;
3372 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3373 if (_selection.insert (*i).second) {
3374 (*i)->set_selected (true);
3378 if (add_mrv_selection) {
3379 PublicEditor& editor (trackview.editor());
3380 editor.get_selection().add (this);
3385 MidiRegionView::color_handler ()
3387 RegionView::color_handler ();
3389 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3390 (*i)->set_selected ((*i)->selected()); // will change color
3393 /* XXX probably more to do here */
3397 MidiRegionView::enable_display (bool yn)
3399 RegionView::enable_display (yn);
3406 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3408 if (_step_edit_cursor == 0) {
3409 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3411 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3412 _step_edit_cursor->property_y1() = 0;
3413 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3414 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3415 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3418 move_step_edit_cursor (pos);
3419 _step_edit_cursor->show ();
3423 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3425 _step_edit_cursor_position = pos;
3427 if (_step_edit_cursor) {
3428 double pixel = trackview.editor().frame_to_pixel (beats_to_frames (pos));
3429 _step_edit_cursor->property_x1() = pixel;
3430 set_step_edit_cursor_width (_step_edit_cursor_width);
3435 MidiRegionView::hide_step_edit_cursor ()
3437 if (_step_edit_cursor) {
3438 _step_edit_cursor->hide ();
3443 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3445 _step_edit_cursor_width = beats;
3447 if (_step_edit_cursor) {
3448 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (beats_to_frames (beats));
3452 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3453 * @param buf Data that has been recorded.
3454 * @param w Source that this data will end up in.
3457 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3459 if (!_active_notes) {
3460 /* we aren't actively being recorded to */
3464 boost::shared_ptr<MidiSource> src = w.lock ();
3465 if (!src || src != midi_region()->midi_source()) {
3466 /* recorded data was not destined for our source */
3470 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3471 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3473 framepos_t back = max_framepos;
3475 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3476 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3477 assert (ev.buffer ());
3479 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3481 if (ev.type() == MIDI_CMD_NOTE_ON) {
3483 boost::shared_ptr<NoteType> note (
3484 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3487 add_note (note, true);
3489 /* fix up our note range */
3490 if (ev.note() < _current_range_min) {
3491 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3492 } else if (ev.note() > _current_range_max) {
3493 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3496 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3497 resolve_note (ev.note (), time_beats);
3503 midi_stream_view()->check_record_layers (region(), back);
3507 MidiRegionView::trim_front_starting ()
3509 /* Reparent the note group to the region view's parent, so that it doesn't change
3510 when the region view is trimmed.
3512 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3513 _temporary_note_group->move (group->property_x(), group->property_y());
3514 _note_group->reparent (*_temporary_note_group);
3518 MidiRegionView::trim_front_ending ()
3520 _note_group->reparent (*group);
3521 delete _temporary_note_group;
3522 _temporary_note_group = 0;
3524 if (_region->start() < 0) {
3525 /* Trim drag made start time -ve; fix this */
3526 midi_region()->fix_negative_start ();
3531 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3533 PatchChangeDialog d (&_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3534 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3538 change_patch_change (pc->patch(), d.patch ());
3543 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3546 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3547 Evoral::midi_note_name (n->note()).c_str(),
3549 (int) n->channel() + 1,
3550 (int) n->velocity());
3552 show_verbose_cursor (buf, 10, 20);
3556 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3560 trackview.editor().get_pointer_position (wx, wy);
3565 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3567 double x1, y1, x2, y2;
3568 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3570 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3571 wy -= (y2 - y1) + 2 * yoffset;
3574 trackview.editor().verbose_cursor()->set (text, wx, wy);
3575 trackview.editor().verbose_cursor()->show ();