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*)
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 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
723 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
724 it still works for zoom.
729 trackview.editor().verbose_cursor()->hide ();
731 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
733 if (ev->direction == GDK_SCROLL_UP) {
734 change_velocities (true, fine, false);
735 } else if (ev->direction == GDK_SCROLL_DOWN) {
736 change_velocities (false, fine, false);
742 MidiRegionView::key_press (GdkEventKey* ev)
744 /* since GTK bindings are generally activated on press, and since
745 detectable auto-repeat is the name of the game and only sends
746 repeated presses, carry out key actions at key press, not release.
749 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
751 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
752 _mouse_state = SelectTouchDragging;
755 } else if (ev->keyval == GDK_Escape && unmodified) {
759 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
761 bool start = (ev->keyval == GDK_comma);
762 bool end = (ev->keyval == GDK_period);
763 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
764 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
766 change_note_lengths (fine, shorter, 0.0, start, end);
770 } else if (ev->keyval == GDK_Delete && unmodified) {
775 } else if (ev->keyval == GDK_Tab) {
777 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
778 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
780 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
784 } else if (ev->keyval == GDK_ISO_Left_Tab) {
786 /* Shift-TAB generates ISO Left Tab, for some reason */
788 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
789 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
791 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
797 } else if (ev->keyval == GDK_Up) {
799 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
800 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
802 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
803 change_velocities (true, fine, allow_smush);
805 transpose (true, fine, allow_smush);
809 } else if (ev->keyval == GDK_Down) {
811 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
812 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
814 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
815 change_velocities (false, fine, allow_smush);
817 transpose (false, fine, allow_smush);
821 } else if (ev->keyval == GDK_Left && unmodified) {
826 } else if (ev->keyval == GDK_Right && unmodified) {
831 } else if (ev->keyval == GDK_c && unmodified) {
840 MidiRegionView::key_release (GdkEventKey* ev)
842 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
850 MidiRegionView::channel_edit ()
853 uint8_t current_channel = 0;
855 if (_selection.empty()) {
859 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
861 current_channel = (*i)->note()->channel ();
866 MidiChannelDialog channel_dialog (current_channel);
867 int ret = channel_dialog.run ();
870 case Gtk::RESPONSE_OK:
876 uint8_t new_channel = channel_dialog.active_channel ();
878 start_note_diff_command (_("channel edit"));
880 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
881 Selection::iterator next = i;
883 change_note_channel (*i, new_channel);
891 MidiRegionView::show_list_editor ()
894 _list_editor = new MidiListEditor (trackview.session(), midi_region());
896 _list_editor->present ();
899 /** Add a note to the model, and the view, at a canvas (click) coordinate.
900 * \param x horizontal position in pixels
901 * \param y vertical position in pixels
902 * \param length duration of the note in beats, which will be snapped to the grid
903 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
904 * \param snap_x true to snap x to the grid, otherwise false.
907 MidiRegionView::create_note_at (double x, double y, double length, bool sh, bool snap_x)
909 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
910 MidiStreamView* const view = mtv->midi_view();
912 double note = view->y_to_note(y);
915 assert(note <= 127.0);
917 // Start of note in frames relative to region start
918 framepos_t start_frames = trackview.editor().pixel_to_frame (x);
920 framecnt_t grid_frames;
921 start_frames = snap_frame_to_grid_underneath (start_frames, grid_frames);
923 assert(start_frames >= 0);
926 length = region_frames_to_region_beats(
927 snap_frame_to_frame (start_frames + region_beats_to_region_frames(length)) - start_frames);
929 assert (length != 0);
932 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
935 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
936 region_frames_to_region_beats(start_frames + _region->start()), length,
937 (uint8_t)note, 0x40));
939 if (_model->contains (new_note)) {
943 view->update_note_range(new_note->note());
945 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
947 _model->apply_command(*trackview.session(), cmd);
949 play_midi_note (new_note);
953 MidiRegionView::clear_events()
958 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
959 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
964 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
969 _patch_changes.clear();
971 _optimization_iterator = _events.end();
975 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
979 content_connection.disconnect ();
980 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
984 if (_enable_display) {
990 MidiRegionView::start_note_diff_command (string name)
992 if (!_note_diff_command) {
993 _note_diff_command = _model->new_note_diff_command (name);
998 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1000 if (_note_diff_command) {
1001 _note_diff_command->add (note);
1004 _marked_for_selection.insert(note);
1006 if (show_velocity) {
1007 _marked_for_velocity.insert(note);
1012 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
1014 if (_note_diff_command && ev->note()) {
1015 _note_diff_command->remove(ev->note());
1020 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1021 MidiModel::NoteDiffCommand::Property property,
1024 if (_note_diff_command) {
1025 _note_diff_command->change (ev->note(), property, val);
1030 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1031 MidiModel::NoteDiffCommand::Property property,
1032 Evoral::MusicalTime val)
1034 if (_note_diff_command) {
1035 _note_diff_command->change (ev->note(), property, val);
1040 MidiRegionView::apply_diff (bool as_subcommand)
1044 if (!_note_diff_command) {
1048 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1049 // Mark all selected notes for selection when model reloads
1050 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1051 _marked_for_selection.insert((*i)->note());
1055 if (as_subcommand) {
1056 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1058 _model->apply_command (*trackview.session(), _note_diff_command);
1061 _note_diff_command = 0;
1062 midi_view()->midi_track()->playlist_modified();
1064 if (add_or_remove) {
1065 _marked_for_selection.clear();
1068 _marked_for_velocity.clear();
1072 MidiRegionView::abort_command()
1074 delete _note_diff_command;
1075 _note_diff_command = 0;
1080 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1082 if (_optimization_iterator != _events.end()) {
1083 ++_optimization_iterator;
1086 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1087 return *_optimization_iterator;
1090 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1091 if ((*_optimization_iterator)->note() == note) {
1092 return *_optimization_iterator;
1100 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1102 MidiModel::Notes notes;
1103 _model->get_notes (notes, op, val, chan_mask);
1105 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1106 CanvasNoteEvent* cne = find_canvas_note (*n);
1114 MidiRegionView::redisplay_model()
1116 // Don't redisplay the model if we're currently recording and displaying that
1117 if (_active_notes) {
1125 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1126 (*i)->invalidate ();
1129 MidiModel::ReadLock lock(_model->read_lock());
1131 MidiModel::Notes& notes (_model->notes());
1132 _optimization_iterator = _events.begin();
1134 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1136 boost::shared_ptr<NoteType> note (*n);
1137 CanvasNoteEvent* cne;
1140 if (note_in_region_range (note, visible)) {
1142 if ((cne = find_canvas_note (note)) != 0) {
1149 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1151 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1163 add_note (note, visible);
1168 if ((cne = find_canvas_note (note)) != 0) {
1176 /* remove note items that are no longer valid */
1178 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1179 if (!(*i)->valid ()) {
1181 i = _events.erase (i);
1187 _patch_changes.clear();
1191 display_patch_changes ();
1193 _marked_for_selection.clear ();
1194 _marked_for_velocity.clear ();
1196 /* we may have caused _events to contain things out of order (e.g. if a note
1197 moved earlier or later). we don't generally need them in time order, but
1198 make a note that a sort is required for those cases that require it.
1201 _sort_needed = true;
1205 MidiRegionView::display_patch_changes ()
1207 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1208 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1210 for (uint8_t i = 0; i < 16; ++i) {
1211 if (chn_mask & (1<<i)) {
1212 display_patch_changes_on_channel (i);
1214 /* TODO gray-out patch instad of not displaying it */
1219 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1221 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1223 if ((*i)->channel() != channel) {
1227 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1229 boost::shared_ptr<MIDI::Name::Patch> patch =
1230 MIDI::Name::MidiPatchManager::instance().find_patch(
1231 _model_name, _custom_device_mode, channel, patch_key);
1234 add_canvas_patch_change (*i, patch->name());
1237 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1238 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1239 add_canvas_patch_change (*i, buf);
1245 MidiRegionView::display_sysexes()
1247 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1248 Evoral::MusicalTime time = (*i)->time();
1253 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1254 str << int((*i)->buffer()[b]);
1255 if (b != (*i)->size() -1) {
1259 string text = str.str();
1261 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1263 double height = midi_stream_view()->contents_height();
1265 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1266 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1268 // Show unless patch change is beyond the region bounds
1269 if (time - _region->start() >= _region->length() || time < _region->start()) {
1275 _sys_exes.push_back(sysex);
1280 MidiRegionView::~MidiRegionView ()
1282 in_destructor = true;
1284 trackview.editor().verbose_cursor()->hide ();
1286 note_delete_connection.disconnect ();
1288 delete _list_editor;
1290 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1292 if (_active_notes) {
1300 delete _note_diff_command;
1301 delete _step_edit_cursor;
1302 delete _temporary_note_group;
1306 MidiRegionView::region_resized (const PropertyChange& what_changed)
1308 RegionView::region_resized(what_changed);
1310 if (what_changed.contains (ARDOUR::Properties::position)) {
1311 set_duration(_region->length(), 0);
1312 if (_enable_display) {
1319 MidiRegionView::reset_width_dependent_items (double pixel_width)
1321 RegionView::reset_width_dependent_items(pixel_width);
1322 assert(_pixel_width == pixel_width);
1324 if (_enable_display) {
1328 move_step_edit_cursor (_step_edit_cursor_position);
1329 set_step_edit_cursor_width (_step_edit_cursor_width);
1333 MidiRegionView::set_height (double height)
1335 static const double FUDGE = 2.0;
1336 const double old_height = _height;
1337 RegionView::set_height(height);
1338 _height = height - FUDGE;
1340 apply_note_range(midi_stream_view()->lowest_note(),
1341 midi_stream_view()->highest_note(),
1342 height != old_height + FUDGE);
1345 name_pixbuf->raise_to_top();
1348 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1349 (*x)->set_height (midi_stream_view()->contents_height());
1352 if (_step_edit_cursor) {
1353 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1358 /** Apply the current note range from the stream view
1359 * by repositioning/hiding notes as necessary
1362 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1364 if (!_enable_display) {
1368 if (!force && _current_range_min == min && _current_range_max == max) {
1372 _current_range_min = min;
1373 _current_range_max = max;
1375 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1376 CanvasNoteEvent* event = *i;
1377 boost::shared_ptr<NoteType> note (event->note());
1379 if (note->note() < _current_range_min ||
1380 note->note() > _current_range_max) {
1386 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1388 const double y1 = midi_stream_view()->note_to_y(note->note());
1389 const double y2 = y1 + floor(midi_stream_view()->note_height());
1391 cnote->property_y1() = y1;
1392 cnote->property_y2() = y2;
1394 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1396 const double diamond_size = update_hit (chit);
1398 chit->set_height (diamond_size);
1404 MidiRegionView::add_ghost (TimeAxisView& tv)
1408 double unit_position = _region->position () / samples_per_unit;
1409 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1410 MidiGhostRegion* ghost;
1412 if (mtv && mtv->midi_view()) {
1413 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1414 to allow having midi notes on top of note lines and waveforms.
1416 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1418 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1421 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1422 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1423 ghost->add_note(note);
1427 ghost->set_height ();
1428 ghost->set_duration (_region->length() / samples_per_unit);
1429 ghosts.push_back (ghost);
1431 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1437 /** Begin tracking note state for successive calls to add_event
1440 MidiRegionView::begin_write()
1442 assert(!_active_notes);
1443 _active_notes = new CanvasNote*[128];
1444 for (unsigned i=0; i < 128; ++i) {
1445 _active_notes[i] = 0;
1450 /** Destroy note state for add_event
1453 MidiRegionView::end_write()
1455 delete[] _active_notes;
1457 _marked_for_selection.clear();
1458 _marked_for_velocity.clear();
1462 /** Resolve an active MIDI note (while recording).
1465 MidiRegionView::resolve_note(uint8_t note, double end_time)
1467 if (midi_view()->note_mode() != Sustained) {
1471 if (_active_notes && _active_notes[note]) {
1473 /* XXX is end_time really region-centric? I think so, because
1474 this is a new region that we're recording, so source zero is
1475 the same as region zero
1477 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1479 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1480 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1481 _active_notes[note] = 0;
1486 /** Extend active notes to rightmost edge of region (if length is changed)
1489 MidiRegionView::extend_active_notes()
1491 if (!_active_notes) {
1495 for (unsigned i=0; i < 128; ++i) {
1496 if (_active_notes[i]) {
1497 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1504 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1506 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1510 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1512 if (!route_ui || !route_ui->midi_track()) {
1516 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1522 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1524 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1528 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1530 if (!route_ui || !route_ui->midi_track()) {
1534 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1536 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1545 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1547 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1548 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1550 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1551 (note->note() <= midi_stream_view()->highest_note());
1556 /** Update a canvas note's size from its model note.
1557 * @param ev Canvas note to update.
1558 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1561 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1563 boost::shared_ptr<NoteType> note = ev->note();
1565 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1566 const double y1 = midi_stream_view()->note_to_y(note->note());
1568 ev->property_x1() = x;
1569 ev->property_y1() = y1;
1571 /* trim note display to not overlap the end of its region */
1573 if (note->length() > 0) {
1574 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1575 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1577 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1580 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1582 if (note->length() == 0) {
1583 if (_active_notes) {
1584 assert(note->note() < 128);
1585 // If this note is already active there's a stuck note,
1586 // finish the old note rectangle
1587 if (_active_notes[note->note()]) {
1588 CanvasNote* const old_rect = _active_notes[note->note()];
1589 boost::shared_ptr<NoteType> old_note = old_rect->note();
1590 old_rect->property_x2() = x;
1591 old_rect->property_outline_what() = (guint32) 0xF;
1593 _active_notes[note->note()] = ev;
1595 /* outline all but right edge */
1596 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1598 /* outline all edges */
1599 ev->property_outline_what() = (guint32) 0xF;
1602 if (update_ghost_regions) {
1603 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1604 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1606 gr->update_note (ev);
1613 MidiRegionView::update_hit (CanvasHit* ev)
1615 boost::shared_ptr<NoteType> note = ev->note();
1617 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1618 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1619 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1620 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1624 return diamond_size;
1627 /** Add a MIDI note to the view (with length).
1629 * If in sustained mode, notes with length 0 will be considered active
1630 * notes, and resolve_note should be called when the corresponding note off
1631 * event arrives, to properly display the note.
1634 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1636 CanvasNoteEvent* event = 0;
1638 assert(note->time() >= 0);
1639 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1641 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1643 if (midi_view()->note_mode() == Sustained) {
1645 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1647 update_note (ev_rect);
1651 MidiGhostRegion* gr;
1653 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1654 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1655 gr->add_note(ev_rect);
1659 } else if (midi_view()->note_mode() == Percussive) {
1661 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1663 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1665 update_hit (ev_diamond);
1674 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1675 note_selected(event, true);
1678 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1679 event->show_velocity();
1682 event->on_channel_selection_change(_last_channel_selection);
1683 _events.push_back(event);
1692 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1693 MidiStreamView* const view = mtv->midi_view();
1695 view->update_note_range (note->note());
1699 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1700 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1702 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1704 /* potentially extend region to hold new note */
1706 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1707 framepos_t region_end = _region->last_frame();
1709 if (end_frame > region_end) {
1710 _region->set_length (end_frame - _region->position());
1713 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1714 MidiStreamView* const view = mtv->midi_view();
1716 view->update_note_range(new_note->note());
1718 _marked_for_selection.clear ();
1721 start_note_diff_command (_("step add"));
1722 note_diff_add_note (new_note, true, false);
1725 // last_step_edit_note = new_note;
1729 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1731 change_note_lengths (false, false, beats, false, true);
1735 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1737 assert (patch->time() >= 0);
1739 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (patch->time()));
1741 double const height = midi_stream_view()->contents_height();
1743 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1744 new CanvasPatchChange(*this, *_note_group,
1749 _custom_device_mode,
1753 // Show unless patch change is beyond the region bounds
1754 if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1755 patch_change->hide();
1757 patch_change->show();
1760 _patch_changes.push_back (patch_change);
1764 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1766 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1767 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1771 if (i != _model->patch_changes().end()) {
1772 key.msb = (*i)->bank_msb ();
1773 key.lsb = (*i)->bank_lsb ();
1774 key.program_number = (*i)->program ();
1776 key.msb = key.lsb = key.program_number = 0;
1779 assert (key.is_sane());
1784 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1786 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1788 if (pc.patch()->program() != new_patch.program_number) {
1789 c->change_program (pc.patch (), new_patch.program_number);
1792 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1793 if (pc.patch()->bank() != new_bank) {
1794 c->change_bank (pc.patch (), new_bank);
1797 _model->apply_command (*trackview.session(), c);
1799 _patch_changes.clear ();
1800 display_patch_changes ();
1804 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1806 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1808 if (old_change->time() != new_change.time()) {
1809 c->change_time (old_change, new_change.time());
1812 if (old_change->channel() != new_change.channel()) {
1813 c->change_channel (old_change, new_change.channel());
1816 if (old_change->program() != new_change.program()) {
1817 c->change_program (old_change, new_change.program());
1820 if (old_change->bank() != new_change.bank()) {
1821 c->change_bank (old_change, new_change.bank());
1824 _model->apply_command (*trackview.session(), c);
1826 _patch_changes.clear ();
1827 display_patch_changes ();
1830 /** Add a patch change to the region.
1831 * @param t Time in frames relative to region position
1832 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1833 * MidiTimeAxisView::get_channel_for_add())
1836 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1838 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1840 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1841 c->add (MidiModel::PatchChangePtr (
1842 new Evoral::PatchChange<Evoral::MusicalTime> (
1843 absolute_frames_to_source_beats (_region->position() + t),
1844 mtv->get_channel_for_add(), patch.program(), patch.bank()
1849 _model->apply_command (*trackview.session(), c);
1851 _patch_changes.clear ();
1852 display_patch_changes ();
1856 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1858 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1859 c->change_time (pc.patch (), t);
1860 _model->apply_command (*trackview.session(), c);
1862 _patch_changes.clear ();
1863 display_patch_changes ();
1867 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1869 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1870 c->remove (pc->patch ());
1871 _model->apply_command (*trackview.session(), c);
1873 _patch_changes.clear ();
1874 display_patch_changes ();
1878 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1880 if (patch.patch()->program() < 127) {
1881 MIDI::Name::PatchPrimaryKey key;
1882 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1883 key.program_number++;
1884 change_patch_change (patch, key);
1889 MidiRegionView::next_patch (CanvasPatchChange& patch)
1891 if (patch.patch()->program() > 0) {
1892 MIDI::Name::PatchPrimaryKey key;
1893 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1894 key.program_number--;
1895 change_patch_change (patch, key);
1900 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1902 if (patch.patch()->program() < 127) {
1903 MIDI::Name::PatchPrimaryKey key;
1904 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1907 change_patch_change (patch, key);
1912 change_patch_change (patch, key);
1919 MidiRegionView::next_bank (CanvasPatchChange& patch)
1921 if (patch.patch()->program() > 0) {
1922 MIDI::Name::PatchPrimaryKey key;
1923 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1924 if (key.lsb < 127) {
1926 change_patch_change (patch, key);
1928 if (key.msb < 127) {
1931 change_patch_change (patch, key);
1938 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1940 if (_selection.empty()) {
1944 _selection.erase (cne);
1948 MidiRegionView::delete_selection()
1950 if (_selection.empty()) {
1954 start_note_diff_command (_("delete selection"));
1956 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1957 if ((*i)->selected()) {
1958 _note_diff_command->remove((*i)->note());
1968 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1970 start_note_diff_command (_("delete note"));
1971 _note_diff_command->remove (n);
1974 trackview.editor().verbose_cursor()->hide ();
1978 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1980 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1982 Selection::iterator tmp = i;
1985 (*i)->set_selected (false);
1986 (*i)->hide_velocity ();
1987 _selection.erase (i);
1995 /* this does not change the status of this regionview w.r.t the editor
2000 SelectionCleared (this); /* EMIT SIGNAL */
2005 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2007 clear_selection_except (ev);
2009 /* don't bother with checking to see if we should remove this
2010 regionview from the editor selection, since we're about to add
2011 another note, and thus put/keep this regionview in the editor
2015 if (!ev->selected()) {
2016 add_to_selection (ev);
2021 MidiRegionView::select_all_notes ()
2025 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2026 add_to_selection (*i);
2031 MidiRegionView::select_range (framepos_t start, framepos_t end)
2035 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2036 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2037 if (t >= start && t <= end) {
2038 add_to_selection (*i);
2044 MidiRegionView::invert_selection ()
2046 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2047 if ((*i)->selected()) {
2048 remove_from_selection(*i);
2050 add_to_selection (*i);
2056 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2058 uint8_t low_note = 127;
2059 uint8_t high_note = 0;
2060 MidiModel::Notes& notes (_model->notes());
2061 _optimization_iterator = _events.begin();
2067 if (extend && _selection.empty()) {
2073 /* scan existing selection to get note range */
2075 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2076 if ((*i)->note()->note() < low_note) {
2077 low_note = (*i)->note()->note();
2079 if ((*i)->note()->note() > high_note) {
2080 high_note = (*i)->note()->note();
2084 low_note = min (low_note, notenum);
2085 high_note = max (high_note, notenum);
2088 _no_sound_notes = true;
2090 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2092 boost::shared_ptr<NoteType> note (*n);
2093 CanvasNoteEvent* cne;
2094 bool select = false;
2096 if (((1 << note->channel()) & channel_mask) != 0) {
2098 if ((note->note() >= low_note && note->note() <= high_note)) {
2101 } else if (note->note() == notenum) {
2107 if ((cne = find_canvas_note (note)) != 0) {
2108 // extend is false because we've taken care of it,
2109 // since it extends by time range, not pitch.
2110 note_selected (cne, add, false);
2114 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2118 _no_sound_notes = false;
2122 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2124 MidiModel::Notes& notes (_model->notes());
2125 _optimization_iterator = _events.begin();
2127 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2129 boost::shared_ptr<NoteType> note (*n);
2130 CanvasNoteEvent* cne;
2132 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2133 if ((cne = find_canvas_note (note)) != 0) {
2134 if (cne->selected()) {
2135 note_deselected (cne);
2137 note_selected (cne, true, false);
2145 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2148 clear_selection_except (ev);
2149 if (!_selection.empty()) {
2150 PublicEditor& editor (trackview.editor());
2151 editor.get_selection().add (this);
2157 if (!ev->selected()) {
2158 add_to_selection (ev);
2162 /* find end of latest note selected, select all between that and the start of "ev" */
2164 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2165 Evoral::MusicalTime latest = 0;
2167 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2168 if ((*i)->note()->end_time() > latest) {
2169 latest = (*i)->note()->end_time();
2171 if ((*i)->note()->time() < earliest) {
2172 earliest = (*i)->note()->time();
2176 if (ev->note()->end_time() > latest) {
2177 latest = ev->note()->end_time();
2180 if (ev->note()->time() < earliest) {
2181 earliest = ev->note()->time();
2184 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2186 /* find notes entirely within OR spanning the earliest..latest range */
2188 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2189 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2190 add_to_selection (*i);
2198 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2200 remove_from_selection (ev);
2204 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2214 // TODO: Make this faster by storing the last updated selection rect, and only
2215 // adjusting things that are in the area that appears/disappeared.
2216 // We probably need a tree to be able to find events in O(log(n)) time.
2218 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2220 /* check if any corner of the note is inside the rect
2223 1) this is computing "touched by", not "contained by" the rect.
2224 2) this does not require that events be sorted in time.
2227 const double ix1 = (*i)->x1();
2228 const double ix2 = (*i)->x2();
2229 const double iy1 = (*i)->y1();
2230 const double iy2 = (*i)->y2();
2232 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2233 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2234 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2235 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2238 if (!(*i)->selected()) {
2239 add_to_selection (*i);
2241 } else if ((*i)->selected() && !extend) {
2242 // Not inside rectangle
2243 remove_from_selection (*i);
2249 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2251 Selection::iterator i = _selection.find (ev);
2253 if (i != _selection.end()) {
2254 _selection.erase (i);
2257 ev->set_selected (false);
2258 ev->hide_velocity ();
2260 if (_selection.empty()) {
2261 PublicEditor& editor (trackview.editor());
2262 editor.get_selection().remove (this);
2267 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2269 bool add_mrv_selection = false;
2271 if (_selection.empty()) {
2272 add_mrv_selection = true;
2275 if (_selection.insert (ev).second) {
2276 ev->set_selected (true);
2277 play_midi_note ((ev)->note());
2280 if (add_mrv_selection) {
2281 PublicEditor& editor (trackview.editor());
2282 editor.get_selection().add (this);
2287 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2289 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2290 PossibleChord to_play;
2291 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2293 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2294 if ((*i)->note()->time() < earliest) {
2295 earliest = (*i)->note()->time();
2299 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2300 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2301 to_play.push_back ((*i)->note());
2303 (*i)->move_event(dx, dy);
2306 if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2308 if (to_play.size() > 1) {
2310 PossibleChord shifted;
2312 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2313 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2314 moved_note->set_note (moved_note->note() + cumulative_dy);
2315 shifted.push_back (moved_note);
2318 play_midi_chord (shifted);
2320 } else if (!to_play.empty()) {
2322 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2323 moved_note->set_note (moved_note->note() + cumulative_dy);
2324 play_midi_note (moved_note);
2330 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2332 assert (!_selection.empty());
2334 uint8_t lowest_note_in_selection = 127;
2335 uint8_t highest_note_in_selection = 0;
2336 uint8_t highest_note_difference = 0;
2338 // find highest and lowest notes first
2340 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2341 uint8_t pitch = (*i)->note()->note();
2342 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2343 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2347 cerr << "dnote: " << (int) dnote << endl;
2348 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2349 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2350 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2351 << int(highest_note_in_selection) << endl;
2352 cerr << "selection size: " << _selection.size() << endl;
2353 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2356 // Make sure the note pitch does not exceed the MIDI standard range
2357 if (highest_note_in_selection + dnote > 127) {
2358 highest_note_difference = highest_note_in_selection - 127;
2361 start_note_diff_command (_("move notes"));
2363 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2365 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2371 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2373 uint8_t original_pitch = (*i)->note()->note();
2374 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2376 // keep notes in standard midi range
2377 clamp_to_0_127(new_pitch);
2379 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2380 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2382 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2387 // care about notes being moved beyond the upper/lower bounds on the canvas
2388 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2389 highest_note_in_selection > midi_stream_view()->highest_note()) {
2390 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2395 MidiRegionView::snap_pixel_to_frame(double x)
2397 PublicEditor& editor (trackview.editor());
2398 return snap_frame_to_frame (editor.pixel_to_frame (x));
2402 MidiRegionView::snap_to_pixel(double x)
2404 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2408 MidiRegionView::get_position_pixels()
2410 framepos_t region_frame = get_position();
2411 return trackview.editor().frame_to_pixel(region_frame);
2415 MidiRegionView::get_end_position_pixels()
2417 framepos_t frame = get_position() + get_duration ();
2418 return trackview.editor().frame_to_pixel(frame);
2422 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2424 /* the time converter will return the frame corresponding to `beats'
2425 relative to the start of the source. The start of the source
2426 is an implied position given by region->position - region->start
2428 const framepos_t source_start = _region->position() - _region->start();
2429 return source_start + _source_relative_time_converter.to (beats);
2433 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2435 /* the `frames' argument needs to be converted into a frame count
2436 relative to the start of the source before being passed in to the
2439 const framepos_t source_start = _region->position() - _region->start();
2440 return _source_relative_time_converter.from (frames - source_start);
2444 MidiRegionView::region_beats_to_region_frames(double beats) const
2446 return _region_relative_time_converter.to(beats);
2450 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2452 return _region_relative_time_converter.from(frames);
2456 MidiRegionView::begin_resizing (bool /*at_front*/)
2458 _resize_data.clear();
2460 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2461 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2463 // only insert CanvasNotes into the map
2465 NoteResizeData *resize_data = new NoteResizeData();
2466 resize_data->canvas_note = note;
2468 // create a new SimpleRect from the note which will be the resize preview
2469 SimpleRect *resize_rect = new SimpleRect(
2470 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2472 // calculate the colors: get the color settings
2473 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2474 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2477 // make the resize preview notes more transparent and bright
2478 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2480 // calculate color based on note velocity
2481 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2482 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2486 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2487 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2489 resize_data->resize_rect = resize_rect;
2490 _resize_data.push_back(resize_data);
2495 /** Update resizing notes while user drags.
2496 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2497 * @param at_front which end of the note (true == note on, false == note off)
2498 * @param delta_x change in mouse position since the start of the drag
2499 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2500 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2501 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2502 * as the \a primary note.
2505 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2507 bool cursor_set = false;
2509 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2510 SimpleRect* resize_rect = (*i)->resize_rect;
2511 CanvasNote* canvas_note = (*i)->canvas_note;
2516 current_x = canvas_note->x1() + delta_x;
2518 current_x = primary->x1() + delta_x;
2522 current_x = canvas_note->x2() + delta_x;
2524 current_x = primary->x2() + delta_x;
2529 resize_rect->property_x1() = snap_to_pixel(current_x);
2530 resize_rect->property_x2() = canvas_note->x2();
2532 resize_rect->property_x2() = snap_to_pixel(current_x);
2533 resize_rect->property_x1() = canvas_note->x1();
2539 beats = snap_pixel_to_frame (current_x);
2540 /* XXX not sure this is correct - snap_pixel_to_frame()
2541 returns an absolute frame.
2543 beats = region_frames_to_region_beats (beats);
2548 if (beats < canvas_note->note()->end_time()) {
2549 len = canvas_note->note()->time() - beats;
2550 len += canvas_note->note()->length();
2555 if (beats >= canvas_note->note()->time()) {
2556 len = beats - canvas_note->note()->time();
2563 snprintf (buf, sizeof (buf), "%.3g beats", len);
2564 show_verbose_cursor (buf, 0, 0);
2573 /** Finish resizing notes when the user releases the mouse button.
2574 * Parameters the same as for \a update_resizing().
2577 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2579 start_note_diff_command (_("resize notes"));
2581 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2582 CanvasNote* canvas_note = (*i)->canvas_note;
2583 SimpleRect* resize_rect = (*i)->resize_rect;
2585 /* Get the new x position for this resize, which is in pixels relative
2586 * to the region position.
2593 current_x = canvas_note->x1() + delta_x;
2595 current_x = primary->x1() + delta_x;
2599 current_x = canvas_note->x2() + delta_x;
2601 current_x = primary->x2() + delta_x;
2605 /* Convert that to a frame within the region */
2606 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2608 /* and then to beats */
2609 /* XXX not sure this is correct - snap_pixel_to_frame()
2610 returns an absolute frame.
2612 current_x = region_frames_to_region_beats (current_x);
2614 if (at_front && current_x < canvas_note->note()->end_time()) {
2615 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2617 double len = canvas_note->note()->time() - current_x;
2618 len += canvas_note->note()->length();
2621 /* XXX convert to beats */
2622 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2627 double len = current_x - canvas_note->note()->time();
2630 /* XXX convert to beats */
2631 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2639 _resize_data.clear();
2644 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2646 uint8_t new_velocity;
2649 new_velocity = event->note()->velocity() + velocity;
2650 clamp_to_0_127(new_velocity);
2652 new_velocity = velocity;
2655 event->set_selected (event->selected()); // change color
2657 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2661 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2666 new_note = event->note()->note() + note;
2671 clamp_to_0_127 (new_note);
2672 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2676 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2678 bool change_start = false;
2679 bool change_length = false;
2680 Evoral::MusicalTime new_start = 0;
2681 Evoral::MusicalTime new_length = 0;
2683 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2685 front_delta: if positive - move the start of the note later in time (shortening it)
2686 if negative - move the start of the note earlier in time (lengthening it)
2688 end_delta: if positive - move the end of the note later in time (lengthening it)
2689 if negative - move the end of the note earlier in time (shortening it)
2693 if (front_delta < 0) {
2695 if (event->note()->time() < -front_delta) {
2698 new_start = event->note()->time() + front_delta; // moves earlier
2701 /* start moved toward zero, so move the end point out to where it used to be.
2702 Note that front_delta is negative, so this increases the length.
2705 new_length = event->note()->length() - front_delta;
2706 change_start = true;
2707 change_length = true;
2711 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2713 if (new_pos < event->note()->end_time()) {
2714 new_start = event->note()->time() + front_delta;
2715 /* start moved toward the end, so move the end point back to where it used to be */
2716 new_length = event->note()->length() - front_delta;
2717 change_start = true;
2718 change_length = true;
2725 bool can_change = true;
2726 if (end_delta < 0) {
2727 if (event->note()->length() < -end_delta) {
2733 new_length = event->note()->length() + end_delta;
2734 change_length = true;
2739 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2742 if (change_length) {
2743 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2748 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2750 uint8_t new_channel;
2754 if (event->note()->channel() < -chn) {
2757 new_channel = event->note()->channel() + chn;
2760 new_channel = event->note()->channel() + chn;
2763 new_channel = (uint8_t) chn;
2766 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2770 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2772 Evoral::MusicalTime new_time;
2776 if (event->note()->time() < -delta) {
2779 new_time = event->note()->time() + delta;
2782 new_time = event->note()->time() + delta;
2788 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2792 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2794 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2798 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2802 if (_selection.empty()) {
2817 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2818 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2824 start_note_diff_command (_("change velocities"));
2826 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2827 Selection::iterator next = i;
2829 change_note_velocity (*i, delta, true);
2835 if (!_selection.empty()) {
2837 snprintf (buf, sizeof (buf), "Vel %d",
2838 (int) (*_selection.begin())->note()->velocity());
2839 show_verbose_cursor (buf, 10, 10);
2845 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2847 if (_selection.empty()) {
2864 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2866 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2870 if ((int8_t) (*i)->note()->note() + delta > 127) {
2877 start_note_diff_command (_("transpose"));
2879 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2880 Selection::iterator next = i;
2882 change_note_note (*i, delta, true);
2890 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2896 /* grab the current grid distance */
2898 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2900 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2901 cerr << "Grid type not available as beats - TO BE FIXED\n";
2911 start_note_diff_command (_("change note lengths"));
2913 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2914 Selection::iterator next = i;
2917 /* note the negation of the delta for start */
2919 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2928 MidiRegionView::nudge_notes (bool forward)
2930 if (_selection.empty()) {
2934 /* pick a note as the point along the timeline to get the nudge distance.
2935 its not necessarily the earliest note, so we may want to pull the notes out
2936 into a vector and sort before using the first one.
2939 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2941 framepos_t distance;
2943 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2945 /* grid is off - use nudge distance */
2947 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2953 framepos_t next_pos = ref_point;
2956 if (max_framepos - 1 < next_pos) {
2960 if (next_pos == 0) {
2966 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2967 distance = ref_point - next_pos;
2970 if (distance == 0) {
2974 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2980 start_note_diff_command (_("nudge"));
2982 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2983 Selection::iterator next = i;
2985 change_note_time (*i, delta, true);
2993 MidiRegionView::change_channel(uint8_t channel)
2995 start_note_diff_command(_("change channel"));
2996 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2997 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3005 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3007 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3009 _pre_enter_cursor = editor->get_canvas_cursor ();
3011 if (_mouse_state == SelectTouchDragging) {
3012 note_selected (ev, true);
3015 show_verbose_cursor (ev->note ());
3019 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3021 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3023 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3024 (*i)->hide_velocity ();
3027 editor->verbose_cursor()->hide ();
3029 if (_pre_enter_cursor) {
3030 editor->set_canvas_cursor (_pre_enter_cursor);
3031 _pre_enter_cursor = 0;
3036 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3039 /* XXX should get patch name if we can */
3040 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3041 show_verbose_cursor (s.str(), 10, 20);
3045 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3047 trackview.editor().verbose_cursor()->hide ();
3051 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3053 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3055 if (x_fraction > 0.0 && x_fraction < 0.25) {
3056 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3057 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3058 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3060 if (_pre_enter_cursor && can_set_cursor) {
3061 editor->set_canvas_cursor (_pre_enter_cursor);
3067 MidiRegionView::set_frame_color()
3071 TimeAxisViewItem::set_frame_color ();
3078 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3079 } else if (high_enough_for_name) {
3080 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3085 if (!rect_visible) {
3086 f = UINT_RGBA_CHANGE_A (f, 0);
3089 frame->property_fill_color_rgba() = f;
3093 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3097 case FilterChannels:
3098 _force_channel = -1;
3101 _force_channel = mask;
3102 mask = 0xFFFF; // Show all notes as active (below)
3105 // Update notes for selection
3106 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3107 (*i)->on_channel_selection_change(mask);
3110 _last_channel_selection = mask;
3112 _patch_changes.clear ();
3113 display_patch_changes ();
3117 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3119 _model_name = model;
3120 _custom_device_mode = custom_device_mode;
3125 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3127 if (_selection.empty()) {
3131 PublicEditor& editor (trackview.editor());
3135 /* XXX what to do ? */
3139 editor.get_cut_buffer().add (selection_as_cut_buffer());
3147 start_note_diff_command();
3149 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3156 note_diff_remove_note (*i);
3166 MidiRegionView::selection_as_cut_buffer () const
3170 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3171 NoteType* n = (*i)->note().get();
3172 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3175 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3181 /** This method handles undo */
3183 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3189 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3191 trackview.session()->begin_reversible_command (_("paste"));
3193 start_note_diff_command (_("paste"));
3195 Evoral::MusicalTime beat_delta;
3196 Evoral::MusicalTime paste_pos_beats;
3197 Evoral::MusicalTime duration;
3198 Evoral::MusicalTime end_point = 0;
3200 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3201 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3202 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3203 paste_pos_beats = 0;
3205 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",
3206 (*mcb.notes().begin())->time(),
3207 (*mcb.notes().rbegin())->end_time(),
3208 duration, pos, _region->position(),
3209 paste_pos_beats, beat_delta));
3213 for (int n = 0; n < (int) times; ++n) {
3215 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3217 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3218 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3220 /* make all newly added notes selected */
3222 note_diff_add_note (copied_note, true);
3223 end_point = copied_note->end_time();
3226 paste_pos_beats += duration;
3229 /* if we pasted past the current end of the region, extend the region */
3231 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3232 framepos_t region_end = _region->position() + _region->length() - 1;
3234 if (end_frame > region_end) {
3236 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3238 _region->clear_changes ();
3239 _region->set_length (end_frame);
3240 trackview.session()->add_command (new StatefulDiffCommand (_region));
3245 trackview.session()->commit_reversible_command ();
3248 struct EventNoteTimeEarlyFirstComparator {
3249 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3250 return a->note()->time() < b->note()->time();
3255 MidiRegionView::time_sort_events ()
3257 if (!_sort_needed) {
3261 EventNoteTimeEarlyFirstComparator cmp;
3264 _sort_needed = false;
3268 MidiRegionView::goto_next_note (bool add_to_selection)
3270 bool use_next = false;
3272 if (_events.back()->selected()) {
3276 time_sort_events ();
3278 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3279 if ((*i)->selected()) {
3282 } else if (use_next) {
3283 if (!add_to_selection) {
3286 note_selected (*i, true, false);
3292 /* use the first one */
3294 unique_select (_events.front());
3299 MidiRegionView::goto_previous_note (bool add_to_selection)
3301 bool use_next = false;
3303 if (_events.front()->selected()) {
3307 time_sort_events ();
3309 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3310 if ((*i)->selected()) {
3313 } else if (use_next) {
3314 if (!add_to_selection) {
3317 note_selected (*i, true, false);
3323 /* use the last one */
3325 unique_select (*(_events.rbegin()));
3329 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3331 bool had_selected = false;
3333 time_sort_events ();
3335 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3336 if ((*i)->selected()) {
3337 selected.insert ((*i)->note());
3338 had_selected = true;
3342 if (allow_all_if_none_selected && !had_selected) {
3343 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3344 selected.insert ((*i)->note());
3350 MidiRegionView::update_ghost_note (double x, double y)
3352 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3357 _note_group->w2i (x, y);
3359 PublicEditor& editor = trackview.editor ();
3361 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3362 framecnt_t grid_frames;
3363 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3365 /* use region_frames... because we are converting a delta within the region
3368 double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3370 /* note that this sets the time of the ghost note in beats relative to
3371 the start of the source; that is how all note times are stored.
3373 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3374 _ghost_note->note()->set_length (length);
3375 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3376 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3378 /* the ghost note does not appear in ghost regions, so pass false in here */
3379 update_note (_ghost_note, false);
3381 show_verbose_cursor (_ghost_note->note ());
3385 MidiRegionView::create_ghost_note (double x, double y)
3390 boost::shared_ptr<NoteType> g (new NoteType);
3391 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3392 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3393 update_ghost_note (x, y);
3394 _ghost_note->show ();
3399 show_verbose_cursor (_ghost_note->note ());
3403 MidiRegionView::snap_changed ()
3409 create_ghost_note (_last_ghost_x, _last_ghost_y);
3413 MidiRegionView::drop_down_keys ()
3415 _mouse_state = None;
3419 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3421 double note = midi_stream_view()->y_to_note(y);
3423 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3425 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3427 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3428 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3429 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3430 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3435 bool add_mrv_selection = false;
3437 if (_selection.empty()) {
3438 add_mrv_selection = true;
3441 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3442 if (_selection.insert (*i).second) {
3443 (*i)->set_selected (true);
3447 if (add_mrv_selection) {
3448 PublicEditor& editor (trackview.editor());
3449 editor.get_selection().add (this);
3454 MidiRegionView::color_handler ()
3456 RegionView::color_handler ();
3458 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3459 (*i)->set_selected ((*i)->selected()); // will change color
3462 /* XXX probably more to do here */
3466 MidiRegionView::enable_display (bool yn)
3468 RegionView::enable_display (yn);
3475 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3477 if (_step_edit_cursor == 0) {
3478 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3480 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3481 _step_edit_cursor->property_y1() = 0;
3482 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3483 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3484 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3487 move_step_edit_cursor (pos);
3488 _step_edit_cursor->show ();
3492 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3494 _step_edit_cursor_position = pos;
3496 if (_step_edit_cursor) {
3497 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3498 _step_edit_cursor->property_x1() = pixel;
3499 set_step_edit_cursor_width (_step_edit_cursor_width);
3504 MidiRegionView::hide_step_edit_cursor ()
3506 if (_step_edit_cursor) {
3507 _step_edit_cursor->hide ();
3512 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3514 _step_edit_cursor_width = beats;
3516 if (_step_edit_cursor) {
3517 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3521 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3522 * @param buf Data that has been recorded.
3523 * @param w Source that this data will end up in.
3526 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3528 if (!_active_notes) {
3529 /* we aren't actively being recorded to */
3533 boost::shared_ptr<MidiSource> src = w.lock ();
3534 if (!src || src != midi_region()->midi_source()) {
3535 /* recorded data was not destined for our source */
3539 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3540 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3542 framepos_t back = max_framepos;
3544 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3545 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3546 assert (ev.buffer ());
3548 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3550 if (ev.type() == MIDI_CMD_NOTE_ON) {
3552 boost::shared_ptr<NoteType> note (
3553 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3556 add_note (note, true);
3558 /* fix up our note range */
3559 if (ev.note() < _current_range_min) {
3560 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3561 } else if (ev.note() > _current_range_max) {
3562 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3565 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3566 resolve_note (ev.note (), time_beats);
3572 midi_stream_view()->check_record_layers (region(), back);
3576 MidiRegionView::trim_front_starting ()
3578 /* Reparent the note group to the region view's parent, so that it doesn't change
3579 when the region view is trimmed.
3581 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3582 _temporary_note_group->move (group->property_x(), group->property_y());
3583 _note_group->reparent (*_temporary_note_group);
3587 MidiRegionView::trim_front_ending ()
3589 _note_group->reparent (*group);
3590 delete _temporary_note_group;
3591 _temporary_note_group = 0;
3593 if (_region->start() < 0) {
3594 /* Trim drag made start time -ve; fix this */
3595 midi_region()->fix_negative_start ();
3600 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3602 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3603 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3607 change_patch_change (pc->patch(), d.patch ());
3612 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3615 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3616 Evoral::midi_note_name (n->note()).c_str(),
3618 (int) n->channel() + 1,
3619 (int) n->velocity());
3621 show_verbose_cursor (buf, 10, 20);
3625 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3629 trackview.editor().get_pointer_position (wx, wy);
3634 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3636 double x1, y1, x2, y2;
3637 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3639 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3640 wy -= (y2 - y1) + 2 * yoffset;
3643 trackview.editor().verbose_cursor()->set (text, wx, wy);
3644 trackview.editor().verbose_cursor()->show ();
3647 /** @param p A session framepos.
3648 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3649 * @return p snapped to the grid subdivision underneath it.
3652 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3654 PublicEditor& editor = trackview.editor ();
3657 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3663 grid_frames = region_beats_to_region_frames (grid_beats);
3665 /* Hack so that we always snap to the note that we are over, instead of snapping
3666 to the next one if we're more than halfway through the one we're over.
3668 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3669 p -= grid_frames / 2;
3672 return snap_frame_to_frame (p);
3675 /** Called when the selection has been cleared in any MidiRegionView.
3676 * @param rv MidiRegionView that the selection was cleared in.
3679 MidiRegionView::selection_cleared (MidiRegionView* rv)
3685 /* Clear our selection in sympathy; but don't signal the fact */
3686 clear_selection (false);