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 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;
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_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2033 uint8_t low_note = 127;
2034 uint8_t high_note = 0;
2035 MidiModel::Notes& notes (_model->notes());
2036 _optimization_iterator = _events.begin();
2042 if (extend && _selection.empty()) {
2048 /* scan existing selection to get note range */
2050 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2051 if ((*i)->note()->note() < low_note) {
2052 low_note = (*i)->note()->note();
2054 if ((*i)->note()->note() > high_note) {
2055 high_note = (*i)->note()->note();
2059 low_note = min (low_note, notenum);
2060 high_note = max (high_note, notenum);
2063 _no_sound_notes = true;
2065 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2067 boost::shared_ptr<NoteType> note (*n);
2068 CanvasNoteEvent* cne;
2069 bool select = false;
2071 if (((1 << note->channel()) & channel_mask) != 0) {
2073 if ((note->note() >= low_note && note->note() <= high_note)) {
2076 } else if (note->note() == notenum) {
2082 if ((cne = find_canvas_note (note)) != 0) {
2083 // extend is false because we've taken care of it,
2084 // since it extends by time range, not pitch.
2085 note_selected (cne, add, false);
2089 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2093 _no_sound_notes = false;
2097 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2099 MidiModel::Notes& notes (_model->notes());
2100 _optimization_iterator = _events.begin();
2102 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2104 boost::shared_ptr<NoteType> note (*n);
2105 CanvasNoteEvent* cne;
2107 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2108 if ((cne = find_canvas_note (note)) != 0) {
2109 if (cne->selected()) {
2110 note_deselected (cne);
2112 note_selected (cne, true, false);
2120 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2123 clear_selection_except (ev);
2124 if (!_selection.empty()) {
2125 PublicEditor& editor (trackview.editor());
2126 editor.get_selection().add (this);
2132 if (!ev->selected()) {
2133 add_to_selection (ev);
2137 /* find end of latest note selected, select all between that and the start of "ev" */
2139 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2140 Evoral::MusicalTime latest = 0;
2142 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2143 if ((*i)->note()->end_time() > latest) {
2144 latest = (*i)->note()->end_time();
2146 if ((*i)->note()->time() < earliest) {
2147 earliest = (*i)->note()->time();
2151 if (ev->note()->end_time() > latest) {
2152 latest = ev->note()->end_time();
2155 if (ev->note()->time() < earliest) {
2156 earliest = ev->note()->time();
2159 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2161 /* find notes entirely within OR spanning the earliest..latest range */
2163 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2164 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2165 add_to_selection (*i);
2173 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2175 remove_from_selection (ev);
2179 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2189 // TODO: Make this faster by storing the last updated selection rect, and only
2190 // adjusting things that are in the area that appears/disappeared.
2191 // We probably need a tree to be able to find events in O(log(n)) time.
2193 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2195 /* check if any corner of the note is inside the rect
2198 1) this is computing "touched by", not "contained by" the rect.
2199 2) this does not require that events be sorted in time.
2202 const double ix1 = (*i)->x1();
2203 const double ix2 = (*i)->x2();
2204 const double iy1 = (*i)->y1();
2205 const double iy2 = (*i)->y2();
2207 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2208 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2209 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2210 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2213 if (!(*i)->selected()) {
2214 add_to_selection (*i);
2216 } else if ((*i)->selected() && !extend) {
2217 // Not inside rectangle
2218 remove_from_selection (*i);
2224 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2226 Selection::iterator i = _selection.find (ev);
2228 if (i != _selection.end()) {
2229 _selection.erase (i);
2232 ev->set_selected (false);
2233 ev->hide_velocity ();
2235 if (_selection.empty()) {
2236 PublicEditor& editor (trackview.editor());
2237 editor.get_selection().remove (this);
2242 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2244 bool add_mrv_selection = false;
2246 if (_selection.empty()) {
2247 add_mrv_selection = true;
2250 if (_selection.insert (ev).second) {
2251 ev->set_selected (true);
2252 play_midi_note ((ev)->note());
2255 if (add_mrv_selection) {
2256 PublicEditor& editor (trackview.editor());
2257 editor.get_selection().add (this);
2262 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2264 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2265 PossibleChord to_play;
2266 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2268 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2269 if ((*i)->note()->time() < earliest) {
2270 earliest = (*i)->note()->time();
2274 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2275 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2276 to_play.push_back ((*i)->note());
2278 (*i)->move_event(dx, dy);
2281 if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2283 if (to_play.size() > 1) {
2285 PossibleChord shifted;
2287 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2288 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2289 moved_note->set_note (moved_note->note() + cumulative_dy);
2290 shifted.push_back (moved_note);
2293 play_midi_chord (shifted);
2295 } else if (!to_play.empty()) {
2297 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2298 moved_note->set_note (moved_note->note() + cumulative_dy);
2299 play_midi_note (moved_note);
2305 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2307 assert (!_selection.empty());
2309 uint8_t lowest_note_in_selection = 127;
2310 uint8_t highest_note_in_selection = 0;
2311 uint8_t highest_note_difference = 0;
2313 // find highest and lowest notes first
2315 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2316 uint8_t pitch = (*i)->note()->note();
2317 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2318 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2322 cerr << "dnote: " << (int) dnote << endl;
2323 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2324 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2325 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2326 << int(highest_note_in_selection) << endl;
2327 cerr << "selection size: " << _selection.size() << endl;
2328 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2331 // Make sure the note pitch does not exceed the MIDI standard range
2332 if (highest_note_in_selection + dnote > 127) {
2333 highest_note_difference = highest_note_in_selection - 127;
2336 start_note_diff_command (_("move notes"));
2338 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2340 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2346 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2348 uint8_t original_pitch = (*i)->note()->note();
2349 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2351 // keep notes in standard midi range
2352 clamp_to_0_127(new_pitch);
2354 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2355 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2357 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2362 // care about notes being moved beyond the upper/lower bounds on the canvas
2363 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2364 highest_note_in_selection > midi_stream_view()->highest_note()) {
2365 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2370 MidiRegionView::snap_pixel_to_frame(double x)
2372 PublicEditor& editor (trackview.editor());
2373 return snap_frame_to_frame (editor.pixel_to_frame (x));
2377 MidiRegionView::snap_to_pixel(double x)
2379 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2383 MidiRegionView::get_position_pixels()
2385 framepos_t region_frame = get_position();
2386 return trackview.editor().frame_to_pixel(region_frame);
2390 MidiRegionView::get_end_position_pixels()
2392 framepos_t frame = get_position() + get_duration ();
2393 return trackview.editor().frame_to_pixel(frame);
2397 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2399 /* the time converter will return the frame corresponding to `beats'
2400 relative to the start of the source. The start of the source
2401 is an implied position given by region->position - region->start
2403 const framepos_t source_start = _region->position() - _region->start();
2404 return source_start + _source_relative_time_converter.to (beats);
2408 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2410 /* the `frames' argument needs to be converted into a frame count
2411 relative to the start of the source before being passed in to the
2414 const framepos_t source_start = _region->position() - _region->start();
2415 return _source_relative_time_converter.from (frames - source_start);
2419 MidiRegionView::region_beats_to_region_frames(double beats) const
2421 return _region_relative_time_converter.to(beats);
2425 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2427 return _region_relative_time_converter.from(frames);
2431 MidiRegionView::begin_resizing (bool /*at_front*/)
2433 _resize_data.clear();
2435 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2436 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2438 // only insert CanvasNotes into the map
2440 NoteResizeData *resize_data = new NoteResizeData();
2441 resize_data->canvas_note = note;
2443 // create a new SimpleRect from the note which will be the resize preview
2444 SimpleRect *resize_rect = new SimpleRect(
2445 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2447 // calculate the colors: get the color settings
2448 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2449 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2452 // make the resize preview notes more transparent and bright
2453 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2455 // calculate color based on note velocity
2456 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2457 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2461 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2462 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2464 resize_data->resize_rect = resize_rect;
2465 _resize_data.push_back(resize_data);
2470 /** Update resizing notes while user drags.
2471 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2472 * @param at_front which end of the note (true == note on, false == note off)
2473 * @param delta_x change in mouse position since the start of the drag
2474 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2475 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2476 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2477 * as the \a primary note.
2480 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2482 bool cursor_set = false;
2484 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2485 SimpleRect* resize_rect = (*i)->resize_rect;
2486 CanvasNote* canvas_note = (*i)->canvas_note;
2491 current_x = canvas_note->x1() + delta_x;
2493 current_x = primary->x1() + delta_x;
2497 current_x = canvas_note->x2() + delta_x;
2499 current_x = primary->x2() + delta_x;
2504 resize_rect->property_x1() = snap_to_pixel(current_x);
2505 resize_rect->property_x2() = canvas_note->x2();
2507 resize_rect->property_x2() = snap_to_pixel(current_x);
2508 resize_rect->property_x1() = canvas_note->x1();
2514 beats = snap_pixel_to_frame (current_x);
2515 /* XXX not sure this is correct - snap_pixel_to_frame()
2516 returns an absolute frame.
2518 beats = region_frames_to_region_beats (beats);
2523 if (beats < canvas_note->note()->end_time()) {
2524 len = canvas_note->note()->time() - beats;
2525 len += canvas_note->note()->length();
2530 if (beats >= canvas_note->note()->time()) {
2531 len = beats - canvas_note->note()->time();
2538 snprintf (buf, sizeof (buf), "%.3g beats", len);
2539 show_verbose_cursor (buf, 0, 0);
2548 /** Finish resizing notes when the user releases the mouse button.
2549 * Parameters the same as for \a update_resizing().
2552 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2554 start_note_diff_command (_("resize notes"));
2556 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2557 CanvasNote* canvas_note = (*i)->canvas_note;
2558 SimpleRect* resize_rect = (*i)->resize_rect;
2560 /* Get the new x position for this resize, which is in pixels relative
2561 * to the region position.
2568 current_x = canvas_note->x1() + delta_x;
2570 current_x = primary->x1() + delta_x;
2574 current_x = canvas_note->x2() + delta_x;
2576 current_x = primary->x2() + delta_x;
2580 /* Convert that to a frame within the region */
2581 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2583 /* and then to beats */
2584 /* XXX not sure this is correct - snap_pixel_to_frame()
2585 returns an absolute frame.
2587 current_x = region_frames_to_region_beats (current_x);
2589 if (at_front && current_x < canvas_note->note()->end_time()) {
2590 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2592 double len = canvas_note->note()->time() - current_x;
2593 len += canvas_note->note()->length();
2596 /* XXX convert to beats */
2597 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2602 double len = current_x - canvas_note->note()->time();
2605 /* XXX convert to beats */
2606 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2614 _resize_data.clear();
2619 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2621 uint8_t new_velocity;
2624 new_velocity = event->note()->velocity() + velocity;
2625 clamp_to_0_127(new_velocity);
2627 new_velocity = velocity;
2630 event->set_selected (event->selected()); // change color
2632 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2636 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2641 new_note = event->note()->note() + note;
2646 clamp_to_0_127 (new_note);
2647 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2651 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2653 bool change_start = false;
2654 bool change_length = false;
2655 Evoral::MusicalTime new_start = 0;
2656 Evoral::MusicalTime new_length = 0;
2658 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2660 front_delta: if positive - move the start of the note later in time (shortening it)
2661 if negative - move the start of the note earlier in time (lengthening it)
2663 end_delta: if positive - move the end of the note later in time (lengthening it)
2664 if negative - move the end of the note earlier in time (shortening it)
2668 if (front_delta < 0) {
2670 if (event->note()->time() < -front_delta) {
2673 new_start = event->note()->time() + front_delta; // moves earlier
2676 /* start moved toward zero, so move the end point out to where it used to be.
2677 Note that front_delta is negative, so this increases the length.
2680 new_length = event->note()->length() - front_delta;
2681 change_start = true;
2682 change_length = true;
2686 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2688 if (new_pos < event->note()->end_time()) {
2689 new_start = event->note()->time() + front_delta;
2690 /* start moved toward the end, so move the end point back to where it used to be */
2691 new_length = event->note()->length() - front_delta;
2692 change_start = true;
2693 change_length = true;
2700 bool can_change = true;
2701 if (end_delta < 0) {
2702 if (event->note()->length() < -end_delta) {
2708 new_length = event->note()->length() + end_delta;
2709 change_length = true;
2714 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2717 if (change_length) {
2718 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2723 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2725 uint8_t new_channel;
2729 if (event->note()->channel() < -chn) {
2732 new_channel = event->note()->channel() + chn;
2735 new_channel = event->note()->channel() + chn;
2738 new_channel = (uint8_t) chn;
2741 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2745 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2747 Evoral::MusicalTime new_time;
2751 if (event->note()->time() < -delta) {
2754 new_time = event->note()->time() + delta;
2757 new_time = event->note()->time() + delta;
2763 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2767 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2769 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2773 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2777 if (_selection.empty()) {
2792 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2793 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2799 start_note_diff_command (_("change velocities"));
2801 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2802 Selection::iterator next = i;
2804 change_note_velocity (*i, delta, true);
2810 if (!_selection.empty()) {
2812 snprintf (buf, sizeof (buf), "Vel %d",
2813 (int) (*_selection.begin())->note()->velocity());
2814 show_verbose_cursor (buf, 10, 10);
2820 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2822 if (_selection.empty()) {
2839 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2841 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2845 if ((int8_t) (*i)->note()->note() + delta > 127) {
2852 start_note_diff_command (_("transpose"));
2854 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2855 Selection::iterator next = i;
2857 change_note_note (*i, delta, true);
2865 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2871 /* grab the current grid distance */
2873 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2875 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2876 cerr << "Grid type not available as beats - TO BE FIXED\n";
2886 start_note_diff_command (_("change note lengths"));
2888 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2889 Selection::iterator next = i;
2892 /* note the negation of the delta for start */
2894 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2903 MidiRegionView::nudge_notes (bool forward)
2905 if (_selection.empty()) {
2909 /* pick a note as the point along the timeline to get the nudge distance.
2910 its not necessarily the earliest note, so we may want to pull the notes out
2911 into a vector and sort before using the first one.
2914 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2916 framepos_t distance;
2918 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2920 /* grid is off - use nudge distance */
2922 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2928 framepos_t next_pos = ref_point;
2931 if (max_framepos - 1 < next_pos) {
2935 if (next_pos == 0) {
2941 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2942 distance = ref_point - next_pos;
2945 if (distance == 0) {
2949 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2955 start_note_diff_command (_("nudge"));
2957 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2958 Selection::iterator next = i;
2960 change_note_time (*i, delta, true);
2968 MidiRegionView::change_channel(uint8_t channel)
2970 start_note_diff_command(_("change channel"));
2971 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2972 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2980 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2982 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2984 _pre_enter_cursor = editor->get_canvas_cursor ();
2986 if (_mouse_state == SelectTouchDragging) {
2987 note_selected (ev, true);
2990 show_verbose_cursor (ev->note ());
2994 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2996 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2998 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2999 (*i)->hide_velocity ();
3002 editor->verbose_cursor()->hide ();
3004 if (_pre_enter_cursor) {
3005 editor->set_canvas_cursor (_pre_enter_cursor);
3006 _pre_enter_cursor = 0;
3011 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3014 /* XXX should get patch name if we can */
3015 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3016 show_verbose_cursor (s.str(), 10, 20);
3020 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3022 trackview.editor().verbose_cursor()->hide ();
3026 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3028 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3030 if (x_fraction > 0.0 && x_fraction < 0.25) {
3031 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3032 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3033 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3035 if (_pre_enter_cursor && can_set_cursor) {
3036 editor->set_canvas_cursor (_pre_enter_cursor);
3042 MidiRegionView::set_frame_color()
3046 TimeAxisViewItem::set_frame_color ();
3053 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3054 } else if (high_enough_for_name) {
3055 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3060 if (!rect_visible) {
3061 f = UINT_RGBA_CHANGE_A (f, 0);
3064 frame->property_fill_color_rgba() = f;
3068 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3072 case FilterChannels:
3073 _force_channel = -1;
3076 _force_channel = mask;
3077 mask = 0xFFFF; // Show all notes as active (below)
3080 // Update notes for selection
3081 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3082 (*i)->on_channel_selection_change(mask);
3085 _last_channel_selection = mask;
3087 _patch_changes.clear ();
3088 display_patch_changes ();
3092 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3094 _model_name = model;
3095 _custom_device_mode = custom_device_mode;
3100 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3102 if (_selection.empty()) {
3106 PublicEditor& editor (trackview.editor());
3110 /* XXX what to do ? */
3114 editor.get_cut_buffer().add (selection_as_cut_buffer());
3122 start_note_diff_command();
3124 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3131 note_diff_remove_note (*i);
3141 MidiRegionView::selection_as_cut_buffer () const
3145 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3146 NoteType* n = (*i)->note().get();
3147 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3150 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3156 /** This method handles undo */
3158 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3164 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3166 trackview.session()->begin_reversible_command (_("paste"));
3168 start_note_diff_command (_("paste"));
3170 Evoral::MusicalTime beat_delta;
3171 Evoral::MusicalTime paste_pos_beats;
3172 Evoral::MusicalTime duration;
3173 Evoral::MusicalTime end_point = 0;
3175 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3176 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3177 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3178 paste_pos_beats = 0;
3180 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",
3181 (*mcb.notes().begin())->time(),
3182 (*mcb.notes().rbegin())->end_time(),
3183 duration, pos, _region->position(),
3184 paste_pos_beats, beat_delta));
3188 for (int n = 0; n < (int) times; ++n) {
3190 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3192 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3193 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3195 /* make all newly added notes selected */
3197 note_diff_add_note (copied_note, true);
3198 end_point = copied_note->end_time();
3201 paste_pos_beats += duration;
3204 /* if we pasted past the current end of the region, extend the region */
3206 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3207 framepos_t region_end = _region->position() + _region->length() - 1;
3209 if (end_frame > region_end) {
3211 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3213 _region->clear_changes ();
3214 _region->set_length (end_frame);
3215 trackview.session()->add_command (new StatefulDiffCommand (_region));
3220 trackview.session()->commit_reversible_command ();
3223 struct EventNoteTimeEarlyFirstComparator {
3224 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3225 return a->note()->time() < b->note()->time();
3230 MidiRegionView::time_sort_events ()
3232 if (!_sort_needed) {
3236 EventNoteTimeEarlyFirstComparator cmp;
3239 _sort_needed = false;
3243 MidiRegionView::goto_next_note (bool add_to_selection)
3245 bool use_next = false;
3247 if (_events.back()->selected()) {
3251 time_sort_events ();
3253 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3254 if ((*i)->selected()) {
3257 } else if (use_next) {
3258 if (!add_to_selection) {
3261 note_selected (*i, true, false);
3267 /* use the first one */
3269 unique_select (_events.front());
3274 MidiRegionView::goto_previous_note (bool add_to_selection)
3276 bool use_next = false;
3278 if (_events.front()->selected()) {
3282 time_sort_events ();
3284 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3285 if ((*i)->selected()) {
3288 } else if (use_next) {
3289 if (!add_to_selection) {
3292 note_selected (*i, true, false);
3298 /* use the last one */
3300 unique_select (*(_events.rbegin()));
3304 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3306 bool had_selected = false;
3308 time_sort_events ();
3310 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3311 if ((*i)->selected()) {
3312 selected.insert ((*i)->note());
3313 had_selected = true;
3317 if (allow_all_if_none_selected && !had_selected) {
3318 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3319 selected.insert ((*i)->note());
3325 MidiRegionView::update_ghost_note (double x, double y)
3327 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3332 _note_group->w2i (x, y);
3334 PublicEditor& editor = trackview.editor ();
3336 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3337 framecnt_t grid_frames;
3338 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3340 /* use region_frames... because we are converting a delta within the region
3343 double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3345 /* note that this sets the time of the ghost note in beats relative to
3346 the start of the source; that is how all note times are stored.
3348 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3349 _ghost_note->note()->set_length (length);
3350 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3351 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3353 /* the ghost note does not appear in ghost regions, so pass false in here */
3354 update_note (_ghost_note, false);
3356 show_verbose_cursor (_ghost_note->note ());
3360 MidiRegionView::create_ghost_note (double x, double y)
3365 boost::shared_ptr<NoteType> g (new NoteType);
3366 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3367 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3368 update_ghost_note (x, y);
3369 _ghost_note->show ();
3374 show_verbose_cursor (_ghost_note->note ());
3378 MidiRegionView::snap_changed ()
3384 create_ghost_note (_last_ghost_x, _last_ghost_y);
3388 MidiRegionView::drop_down_keys ()
3390 _mouse_state = None;
3394 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3396 double note = midi_stream_view()->y_to_note(y);
3398 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3400 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3402 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3403 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3404 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3405 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3410 bool add_mrv_selection = false;
3412 if (_selection.empty()) {
3413 add_mrv_selection = true;
3416 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3417 if (_selection.insert (*i).second) {
3418 (*i)->set_selected (true);
3422 if (add_mrv_selection) {
3423 PublicEditor& editor (trackview.editor());
3424 editor.get_selection().add (this);
3429 MidiRegionView::color_handler ()
3431 RegionView::color_handler ();
3433 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3434 (*i)->set_selected ((*i)->selected()); // will change color
3437 /* XXX probably more to do here */
3441 MidiRegionView::enable_display (bool yn)
3443 RegionView::enable_display (yn);
3450 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3452 if (_step_edit_cursor == 0) {
3453 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3455 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3456 _step_edit_cursor->property_y1() = 0;
3457 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3458 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3459 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3462 move_step_edit_cursor (pos);
3463 _step_edit_cursor->show ();
3467 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3469 _step_edit_cursor_position = pos;
3471 if (_step_edit_cursor) {
3472 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3473 _step_edit_cursor->property_x1() = pixel;
3474 set_step_edit_cursor_width (_step_edit_cursor_width);
3479 MidiRegionView::hide_step_edit_cursor ()
3481 if (_step_edit_cursor) {
3482 _step_edit_cursor->hide ();
3487 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3489 _step_edit_cursor_width = beats;
3491 if (_step_edit_cursor) {
3492 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3496 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3497 * @param buf Data that has been recorded.
3498 * @param w Source that this data will end up in.
3501 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3503 if (!_active_notes) {
3504 /* we aren't actively being recorded to */
3508 boost::shared_ptr<MidiSource> src = w.lock ();
3509 if (!src || src != midi_region()->midi_source()) {
3510 /* recorded data was not destined for our source */
3514 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3515 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3517 framepos_t back = max_framepos;
3519 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3520 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3521 assert (ev.buffer ());
3523 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3525 if (ev.type() == MIDI_CMD_NOTE_ON) {
3527 boost::shared_ptr<NoteType> note (
3528 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3531 add_note (note, true);
3533 /* fix up our note range */
3534 if (ev.note() < _current_range_min) {
3535 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3536 } else if (ev.note() > _current_range_max) {
3537 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3540 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3541 resolve_note (ev.note (), time_beats);
3547 midi_stream_view()->check_record_layers (region(), back);
3551 MidiRegionView::trim_front_starting ()
3553 /* Reparent the note group to the region view's parent, so that it doesn't change
3554 when the region view is trimmed.
3556 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3557 _temporary_note_group->move (group->property_x(), group->property_y());
3558 _note_group->reparent (*_temporary_note_group);
3562 MidiRegionView::trim_front_ending ()
3564 _note_group->reparent (*group);
3565 delete _temporary_note_group;
3566 _temporary_note_group = 0;
3568 if (_region->start() < 0) {
3569 /* Trim drag made start time -ve; fix this */
3570 midi_region()->fix_negative_start ();
3575 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3577 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3578 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3582 change_patch_change (pc->patch(), d.patch ());
3587 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3590 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3591 Evoral::midi_note_name (n->note()).c_str(),
3593 (int) n->channel() + 1,
3594 (int) n->velocity());
3596 show_verbose_cursor (buf, 10, 20);
3600 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3604 trackview.editor().get_pointer_position (wx, wy);
3609 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3611 double x1, y1, x2, y2;
3612 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3614 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3615 wy -= (y2 - y1) + 2 * yoffset;
3618 trackview.editor().verbose_cursor()->set (text, wx, wy);
3619 trackview.editor().verbose_cursor()->show ();
3622 /** @param p A session framepos.
3623 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3624 * @return p snapped to the grid subdivision underneath it.
3627 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3629 PublicEditor& editor = trackview.editor ();
3632 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3638 grid_frames = region_beats_to_region_frames (grid_beats);
3640 /* Hack so that we always snap to the note that we are over, instead of snapping
3641 to the next one if we're more than halfway through the one we're over.
3643 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3644 p -= grid_frames / 2;
3647 return snap_frame_to_frame (p);
3650 /** Called when the selection has been cleared in any MidiRegionView.
3651 * @param rv MidiRegionView that the selection was cleared in.
3654 MidiRegionView::selection_cleared (MidiRegionView* rv)
3660 /* Clear our selection in sympathy; but don't signal the fact */
3661 clear_selection (false);