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::invert_selection ()
2033 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2034 if ((*i)->selected()) {
2035 remove_from_selection(*i);
2037 add_to_selection (*i);
2043 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2045 uint8_t low_note = 127;
2046 uint8_t high_note = 0;
2047 MidiModel::Notes& notes (_model->notes());
2048 _optimization_iterator = _events.begin();
2054 if (extend && _selection.empty()) {
2060 /* scan existing selection to get note range */
2062 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2063 if ((*i)->note()->note() < low_note) {
2064 low_note = (*i)->note()->note();
2066 if ((*i)->note()->note() > high_note) {
2067 high_note = (*i)->note()->note();
2071 low_note = min (low_note, notenum);
2072 high_note = max (high_note, notenum);
2075 _no_sound_notes = true;
2077 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2079 boost::shared_ptr<NoteType> note (*n);
2080 CanvasNoteEvent* cne;
2081 bool select = false;
2083 if (((1 << note->channel()) & channel_mask) != 0) {
2085 if ((note->note() >= low_note && note->note() <= high_note)) {
2088 } else if (note->note() == notenum) {
2094 if ((cne = find_canvas_note (note)) != 0) {
2095 // extend is false because we've taken care of it,
2096 // since it extends by time range, not pitch.
2097 note_selected (cne, add, false);
2101 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2105 _no_sound_notes = false;
2109 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2111 MidiModel::Notes& notes (_model->notes());
2112 _optimization_iterator = _events.begin();
2114 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2116 boost::shared_ptr<NoteType> note (*n);
2117 CanvasNoteEvent* cne;
2119 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2120 if ((cne = find_canvas_note (note)) != 0) {
2121 if (cne->selected()) {
2122 note_deselected (cne);
2124 note_selected (cne, true, false);
2132 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2135 clear_selection_except (ev);
2136 if (!_selection.empty()) {
2137 PublicEditor& editor (trackview.editor());
2138 editor.get_selection().add (this);
2144 if (!ev->selected()) {
2145 add_to_selection (ev);
2149 /* find end of latest note selected, select all between that and the start of "ev" */
2151 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2152 Evoral::MusicalTime latest = 0;
2154 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2155 if ((*i)->note()->end_time() > latest) {
2156 latest = (*i)->note()->end_time();
2158 if ((*i)->note()->time() < earliest) {
2159 earliest = (*i)->note()->time();
2163 if (ev->note()->end_time() > latest) {
2164 latest = ev->note()->end_time();
2167 if (ev->note()->time() < earliest) {
2168 earliest = ev->note()->time();
2171 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2173 /* find notes entirely within OR spanning the earliest..latest range */
2175 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2176 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2177 add_to_selection (*i);
2185 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2187 remove_from_selection (ev);
2191 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2201 // TODO: Make this faster by storing the last updated selection rect, and only
2202 // adjusting things that are in the area that appears/disappeared.
2203 // We probably need a tree to be able to find events in O(log(n)) time.
2205 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2207 /* check if any corner of the note is inside the rect
2210 1) this is computing "touched by", not "contained by" the rect.
2211 2) this does not require that events be sorted in time.
2214 const double ix1 = (*i)->x1();
2215 const double ix2 = (*i)->x2();
2216 const double iy1 = (*i)->y1();
2217 const double iy2 = (*i)->y2();
2219 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2220 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2221 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2222 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2225 if (!(*i)->selected()) {
2226 add_to_selection (*i);
2228 } else if ((*i)->selected() && !extend) {
2229 // Not inside rectangle
2230 remove_from_selection (*i);
2236 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2238 Selection::iterator i = _selection.find (ev);
2240 if (i != _selection.end()) {
2241 _selection.erase (i);
2244 ev->set_selected (false);
2245 ev->hide_velocity ();
2247 if (_selection.empty()) {
2248 PublicEditor& editor (trackview.editor());
2249 editor.get_selection().remove (this);
2254 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2256 bool add_mrv_selection = false;
2258 if (_selection.empty()) {
2259 add_mrv_selection = true;
2262 if (_selection.insert (ev).second) {
2263 ev->set_selected (true);
2264 play_midi_note ((ev)->note());
2267 if (add_mrv_selection) {
2268 PublicEditor& editor (trackview.editor());
2269 editor.get_selection().add (this);
2274 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2276 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2277 PossibleChord to_play;
2278 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2280 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2281 if ((*i)->note()->time() < earliest) {
2282 earliest = (*i)->note()->time();
2286 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2287 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2288 to_play.push_back ((*i)->note());
2290 (*i)->move_event(dx, dy);
2293 if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2295 if (to_play.size() > 1) {
2297 PossibleChord shifted;
2299 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2300 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2301 moved_note->set_note (moved_note->note() + cumulative_dy);
2302 shifted.push_back (moved_note);
2305 play_midi_chord (shifted);
2307 } else if (!to_play.empty()) {
2309 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2310 moved_note->set_note (moved_note->note() + cumulative_dy);
2311 play_midi_note (moved_note);
2317 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2319 assert (!_selection.empty());
2321 uint8_t lowest_note_in_selection = 127;
2322 uint8_t highest_note_in_selection = 0;
2323 uint8_t highest_note_difference = 0;
2325 // find highest and lowest notes first
2327 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2328 uint8_t pitch = (*i)->note()->note();
2329 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2330 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2334 cerr << "dnote: " << (int) dnote << endl;
2335 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2336 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2337 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2338 << int(highest_note_in_selection) << endl;
2339 cerr << "selection size: " << _selection.size() << endl;
2340 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2343 // Make sure the note pitch does not exceed the MIDI standard range
2344 if (highest_note_in_selection + dnote > 127) {
2345 highest_note_difference = highest_note_in_selection - 127;
2348 start_note_diff_command (_("move notes"));
2350 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2352 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2358 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2360 uint8_t original_pitch = (*i)->note()->note();
2361 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2363 // keep notes in standard midi range
2364 clamp_to_0_127(new_pitch);
2366 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2367 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2369 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2374 // care about notes being moved beyond the upper/lower bounds on the canvas
2375 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2376 highest_note_in_selection > midi_stream_view()->highest_note()) {
2377 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2382 MidiRegionView::snap_pixel_to_frame(double x)
2384 PublicEditor& editor (trackview.editor());
2385 return snap_frame_to_frame (editor.pixel_to_frame (x));
2389 MidiRegionView::snap_to_pixel(double x)
2391 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2395 MidiRegionView::get_position_pixels()
2397 framepos_t region_frame = get_position();
2398 return trackview.editor().frame_to_pixel(region_frame);
2402 MidiRegionView::get_end_position_pixels()
2404 framepos_t frame = get_position() + get_duration ();
2405 return trackview.editor().frame_to_pixel(frame);
2409 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2411 /* the time converter will return the frame corresponding to `beats'
2412 relative to the start of the source. The start of the source
2413 is an implied position given by region->position - region->start
2415 const framepos_t source_start = _region->position() - _region->start();
2416 return source_start + _source_relative_time_converter.to (beats);
2420 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2422 /* the `frames' argument needs to be converted into a frame count
2423 relative to the start of the source before being passed in to the
2426 const framepos_t source_start = _region->position() - _region->start();
2427 return _source_relative_time_converter.from (frames - source_start);
2431 MidiRegionView::region_beats_to_region_frames(double beats) const
2433 return _region_relative_time_converter.to(beats);
2437 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2439 return _region_relative_time_converter.from(frames);
2443 MidiRegionView::begin_resizing (bool /*at_front*/)
2445 _resize_data.clear();
2447 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2448 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2450 // only insert CanvasNotes into the map
2452 NoteResizeData *resize_data = new NoteResizeData();
2453 resize_data->canvas_note = note;
2455 // create a new SimpleRect from the note which will be the resize preview
2456 SimpleRect *resize_rect = new SimpleRect(
2457 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2459 // calculate the colors: get the color settings
2460 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2461 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2464 // make the resize preview notes more transparent and bright
2465 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2467 // calculate color based on note velocity
2468 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2469 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2473 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2474 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2476 resize_data->resize_rect = resize_rect;
2477 _resize_data.push_back(resize_data);
2482 /** Update resizing notes while user drags.
2483 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2484 * @param at_front which end of the note (true == note on, false == note off)
2485 * @param delta_x change in mouse position since the start of the drag
2486 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2487 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2488 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2489 * as the \a primary note.
2492 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2494 bool cursor_set = false;
2496 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2497 SimpleRect* resize_rect = (*i)->resize_rect;
2498 CanvasNote* canvas_note = (*i)->canvas_note;
2503 current_x = canvas_note->x1() + delta_x;
2505 current_x = primary->x1() + delta_x;
2509 current_x = canvas_note->x2() + delta_x;
2511 current_x = primary->x2() + delta_x;
2516 resize_rect->property_x1() = snap_to_pixel(current_x);
2517 resize_rect->property_x2() = canvas_note->x2();
2519 resize_rect->property_x2() = snap_to_pixel(current_x);
2520 resize_rect->property_x1() = canvas_note->x1();
2526 beats = snap_pixel_to_frame (current_x);
2527 /* XXX not sure this is correct - snap_pixel_to_frame()
2528 returns an absolute frame.
2530 beats = region_frames_to_region_beats (beats);
2535 if (beats < canvas_note->note()->end_time()) {
2536 len = canvas_note->note()->time() - beats;
2537 len += canvas_note->note()->length();
2542 if (beats >= canvas_note->note()->time()) {
2543 len = beats - canvas_note->note()->time();
2550 snprintf (buf, sizeof (buf), "%.3g beats", len);
2551 show_verbose_cursor (buf, 0, 0);
2560 /** Finish resizing notes when the user releases the mouse button.
2561 * Parameters the same as for \a update_resizing().
2564 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2566 start_note_diff_command (_("resize notes"));
2568 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2569 CanvasNote* canvas_note = (*i)->canvas_note;
2570 SimpleRect* resize_rect = (*i)->resize_rect;
2572 /* Get the new x position for this resize, which is in pixels relative
2573 * to the region position.
2580 current_x = canvas_note->x1() + delta_x;
2582 current_x = primary->x1() + delta_x;
2586 current_x = canvas_note->x2() + delta_x;
2588 current_x = primary->x2() + delta_x;
2592 /* Convert that to a frame within the region */
2593 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2595 /* and then to beats */
2596 /* XXX not sure this is correct - snap_pixel_to_frame()
2597 returns an absolute frame.
2599 current_x = region_frames_to_region_beats (current_x);
2601 if (at_front && current_x < canvas_note->note()->end_time()) {
2602 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2604 double len = canvas_note->note()->time() - current_x;
2605 len += canvas_note->note()->length();
2608 /* XXX convert to beats */
2609 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2614 double len = current_x - canvas_note->note()->time();
2617 /* XXX convert to beats */
2618 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2626 _resize_data.clear();
2631 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2633 uint8_t new_velocity;
2636 new_velocity = event->note()->velocity() + velocity;
2637 clamp_to_0_127(new_velocity);
2639 new_velocity = velocity;
2642 event->set_selected (event->selected()); // change color
2644 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2648 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2653 new_note = event->note()->note() + note;
2658 clamp_to_0_127 (new_note);
2659 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2663 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2665 bool change_start = false;
2666 bool change_length = false;
2667 Evoral::MusicalTime new_start = 0;
2668 Evoral::MusicalTime new_length = 0;
2670 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2672 front_delta: if positive - move the start of the note later in time (shortening it)
2673 if negative - move the start of the note earlier in time (lengthening it)
2675 end_delta: if positive - move the end of the note later in time (lengthening it)
2676 if negative - move the end of the note earlier in time (shortening it)
2680 if (front_delta < 0) {
2682 if (event->note()->time() < -front_delta) {
2685 new_start = event->note()->time() + front_delta; // moves earlier
2688 /* start moved toward zero, so move the end point out to where it used to be.
2689 Note that front_delta is negative, so this increases the length.
2692 new_length = event->note()->length() - front_delta;
2693 change_start = true;
2694 change_length = true;
2698 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2700 if (new_pos < event->note()->end_time()) {
2701 new_start = event->note()->time() + front_delta;
2702 /* start moved toward the end, so move the end point back to where it used to be */
2703 new_length = event->note()->length() - front_delta;
2704 change_start = true;
2705 change_length = true;
2712 bool can_change = true;
2713 if (end_delta < 0) {
2714 if (event->note()->length() < -end_delta) {
2720 new_length = event->note()->length() + end_delta;
2721 change_length = true;
2726 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2729 if (change_length) {
2730 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2735 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2737 uint8_t new_channel;
2741 if (event->note()->channel() < -chn) {
2744 new_channel = event->note()->channel() + chn;
2747 new_channel = event->note()->channel() + chn;
2750 new_channel = (uint8_t) chn;
2753 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2757 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2759 Evoral::MusicalTime new_time;
2763 if (event->note()->time() < -delta) {
2766 new_time = event->note()->time() + delta;
2769 new_time = event->note()->time() + delta;
2775 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2779 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2781 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2785 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2789 if (_selection.empty()) {
2804 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2805 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2811 start_note_diff_command (_("change velocities"));
2813 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2814 Selection::iterator next = i;
2816 change_note_velocity (*i, delta, true);
2822 if (!_selection.empty()) {
2824 snprintf (buf, sizeof (buf), "Vel %d",
2825 (int) (*_selection.begin())->note()->velocity());
2826 show_verbose_cursor (buf, 10, 10);
2832 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2834 if (_selection.empty()) {
2851 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2853 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2857 if ((int8_t) (*i)->note()->note() + delta > 127) {
2864 start_note_diff_command (_("transpose"));
2866 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2867 Selection::iterator next = i;
2869 change_note_note (*i, delta, true);
2877 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2883 /* grab the current grid distance */
2885 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2887 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2888 cerr << "Grid type not available as beats - TO BE FIXED\n";
2898 start_note_diff_command (_("change note lengths"));
2900 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2901 Selection::iterator next = i;
2904 /* note the negation of the delta for start */
2906 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2915 MidiRegionView::nudge_notes (bool forward)
2917 if (_selection.empty()) {
2921 /* pick a note as the point along the timeline to get the nudge distance.
2922 its not necessarily the earliest note, so we may want to pull the notes out
2923 into a vector and sort before using the first one.
2926 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2928 framepos_t distance;
2930 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2932 /* grid is off - use nudge distance */
2934 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2940 framepos_t next_pos = ref_point;
2943 if (max_framepos - 1 < next_pos) {
2947 if (next_pos == 0) {
2953 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2954 distance = ref_point - next_pos;
2957 if (distance == 0) {
2961 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2967 start_note_diff_command (_("nudge"));
2969 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2970 Selection::iterator next = i;
2972 change_note_time (*i, delta, true);
2980 MidiRegionView::change_channel(uint8_t channel)
2982 start_note_diff_command(_("change channel"));
2983 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2984 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2992 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2994 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2996 _pre_enter_cursor = editor->get_canvas_cursor ();
2998 if (_mouse_state == SelectTouchDragging) {
2999 note_selected (ev, true);
3002 show_verbose_cursor (ev->note ());
3006 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3008 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3010 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3011 (*i)->hide_velocity ();
3014 editor->verbose_cursor()->hide ();
3016 if (_pre_enter_cursor) {
3017 editor->set_canvas_cursor (_pre_enter_cursor);
3018 _pre_enter_cursor = 0;
3023 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3026 /* XXX should get patch name if we can */
3027 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3028 show_verbose_cursor (s.str(), 10, 20);
3032 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3034 trackview.editor().verbose_cursor()->hide ();
3038 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3040 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3042 if (x_fraction > 0.0 && x_fraction < 0.25) {
3043 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3044 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3045 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3047 if (_pre_enter_cursor && can_set_cursor) {
3048 editor->set_canvas_cursor (_pre_enter_cursor);
3054 MidiRegionView::set_frame_color()
3058 TimeAxisViewItem::set_frame_color ();
3065 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3066 } else if (high_enough_for_name) {
3067 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3072 if (!rect_visible) {
3073 f = UINT_RGBA_CHANGE_A (f, 0);
3076 frame->property_fill_color_rgba() = f;
3080 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3084 case FilterChannels:
3085 _force_channel = -1;
3088 _force_channel = mask;
3089 mask = 0xFFFF; // Show all notes as active (below)
3092 // Update notes for selection
3093 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3094 (*i)->on_channel_selection_change(mask);
3097 _last_channel_selection = mask;
3099 _patch_changes.clear ();
3100 display_patch_changes ();
3104 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3106 _model_name = model;
3107 _custom_device_mode = custom_device_mode;
3112 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3114 if (_selection.empty()) {
3118 PublicEditor& editor (trackview.editor());
3122 /* XXX what to do ? */
3126 editor.get_cut_buffer().add (selection_as_cut_buffer());
3134 start_note_diff_command();
3136 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3143 note_diff_remove_note (*i);
3153 MidiRegionView::selection_as_cut_buffer () const
3157 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3158 NoteType* n = (*i)->note().get();
3159 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3162 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3168 /** This method handles undo */
3170 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3176 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3178 trackview.session()->begin_reversible_command (_("paste"));
3180 start_note_diff_command (_("paste"));
3182 Evoral::MusicalTime beat_delta;
3183 Evoral::MusicalTime paste_pos_beats;
3184 Evoral::MusicalTime duration;
3185 Evoral::MusicalTime end_point = 0;
3187 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3188 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3189 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3190 paste_pos_beats = 0;
3192 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",
3193 (*mcb.notes().begin())->time(),
3194 (*mcb.notes().rbegin())->end_time(),
3195 duration, pos, _region->position(),
3196 paste_pos_beats, beat_delta));
3200 for (int n = 0; n < (int) times; ++n) {
3202 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3204 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3205 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3207 /* make all newly added notes selected */
3209 note_diff_add_note (copied_note, true);
3210 end_point = copied_note->end_time();
3213 paste_pos_beats += duration;
3216 /* if we pasted past the current end of the region, extend the region */
3218 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3219 framepos_t region_end = _region->position() + _region->length() - 1;
3221 if (end_frame > region_end) {
3223 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3225 _region->clear_changes ();
3226 _region->set_length (end_frame);
3227 trackview.session()->add_command (new StatefulDiffCommand (_region));
3232 trackview.session()->commit_reversible_command ();
3235 struct EventNoteTimeEarlyFirstComparator {
3236 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3237 return a->note()->time() < b->note()->time();
3242 MidiRegionView::time_sort_events ()
3244 if (!_sort_needed) {
3248 EventNoteTimeEarlyFirstComparator cmp;
3251 _sort_needed = false;
3255 MidiRegionView::goto_next_note (bool add_to_selection)
3257 bool use_next = false;
3259 if (_events.back()->selected()) {
3263 time_sort_events ();
3265 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3266 if ((*i)->selected()) {
3269 } else if (use_next) {
3270 if (!add_to_selection) {
3273 note_selected (*i, true, false);
3279 /* use the first one */
3281 unique_select (_events.front());
3286 MidiRegionView::goto_previous_note (bool add_to_selection)
3288 bool use_next = false;
3290 if (_events.front()->selected()) {
3294 time_sort_events ();
3296 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3297 if ((*i)->selected()) {
3300 } else if (use_next) {
3301 if (!add_to_selection) {
3304 note_selected (*i, true, false);
3310 /* use the last one */
3312 unique_select (*(_events.rbegin()));
3316 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3318 bool had_selected = false;
3320 time_sort_events ();
3322 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3323 if ((*i)->selected()) {
3324 selected.insert ((*i)->note());
3325 had_selected = true;
3329 if (allow_all_if_none_selected && !had_selected) {
3330 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3331 selected.insert ((*i)->note());
3337 MidiRegionView::update_ghost_note (double x, double y)
3339 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3344 _note_group->w2i (x, y);
3346 PublicEditor& editor = trackview.editor ();
3348 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3349 framecnt_t grid_frames;
3350 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3352 /* use region_frames... because we are converting a delta within the region
3355 double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3357 /* note that this sets the time of the ghost note in beats relative to
3358 the start of the source; that is how all note times are stored.
3360 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3361 _ghost_note->note()->set_length (length);
3362 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3363 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3365 /* the ghost note does not appear in ghost regions, so pass false in here */
3366 update_note (_ghost_note, false);
3368 show_verbose_cursor (_ghost_note->note ());
3372 MidiRegionView::create_ghost_note (double x, double y)
3377 boost::shared_ptr<NoteType> g (new NoteType);
3378 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3379 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3380 update_ghost_note (x, y);
3381 _ghost_note->show ();
3386 show_verbose_cursor (_ghost_note->note ());
3390 MidiRegionView::snap_changed ()
3396 create_ghost_note (_last_ghost_x, _last_ghost_y);
3400 MidiRegionView::drop_down_keys ()
3402 _mouse_state = None;
3406 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3408 double note = midi_stream_view()->y_to_note(y);
3410 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3412 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3414 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3415 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3416 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3417 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3422 bool add_mrv_selection = false;
3424 if (_selection.empty()) {
3425 add_mrv_selection = true;
3428 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3429 if (_selection.insert (*i).second) {
3430 (*i)->set_selected (true);
3434 if (add_mrv_selection) {
3435 PublicEditor& editor (trackview.editor());
3436 editor.get_selection().add (this);
3441 MidiRegionView::color_handler ()
3443 RegionView::color_handler ();
3445 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3446 (*i)->set_selected ((*i)->selected()); // will change color
3449 /* XXX probably more to do here */
3453 MidiRegionView::enable_display (bool yn)
3455 RegionView::enable_display (yn);
3462 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3464 if (_step_edit_cursor == 0) {
3465 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3467 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3468 _step_edit_cursor->property_y1() = 0;
3469 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3470 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3471 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3474 move_step_edit_cursor (pos);
3475 _step_edit_cursor->show ();
3479 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3481 _step_edit_cursor_position = pos;
3483 if (_step_edit_cursor) {
3484 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3485 _step_edit_cursor->property_x1() = pixel;
3486 set_step_edit_cursor_width (_step_edit_cursor_width);
3491 MidiRegionView::hide_step_edit_cursor ()
3493 if (_step_edit_cursor) {
3494 _step_edit_cursor->hide ();
3499 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3501 _step_edit_cursor_width = beats;
3503 if (_step_edit_cursor) {
3504 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3508 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3509 * @param buf Data that has been recorded.
3510 * @param w Source that this data will end up in.
3513 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3515 if (!_active_notes) {
3516 /* we aren't actively being recorded to */
3520 boost::shared_ptr<MidiSource> src = w.lock ();
3521 if (!src || src != midi_region()->midi_source()) {
3522 /* recorded data was not destined for our source */
3526 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3527 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3529 framepos_t back = max_framepos;
3531 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3532 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3533 assert (ev.buffer ());
3535 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3537 if (ev.type() == MIDI_CMD_NOTE_ON) {
3539 boost::shared_ptr<NoteType> note (
3540 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3543 add_note (note, true);
3545 /* fix up our note range */
3546 if (ev.note() < _current_range_min) {
3547 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3548 } else if (ev.note() > _current_range_max) {
3549 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3552 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3553 resolve_note (ev.note (), time_beats);
3559 midi_stream_view()->check_record_layers (region(), back);
3563 MidiRegionView::trim_front_starting ()
3565 /* Reparent the note group to the region view's parent, so that it doesn't change
3566 when the region view is trimmed.
3568 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3569 _temporary_note_group->move (group->property_x(), group->property_y());
3570 _note_group->reparent (*_temporary_note_group);
3574 MidiRegionView::trim_front_ending ()
3576 _note_group->reparent (*group);
3577 delete _temporary_note_group;
3578 _temporary_note_group = 0;
3580 if (_region->start() < 0) {
3581 /* Trim drag made start time -ve; fix this */
3582 midi_region()->fix_negative_start ();
3587 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3589 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3590 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3594 change_patch_change (pc->patch(), d.patch ());
3599 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3602 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3603 Evoral::midi_note_name (n->note()).c_str(),
3605 (int) n->channel() + 1,
3606 (int) n->velocity());
3608 show_verbose_cursor (buf, 10, 20);
3612 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3616 trackview.editor().get_pointer_position (wx, wy);
3621 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3623 double x1, y1, x2, y2;
3624 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3626 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3627 wy -= (y2 - y1) + 2 * yoffset;
3630 trackview.editor().verbose_cursor()->set (text, wx, wy);
3631 trackview.editor().verbose_cursor()->show ();
3634 /** @param p A session framepos.
3635 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3636 * @return p snapped to the grid subdivision underneath it.
3639 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3641 PublicEditor& editor = trackview.editor ();
3644 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3650 grid_frames = region_beats_to_region_frames (grid_beats);
3652 /* Hack so that we always snap to the note that we are over, instead of snapping
3653 to the next one if we're more than halfway through the one we're over.
3655 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3656 p -= grid_frames / 2;
3659 return snap_frame_to_frame (p);
3662 /** Called when the selection has been cleared in any MidiRegionView.
3663 * @param rv MidiRegionView that the selection was cleared in.
3666 MidiRegionView::selection_cleared (MidiRegionView* rv)
3672 /* Clear our selection in sympathy; but don't signal the fact */
3673 clear_selection (false);