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 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
85 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
86 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
87 : RegionView (parent, tv, r, spu, basic_color)
89 , _last_channel_selection(0xFFFF)
90 , _current_range_min(0)
91 , _current_range_max(0)
92 , _model_name(string())
93 , _custom_device_mode(string())
95 , _note_group(new ArdourCanvas::Group(*group))
96 , _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)
114 _note_group->raise_to_top();
115 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
117 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
118 connect_to_diskstream ();
121 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
122 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
123 TimeAxisViewItem::Visibility visibility)
124 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
126 , _last_channel_selection(0xFFFF)
127 , _model_name(string())
128 , _custom_device_mode(string())
130 , _note_group(new ArdourCanvas::Group(*parent))
131 , _note_diff_command (0)
134 , _step_edit_cursor (0)
135 , _step_edit_cursor_width (1.0)
136 , _step_edit_cursor_position (0.0)
137 , _channel_selection_scoped_note (0)
138 , _temporary_note_group (0)
141 , _sort_needed (true)
142 , _optimization_iterator (_events.end())
144 , _no_sound_notes (false)
148 _note_group->raise_to_top();
149 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
151 connect_to_diskstream ();
155 MidiRegionView::parameter_changed (std::string const & p)
157 if (p == "diplay-first-midi-bank-as-zero") {
158 if (_enable_display) {
164 MidiRegionView::MidiRegionView (const MidiRegionView& other)
165 : sigc::trackable(other)
168 , _last_channel_selection(0xFFFF)
169 , _model_name(string())
170 , _custom_device_mode(string())
172 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
173 , _note_diff_command (0)
176 , _step_edit_cursor (0)
177 , _step_edit_cursor_width (1.0)
178 , _step_edit_cursor_position (0.0)
179 , _channel_selection_scoped_note (0)
180 , _temporary_note_group (0)
183 , _sort_needed (true)
184 , _optimization_iterator (_events.end())
186 , _no_sound_notes (false)
193 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
194 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
199 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
200 : RegionView (other, boost::shared_ptr<Region> (region))
202 , _last_channel_selection(0xFFFF)
203 , _model_name(string())
204 , _custom_device_mode(string())
206 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
207 , _note_diff_command (0)
210 , _step_edit_cursor (0)
211 , _step_edit_cursor_width (1.0)
212 , _step_edit_cursor_position (0.0)
213 , _channel_selection_scoped_note (0)
214 , _temporary_note_group (0)
217 , _sort_needed (true)
218 , _optimization_iterator (_events.end())
220 , _no_sound_notes (false)
227 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
228 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
234 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
236 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
238 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
239 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
243 midi_region()->midi_source(0)->load_model();
246 _model = midi_region()->midi_source(0)->model();
247 _enable_display = false;
249 RegionView::init (basic_color, false);
251 compute_colors (basic_color);
253 set_height (trackview.current_height());
256 region_sync_changed ();
257 region_resized (ARDOUR::bounds_change);
260 reset_width_dependent_items (_pixel_width);
264 _enable_display = true;
267 display_model (_model);
271 group->raise_to_top();
272 group->signal_event().connect(
273 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
275 midi_view()->signal_channel_mode_changed().connect(
276 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
278 midi_view()->signal_midi_patch_settings_changed().connect(
279 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
281 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
282 ui_bind(&MidiRegionView::snap_changed, this),
285 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
286 connect_to_diskstream ();
290 MidiRegionView::connect_to_diskstream ()
292 midi_view()->midi_track()->DataRecorded.connect(
293 *this, invalidator(*this),
294 ui_bind(&MidiRegionView::data_recorded, this, _1, _2),
299 MidiRegionView::canvas_event(GdkEvent* ev)
302 case GDK_ENTER_NOTIFY:
303 case GDK_LEAVE_NOTIFY:
304 _last_event_x = ev->crossing.x;
305 _last_event_y = ev->crossing.y;
307 case GDK_MOTION_NOTIFY:
308 _last_event_x = ev->motion.x;
309 _last_event_y = ev->motion.y;
315 if (!trackview.editor().internal_editing()) {
321 return scroll (&ev->scroll);
324 return key_press (&ev->key);
326 case GDK_KEY_RELEASE:
327 return key_release (&ev->key);
329 case GDK_BUTTON_PRESS:
330 return button_press (&ev->button);
332 case GDK_2BUTTON_PRESS:
335 case GDK_BUTTON_RELEASE:
336 return button_release (&ev->button);
338 case GDK_ENTER_NOTIFY:
339 return enter_notify (&ev->crossing);
341 case GDK_LEAVE_NOTIFY:
342 return leave_notify (&ev->crossing);
344 case GDK_MOTION_NOTIFY:
345 return motion (&ev->motion);
355 MidiRegionView::remove_ghost_note ()
362 MidiRegionView::enter_notify (GdkEventCrossing* ev)
364 trackview.editor().MouseModeChanged.connect (
365 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
368 Keyboard::magic_widget_grab_focus();
371 if (trackview.editor().current_mouse_mode() == MouseRange) {
372 create_ghost_note (ev->x, ev->y);
379 MidiRegionView::leave_notify (GdkEventCrossing*)
381 _mouse_mode_connection.disconnect ();
383 trackview.editor().verbose_cursor()->hide ();
384 remove_ghost_note ();
389 MidiRegionView::mouse_mode_changed ()
391 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
392 create_ghost_note (_last_event_x, _last_event_y);
394 remove_ghost_note ();
395 trackview.editor().verbose_cursor()->hide ();
400 MidiRegionView::button_press (GdkEventButton* ev)
402 if (ev->button != 1) {
409 group->w2i (_last_x, _last_y);
411 if (_mouse_state != SelectTouchDragging) {
413 _pressed_button = ev->button;
414 _mouse_state = Pressed;
419 _pressed_button = ev->button;
425 MidiRegionView::button_release (GdkEventButton* ev)
427 double event_x, event_y;
429 if (ev->button != 1) {
436 group->w2i(event_x, event_y);
437 group->ungrab(ev->time);
439 switch (_mouse_state) {
440 case Pressed: // Clicked
442 switch (trackview.editor().current_mouse_mode()) {
448 if (Keyboard::is_insert_note_event(ev)) {
450 double event_x, event_y;
454 group->w2i(event_x, event_y);
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);
471 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
477 create_note_at (event_x, event_y, beats, true);
488 case SelectRectDragging: // Select drag done
495 case AddDragging: // Add drag done
499 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) {
501 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
503 const double x = _drag_rect->property_x1();
504 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
506 create_note_at (x, _drag_rect->property_y1(), region_frames_to_region_beats(length), true);
513 create_ghost_note (ev->x, ev->y);
523 MidiRegionView::motion (GdkEventMotion* ev)
525 double event_x, event_y;
526 framepos_t event_frame = 0;
530 group->w2i(event_x, event_y);
532 // convert event_x to global frame
533 event_frame = snap_pixel_to_frame (event_x);
535 if (!_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
536 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
537 && _mouse_state != AddDragging) {
539 create_ghost_note (ev->x, ev->y);
540 } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
541 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
543 update_ghost_note (ev->x, ev->y);
544 } else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange) {
549 trackview.editor().verbose_cursor()->hide ();
550 } else if (_ghost_note && trackview.editor().current_mouse_mode() == MouseRange) {
551 update_ghost_note (ev->x, ev->y);
554 /* any motion immediately hides velocity text that may have been visible */
556 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
557 (*i)->hide_velocity ();
560 switch (_mouse_state) {
561 case Pressed: // Maybe start a drag, if we've moved a bit
563 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
564 /* no appreciable movement since the button was pressed */
568 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject
569 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
572 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
573 Gdk::Cursor(Gdk::FLEUR), ev->time);
577 _drag_start_x = event_x;
578 _drag_start_y = event_y;
580 _drag_rect = new ArdourCanvas::SimpleRect(*group);
581 _drag_rect->property_x1() = event_x;
582 _drag_rect->property_y1() = event_y;
583 _drag_rect->property_x2() = event_x;
584 _drag_rect->property_y2() = event_y;
585 _drag_rect->property_outline_what() = 0xFF;
586 _drag_rect->property_outline_color_rgba()
587 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
588 _drag_rect->property_fill_color_rgba()
589 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
591 _mouse_state = SelectRectDragging;
594 } else if (trackview.editor().internal_editing()) {
595 // Add note drag start
597 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
598 Gdk::Cursor(Gdk::FLEUR), ev->time);
602 _drag_start_x = event_x;
603 _drag_start_y = event_y;
605 _drag_rect = new ArdourCanvas::SimpleRect(*group);
606 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
608 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
609 midi_stream_view()->y_to_note(event_y));
610 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
611 _drag_rect->property_y2() = _drag_rect->property_y1()
612 + floor(midi_stream_view()->note_height());
613 _drag_rect->property_outline_what() = 0xFF;
614 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
615 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
617 _mouse_state = AddDragging;
622 trackview.editor().verbose_cursor()->hide ();
629 case SelectRectDragging: // Select drag motion
630 case AddDragging: // Add note drag motion
635 GdkModifierType state;
636 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
641 if (_mouse_state == AddDragging) {
642 event_x = trackview.editor().frame_to_pixel(event_frame);
647 if (event_x > _drag_start_x) {
648 _drag_rect->property_x2() = event_x;
651 _drag_rect->property_x1() = event_x;
655 if (_drag_rect && _mouse_state == SelectRectDragging) {
657 if (event_y > _drag_start_y) {
658 _drag_rect->property_y2() = event_y;
661 _drag_rect->property_y1() = event_y;
664 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
670 case SelectTouchDragging:
682 MidiRegionView::scroll (GdkEventScroll* ev)
684 if (_selection.empty()) {
688 trackview.editor().verbose_cursor()->hide ();
690 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
692 if (ev->direction == GDK_SCROLL_UP) {
693 change_velocities (true, fine, false);
694 } else if (ev->direction == GDK_SCROLL_DOWN) {
695 change_velocities (false, fine, false);
701 MidiRegionView::key_press (GdkEventKey* ev)
703 /* since GTK bindings are generally activated on press, and since
704 detectable auto-repeat is the name of the game and only sends
705 repeated presses, carry out key actions at key press, not release.
708 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
709 _mouse_state = SelectTouchDragging;
712 } else if (ev->keyval == GDK_Escape) {
716 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
718 bool start = (ev->keyval == GDK_comma);
719 bool end = (ev->keyval == GDK_period);
720 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
721 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
723 change_note_lengths (fine, shorter, 0.0, start, end);
727 } else if (ev->keyval == GDK_Delete) {
732 } else if (ev->keyval == GDK_Tab) {
734 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
735 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
737 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
741 } else if (ev->keyval == GDK_ISO_Left_Tab) {
743 /* Shift-TAB generates ISO Left Tab, for some reason */
745 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
746 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
748 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
754 } else if (ev->keyval == GDK_Up) {
756 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
757 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
759 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
760 change_velocities (true, fine, allow_smush);
762 transpose (true, fine, allow_smush);
766 } else if (ev->keyval == GDK_Down) {
768 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
769 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
771 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
772 change_velocities (false, fine, allow_smush);
774 transpose (false, fine, allow_smush);
778 } else if (ev->keyval == GDK_Left) {
783 } else if (ev->keyval == GDK_Right) {
788 } else if (ev->keyval == GDK_Control_L) {
791 } else if (ev->keyval == GDK_c) {
800 MidiRegionView::key_release (GdkEventKey* ev)
802 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
810 MidiRegionView::channel_edit ()
813 uint8_t current_channel;
815 if (_selection.empty()) {
819 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
821 current_channel = (*i)->note()->channel ();
826 MidiChannelDialog channel_dialog (current_channel);
827 int ret = channel_dialog.run ();
830 case Gtk::RESPONSE_OK:
836 uint8_t new_channel = channel_dialog.active_channel ();
838 start_note_diff_command (_("channel edit"));
840 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
841 Selection::iterator next = i;
843 change_note_channel (*i, new_channel);
851 MidiRegionView::show_list_editor ()
854 _list_editor = new MidiListEditor (trackview.session(), midi_region());
856 _list_editor->present ();
859 /** Add a note to the model, and the view, at a canvas (click) coordinate.
860 * \param x horizontal position in pixels
861 * \param y vertical position in pixels
862 * \param length duration of the note in beats, which will be snapped to the grid
863 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
866 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
868 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
869 MidiStreamView* const view = mtv->midi_view();
871 double note = view->y_to_note(y);
874 assert(note <= 127.0);
876 // Start of note in frames relative to region start
877 framepos_t const start_frames = snap_pixel_to_frame (x);
878 assert(start_frames >= 0);
881 length = region_frames_to_region_beats(
882 snap_frame_to_frame (start_frames + region_beats_to_region_frames(length)) - start_frames);
884 assert (length != 0);
887 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
890 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
891 region_frames_to_region_beats(start_frames + _region->start()), length,
892 (uint8_t)note, 0x40));
894 if (_model->contains (new_note)) {
898 view->update_note_range(new_note->note());
900 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
902 _model->apply_command(*trackview.session(), cmd);
904 play_midi_note (new_note);
908 MidiRegionView::clear_events()
913 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
914 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
919 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
924 _patch_changes.clear();
926 _optimization_iterator = _events.end();
930 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
934 content_connection.disconnect ();
935 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
939 if (_enable_display) {
945 MidiRegionView::start_note_diff_command (string name)
947 if (!_note_diff_command) {
948 _note_diff_command = _model->new_note_diff_command (name);
953 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
955 if (_note_diff_command) {
956 _note_diff_command->add (note);
959 _marked_for_selection.insert(note);
962 _marked_for_velocity.insert(note);
967 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
969 if (_note_diff_command && ev->note()) {
970 _note_diff_command->remove(ev->note());
975 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
976 MidiModel::NoteDiffCommand::Property property,
979 if (_note_diff_command) {
980 _note_diff_command->change (ev->note(), property, val);
985 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
986 MidiModel::NoteDiffCommand::Property property,
987 Evoral::MusicalTime val)
989 if (_note_diff_command) {
990 _note_diff_command->change (ev->note(), property, val);
995 MidiRegionView::apply_diff (bool as_subcommand)
999 if (!_note_diff_command) {
1003 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1004 // Mark all selected notes for selection when model reloads
1005 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1006 _marked_for_selection.insert((*i)->note());
1010 if (as_subcommand) {
1011 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1013 _model->apply_command (*trackview.session(), _note_diff_command);
1016 _note_diff_command = 0;
1017 midi_view()->midi_track()->playlist_modified();
1019 if (add_or_remove) {
1020 _marked_for_selection.clear();
1023 _marked_for_velocity.clear();
1027 MidiRegionView::abort_command()
1029 delete _note_diff_command;
1030 _note_diff_command = 0;
1035 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1037 if (_optimization_iterator != _events.end()) {
1038 ++_optimization_iterator;
1041 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1042 return *_optimization_iterator;
1045 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1046 if ((*_optimization_iterator)->note() == note) {
1047 return *_optimization_iterator;
1055 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1057 MidiModel::Notes notes;
1058 _model->get_notes (notes, op, val, chan_mask);
1060 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1061 CanvasNoteEvent* cne = find_canvas_note (*n);
1069 MidiRegionView::redisplay_model()
1071 // Don't redisplay the model if we're currently recording and displaying that
1072 if (_active_notes) {
1080 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1081 (*i)->invalidate ();
1084 MidiModel::ReadLock lock(_model->read_lock());
1086 MidiModel::Notes& notes (_model->notes());
1087 _optimization_iterator = _events.begin();
1089 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1091 boost::shared_ptr<NoteType> note (*n);
1092 CanvasNoteEvent* cne;
1095 if (note_in_region_range (note, visible)) {
1097 if ((cne = find_canvas_note (note)) != 0) {
1104 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1106 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1118 add_note (note, visible);
1123 if ((cne = find_canvas_note (note)) != 0) {
1131 /* remove note items that are no longer valid */
1133 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1134 if (!(*i)->valid ()) {
1136 i = _events.erase (i);
1142 _patch_changes.clear();
1146 display_patch_changes ();
1148 _marked_for_selection.clear ();
1149 _marked_for_velocity.clear ();
1151 /* we may have caused _events to contain things out of order (e.g. if a note
1152 moved earlier or later). we don't generally need them in time order, but
1153 make a note that a sort is required for those cases that require it.
1156 _sort_needed = true;
1160 MidiRegionView::display_patch_changes ()
1162 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1163 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1165 for (uint8_t i = 0; i < 16; ++i) {
1166 if (chn_mask & (1<<i)) {
1167 display_patch_changes_on_channel (i);
1169 /* TODO gray-out patch instad of not displaying it */
1174 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1176 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1178 if ((*i)->channel() != channel) {
1182 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1184 boost::shared_ptr<MIDI::Name::Patch> patch =
1185 MIDI::Name::MidiPatchManager::instance().find_patch(
1186 _model_name, _custom_device_mode, channel, patch_key);
1189 add_canvas_patch_change (*i, patch->name());
1192 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1193 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1194 add_canvas_patch_change (*i, buf);
1200 MidiRegionView::display_sysexes()
1202 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1203 Evoral::MusicalTime time = (*i)->time();
1208 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1209 str << int((*i)->buffer()[b]);
1210 if (b != (*i)->size() -1) {
1214 string text = str.str();
1216 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1218 double height = midi_stream_view()->contents_height();
1220 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1221 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1223 // Show unless patch change is beyond the region bounds
1224 if (time - _region->start() >= _region->length() || time < _region->start()) {
1230 _sys_exes.push_back(sysex);
1235 MidiRegionView::~MidiRegionView ()
1237 in_destructor = true;
1239 trackview.editor().verbose_cursor()->hide ();
1241 note_delete_connection.disconnect ();
1243 delete _list_editor;
1245 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1247 if (_active_notes) {
1255 delete _note_diff_command;
1256 delete _step_edit_cursor;
1257 delete _temporary_note_group;
1261 MidiRegionView::region_resized (const PropertyChange& what_changed)
1263 RegionView::region_resized(what_changed);
1265 if (what_changed.contains (ARDOUR::Properties::position)) {
1266 set_duration(_region->length(), 0);
1267 if (_enable_display) {
1274 MidiRegionView::reset_width_dependent_items (double pixel_width)
1276 RegionView::reset_width_dependent_items(pixel_width);
1277 assert(_pixel_width == pixel_width);
1279 if (_enable_display) {
1283 move_step_edit_cursor (_step_edit_cursor_position);
1284 set_step_edit_cursor_width (_step_edit_cursor_width);
1288 MidiRegionView::set_height (double height)
1290 static const double FUDGE = 2.0;
1291 const double old_height = _height;
1292 RegionView::set_height(height);
1293 _height = height - FUDGE;
1295 apply_note_range(midi_stream_view()->lowest_note(),
1296 midi_stream_view()->highest_note(),
1297 height != old_height + FUDGE);
1300 name_pixbuf->raise_to_top();
1303 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1304 (*x)->set_height (midi_stream_view()->contents_height());
1307 if (_step_edit_cursor) {
1308 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1313 /** Apply the current note range from the stream view
1314 * by repositioning/hiding notes as necessary
1317 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1319 if (!_enable_display) {
1323 if (!force && _current_range_min == min && _current_range_max == max) {
1327 _current_range_min = min;
1328 _current_range_max = max;
1330 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1331 CanvasNoteEvent* event = *i;
1332 boost::shared_ptr<NoteType> note (event->note());
1334 if (note->note() < _current_range_min ||
1335 note->note() > _current_range_max) {
1341 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1343 const double y1 = midi_stream_view()->note_to_y(note->note());
1344 const double y2 = y1 + floor(midi_stream_view()->note_height());
1346 cnote->property_y1() = y1;
1347 cnote->property_y2() = y2;
1349 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1351 const double diamond_size = update_hit (chit);
1353 chit->set_height (diamond_size);
1359 MidiRegionView::add_ghost (TimeAxisView& tv)
1363 double unit_position = _region->position () / samples_per_unit;
1364 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1365 MidiGhostRegion* ghost;
1367 if (mtv && mtv->midi_view()) {
1368 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1369 to allow having midi notes on top of note lines and waveforms.
1371 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1373 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1376 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1377 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1378 ghost->add_note(note);
1382 ghost->set_height ();
1383 ghost->set_duration (_region->length() / samples_per_unit);
1384 ghosts.push_back (ghost);
1386 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1392 /** Begin tracking note state for successive calls to add_event
1395 MidiRegionView::begin_write()
1397 assert(!_active_notes);
1398 _active_notes = new CanvasNote*[128];
1399 for (unsigned i=0; i < 128; ++i) {
1400 _active_notes[i] = 0;
1405 /** Destroy note state for add_event
1408 MidiRegionView::end_write()
1410 delete[] _active_notes;
1412 _marked_for_selection.clear();
1413 _marked_for_velocity.clear();
1417 /** Resolve an active MIDI note (while recording).
1420 MidiRegionView::resolve_note(uint8_t note, double end_time)
1422 if (midi_view()->note_mode() != Sustained) {
1426 if (_active_notes && _active_notes[note]) {
1428 /* XXX is end_time really region-centric? I think so, because
1429 this is a new region that we're recording, so source zero is
1430 the same as region zero
1432 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1434 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1435 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1436 _active_notes[note] = 0;
1441 /** Extend active notes to rightmost edge of region (if length is changed)
1444 MidiRegionView::extend_active_notes()
1446 if (!_active_notes) {
1450 for (unsigned i=0; i < 128; ++i) {
1451 if (_active_notes[i]) {
1452 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1459 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1461 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1465 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1467 if (!route_ui || !route_ui->midi_track()) {
1471 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1477 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1479 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1483 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1485 if (!route_ui || !route_ui->midi_track()) {
1489 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1491 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1500 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1502 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1503 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1505 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1506 (note->note() <= midi_stream_view()->highest_note());
1511 /** Update a canvas note's size from its model note.
1512 * @param ev Canvas note to update.
1513 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1516 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1518 boost::shared_ptr<NoteType> note = ev->note();
1520 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1521 const double y1 = midi_stream_view()->note_to_y(note->note());
1523 ev->property_x1() = x;
1524 ev->property_y1() = y1;
1526 /* trim note display to not overlap the end of its region */
1528 if (note->length() > 0) {
1529 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1530 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1532 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1535 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1537 if (note->length() == 0) {
1538 if (_active_notes) {
1539 assert(note->note() < 128);
1540 // If this note is already active there's a stuck note,
1541 // finish the old note rectangle
1542 if (_active_notes[note->note()]) {
1543 CanvasNote* const old_rect = _active_notes[note->note()];
1544 boost::shared_ptr<NoteType> old_note = old_rect->note();
1545 old_rect->property_x2() = x;
1546 old_rect->property_outline_what() = (guint32) 0xF;
1548 _active_notes[note->note()] = ev;
1550 /* outline all but right edge */
1551 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1553 /* outline all edges */
1554 ev->property_outline_what() = (guint32) 0xF;
1557 if (update_ghost_regions) {
1558 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1559 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1561 gr->update_note (ev);
1568 MidiRegionView::update_hit (CanvasHit* ev)
1570 boost::shared_ptr<NoteType> note = ev->note();
1572 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1573 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1574 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1575 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1579 return diamond_size;
1582 /** Add a MIDI note to the view (with length).
1584 * If in sustained mode, notes with length 0 will be considered active
1585 * notes, and resolve_note should be called when the corresponding note off
1586 * event arrives, to properly display the note.
1589 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1591 CanvasNoteEvent* event = 0;
1593 assert(note->time() >= 0);
1594 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1596 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1598 if (midi_view()->note_mode() == Sustained) {
1600 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1602 update_note (ev_rect);
1606 MidiGhostRegion* gr;
1608 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1609 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1610 gr->add_note(ev_rect);
1614 } else if (midi_view()->note_mode() == Percussive) {
1616 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1618 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1620 update_hit (ev_diamond);
1629 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1630 note_selected(event, true);
1633 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1634 event->show_velocity();
1637 event->on_channel_selection_change(_last_channel_selection);
1638 _events.push_back(event);
1647 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1648 MidiStreamView* const view = mtv->midi_view();
1650 view->update_note_range (note->note());
1654 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1655 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1657 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1659 /* potentially extend region to hold new note */
1661 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1662 framepos_t region_end = _region->last_frame();
1664 if (end_frame > region_end) {
1665 _region->set_length (end_frame - _region->position());
1668 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1669 MidiStreamView* const view = mtv->midi_view();
1671 view->update_note_range(new_note->note());
1673 _marked_for_selection.clear ();
1676 start_note_diff_command (_("step add"));
1677 note_diff_add_note (new_note, true, false);
1680 // last_step_edit_note = new_note;
1684 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1686 change_note_lengths (false, false, beats, false, true);
1690 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1692 assert (patch->time() >= 0);
1694 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (patch->time()));
1696 double const height = midi_stream_view()->contents_height();
1698 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1699 new CanvasPatchChange(*this, *_note_group,
1704 _custom_device_mode,
1708 // Show unless patch change is beyond the region bounds
1709 if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1710 patch_change->hide();
1712 patch_change->show();
1715 _patch_changes.push_back (patch_change);
1719 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1721 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1722 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1726 if (i != _model->patch_changes().end()) {
1727 key.msb = (*i)->bank_msb ();
1728 key.lsb = (*i)->bank_lsb ();
1729 key.program_number = (*i)->program ();
1731 key.msb = key.lsb = key.program_number = 0;
1734 assert (key.is_sane());
1739 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1741 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1743 if (pc.patch()->program() != new_patch.program_number) {
1744 c->change_program (pc.patch (), new_patch.program_number);
1747 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1748 if (pc.patch()->bank() != new_bank) {
1749 c->change_bank (pc.patch (), new_bank);
1752 _model->apply_command (*trackview.session(), c);
1754 _patch_changes.clear ();
1755 display_patch_changes ();
1759 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1761 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1763 if (old_change->time() != new_change.time()) {
1764 c->change_time (old_change, new_change.time());
1767 if (old_change->channel() != new_change.channel()) {
1768 c->change_channel (old_change, new_change.channel());
1771 if (old_change->program() != new_change.program()) {
1772 c->change_program (old_change, new_change.program());
1775 if (old_change->bank() != new_change.bank()) {
1776 c->change_bank (old_change, new_change.bank());
1779 _model->apply_command (*trackview.session(), c);
1781 _patch_changes.clear ();
1782 display_patch_changes ();
1785 /** Add a patch change to the region.
1786 * @param t Time in frames relative to region position
1787 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1788 * MidiTimeAxisView::get_channel_for_add())
1791 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1793 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1795 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1796 c->add (MidiModel::PatchChangePtr (
1797 new Evoral::PatchChange<Evoral::MusicalTime> (
1798 absolute_frames_to_source_beats (_region->position() + t),
1799 mtv->get_channel_for_add(), patch.program(), patch.bank()
1804 _model->apply_command (*trackview.session(), c);
1806 _patch_changes.clear ();
1807 display_patch_changes ();
1811 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1813 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1814 c->change_time (pc.patch (), t);
1815 _model->apply_command (*trackview.session(), c);
1817 _patch_changes.clear ();
1818 display_patch_changes ();
1822 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1824 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1825 c->remove (pc->patch ());
1826 _model->apply_command (*trackview.session(), c);
1828 _patch_changes.clear ();
1829 display_patch_changes ();
1833 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1835 if (patch.patch()->program() < 127) {
1836 MIDI::Name::PatchPrimaryKey key;
1837 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1838 key.program_number++;
1839 change_patch_change (patch, key);
1844 MidiRegionView::next_patch (CanvasPatchChange& patch)
1846 if (patch.patch()->program() > 0) {
1847 MIDI::Name::PatchPrimaryKey key;
1848 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1849 key.program_number--;
1850 change_patch_change (patch, key);
1855 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1857 if (patch.patch()->program() < 127) {
1858 MIDI::Name::PatchPrimaryKey key;
1859 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1862 change_patch_change (patch, key);
1867 change_patch_change (patch, key);
1874 MidiRegionView::next_bank (CanvasPatchChange& patch)
1876 if (patch.patch()->program() > 0) {
1877 MIDI::Name::PatchPrimaryKey key;
1878 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1879 if (key.lsb < 127) {
1881 change_patch_change (patch, key);
1883 if (key.msb < 127) {
1886 change_patch_change (patch, key);
1893 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1895 if (_selection.empty()) {
1899 _selection.erase (cne);
1903 MidiRegionView::delete_selection()
1905 if (_selection.empty()) {
1909 start_note_diff_command (_("delete selection"));
1911 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1912 if ((*i)->selected()) {
1913 _note_diff_command->remove((*i)->note());
1923 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1925 start_note_diff_command (_("delete note"));
1926 _note_diff_command->remove (n);
1929 trackview.editor().verbose_cursor()->hide ();
1933 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1935 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1936 if ((*i)->selected() && (*i) != ev) {
1937 (*i)->set_selected(false);
1938 (*i)->hide_velocity();
1946 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1948 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1951 Selection::iterator tmp = i;
1954 (*i)->set_selected (false);
1955 _selection.erase (i);
1964 /* don't bother with removing this regionview from the editor selection,
1965 since we're about to add another note, and thus put/keep this
1966 regionview in the editor selection.
1969 if (!ev->selected()) {
1970 add_to_selection (ev);
1975 MidiRegionView::select_all_notes ()
1979 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1980 add_to_selection (*i);
1985 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1987 uint8_t low_note = 127;
1988 uint8_t high_note = 0;
1989 MidiModel::Notes& notes (_model->notes());
1990 _optimization_iterator = _events.begin();
1996 if (extend && _selection.empty()) {
2002 /* scan existing selection to get note range */
2004 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2005 if ((*i)->note()->note() < low_note) {
2006 low_note = (*i)->note()->note();
2008 if ((*i)->note()->note() > high_note) {
2009 high_note = (*i)->note()->note();
2013 low_note = min (low_note, notenum);
2014 high_note = max (high_note, notenum);
2017 _no_sound_notes = true;
2019 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2021 boost::shared_ptr<NoteType> note (*n);
2022 CanvasNoteEvent* cne;
2023 bool select = false;
2025 if (((1 << note->channel()) & channel_mask) != 0) {
2027 if ((note->note() >= low_note && note->note() <= high_note)) {
2030 } else if (note->note() == notenum) {
2036 if ((cne = find_canvas_note (note)) != 0) {
2037 // extend is false because we've taken care of it,
2038 // since it extends by time range, not pitch.
2039 note_selected (cne, add, false);
2043 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2047 _no_sound_notes = false;
2051 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2053 MidiModel::Notes& notes (_model->notes());
2054 _optimization_iterator = _events.begin();
2056 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2058 boost::shared_ptr<NoteType> note (*n);
2059 CanvasNoteEvent* cne;
2061 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2062 if ((cne = find_canvas_note (note)) != 0) {
2063 if (cne->selected()) {
2064 note_deselected (cne);
2066 note_selected (cne, true, false);
2074 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2077 clear_selection_except(ev);
2082 if (!ev->selected()) {
2083 add_to_selection (ev);
2087 /* find end of latest note selected, select all between that and the start of "ev" */
2089 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2090 Evoral::MusicalTime latest = 0;
2092 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2093 if ((*i)->note()->end_time() > latest) {
2094 latest = (*i)->note()->end_time();
2096 if ((*i)->note()->time() < earliest) {
2097 earliest = (*i)->note()->time();
2101 if (ev->note()->end_time() > latest) {
2102 latest = ev->note()->end_time();
2105 if (ev->note()->time() < earliest) {
2106 earliest = ev->note()->time();
2109 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2111 /* find notes entirely within OR spanning the earliest..latest range */
2113 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2114 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2115 add_to_selection (*i);
2123 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2125 remove_from_selection (ev);
2129 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
2139 // TODO: Make this faster by storing the last updated selection rect, and only
2140 // adjusting things that are in the area that appears/disappeared.
2141 // We probably need a tree to be able to find events in O(log(n)) time.
2143 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2145 /* check if any corner of the note is inside the rect
2148 1) this is computing "touched by", not "contained by" the rect.
2149 2) this does not require that events be sorted in time.
2152 const double ix1 = (*i)->x1();
2153 const double ix2 = (*i)->x2();
2154 const double iy1 = (*i)->y1();
2155 const double iy2 = (*i)->y2();
2157 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2158 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2159 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2160 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2163 if (!(*i)->selected()) {
2164 add_to_selection (*i);
2166 } else if ((*i)->selected()) {
2167 // Not inside rectangle
2168 remove_from_selection (*i);
2174 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2176 Selection::iterator i = _selection.find (ev);
2178 if (i != _selection.end()) {
2179 _selection.erase (i);
2182 ev->set_selected (false);
2183 ev->hide_velocity ();
2185 if (_selection.empty()) {
2186 PublicEditor& editor (trackview.editor());
2187 editor.get_selection().remove (this);
2192 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2194 bool add_mrv_selection = false;
2196 if (_selection.empty()) {
2197 add_mrv_selection = true;
2200 if (_selection.insert (ev).second) {
2201 ev->set_selected (true);
2202 play_midi_note ((ev)->note());
2205 if (add_mrv_selection) {
2206 PublicEditor& editor (trackview.editor());
2207 editor.get_selection().add (this);
2212 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2214 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2215 PossibleChord to_play;
2216 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2218 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2219 if ((*i)->note()->time() < earliest) {
2220 earliest = (*i)->note()->time();
2224 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2225 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2226 to_play.push_back ((*i)->note());
2228 (*i)->move_event(dx, dy);
2231 if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2233 if (to_play.size() > 1) {
2235 PossibleChord shifted;
2237 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2238 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2239 moved_note->set_note (moved_note->note() + cumulative_dy);
2240 shifted.push_back (moved_note);
2243 play_midi_chord (shifted);
2245 } else if (!to_play.empty()) {
2247 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2248 moved_note->set_note (moved_note->note() + cumulative_dy);
2249 play_midi_note (moved_note);
2255 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2257 assert (!_selection.empty());
2259 uint8_t lowest_note_in_selection = 127;
2260 uint8_t highest_note_in_selection = 0;
2261 uint8_t highest_note_difference = 0;
2263 // find highest and lowest notes first
2265 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2266 uint8_t pitch = (*i)->note()->note();
2267 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2268 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2272 cerr << "dnote: " << (int) dnote << endl;
2273 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2274 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2275 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2276 << int(highest_note_in_selection) << endl;
2277 cerr << "selection size: " << _selection.size() << endl;
2278 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2281 // Make sure the note pitch does not exceed the MIDI standard range
2282 if (highest_note_in_selection + dnote > 127) {
2283 highest_note_difference = highest_note_in_selection - 127;
2286 start_note_diff_command (_("move notes"));
2288 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2290 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2296 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2298 uint8_t original_pitch = (*i)->note()->note();
2299 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2301 // keep notes in standard midi range
2302 clamp_to_0_127(new_pitch);
2304 // keep original pitch if note is dragged outside valid midi range
2305 if ((original_pitch != 0 && new_pitch == 0)
2306 || (original_pitch != 127 && new_pitch == 127)) {
2307 new_pitch = original_pitch;
2310 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2311 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2313 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2318 // care about notes being moved beyond the upper/lower bounds on the canvas
2319 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2320 highest_note_in_selection > midi_stream_view()->highest_note()) {
2321 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2326 MidiRegionView::snap_pixel_to_frame(double x)
2328 PublicEditor& editor (trackview.editor());
2329 return snap_frame_to_frame (editor.pixel_to_frame (x));
2332 /** Snap a frame offset within our region using the current snap settings.
2333 * @param x Frame offset from this region's position.
2334 * @return Snapped frame offset from this region's position.
2337 MidiRegionView::snap_frame_to_frame (frameoffset_t x)
2339 PublicEditor& editor = trackview.editor();
2341 /* x is region relative, convert it to global absolute frames */
2342 framepos_t const session_frame = x + _region->position();
2344 /* try a snap in either direction */
2345 framepos_t frame = session_frame;
2346 editor.snap_to (frame, 0);
2348 /* if we went off the beginning of the region, snap forwards */
2349 if (frame < _region->position ()) {
2350 frame = session_frame;
2351 editor.snap_to (frame, 1);
2354 /* back to region relative */
2355 return frame - _region->position();
2359 MidiRegionView::snap_to_pixel(double x)
2361 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2365 MidiRegionView::get_position_pixels()
2367 framepos_t region_frame = get_position();
2368 return trackview.editor().frame_to_pixel(region_frame);
2372 MidiRegionView::get_end_position_pixels()
2374 framepos_t frame = get_position() + get_duration ();
2375 return trackview.editor().frame_to_pixel(frame);
2379 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2381 /* the time converter will return the frame corresponding to `beats'
2382 relative to the start of the source. The start of the source
2383 is an implied position given by region->position - region->start
2385 const framepos_t source_start = _region->position() - _region->start();
2386 return source_start + _source_relative_time_converter.to (beats);
2390 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2392 /* the `frames' argument needs to be converted into a frame count
2393 relative to the start of the source before being passed in to the
2396 const framepos_t source_start = _region->position() - _region->start();
2397 return _source_relative_time_converter.from (frames - source_start);
2401 MidiRegionView::region_beats_to_region_frames(double beats) const
2403 return _region_relative_time_converter.to(beats);
2407 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2409 return _region_relative_time_converter.from(frames);
2413 MidiRegionView::begin_resizing (bool /*at_front*/)
2415 _resize_data.clear();
2417 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2418 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2420 // only insert CanvasNotes into the map
2422 NoteResizeData *resize_data = new NoteResizeData();
2423 resize_data->canvas_note = note;
2425 // create a new SimpleRect from the note which will be the resize preview
2426 SimpleRect *resize_rect = new SimpleRect(
2427 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2429 // calculate the colors: get the color settings
2430 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2431 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2434 // make the resize preview notes more transparent and bright
2435 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2437 // calculate color based on note velocity
2438 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2439 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2443 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2444 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2446 resize_data->resize_rect = resize_rect;
2447 _resize_data.push_back(resize_data);
2452 /** Update resizing notes while user drags.
2453 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2454 * @param at_front which end of the note (true == note on, false == note off)
2455 * @param delta_x change in mouse position since the start of the drag
2456 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2457 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2458 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2459 * as the \a primary note.
2462 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2464 bool cursor_set = false;
2466 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2467 SimpleRect* resize_rect = (*i)->resize_rect;
2468 CanvasNote* canvas_note = (*i)->canvas_note;
2473 current_x = canvas_note->x1() + delta_x;
2475 current_x = primary->x1() + delta_x;
2479 current_x = canvas_note->x2() + delta_x;
2481 current_x = primary->x2() + delta_x;
2486 resize_rect->property_x1() = snap_to_pixel(current_x);
2487 resize_rect->property_x2() = canvas_note->x2();
2489 resize_rect->property_x2() = snap_to_pixel(current_x);
2490 resize_rect->property_x1() = canvas_note->x1();
2496 beats = snap_pixel_to_frame (current_x);
2497 /* XXX not sure this is correct - snap_pixel_to_frame()
2498 returns an absolute frame.
2500 beats = region_frames_to_region_beats (beats);
2505 if (beats < canvas_note->note()->end_time()) {
2506 len = canvas_note->note()->time() - beats;
2507 len += canvas_note->note()->length();
2512 if (beats >= canvas_note->note()->time()) {
2513 len = beats - canvas_note->note()->time();
2520 snprintf (buf, sizeof (buf), "%.3g beats", len);
2521 show_verbose_cursor (buf, 0, 0);
2530 /** Finish resizing notes when the user releases the mouse button.
2531 * Parameters the same as for \a update_resizing().
2534 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2536 start_note_diff_command (_("resize notes"));
2538 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2539 CanvasNote* canvas_note = (*i)->canvas_note;
2540 SimpleRect* resize_rect = (*i)->resize_rect;
2542 /* Get the new x position for this resize, which is in pixels relative
2543 * to the region position.
2550 current_x = canvas_note->x1() + delta_x;
2552 current_x = primary->x1() + delta_x;
2556 current_x = canvas_note->x2() + delta_x;
2558 current_x = primary->x2() + delta_x;
2562 /* Convert that to a frame within the region */
2563 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2565 /* and then to beats */
2566 /* XXX not sure this is correct - snap_pixel_to_frame()
2567 returns an absolute frame.
2569 current_x = region_frames_to_region_beats (current_x);
2571 if (at_front && current_x < canvas_note->note()->end_time()) {
2572 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2574 double len = canvas_note->note()->time() - current_x;
2575 len += canvas_note->note()->length();
2578 /* XXX convert to beats */
2579 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2584 double len = current_x - canvas_note->note()->time();
2587 /* XXX convert to beats */
2588 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2596 _resize_data.clear();
2601 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2603 uint8_t new_velocity;
2606 new_velocity = event->note()->velocity() + velocity;
2607 clamp_to_0_127(new_velocity);
2609 new_velocity = velocity;
2612 event->set_selected (event->selected()); // change color
2614 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2618 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2623 new_note = event->note()->note() + note;
2628 clamp_to_0_127 (new_note);
2629 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2633 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2635 bool change_start = false;
2636 bool change_length = false;
2637 Evoral::MusicalTime new_start = 0;
2638 Evoral::MusicalTime new_length = 0;
2640 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2642 front_delta: if positive - move the start of the note later in time (shortening it)
2643 if negative - move the start of the note earlier in time (lengthening it)
2645 end_delta: if positive - move the end of the note later in time (lengthening it)
2646 if negative - move the end of the note earlier in time (shortening it)
2650 if (front_delta < 0) {
2652 if (event->note()->time() < -front_delta) {
2655 new_start = event->note()->time() + front_delta; // moves earlier
2658 /* start moved toward zero, so move the end point out to where it used to be.
2659 Note that front_delta is negative, so this increases the length.
2662 new_length = event->note()->length() - front_delta;
2663 change_start = true;
2664 change_length = true;
2668 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2670 if (new_pos < event->note()->end_time()) {
2671 new_start = event->note()->time() + front_delta;
2672 /* start moved toward the end, so move the end point back to where it used to be */
2673 new_length = event->note()->length() - front_delta;
2674 change_start = true;
2675 change_length = true;
2682 bool can_change = true;
2683 if (end_delta < 0) {
2684 if (event->note()->length() < -end_delta) {
2690 new_length = event->note()->length() + end_delta;
2691 change_length = true;
2696 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2699 if (change_length) {
2700 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2705 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2707 uint8_t new_channel;
2711 if (event->note()->channel() < -chn) {
2714 new_channel = event->note()->channel() + chn;
2717 new_channel = event->note()->channel() + chn;
2720 new_channel = (uint8_t) chn;
2723 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2727 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2729 Evoral::MusicalTime new_time;
2733 if (event->note()->time() < -delta) {
2736 new_time = event->note()->time() + delta;
2739 new_time = event->note()->time() + delta;
2745 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2749 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2751 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2755 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2759 if (_selection.empty()) {
2774 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2775 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2781 start_note_diff_command (_("change velocities"));
2783 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2784 Selection::iterator next = i;
2786 change_note_velocity (*i, delta, true);
2792 if (!_selection.empty()) {
2794 snprintf (buf, sizeof (buf), "Vel %d",
2795 (int) (*_selection.begin())->note()->velocity());
2796 show_verbose_cursor (buf, 10, 10);
2802 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2804 if (_selection.empty()) {
2821 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2823 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2827 if ((int8_t) (*i)->note()->note() + delta > 127) {
2834 start_note_diff_command (_("transpose"));
2836 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2837 Selection::iterator next = i;
2839 change_note_note (*i, delta, true);
2847 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2853 /* grab the current grid distance */
2855 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2857 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2858 cerr << "Grid type not available as beats - TO BE FIXED\n";
2868 start_note_diff_command (_("change note lengths"));
2870 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2871 Selection::iterator next = i;
2874 /* note the negation of the delta for start */
2876 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2885 MidiRegionView::nudge_notes (bool forward)
2887 if (_selection.empty()) {
2891 /* pick a note as the point along the timeline to get the nudge distance.
2892 its not necessarily the earliest note, so we may want to pull the notes out
2893 into a vector and sort before using the first one.
2896 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2898 framepos_t distance;
2900 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2902 /* grid is off - use nudge distance */
2904 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2910 framepos_t next_pos = ref_point;
2913 if (max_framepos - 1 < next_pos) {
2917 if (next_pos == 0) {
2923 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2924 distance = ref_point - next_pos;
2927 if (distance == 0) {
2931 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2937 start_note_diff_command (_("nudge"));
2939 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2940 Selection::iterator next = i;
2942 change_note_time (*i, delta, true);
2950 MidiRegionView::change_channel(uint8_t channel)
2952 start_note_diff_command(_("change channel"));
2953 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2954 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2962 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2964 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2966 _pre_enter_cursor = editor->get_canvas_cursor ();
2968 if (_mouse_state == SelectTouchDragging) {
2969 note_selected (ev, true);
2972 show_verbose_cursor (ev->note ());
2976 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2978 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2980 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2981 (*i)->hide_velocity ();
2984 editor->verbose_cursor()->hide ();
2986 if (_pre_enter_cursor) {
2987 editor->set_canvas_cursor (_pre_enter_cursor);
2988 _pre_enter_cursor = 0;
2993 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2996 /* XXX should get patch name if we can */
2997 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
2998 show_verbose_cursor (s.str(), 10, 20);
3002 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3004 trackview.editor().verbose_cursor()->hide ();
3008 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3010 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3012 if (x_fraction > 0.0 && x_fraction < 0.25) {
3013 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3014 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3015 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3017 if (_pre_enter_cursor && can_set_cursor) {
3018 editor->set_canvas_cursor (_pre_enter_cursor);
3024 MidiRegionView::set_frame_color()
3028 TimeAxisViewItem::set_frame_color ();
3035 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3036 } else if (high_enough_for_name) {
3037 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3042 if (!rect_visible) {
3043 f = UINT_RGBA_CHANGE_A (f, 0);
3046 frame->property_fill_color_rgba() = f;
3050 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3054 case FilterChannels:
3055 _force_channel = -1;
3058 _force_channel = mask;
3059 mask = 0xFFFF; // Show all notes as active (below)
3062 // Update notes for selection
3063 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3064 (*i)->on_channel_selection_change(mask);
3067 _last_channel_selection = mask;
3069 _patch_changes.clear ();
3070 display_patch_changes ();
3074 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3076 _model_name = model;
3077 _custom_device_mode = custom_device_mode;
3082 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3084 if (_selection.empty()) {
3088 PublicEditor& editor (trackview.editor());
3092 /* XXX what to do ? */
3096 editor.get_cut_buffer().add (selection_as_cut_buffer());
3104 start_note_diff_command();
3106 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3113 note_diff_remove_note (*i);
3123 MidiRegionView::selection_as_cut_buffer () const
3127 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3128 NoteType* n = (*i)->note().get();
3129 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3132 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3138 /** This method handles undo */
3140 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3146 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3148 trackview.session()->begin_reversible_command (_("paste"));
3150 start_note_diff_command (_("paste"));
3152 Evoral::MusicalTime beat_delta;
3153 Evoral::MusicalTime paste_pos_beats;
3154 Evoral::MusicalTime duration;
3155 Evoral::MusicalTime end_point = 0;
3157 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3158 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3159 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3160 paste_pos_beats = 0;
3162 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",
3163 (*mcb.notes().begin())->time(),
3164 (*mcb.notes().rbegin())->end_time(),
3165 duration, pos, _region->position(),
3166 paste_pos_beats, beat_delta));
3170 for (int n = 0; n < (int) times; ++n) {
3172 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3174 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3175 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3177 /* make all newly added notes selected */
3179 note_diff_add_note (copied_note, true);
3180 end_point = copied_note->end_time();
3183 paste_pos_beats += duration;
3186 /* if we pasted past the current end of the region, extend the region */
3188 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3189 framepos_t region_end = _region->position() + _region->length() - 1;
3191 if (end_frame > region_end) {
3193 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3195 _region->clear_changes ();
3196 _region->set_length (end_frame);
3197 trackview.session()->add_command (new StatefulDiffCommand (_region));
3202 trackview.session()->commit_reversible_command ();
3205 struct EventNoteTimeEarlyFirstComparator {
3206 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3207 return a->note()->time() < b->note()->time();
3212 MidiRegionView::time_sort_events ()
3214 if (!_sort_needed) {
3218 EventNoteTimeEarlyFirstComparator cmp;
3221 _sort_needed = false;
3225 MidiRegionView::goto_next_note (bool add_to_selection)
3227 bool use_next = false;
3229 if (_events.back()->selected()) {
3233 time_sort_events ();
3235 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3236 if ((*i)->selected()) {
3239 } else if (use_next) {
3240 if (!add_to_selection) {
3243 note_selected (*i, true, false);
3249 /* use the first one */
3251 unique_select (_events.front());
3256 MidiRegionView::goto_previous_note (bool add_to_selection)
3258 bool use_next = false;
3260 if (_events.front()->selected()) {
3264 time_sort_events ();
3266 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3267 if ((*i)->selected()) {
3270 } else if (use_next) {
3271 if (!add_to_selection) {
3274 note_selected (*i, true, false);
3280 /* use the last one */
3282 unique_select (*(_events.rbegin()));
3286 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3288 bool had_selected = false;
3290 time_sort_events ();
3292 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3293 if ((*i)->selected()) {
3294 selected.insert ((*i)->note());
3295 had_selected = true;
3299 if (allow_all_if_none_selected && !had_selected) {
3300 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3301 selected.insert ((*i)->note());
3307 MidiRegionView::update_ghost_note (double x, double y)
3309 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3314 _note_group->w2i (x, y);
3316 PublicEditor& editor = trackview.editor ();
3318 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3320 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, unsnapped_frame);
3326 framecnt_t const grid_frames = region_beats_to_region_frames (grid_beats);
3327 framepos_t f = snap_frame_to_frame (unsnapped_frame);
3328 /* use region_frames... because we are converting a delta within the region
3331 double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3333 /* note that this sets the time of the ghost note in beats relative to
3334 the start of the region.
3336 _ghost_note->note()->set_time (region_frames_to_region_beats (f - _region->position()));
3337 _ghost_note->note()->set_length (length);
3338 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3339 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3341 /* the ghost note does not appear in ghost regions, so pass false in here */
3342 update_note (_ghost_note, false);
3344 show_verbose_cursor (_ghost_note->note ());
3348 MidiRegionView::create_ghost_note (double x, double y)
3353 boost::shared_ptr<NoteType> g (new NoteType);
3354 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3355 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3356 update_ghost_note (x, y);
3357 _ghost_note->show ();
3362 show_verbose_cursor (_ghost_note->note ());
3366 MidiRegionView::snap_changed ()
3372 create_ghost_note (_last_ghost_x, _last_ghost_y);
3376 MidiRegionView::drop_down_keys ()
3378 _mouse_state = None;
3382 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3384 double note = midi_stream_view()->y_to_note(y);
3386 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3388 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3390 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3391 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3392 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3393 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3398 bool add_mrv_selection = false;
3400 if (_selection.empty()) {
3401 add_mrv_selection = true;
3404 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3405 if (_selection.insert (*i).second) {
3406 (*i)->set_selected (true);
3410 if (add_mrv_selection) {
3411 PublicEditor& editor (trackview.editor());
3412 editor.get_selection().add (this);
3417 MidiRegionView::color_handler ()
3419 RegionView::color_handler ();
3421 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3422 (*i)->set_selected ((*i)->selected()); // will change color
3425 /* XXX probably more to do here */
3429 MidiRegionView::enable_display (bool yn)
3431 RegionView::enable_display (yn);
3438 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3440 if (_step_edit_cursor == 0) {
3441 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3443 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3444 _step_edit_cursor->property_y1() = 0;
3445 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3446 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3447 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3450 move_step_edit_cursor (pos);
3451 _step_edit_cursor->show ();
3455 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3457 _step_edit_cursor_position = pos;
3459 if (_step_edit_cursor) {
3460 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3461 _step_edit_cursor->property_x1() = pixel;
3462 set_step_edit_cursor_width (_step_edit_cursor_width);
3467 MidiRegionView::hide_step_edit_cursor ()
3469 if (_step_edit_cursor) {
3470 _step_edit_cursor->hide ();
3475 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3477 _step_edit_cursor_width = beats;
3479 if (_step_edit_cursor) {
3480 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3484 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3485 * @param buf Data that has been recorded.
3486 * @param w Source that this data will end up in.
3489 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3491 if (!_active_notes) {
3492 /* we aren't actively being recorded to */
3496 boost::shared_ptr<MidiSource> src = w.lock ();
3497 if (!src || src != midi_region()->midi_source()) {
3498 /* recorded data was not destined for our source */
3502 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3503 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3505 framepos_t back = max_framepos;
3507 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3508 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3509 assert (ev.buffer ());
3511 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3513 if (ev.type() == MIDI_CMD_NOTE_ON) {
3515 boost::shared_ptr<NoteType> note (
3516 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3519 add_note (note, true);
3521 /* fix up our note range */
3522 if (ev.note() < _current_range_min) {
3523 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3524 } else if (ev.note() > _current_range_max) {
3525 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3528 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3529 resolve_note (ev.note (), time_beats);
3535 midi_stream_view()->check_record_layers (region(), back);
3539 MidiRegionView::trim_front_starting ()
3541 /* Reparent the note group to the region view's parent, so that it doesn't change
3542 when the region view is trimmed.
3544 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3545 _temporary_note_group->move (group->property_x(), group->property_y());
3546 _note_group->reparent (*_temporary_note_group);
3550 MidiRegionView::trim_front_ending ()
3552 _note_group->reparent (*group);
3553 delete _temporary_note_group;
3554 _temporary_note_group = 0;
3556 if (_region->start() < 0) {
3557 /* Trim drag made start time -ve; fix this */
3558 midi_region()->fix_negative_start ();
3563 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3565 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3566 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3570 change_patch_change (pc->patch(), d.patch ());
3575 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3578 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3579 Evoral::midi_note_name (n->note()).c_str(),
3581 (int) n->channel() + 1,
3582 (int) n->velocity());
3584 show_verbose_cursor (buf, 10, 20);
3588 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3592 trackview.editor().get_pointer_position (wx, wy);
3597 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3599 double x1, y1, x2, y2;
3600 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3602 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3603 wy -= (y2 - y1) + 2 * yoffset;
3606 trackview.editor().verbose_cursor()->set (text, wx, wy);
3607 trackview.editor().verbose_cursor()->show ();