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 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
85 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
87 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
88 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
89 : RegionView (parent, tv, r, spu, basic_color)
91 , _last_channel_selection(0xFFFF)
92 , _current_range_min(0)
93 , _current_range_max(0)
94 , _model_name(string())
95 , _custom_device_mode(string())
97 , _note_group(new ArdourCanvas::Group(*group))
98 , _note_diff_command (0)
101 , _step_edit_cursor (0)
102 , _step_edit_cursor_width (1.0)
103 , _step_edit_cursor_position (0.0)
104 , _channel_selection_scoped_note (0)
105 , _temporary_note_group (0)
108 , _sort_needed (true)
109 , _optimization_iterator (_events.end())
111 , _no_sound_notes (false)
114 , _pre_enter_cursor (0)
116 _note_group->raise_to_top();
117 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
119 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
120 connect_to_diskstream ();
122 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
125 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
126 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
127 TimeAxisViewItem::Visibility visibility)
128 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
130 , _last_channel_selection(0xFFFF)
131 , _model_name(string())
132 , _custom_device_mode(string())
134 , _note_group(new ArdourCanvas::Group(*parent))
135 , _note_diff_command (0)
138 , _step_edit_cursor (0)
139 , _step_edit_cursor_width (1.0)
140 , _step_edit_cursor_position (0.0)
141 , _channel_selection_scoped_note (0)
142 , _temporary_note_group (0)
145 , _sort_needed (true)
146 , _optimization_iterator (_events.end())
148 , _no_sound_notes (false)
152 _note_group->raise_to_top();
153 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
155 connect_to_diskstream ();
157 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
161 MidiRegionView::parameter_changed (std::string const & p)
163 if (p == "diplay-first-midi-bank-as-zero") {
164 if (_enable_display) {
170 MidiRegionView::MidiRegionView (const MidiRegionView& other)
171 : sigc::trackable(other)
174 , _last_channel_selection(0xFFFF)
175 , _model_name(string())
176 , _custom_device_mode(string())
178 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
179 , _note_diff_command (0)
182 , _step_edit_cursor (0)
183 , _step_edit_cursor_width (1.0)
184 , _step_edit_cursor_position (0.0)
185 , _channel_selection_scoped_note (0)
186 , _temporary_note_group (0)
189 , _sort_needed (true)
190 , _optimization_iterator (_events.end())
192 , _no_sound_notes (false)
199 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
200 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
205 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
206 : RegionView (other, boost::shared_ptr<Region> (region))
208 , _last_channel_selection(0xFFFF)
209 , _model_name(string())
210 , _custom_device_mode(string())
212 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
213 , _note_diff_command (0)
216 , _step_edit_cursor (0)
217 , _step_edit_cursor_width (1.0)
218 , _step_edit_cursor_position (0.0)
219 , _channel_selection_scoped_note (0)
220 , _temporary_note_group (0)
223 , _sort_needed (true)
224 , _optimization_iterator (_events.end())
226 , _no_sound_notes (false)
233 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
234 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
240 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
242 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
244 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
245 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
249 midi_region()->midi_source(0)->load_model();
252 _model = midi_region()->midi_source(0)->model();
253 _enable_display = false;
255 RegionView::init (basic_color, false);
257 compute_colors (basic_color);
259 set_height (trackview.current_height());
262 region_sync_changed ();
263 region_resized (ARDOUR::bounds_change);
266 reset_width_dependent_items (_pixel_width);
270 _enable_display = true;
273 display_model (_model);
277 group->raise_to_top();
278 group->signal_event().connect(
279 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
281 midi_view()->signal_channel_mode_changed().connect(
282 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
284 midi_view()->signal_midi_patch_settings_changed().connect(
285 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
287 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
288 ui_bind(&MidiRegionView::snap_changed, this),
291 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
292 connect_to_diskstream ();
294 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
298 MidiRegionView::connect_to_diskstream ()
300 midi_view()->midi_track()->DataRecorded.connect(
301 *this, invalidator(*this),
302 ui_bind(&MidiRegionView::data_recorded, this, _1, _2),
307 MidiRegionView::canvas_event(GdkEvent* ev)
310 case GDK_ENTER_NOTIFY:
311 case GDK_LEAVE_NOTIFY:
312 _last_event_x = ev->crossing.x;
313 _last_event_y = ev->crossing.y;
315 case GDK_MOTION_NOTIFY:
316 _last_event_x = ev->motion.x;
317 _last_event_y = ev->motion.y;
323 if (!trackview.editor().internal_editing()) {
329 return scroll (&ev->scroll);
332 return key_press (&ev->key);
334 case GDK_KEY_RELEASE:
335 return key_release (&ev->key);
337 case GDK_BUTTON_PRESS:
338 return button_press (&ev->button);
340 case GDK_2BUTTON_PRESS:
343 case GDK_BUTTON_RELEASE:
344 return button_release (&ev->button);
346 case GDK_ENTER_NOTIFY:
347 return enter_notify (&ev->crossing);
349 case GDK_LEAVE_NOTIFY:
350 return leave_notify (&ev->crossing);
352 case GDK_MOTION_NOTIFY:
353 return motion (&ev->motion);
363 MidiRegionView::remove_ghost_note ()
370 MidiRegionView::enter_notify (GdkEventCrossing* ev)
372 trackview.editor().MouseModeChanged.connect (
373 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
376 if (trackview.editor().current_mouse_mode() == MouseRange) {
377 create_ghost_note (ev->x, ev->y);
380 if (!trackview.editor().internal_editing()) {
381 Keyboard::magic_widget_drop_focus();
383 Keyboard::magic_widget_grab_focus();
391 MidiRegionView::leave_notify (GdkEventCrossing* ev)
393 _mouse_mode_connection.disconnect ();
395 trackview.editor().verbose_cursor()->hide ();
396 remove_ghost_note ();
402 MidiRegionView::mouse_mode_changed ()
404 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
405 create_ghost_note (_last_event_x, _last_event_y);
407 remove_ghost_note ();
408 trackview.editor().verbose_cursor()->hide ();
411 if (!trackview.editor().internal_editing()) {
412 Keyboard::magic_widget_drop_focus();
414 Keyboard::magic_widget_grab_focus();
420 MidiRegionView::button_press (GdkEventButton* ev)
422 if (ev->button != 1) {
429 group->w2i (_last_x, _last_y);
431 if (_mouse_state != SelectTouchDragging) {
433 _pressed_button = ev->button;
434 _mouse_state = Pressed;
439 _pressed_button = ev->button;
445 MidiRegionView::button_release (GdkEventButton* ev)
447 double event_x, event_y;
449 if (ev->button != 1) {
456 group->w2i(event_x, event_y);
457 group->ungrab(ev->time);
459 switch (_mouse_state) {
460 case Pressed: // Clicked
462 switch (trackview.editor().current_mouse_mode()) {
468 if (Keyboard::is_insert_note_event(ev)) {
470 double event_x, event_y;
474 group->w2i(event_x, event_y);
477 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
483 create_note_at (event_x, event_y, beats, true, true);
491 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
497 create_note_at (event_x, event_y, beats, true, true);
508 case SelectRectDragging: // Select drag done
515 case AddDragging: // Add drag done
519 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) {
521 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
523 const double x = _drag_rect->property_x1();
524 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
526 create_note_at (x, _drag_rect->property_y1(), region_frames_to_region_beats(length), true, false);
533 create_ghost_note (ev->x, ev->y);
543 MidiRegionView::motion (GdkEventMotion* ev)
545 double event_x, event_y;
546 framepos_t event_frame = 0;
550 group->w2i(event_x, event_y);
552 PublicEditor& editor = trackview.editor ();
554 // convert event_x to global frame
555 framecnt_t grid_frames;
556 event_frame = snap_frame_to_grid_underneath (editor.pixel_to_frame (event_x), grid_frames);
558 if (!_ghost_note && editor.current_mouse_mode() != MouseRange
559 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
560 && _mouse_state != AddDragging) {
562 create_ghost_note (ev->x, ev->y);
563 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
564 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
566 update_ghost_note (ev->x, ev->y);
567 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
572 editor.verbose_cursor()->hide ();
573 } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
574 update_ghost_note (ev->x, ev->y);
577 /* any motion immediately hides velocity text that may have been visible */
579 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
580 (*i)->hide_velocity ();
583 switch (_mouse_state) {
584 case Pressed: // Maybe start a drag, if we've moved a bit
586 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
587 /* no appreciable movement since the button was pressed */
591 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
592 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
595 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
596 Gdk::Cursor(Gdk::FLEUR), ev->time);
600 _drag_start_x = event_x;
601 _drag_start_y = event_y;
603 _drag_rect = new ArdourCanvas::SimpleRect(*group);
604 _drag_rect->property_x1() = event_x;
605 _drag_rect->property_y1() = event_y;
606 _drag_rect->property_x2() = event_x;
607 _drag_rect->property_y2() = event_y;
608 _drag_rect->property_outline_what() = 0xFF;
609 _drag_rect->property_outline_color_rgba()
610 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
611 _drag_rect->property_fill_color_rgba()
612 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
614 _mouse_state = SelectRectDragging;
617 } else if (editor.internal_editing()) {
618 // Add note drag start
620 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
621 Gdk::Cursor(Gdk::FLEUR), ev->time);
625 _drag_start_x = event_x;
626 _drag_start_y = event_y;
628 _drag_rect = new ArdourCanvas::SimpleRect(*group);
629 _drag_rect->property_x1() = editor.frame_to_pixel(event_frame);
631 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
632 midi_stream_view()->y_to_note(event_y));
633 _drag_rect->property_x2() = editor.frame_to_pixel(event_frame);
635 _drag_rect->property_y2() = _drag_rect->property_y1()
636 + floor(midi_stream_view()->note_height());
637 _drag_rect->property_outline_what() = 0xFF;
638 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
639 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
641 _mouse_state = AddDragging;
646 editor.verbose_cursor()->hide ();
653 case SelectRectDragging: // Select drag motion
654 case AddDragging: // Add note drag motion
659 GdkModifierType state;
660 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
665 if (_mouse_state == AddDragging) {
666 event_x = editor.frame_to_pixel(event_frame);
668 if (editor.snap_mode() == SnapNormal) {
669 /* event_frame will have been snapped to the start of the note we are under;
670 it's more intuitive if we use the end of that note here
672 event_x = editor.frame_to_pixel (event_frame + grid_frames);
674 event_x = editor.frame_to_pixel (event_frame);
681 if (event_x > _drag_start_x) {
682 _drag_rect->property_x2() = event_x;
685 _drag_rect->property_x1() = event_x;
689 if (_drag_rect && _mouse_state == SelectRectDragging) {
691 if (event_y > _drag_start_y) {
692 _drag_rect->property_y2() = event_y;
695 _drag_rect->property_y1() = event_y;
698 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y, Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
704 case SelectTouchDragging:
716 MidiRegionView::scroll (GdkEventScroll* ev)
718 if (_selection.empty()) {
722 trackview.editor().verbose_cursor()->hide ();
724 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
726 if (ev->direction == GDK_SCROLL_UP) {
727 change_velocities (true, fine, false);
728 } else if (ev->direction == GDK_SCROLL_DOWN) {
729 change_velocities (false, fine, false);
735 MidiRegionView::key_press (GdkEventKey* ev)
737 /* since GTK bindings are generally activated on press, and since
738 detectable auto-repeat is the name of the game and only sends
739 repeated presses, carry out key actions at key press, not release.
742 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
744 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
745 _mouse_state = SelectTouchDragging;
748 } else if (ev->keyval == GDK_Escape && unmodified) {
752 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
754 bool start = (ev->keyval == GDK_comma);
755 bool end = (ev->keyval == GDK_period);
756 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
757 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
759 change_note_lengths (fine, shorter, 0.0, start, end);
763 } else if (ev->keyval == GDK_Delete && unmodified) {
768 } else if (ev->keyval == GDK_Tab) {
770 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
771 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
773 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
777 } else if (ev->keyval == GDK_ISO_Left_Tab) {
779 /* Shift-TAB generates ISO Left Tab, for some reason */
781 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
782 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
784 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
790 } else if (ev->keyval == GDK_Up) {
792 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
793 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
795 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
796 change_velocities (true, fine, allow_smush);
798 transpose (true, fine, allow_smush);
802 } else if (ev->keyval == GDK_Down) {
804 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
805 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
807 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
808 change_velocities (false, fine, allow_smush);
810 transpose (false, fine, allow_smush);
814 } else if (ev->keyval == GDK_Left && unmodified) {
819 } else if (ev->keyval == GDK_Right && unmodified) {
824 } else if (ev->keyval == GDK_c && unmodified) {
833 MidiRegionView::key_release (GdkEventKey* ev)
835 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
843 MidiRegionView::channel_edit ()
846 uint8_t current_channel;
848 if (_selection.empty()) {
852 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
854 current_channel = (*i)->note()->channel ();
859 MidiChannelDialog channel_dialog (current_channel);
860 int ret = channel_dialog.run ();
863 case Gtk::RESPONSE_OK:
869 uint8_t new_channel = channel_dialog.active_channel ();
871 start_note_diff_command (_("channel edit"));
873 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
874 Selection::iterator next = i;
876 change_note_channel (*i, new_channel);
884 MidiRegionView::show_list_editor ()
887 _list_editor = new MidiListEditor (trackview.session(), midi_region());
889 _list_editor->present ();
892 /** Add a note to the model, and the view, at a canvas (click) coordinate.
893 * \param x horizontal position in pixels
894 * \param y vertical position in pixels
895 * \param length duration of the note in beats, which will be snapped to the grid
896 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
897 * \param snap_x true to snap x to the grid, otherwise false.
900 MidiRegionView::create_note_at (double x, double y, double length, bool sh, bool snap_x)
902 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
903 MidiStreamView* const view = mtv->midi_view();
905 double note = view->y_to_note(y);
908 assert(note <= 127.0);
910 // Start of note in frames relative to region start
911 framepos_t start_frames = trackview.editor().pixel_to_frame (x);
913 framecnt_t grid_frames;
914 start_frames = snap_frame_to_grid_underneath (start_frames, grid_frames);
916 assert(start_frames >= 0);
919 length = region_frames_to_region_beats(
920 snap_frame_to_frame (start_frames + region_beats_to_region_frames(length)) - start_frames);
922 assert (length != 0);
925 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
928 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
929 region_frames_to_region_beats(start_frames + _region->start()), length,
930 (uint8_t)note, 0x40));
932 if (_model->contains (new_note)) {
936 view->update_note_range(new_note->note());
938 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
940 _model->apply_command(*trackview.session(), cmd);
942 play_midi_note (new_note);
946 MidiRegionView::clear_events()
951 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
952 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
957 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
962 _patch_changes.clear();
964 _optimization_iterator = _events.end();
968 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
972 content_connection.disconnect ();
973 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
977 if (_enable_display) {
983 MidiRegionView::start_note_diff_command (string name)
985 if (!_note_diff_command) {
986 _note_diff_command = _model->new_note_diff_command (name);
991 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
993 if (_note_diff_command) {
994 _note_diff_command->add (note);
997 _marked_for_selection.insert(note);
1000 _marked_for_velocity.insert(note);
1005 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
1007 if (_note_diff_command && ev->note()) {
1008 _note_diff_command->remove(ev->note());
1013 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1014 MidiModel::NoteDiffCommand::Property property,
1017 if (_note_diff_command) {
1018 _note_diff_command->change (ev->note(), property, val);
1023 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1024 MidiModel::NoteDiffCommand::Property property,
1025 Evoral::MusicalTime val)
1027 if (_note_diff_command) {
1028 _note_diff_command->change (ev->note(), property, val);
1033 MidiRegionView::apply_diff (bool as_subcommand)
1037 if (!_note_diff_command) {
1041 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1042 // Mark all selected notes for selection when model reloads
1043 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1044 _marked_for_selection.insert((*i)->note());
1048 if (as_subcommand) {
1049 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1051 _model->apply_command (*trackview.session(), _note_diff_command);
1054 _note_diff_command = 0;
1055 midi_view()->midi_track()->playlist_modified();
1057 if (add_or_remove) {
1058 _marked_for_selection.clear();
1061 _marked_for_velocity.clear();
1065 MidiRegionView::abort_command()
1067 delete _note_diff_command;
1068 _note_diff_command = 0;
1073 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1075 if (_optimization_iterator != _events.end()) {
1076 ++_optimization_iterator;
1079 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1080 return *_optimization_iterator;
1083 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1084 if ((*_optimization_iterator)->note() == note) {
1085 return *_optimization_iterator;
1093 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1095 MidiModel::Notes notes;
1096 _model->get_notes (notes, op, val, chan_mask);
1098 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1099 CanvasNoteEvent* cne = find_canvas_note (*n);
1107 MidiRegionView::redisplay_model()
1109 // Don't redisplay the model if we're currently recording and displaying that
1110 if (_active_notes) {
1118 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1119 (*i)->invalidate ();
1122 MidiModel::ReadLock lock(_model->read_lock());
1124 MidiModel::Notes& notes (_model->notes());
1125 _optimization_iterator = _events.begin();
1127 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1129 boost::shared_ptr<NoteType> note (*n);
1130 CanvasNoteEvent* cne;
1133 if (note_in_region_range (note, visible)) {
1135 if ((cne = find_canvas_note (note)) != 0) {
1142 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1144 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1156 add_note (note, visible);
1161 if ((cne = find_canvas_note (note)) != 0) {
1169 /* remove note items that are no longer valid */
1171 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1172 if (!(*i)->valid ()) {
1174 i = _events.erase (i);
1180 _patch_changes.clear();
1184 display_patch_changes ();
1186 _marked_for_selection.clear ();
1187 _marked_for_velocity.clear ();
1189 /* we may have caused _events to contain things out of order (e.g. if a note
1190 moved earlier or later). we don't generally need them in time order, but
1191 make a note that a sort is required for those cases that require it.
1194 _sort_needed = true;
1198 MidiRegionView::display_patch_changes ()
1200 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1201 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1203 for (uint8_t i = 0; i < 16; ++i) {
1204 if (chn_mask & (1<<i)) {
1205 display_patch_changes_on_channel (i);
1207 /* TODO gray-out patch instad of not displaying it */
1212 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1214 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1216 if ((*i)->channel() != channel) {
1220 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1222 boost::shared_ptr<MIDI::Name::Patch> patch =
1223 MIDI::Name::MidiPatchManager::instance().find_patch(
1224 _model_name, _custom_device_mode, channel, patch_key);
1227 add_canvas_patch_change (*i, patch->name());
1230 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1231 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1232 add_canvas_patch_change (*i, buf);
1238 MidiRegionView::display_sysexes()
1240 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1241 Evoral::MusicalTime time = (*i)->time();
1246 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1247 str << int((*i)->buffer()[b]);
1248 if (b != (*i)->size() -1) {
1252 string text = str.str();
1254 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1256 double height = midi_stream_view()->contents_height();
1258 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1259 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1261 // Show unless patch change is beyond the region bounds
1262 if (time - _region->start() >= _region->length() || time < _region->start()) {
1268 _sys_exes.push_back(sysex);
1273 MidiRegionView::~MidiRegionView ()
1275 in_destructor = true;
1277 trackview.editor().verbose_cursor()->hide ();
1279 note_delete_connection.disconnect ();
1281 delete _list_editor;
1283 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1285 if (_active_notes) {
1293 delete _note_diff_command;
1294 delete _step_edit_cursor;
1295 delete _temporary_note_group;
1299 MidiRegionView::region_resized (const PropertyChange& what_changed)
1301 RegionView::region_resized(what_changed);
1303 if (what_changed.contains (ARDOUR::Properties::position)) {
1304 set_duration(_region->length(), 0);
1305 if (_enable_display) {
1312 MidiRegionView::reset_width_dependent_items (double pixel_width)
1314 RegionView::reset_width_dependent_items(pixel_width);
1315 assert(_pixel_width == pixel_width);
1317 if (_enable_display) {
1321 move_step_edit_cursor (_step_edit_cursor_position);
1322 set_step_edit_cursor_width (_step_edit_cursor_width);
1326 MidiRegionView::set_height (double height)
1328 static const double FUDGE = 2.0;
1329 const double old_height = _height;
1330 RegionView::set_height(height);
1331 _height = height - FUDGE;
1333 apply_note_range(midi_stream_view()->lowest_note(),
1334 midi_stream_view()->highest_note(),
1335 height != old_height + FUDGE);
1338 name_pixbuf->raise_to_top();
1341 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1342 (*x)->set_height (midi_stream_view()->contents_height());
1345 if (_step_edit_cursor) {
1346 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1351 /** Apply the current note range from the stream view
1352 * by repositioning/hiding notes as necessary
1355 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1357 if (!_enable_display) {
1361 if (!force && _current_range_min == min && _current_range_max == max) {
1365 _current_range_min = min;
1366 _current_range_max = max;
1368 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1369 CanvasNoteEvent* event = *i;
1370 boost::shared_ptr<NoteType> note (event->note());
1372 if (note->note() < _current_range_min ||
1373 note->note() > _current_range_max) {
1379 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1381 const double y1 = midi_stream_view()->note_to_y(note->note());
1382 const double y2 = y1 + floor(midi_stream_view()->note_height());
1384 cnote->property_y1() = y1;
1385 cnote->property_y2() = y2;
1387 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1389 const double diamond_size = update_hit (chit);
1391 chit->set_height (diamond_size);
1397 MidiRegionView::add_ghost (TimeAxisView& tv)
1401 double unit_position = _region->position () / samples_per_unit;
1402 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1403 MidiGhostRegion* ghost;
1405 if (mtv && mtv->midi_view()) {
1406 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1407 to allow having midi notes on top of note lines and waveforms.
1409 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1411 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1414 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1415 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1416 ghost->add_note(note);
1420 ghost->set_height ();
1421 ghost->set_duration (_region->length() / samples_per_unit);
1422 ghosts.push_back (ghost);
1424 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1430 /** Begin tracking note state for successive calls to add_event
1433 MidiRegionView::begin_write()
1435 assert(!_active_notes);
1436 _active_notes = new CanvasNote*[128];
1437 for (unsigned i=0; i < 128; ++i) {
1438 _active_notes[i] = 0;
1443 /** Destroy note state for add_event
1446 MidiRegionView::end_write()
1448 delete[] _active_notes;
1450 _marked_for_selection.clear();
1451 _marked_for_velocity.clear();
1455 /** Resolve an active MIDI note (while recording).
1458 MidiRegionView::resolve_note(uint8_t note, double end_time)
1460 if (midi_view()->note_mode() != Sustained) {
1464 if (_active_notes && _active_notes[note]) {
1466 /* XXX is end_time really region-centric? I think so, because
1467 this is a new region that we're recording, so source zero is
1468 the same as region zero
1470 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1472 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1473 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1474 _active_notes[note] = 0;
1479 /** Extend active notes to rightmost edge of region (if length is changed)
1482 MidiRegionView::extend_active_notes()
1484 if (!_active_notes) {
1488 for (unsigned i=0; i < 128; ++i) {
1489 if (_active_notes[i]) {
1490 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1497 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1499 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1503 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1505 if (!route_ui || !route_ui->midi_track()) {
1509 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1515 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1517 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1521 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1523 if (!route_ui || !route_ui->midi_track()) {
1527 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1529 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1538 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1540 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1541 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1543 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1544 (note->note() <= midi_stream_view()->highest_note());
1549 /** Update a canvas note's size from its model note.
1550 * @param ev Canvas note to update.
1551 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1554 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1556 boost::shared_ptr<NoteType> note = ev->note();
1558 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1559 const double y1 = midi_stream_view()->note_to_y(note->note());
1561 ev->property_x1() = x;
1562 ev->property_y1() = y1;
1564 /* trim note display to not overlap the end of its region */
1566 if (note->length() > 0) {
1567 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1568 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1570 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1573 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1575 if (note->length() == 0) {
1576 if (_active_notes) {
1577 assert(note->note() < 128);
1578 // If this note is already active there's a stuck note,
1579 // finish the old note rectangle
1580 if (_active_notes[note->note()]) {
1581 CanvasNote* const old_rect = _active_notes[note->note()];
1582 boost::shared_ptr<NoteType> old_note = old_rect->note();
1583 old_rect->property_x2() = x;
1584 old_rect->property_outline_what() = (guint32) 0xF;
1586 _active_notes[note->note()] = ev;
1588 /* outline all but right edge */
1589 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1591 /* outline all edges */
1592 ev->property_outline_what() = (guint32) 0xF;
1595 if (update_ghost_regions) {
1596 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1597 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1599 gr->update_note (ev);
1606 MidiRegionView::update_hit (CanvasHit* ev)
1608 boost::shared_ptr<NoteType> note = ev->note();
1610 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1611 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1612 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1613 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1617 return diamond_size;
1620 /** Add a MIDI note to the view (with length).
1622 * If in sustained mode, notes with length 0 will be considered active
1623 * notes, and resolve_note should be called when the corresponding note off
1624 * event arrives, to properly display the note.
1627 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1629 CanvasNoteEvent* event = 0;
1631 assert(note->time() >= 0);
1632 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1634 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1636 if (midi_view()->note_mode() == Sustained) {
1638 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1640 update_note (ev_rect);
1644 MidiGhostRegion* gr;
1646 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1647 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1648 gr->add_note(ev_rect);
1652 } else if (midi_view()->note_mode() == Percussive) {
1654 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1656 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1658 update_hit (ev_diamond);
1667 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1668 note_selected(event, true);
1671 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1672 event->show_velocity();
1675 event->on_channel_selection_change(_last_channel_selection);
1676 _events.push_back(event);
1685 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1686 MidiStreamView* const view = mtv->midi_view();
1688 view->update_note_range (note->note());
1692 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1693 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1695 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1697 /* potentially extend region to hold new note */
1699 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1700 framepos_t region_end = _region->last_frame();
1702 if (end_frame > region_end) {
1703 _region->set_length (end_frame - _region->position());
1706 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1707 MidiStreamView* const view = mtv->midi_view();
1709 view->update_note_range(new_note->note());
1711 _marked_for_selection.clear ();
1714 start_note_diff_command (_("step add"));
1715 note_diff_add_note (new_note, true, false);
1718 // last_step_edit_note = new_note;
1722 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1724 change_note_lengths (false, false, beats, false, true);
1728 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1730 assert (patch->time() >= 0);
1732 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (patch->time()));
1734 double const height = midi_stream_view()->contents_height();
1736 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1737 new CanvasPatchChange(*this, *_note_group,
1742 _custom_device_mode,
1746 // Show unless patch change is beyond the region bounds
1747 if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1748 patch_change->hide();
1750 patch_change->show();
1753 _patch_changes.push_back (patch_change);
1757 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1759 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1760 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1764 if (i != _model->patch_changes().end()) {
1765 key.msb = (*i)->bank_msb ();
1766 key.lsb = (*i)->bank_lsb ();
1767 key.program_number = (*i)->program ();
1769 key.msb = key.lsb = key.program_number = 0;
1772 assert (key.is_sane());
1777 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1779 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1781 if (pc.patch()->program() != new_patch.program_number) {
1782 c->change_program (pc.patch (), new_patch.program_number);
1785 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1786 if (pc.patch()->bank() != new_bank) {
1787 c->change_bank (pc.patch (), new_bank);
1790 _model->apply_command (*trackview.session(), c);
1792 _patch_changes.clear ();
1793 display_patch_changes ();
1797 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1799 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1801 if (old_change->time() != new_change.time()) {
1802 c->change_time (old_change, new_change.time());
1805 if (old_change->channel() != new_change.channel()) {
1806 c->change_channel (old_change, new_change.channel());
1809 if (old_change->program() != new_change.program()) {
1810 c->change_program (old_change, new_change.program());
1813 if (old_change->bank() != new_change.bank()) {
1814 c->change_bank (old_change, new_change.bank());
1817 _model->apply_command (*trackview.session(), c);
1819 _patch_changes.clear ();
1820 display_patch_changes ();
1823 /** Add a patch change to the region.
1824 * @param t Time in frames relative to region position
1825 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1826 * MidiTimeAxisView::get_channel_for_add())
1829 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1831 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1833 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1834 c->add (MidiModel::PatchChangePtr (
1835 new Evoral::PatchChange<Evoral::MusicalTime> (
1836 absolute_frames_to_source_beats (_region->position() + t),
1837 mtv->get_channel_for_add(), patch.program(), patch.bank()
1842 _model->apply_command (*trackview.session(), c);
1844 _patch_changes.clear ();
1845 display_patch_changes ();
1849 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1851 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1852 c->change_time (pc.patch (), t);
1853 _model->apply_command (*trackview.session(), c);
1855 _patch_changes.clear ();
1856 display_patch_changes ();
1860 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1862 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1863 c->remove (pc->patch ());
1864 _model->apply_command (*trackview.session(), c);
1866 _patch_changes.clear ();
1867 display_patch_changes ();
1871 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1873 if (patch.patch()->program() < 127) {
1874 MIDI::Name::PatchPrimaryKey key;
1875 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1876 key.program_number++;
1877 change_patch_change (patch, key);
1882 MidiRegionView::next_patch (CanvasPatchChange& patch)
1884 if (patch.patch()->program() > 0) {
1885 MIDI::Name::PatchPrimaryKey key;
1886 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1887 key.program_number--;
1888 change_patch_change (patch, key);
1893 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1895 if (patch.patch()->program() < 127) {
1896 MIDI::Name::PatchPrimaryKey key;
1897 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1900 change_patch_change (patch, key);
1905 change_patch_change (patch, key);
1912 MidiRegionView::next_bank (CanvasPatchChange& patch)
1914 if (patch.patch()->program() > 0) {
1915 MIDI::Name::PatchPrimaryKey key;
1916 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1917 if (key.lsb < 127) {
1919 change_patch_change (patch, key);
1921 if (key.msb < 127) {
1924 change_patch_change (patch, key);
1931 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1933 if (_selection.empty()) {
1937 _selection.erase (cne);
1941 MidiRegionView::delete_selection()
1943 if (_selection.empty()) {
1947 start_note_diff_command (_("delete selection"));
1949 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1950 if ((*i)->selected()) {
1951 _note_diff_command->remove((*i)->note());
1961 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1963 start_note_diff_command (_("delete note"));
1964 _note_diff_command->remove (n);
1967 trackview.editor().verbose_cursor()->hide ();
1971 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1973 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1975 Selection::iterator tmp = i;
1978 (*i)->set_selected (false);
1979 (*i)->hide_velocity ();
1980 _selection.erase (i);
1988 /* this does not change the status of this regionview w.r.t the editor
1993 SelectionCleared (this); /* EMIT SIGNAL */
1998 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2000 clear_selection_except (ev);
2002 /* don't bother with checking to see if we should remove this
2003 regionview from the editor selection, since we're about to add
2004 another note, and thus put/keep this regionview in the editor
2008 if (!ev->selected()) {
2009 add_to_selection (ev);
2014 MidiRegionView::select_all_notes ()
2018 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2019 add_to_selection (*i);
2024 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2026 uint8_t low_note = 127;
2027 uint8_t high_note = 0;
2028 MidiModel::Notes& notes (_model->notes());
2029 _optimization_iterator = _events.begin();
2035 if (extend && _selection.empty()) {
2041 /* scan existing selection to get note range */
2043 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2044 if ((*i)->note()->note() < low_note) {
2045 low_note = (*i)->note()->note();
2047 if ((*i)->note()->note() > high_note) {
2048 high_note = (*i)->note()->note();
2052 low_note = min (low_note, notenum);
2053 high_note = max (high_note, notenum);
2056 _no_sound_notes = true;
2058 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2060 boost::shared_ptr<NoteType> note (*n);
2061 CanvasNoteEvent* cne;
2062 bool select = false;
2064 if (((1 << note->channel()) & channel_mask) != 0) {
2066 if ((note->note() >= low_note && note->note() <= high_note)) {
2069 } else if (note->note() == notenum) {
2075 if ((cne = find_canvas_note (note)) != 0) {
2076 // extend is false because we've taken care of it,
2077 // since it extends by time range, not pitch.
2078 note_selected (cne, add, false);
2082 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2086 _no_sound_notes = false;
2090 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2092 MidiModel::Notes& notes (_model->notes());
2093 _optimization_iterator = _events.begin();
2095 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2097 boost::shared_ptr<NoteType> note (*n);
2098 CanvasNoteEvent* cne;
2100 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2101 if ((cne = find_canvas_note (note)) != 0) {
2102 if (cne->selected()) {
2103 note_deselected (cne);
2105 note_selected (cne, true, false);
2113 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2116 clear_selection_except (ev);
2117 if (!_selection.empty()) {
2118 PublicEditor& editor (trackview.editor());
2119 editor.get_selection().add (this);
2125 if (!ev->selected()) {
2126 add_to_selection (ev);
2130 /* find end of latest note selected, select all between that and the start of "ev" */
2132 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2133 Evoral::MusicalTime latest = 0;
2135 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2136 if ((*i)->note()->end_time() > latest) {
2137 latest = (*i)->note()->end_time();
2139 if ((*i)->note()->time() < earliest) {
2140 earliest = (*i)->note()->time();
2144 if (ev->note()->end_time() > latest) {
2145 latest = ev->note()->end_time();
2148 if (ev->note()->time() < earliest) {
2149 earliest = ev->note()->time();
2152 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2154 /* find notes entirely within OR spanning the earliest..latest range */
2156 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2157 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2158 add_to_selection (*i);
2166 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2168 remove_from_selection (ev);
2172 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2182 // TODO: Make this faster by storing the last updated selection rect, and only
2183 // adjusting things that are in the area that appears/disappeared.
2184 // We probably need a tree to be able to find events in O(log(n)) time.
2186 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2188 /* check if any corner of the note is inside the rect
2191 1) this is computing "touched by", not "contained by" the rect.
2192 2) this does not require that events be sorted in time.
2195 const double ix1 = (*i)->x1();
2196 const double ix2 = (*i)->x2();
2197 const double iy1 = (*i)->y1();
2198 const double iy2 = (*i)->y2();
2200 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2201 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2202 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2203 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2206 if (!(*i)->selected()) {
2207 add_to_selection (*i);
2209 } else if ((*i)->selected() && !extend) {
2210 // Not inside rectangle
2211 remove_from_selection (*i);
2217 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2219 Selection::iterator i = _selection.find (ev);
2221 if (i != _selection.end()) {
2222 _selection.erase (i);
2225 ev->set_selected (false);
2226 ev->hide_velocity ();
2228 if (_selection.empty()) {
2229 PublicEditor& editor (trackview.editor());
2230 editor.get_selection().remove (this);
2235 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2237 bool add_mrv_selection = false;
2239 if (_selection.empty()) {
2240 add_mrv_selection = true;
2243 if (_selection.insert (ev).second) {
2244 ev->set_selected (true);
2245 play_midi_note ((ev)->note());
2248 if (add_mrv_selection) {
2249 PublicEditor& editor (trackview.editor());
2250 editor.get_selection().add (this);
2255 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2257 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2258 PossibleChord to_play;
2259 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2261 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2262 if ((*i)->note()->time() < earliest) {
2263 earliest = (*i)->note()->time();
2267 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2268 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2269 to_play.push_back ((*i)->note());
2271 (*i)->move_event(dx, dy);
2274 if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2276 if (to_play.size() > 1) {
2278 PossibleChord shifted;
2280 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2281 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2282 moved_note->set_note (moved_note->note() + cumulative_dy);
2283 shifted.push_back (moved_note);
2286 play_midi_chord (shifted);
2288 } else if (!to_play.empty()) {
2290 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2291 moved_note->set_note (moved_note->note() + cumulative_dy);
2292 play_midi_note (moved_note);
2298 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2300 assert (!_selection.empty());
2302 uint8_t lowest_note_in_selection = 127;
2303 uint8_t highest_note_in_selection = 0;
2304 uint8_t highest_note_difference = 0;
2306 // find highest and lowest notes first
2308 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2309 uint8_t pitch = (*i)->note()->note();
2310 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2311 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2315 cerr << "dnote: " << (int) dnote << endl;
2316 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2317 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2318 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2319 << int(highest_note_in_selection) << endl;
2320 cerr << "selection size: " << _selection.size() << endl;
2321 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2324 // Make sure the note pitch does not exceed the MIDI standard range
2325 if (highest_note_in_selection + dnote > 127) {
2326 highest_note_difference = highest_note_in_selection - 127;
2329 start_note_diff_command (_("move notes"));
2331 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2333 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2339 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2341 uint8_t original_pitch = (*i)->note()->note();
2342 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2344 // keep notes in standard midi range
2345 clamp_to_0_127(new_pitch);
2347 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2348 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2350 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2355 // care about notes being moved beyond the upper/lower bounds on the canvas
2356 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2357 highest_note_in_selection > midi_stream_view()->highest_note()) {
2358 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2363 MidiRegionView::snap_pixel_to_frame(double x)
2365 PublicEditor& editor (trackview.editor());
2366 return snap_frame_to_frame (editor.pixel_to_frame (x));
2370 MidiRegionView::snap_to_pixel(double x)
2372 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2376 MidiRegionView::get_position_pixels()
2378 framepos_t region_frame = get_position();
2379 return trackview.editor().frame_to_pixel(region_frame);
2383 MidiRegionView::get_end_position_pixels()
2385 framepos_t frame = get_position() + get_duration ();
2386 return trackview.editor().frame_to_pixel(frame);
2390 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2392 /* the time converter will return the frame corresponding to `beats'
2393 relative to the start of the source. The start of the source
2394 is an implied position given by region->position - region->start
2396 const framepos_t source_start = _region->position() - _region->start();
2397 return source_start + _source_relative_time_converter.to (beats);
2401 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2403 /* the `frames' argument needs to be converted into a frame count
2404 relative to the start of the source before being passed in to the
2407 const framepos_t source_start = _region->position() - _region->start();
2408 return _source_relative_time_converter.from (frames - source_start);
2412 MidiRegionView::region_beats_to_region_frames(double beats) const
2414 return _region_relative_time_converter.to(beats);
2418 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2420 return _region_relative_time_converter.from(frames);
2424 MidiRegionView::begin_resizing (bool /*at_front*/)
2426 _resize_data.clear();
2428 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2429 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2431 // only insert CanvasNotes into the map
2433 NoteResizeData *resize_data = new NoteResizeData();
2434 resize_data->canvas_note = note;
2436 // create a new SimpleRect from the note which will be the resize preview
2437 SimpleRect *resize_rect = new SimpleRect(
2438 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2440 // calculate the colors: get the color settings
2441 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2442 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2445 // make the resize preview notes more transparent and bright
2446 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2448 // calculate color based on note velocity
2449 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2450 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2454 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2455 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2457 resize_data->resize_rect = resize_rect;
2458 _resize_data.push_back(resize_data);
2463 /** Update resizing notes while user drags.
2464 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2465 * @param at_front which end of the note (true == note on, false == note off)
2466 * @param delta_x change in mouse position since the start of the drag
2467 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2468 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2469 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2470 * as the \a primary note.
2473 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2475 bool cursor_set = false;
2477 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2478 SimpleRect* resize_rect = (*i)->resize_rect;
2479 CanvasNote* canvas_note = (*i)->canvas_note;
2484 current_x = canvas_note->x1() + delta_x;
2486 current_x = primary->x1() + delta_x;
2490 current_x = canvas_note->x2() + delta_x;
2492 current_x = primary->x2() + delta_x;
2497 resize_rect->property_x1() = snap_to_pixel(current_x);
2498 resize_rect->property_x2() = canvas_note->x2();
2500 resize_rect->property_x2() = snap_to_pixel(current_x);
2501 resize_rect->property_x1() = canvas_note->x1();
2507 beats = snap_pixel_to_frame (current_x);
2508 /* XXX not sure this is correct - snap_pixel_to_frame()
2509 returns an absolute frame.
2511 beats = region_frames_to_region_beats (beats);
2516 if (beats < canvas_note->note()->end_time()) {
2517 len = canvas_note->note()->time() - beats;
2518 len += canvas_note->note()->length();
2523 if (beats >= canvas_note->note()->time()) {
2524 len = beats - canvas_note->note()->time();
2531 snprintf (buf, sizeof (buf), "%.3g beats", len);
2532 show_verbose_cursor (buf, 0, 0);
2541 /** Finish resizing notes when the user releases the mouse button.
2542 * Parameters the same as for \a update_resizing().
2545 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2547 start_note_diff_command (_("resize notes"));
2549 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2550 CanvasNote* canvas_note = (*i)->canvas_note;
2551 SimpleRect* resize_rect = (*i)->resize_rect;
2553 /* Get the new x position for this resize, which is in pixels relative
2554 * to the region position.
2561 current_x = canvas_note->x1() + delta_x;
2563 current_x = primary->x1() + delta_x;
2567 current_x = canvas_note->x2() + delta_x;
2569 current_x = primary->x2() + delta_x;
2573 /* Convert that to a frame within the region */
2574 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2576 /* and then to beats */
2577 /* XXX not sure this is correct - snap_pixel_to_frame()
2578 returns an absolute frame.
2580 current_x = region_frames_to_region_beats (current_x);
2582 if (at_front && current_x < canvas_note->note()->end_time()) {
2583 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2585 double len = canvas_note->note()->time() - current_x;
2586 len += canvas_note->note()->length();
2589 /* XXX convert to beats */
2590 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2595 double len = current_x - canvas_note->note()->time();
2598 /* XXX convert to beats */
2599 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2607 _resize_data.clear();
2612 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2614 uint8_t new_velocity;
2617 new_velocity = event->note()->velocity() + velocity;
2618 clamp_to_0_127(new_velocity);
2620 new_velocity = velocity;
2623 event->set_selected (event->selected()); // change color
2625 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2629 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2634 new_note = event->note()->note() + note;
2639 clamp_to_0_127 (new_note);
2640 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2644 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2646 bool change_start = false;
2647 bool change_length = false;
2648 Evoral::MusicalTime new_start = 0;
2649 Evoral::MusicalTime new_length = 0;
2651 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2653 front_delta: if positive - move the start of the note later in time (shortening it)
2654 if negative - move the start of the note earlier in time (lengthening it)
2656 end_delta: if positive - move the end of the note later in time (lengthening it)
2657 if negative - move the end of the note earlier in time (shortening it)
2661 if (front_delta < 0) {
2663 if (event->note()->time() < -front_delta) {
2666 new_start = event->note()->time() + front_delta; // moves earlier
2669 /* start moved toward zero, so move the end point out to where it used to be.
2670 Note that front_delta is negative, so this increases the length.
2673 new_length = event->note()->length() - front_delta;
2674 change_start = true;
2675 change_length = true;
2679 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2681 if (new_pos < event->note()->end_time()) {
2682 new_start = event->note()->time() + front_delta;
2683 /* start moved toward the end, so move the end point back to where it used to be */
2684 new_length = event->note()->length() - front_delta;
2685 change_start = true;
2686 change_length = true;
2693 bool can_change = true;
2694 if (end_delta < 0) {
2695 if (event->note()->length() < -end_delta) {
2701 new_length = event->note()->length() + end_delta;
2702 change_length = true;
2707 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2710 if (change_length) {
2711 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2716 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2718 uint8_t new_channel;
2722 if (event->note()->channel() < -chn) {
2725 new_channel = event->note()->channel() + chn;
2728 new_channel = event->note()->channel() + chn;
2731 new_channel = (uint8_t) chn;
2734 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2738 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2740 Evoral::MusicalTime new_time;
2744 if (event->note()->time() < -delta) {
2747 new_time = event->note()->time() + delta;
2750 new_time = event->note()->time() + delta;
2756 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2760 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2762 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2766 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2770 if (_selection.empty()) {
2785 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2786 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2792 start_note_diff_command (_("change velocities"));
2794 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2795 Selection::iterator next = i;
2797 change_note_velocity (*i, delta, true);
2803 if (!_selection.empty()) {
2805 snprintf (buf, sizeof (buf), "Vel %d",
2806 (int) (*_selection.begin())->note()->velocity());
2807 show_verbose_cursor (buf, 10, 10);
2813 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2815 if (_selection.empty()) {
2832 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2834 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2838 if ((int8_t) (*i)->note()->note() + delta > 127) {
2845 start_note_diff_command (_("transpose"));
2847 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2848 Selection::iterator next = i;
2850 change_note_note (*i, delta, true);
2858 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2864 /* grab the current grid distance */
2866 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2868 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2869 cerr << "Grid type not available as beats - TO BE FIXED\n";
2879 start_note_diff_command (_("change note lengths"));
2881 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2882 Selection::iterator next = i;
2885 /* note the negation of the delta for start */
2887 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2896 MidiRegionView::nudge_notes (bool forward)
2898 if (_selection.empty()) {
2902 /* pick a note as the point along the timeline to get the nudge distance.
2903 its not necessarily the earliest note, so we may want to pull the notes out
2904 into a vector and sort before using the first one.
2907 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2909 framepos_t distance;
2911 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2913 /* grid is off - use nudge distance */
2915 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2921 framepos_t next_pos = ref_point;
2924 if (max_framepos - 1 < next_pos) {
2928 if (next_pos == 0) {
2934 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2935 distance = ref_point - next_pos;
2938 if (distance == 0) {
2942 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2948 start_note_diff_command (_("nudge"));
2950 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2951 Selection::iterator next = i;
2953 change_note_time (*i, delta, true);
2961 MidiRegionView::change_channel(uint8_t channel)
2963 start_note_diff_command(_("change channel"));
2964 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2965 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2973 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2975 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2977 _pre_enter_cursor = editor->get_canvas_cursor ();
2979 if (_mouse_state == SelectTouchDragging) {
2980 note_selected (ev, true);
2983 show_verbose_cursor (ev->note ());
2987 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2989 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2991 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2992 (*i)->hide_velocity ();
2995 editor->verbose_cursor()->hide ();
2997 if (_pre_enter_cursor) {
2998 editor->set_canvas_cursor (_pre_enter_cursor);
2999 _pre_enter_cursor = 0;
3004 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3007 /* XXX should get patch name if we can */
3008 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3009 show_verbose_cursor (s.str(), 10, 20);
3013 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3015 trackview.editor().verbose_cursor()->hide ();
3019 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3021 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3023 if (x_fraction > 0.0 && x_fraction < 0.25) {
3024 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3025 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3026 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3028 if (_pre_enter_cursor && can_set_cursor) {
3029 editor->set_canvas_cursor (_pre_enter_cursor);
3035 MidiRegionView::set_frame_color()
3039 TimeAxisViewItem::set_frame_color ();
3046 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3047 } else if (high_enough_for_name) {
3048 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3053 if (!rect_visible) {
3054 f = UINT_RGBA_CHANGE_A (f, 0);
3057 frame->property_fill_color_rgba() = f;
3061 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3065 case FilterChannels:
3066 _force_channel = -1;
3069 _force_channel = mask;
3070 mask = 0xFFFF; // Show all notes as active (below)
3073 // Update notes for selection
3074 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3075 (*i)->on_channel_selection_change(mask);
3078 _last_channel_selection = mask;
3080 _patch_changes.clear ();
3081 display_patch_changes ();
3085 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3087 _model_name = model;
3088 _custom_device_mode = custom_device_mode;
3093 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3095 if (_selection.empty()) {
3099 PublicEditor& editor (trackview.editor());
3103 /* XXX what to do ? */
3107 editor.get_cut_buffer().add (selection_as_cut_buffer());
3115 start_note_diff_command();
3117 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3124 note_diff_remove_note (*i);
3134 MidiRegionView::selection_as_cut_buffer () const
3138 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3139 NoteType* n = (*i)->note().get();
3140 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3143 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3149 /** This method handles undo */
3151 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3157 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3159 trackview.session()->begin_reversible_command (_("paste"));
3161 start_note_diff_command (_("paste"));
3163 Evoral::MusicalTime beat_delta;
3164 Evoral::MusicalTime paste_pos_beats;
3165 Evoral::MusicalTime duration;
3166 Evoral::MusicalTime end_point = 0;
3168 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3169 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3170 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3171 paste_pos_beats = 0;
3173 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",
3174 (*mcb.notes().begin())->time(),
3175 (*mcb.notes().rbegin())->end_time(),
3176 duration, pos, _region->position(),
3177 paste_pos_beats, beat_delta));
3181 for (int n = 0; n < (int) times; ++n) {
3183 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3185 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3186 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3188 /* make all newly added notes selected */
3190 note_diff_add_note (copied_note, true);
3191 end_point = copied_note->end_time();
3194 paste_pos_beats += duration;
3197 /* if we pasted past the current end of the region, extend the region */
3199 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3200 framepos_t region_end = _region->position() + _region->length() - 1;
3202 if (end_frame > region_end) {
3204 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3206 _region->clear_changes ();
3207 _region->set_length (end_frame);
3208 trackview.session()->add_command (new StatefulDiffCommand (_region));
3213 trackview.session()->commit_reversible_command ();
3216 struct EventNoteTimeEarlyFirstComparator {
3217 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3218 return a->note()->time() < b->note()->time();
3223 MidiRegionView::time_sort_events ()
3225 if (!_sort_needed) {
3229 EventNoteTimeEarlyFirstComparator cmp;
3232 _sort_needed = false;
3236 MidiRegionView::goto_next_note (bool add_to_selection)
3238 bool use_next = false;
3240 if (_events.back()->selected()) {
3244 time_sort_events ();
3246 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3247 if ((*i)->selected()) {
3250 } else if (use_next) {
3251 if (!add_to_selection) {
3254 note_selected (*i, true, false);
3260 /* use the first one */
3262 unique_select (_events.front());
3267 MidiRegionView::goto_previous_note (bool add_to_selection)
3269 bool use_next = false;
3271 if (_events.front()->selected()) {
3275 time_sort_events ();
3277 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3278 if ((*i)->selected()) {
3281 } else if (use_next) {
3282 if (!add_to_selection) {
3285 note_selected (*i, true, false);
3291 /* use the last one */
3293 unique_select (*(_events.rbegin()));
3297 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3299 bool had_selected = false;
3301 time_sort_events ();
3303 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3304 if ((*i)->selected()) {
3305 selected.insert ((*i)->note());
3306 had_selected = true;
3310 if (allow_all_if_none_selected && !had_selected) {
3311 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3312 selected.insert ((*i)->note());
3318 MidiRegionView::update_ghost_note (double x, double y)
3320 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3325 _note_group->w2i (x, y);
3327 PublicEditor& editor = trackview.editor ();
3329 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3330 framecnt_t grid_frames;
3331 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3333 /* use region_frames... because we are converting a delta within the region
3336 double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3338 /* note that this sets the time of the ghost note in beats relative to
3339 the start of the source; that is how all note times are stored.
3341 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3342 _ghost_note->note()->set_length (length);
3343 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3344 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3346 /* the ghost note does not appear in ghost regions, so pass false in here */
3347 update_note (_ghost_note, false);
3349 show_verbose_cursor (_ghost_note->note ());
3353 MidiRegionView::create_ghost_note (double x, double y)
3358 boost::shared_ptr<NoteType> g (new NoteType);
3359 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3360 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3361 update_ghost_note (x, y);
3362 _ghost_note->show ();
3367 show_verbose_cursor (_ghost_note->note ());
3371 MidiRegionView::snap_changed ()
3377 create_ghost_note (_last_ghost_x, _last_ghost_y);
3381 MidiRegionView::drop_down_keys ()
3383 _mouse_state = None;
3387 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3389 double note = midi_stream_view()->y_to_note(y);
3391 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3393 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3395 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3396 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3397 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3398 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3403 bool add_mrv_selection = false;
3405 if (_selection.empty()) {
3406 add_mrv_selection = true;
3409 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3410 if (_selection.insert (*i).second) {
3411 (*i)->set_selected (true);
3415 if (add_mrv_selection) {
3416 PublicEditor& editor (trackview.editor());
3417 editor.get_selection().add (this);
3422 MidiRegionView::color_handler ()
3424 RegionView::color_handler ();
3426 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3427 (*i)->set_selected ((*i)->selected()); // will change color
3430 /* XXX probably more to do here */
3434 MidiRegionView::enable_display (bool yn)
3436 RegionView::enable_display (yn);
3443 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3445 if (_step_edit_cursor == 0) {
3446 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3448 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3449 _step_edit_cursor->property_y1() = 0;
3450 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3451 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3452 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3455 move_step_edit_cursor (pos);
3456 _step_edit_cursor->show ();
3460 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3462 _step_edit_cursor_position = pos;
3464 if (_step_edit_cursor) {
3465 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3466 _step_edit_cursor->property_x1() = pixel;
3467 set_step_edit_cursor_width (_step_edit_cursor_width);
3472 MidiRegionView::hide_step_edit_cursor ()
3474 if (_step_edit_cursor) {
3475 _step_edit_cursor->hide ();
3480 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3482 _step_edit_cursor_width = beats;
3484 if (_step_edit_cursor) {
3485 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3489 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3490 * @param buf Data that has been recorded.
3491 * @param w Source that this data will end up in.
3494 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3496 if (!_active_notes) {
3497 /* we aren't actively being recorded to */
3501 boost::shared_ptr<MidiSource> src = w.lock ();
3502 if (!src || src != midi_region()->midi_source()) {
3503 /* recorded data was not destined for our source */
3507 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3508 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3510 framepos_t back = max_framepos;
3512 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3513 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3514 assert (ev.buffer ());
3516 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3518 if (ev.type() == MIDI_CMD_NOTE_ON) {
3520 boost::shared_ptr<NoteType> note (
3521 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3524 add_note (note, true);
3526 /* fix up our note range */
3527 if (ev.note() < _current_range_min) {
3528 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3529 } else if (ev.note() > _current_range_max) {
3530 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3533 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3534 resolve_note (ev.note (), time_beats);
3540 midi_stream_view()->check_record_layers (region(), back);
3544 MidiRegionView::trim_front_starting ()
3546 /* Reparent the note group to the region view's parent, so that it doesn't change
3547 when the region view is trimmed.
3549 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3550 _temporary_note_group->move (group->property_x(), group->property_y());
3551 _note_group->reparent (*_temporary_note_group);
3555 MidiRegionView::trim_front_ending ()
3557 _note_group->reparent (*group);
3558 delete _temporary_note_group;
3559 _temporary_note_group = 0;
3561 if (_region->start() < 0) {
3562 /* Trim drag made start time -ve; fix this */
3563 midi_region()->fix_negative_start ();
3568 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3570 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3571 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3575 change_patch_change (pc->patch(), d.patch ());
3580 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3583 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3584 Evoral::midi_note_name (n->note()).c_str(),
3586 (int) n->channel() + 1,
3587 (int) n->velocity());
3589 show_verbose_cursor (buf, 10, 20);
3593 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3597 trackview.editor().get_pointer_position (wx, wy);
3602 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3604 double x1, y1, x2, y2;
3605 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3607 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3608 wy -= (y2 - y1) + 2 * yoffset;
3611 trackview.editor().verbose_cursor()->set (text, wx, wy);
3612 trackview.editor().verbose_cursor()->show ();
3615 /** @param p A session framepos.
3616 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3617 * @return p snapped to the grid subdivision underneath it.
3620 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3622 PublicEditor& editor = trackview.editor ();
3625 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3631 grid_frames = region_beats_to_region_frames (grid_beats);
3633 /* Hack so that we always snap to the note that we are over, instead of snapping
3634 to the next one if we're more than halfway through the one we're over.
3636 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3637 p -= grid_frames / 2;
3640 return snap_frame_to_frame (p);
3643 /** Called when the selection has been cleared in any MidiRegionView.
3644 * @param rv MidiRegionView that the selection was cleared in.
3647 MidiRegionView::selection_cleared (MidiRegionView* rv)
3653 /* Clear our selection in sympathy; but don't signal the fact */
3654 clear_selection (false);