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/midi_region.h"
35 #include "ardour/midi_source.h"
36 #include "ardour/midi_model.h"
37 #include "ardour/midi_patch_manager.h"
38 #include "ardour/session.h"
40 #include "evoral/Parameter.hpp"
41 #include "evoral/MIDIParameters.hpp"
42 #include "evoral/MIDIEvent.hpp"
43 #include "evoral/Control.hpp"
44 #include "evoral/midi_util.h"
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas_patch_change.h"
53 #include "editor_drag.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 "mouse_cursors.h"
65 #include "note_player.h"
66 #include "public_editor.h"
67 #include "rgb_macros.h"
68 #include "selection.h"
69 #include "simpleline.h"
70 #include "streamview.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)
90 , _last_channel_selection(0xFFFF)
91 , _current_range_min(0)
92 , _current_range_max(0)
94 , _note_group(new ArdourCanvas::Group(*group))
95 , _note_diff_command (0)
97 , _step_edit_cursor (0)
98 , _step_edit_cursor_width (1.0)
99 , _step_edit_cursor_position (0.0)
100 , _channel_selection_scoped_note (0)
101 , _temporary_note_group (0)
104 , _sort_needed (true)
105 , _optimization_iterator (_events.end())
107 , _no_sound_notes (false)
110 , pre_enter_cursor (0)
111 , pre_press_cursor (0)
113 _note_group->raise_to_top();
114 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
116 /* Look up MIDNAM details from our MidiTimeAxisView */
117 MidiTimeAxisView& mtv = dynamic_cast<MidiTimeAxisView&> (tv);
118 midi_patch_settings_changed (mtv.midi_patch_model (), mtv.midi_patch_custom_device_node ());
120 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
121 connect_to_diskstream ();
123 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
126 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
127 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
128 TimeAxisViewItem::Visibility visibility)
129 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
130 , _last_channel_selection(0xFFFF)
131 , _current_range_min(0)
132 , _current_range_max(0)
134 , _note_group(new ArdourCanvas::Group(*parent))
135 , _note_diff_command (0)
137 , _step_edit_cursor (0)
138 , _step_edit_cursor_width (1.0)
139 , _step_edit_cursor_position (0.0)
140 , _channel_selection_scoped_note (0)
141 , _temporary_note_group (0)
144 , _sort_needed (true)
145 , _optimization_iterator (_events.end())
147 , _no_sound_notes (false)
150 , pre_enter_cursor (0)
151 , pre_press_cursor (0)
153 _note_group->raise_to_top();
154 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
156 /* Look up MIDNAM details from our MidiTimeAxisView */
157 MidiTimeAxisView& mtv = dynamic_cast<MidiTimeAxisView&> (tv);
158 midi_patch_settings_changed (mtv.midi_patch_model (), mtv.midi_patch_custom_device_node ());
160 connect_to_diskstream ();
162 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
166 MidiRegionView::parameter_changed (std::string const & p)
168 if (p == "diplay-first-midi-bank-as-zero") {
169 if (_enable_display) {
175 MidiRegionView::MidiRegionView (const MidiRegionView& other)
176 : sigc::trackable(other)
178 , _last_channel_selection(0xFFFF)
179 , _current_range_min(0)
180 , _current_range_max(0)
182 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
183 , _note_diff_command (0)
185 , _step_edit_cursor (0)
186 , _step_edit_cursor_width (1.0)
187 , _step_edit_cursor_position (0.0)
188 , _channel_selection_scoped_note (0)
189 , _temporary_note_group (0)
192 , _sort_needed (true)
193 , _optimization_iterator (_events.end())
195 , _no_sound_notes (false)
198 , pre_enter_cursor (0)
199 , pre_press_cursor (0)
204 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
205 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
210 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
211 : RegionView (other, boost::shared_ptr<Region> (region))
212 , _last_channel_selection(0xFFFF)
213 , _current_range_min(0)
214 , _current_range_max(0)
216 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
217 , _note_diff_command (0)
219 , _step_edit_cursor (0)
220 , _step_edit_cursor_width (1.0)
221 , _step_edit_cursor_position (0.0)
222 , _channel_selection_scoped_note (0)
223 , _temporary_note_group (0)
226 , _sort_needed (true)
227 , _optimization_iterator (_events.end())
229 , _no_sound_notes (false)
232 , pre_enter_cursor (0)
233 , pre_press_cursor (0)
238 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
239 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
245 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
247 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
249 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
250 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
254 midi_region()->midi_source(0)->load_model();
257 _model = midi_region()->midi_source(0)->model();
258 _enable_display = false;
260 RegionView::init (basic_color, false);
262 compute_colors (basic_color);
264 set_height (trackview.current_height());
267 region_sync_changed ();
268 region_resized (ARDOUR::bounds_change);
271 reset_width_dependent_items (_pixel_width);
275 _enable_display = true;
278 display_model (_model);
282 group->raise_to_top();
283 group->signal_event().connect(
284 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
286 midi_view()->signal_channel_mode_changed().connect(
287 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
289 midi_view()->signal_midi_patch_settings_changed().connect(
290 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
292 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
293 boost::bind (&MidiRegionView::snap_changed, this),
296 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
297 connect_to_diskstream ();
299 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
302 const boost::shared_ptr<ARDOUR::MidiRegion>
303 MidiRegionView::midi_region() const
305 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
309 MidiRegionView::connect_to_diskstream ()
311 midi_view()->midi_track()->DataRecorded.connect(
312 *this, invalidator(*this),
313 boost::bind (&MidiRegionView::data_recorded, this, _1),
318 MidiRegionView::canvas_event(GdkEvent* ev)
321 case GDK_ENTER_NOTIFY:
322 case GDK_LEAVE_NOTIFY:
323 _last_event_x = ev->crossing.x;
324 _last_event_y = ev->crossing.y;
326 case GDK_MOTION_NOTIFY:
327 _last_event_x = ev->motion.x;
328 _last_event_y = ev->motion.y;
334 if (ev->type == GDK_2BUTTON_PRESS) {
335 return trackview.editor().toggle_internal_editing_from_double_click (ev);
338 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
339 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
340 (trackview.editor().current_mouse_mode() == MouseZoom)) {
341 // handle non-draw modes elsewhere
347 return scroll (&ev->scroll);
350 return key_press (&ev->key);
352 case GDK_KEY_RELEASE:
353 return key_release (&ev->key);
355 case GDK_BUTTON_PRESS:
356 return button_press (&ev->button);
358 case GDK_BUTTON_RELEASE:
359 return button_release (&ev->button);
361 case GDK_ENTER_NOTIFY:
362 return enter_notify (&ev->crossing);
364 case GDK_LEAVE_NOTIFY:
365 return leave_notify (&ev->crossing);
367 case GDK_MOTION_NOTIFY:
368 return motion (&ev->motion);
378 MidiRegionView::remove_ghost_note ()
385 MidiRegionView::enter_notify (GdkEventCrossing* ev)
387 trackview.editor().MouseModeChanged.connect (
388 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
391 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
392 create_ghost_note (ev->x, ev->y);
395 if (!trackview.editor().internal_editing()) {
396 Keyboard::magic_widget_drop_focus();
398 Keyboard::magic_widget_grab_focus();
402 // if current operation is non-operational in a midi region, change the cursor to so indicate
403 if (trackview.editor().current_mouse_mode() == MouseGain) {
404 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
405 pre_enter_cursor = editor->get_canvas_cursor();
406 editor->set_canvas_cursor(editor->cursors()->timebar);
413 MidiRegionView::leave_notify (GdkEventCrossing*)
415 _mouse_mode_connection.disconnect ();
417 trackview.editor().verbose_cursor()->hide ();
418 remove_ghost_note ();
420 if (pre_enter_cursor) {
421 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
422 editor->set_canvas_cursor(pre_enter_cursor);
429 MidiRegionView::mouse_mode_changed ()
431 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
432 create_ghost_note (_last_event_x, _last_event_y);
434 remove_ghost_note ();
435 trackview.editor().verbose_cursor()->hide ();
438 if (!trackview.editor().internal_editing()) {
439 Keyboard::magic_widget_drop_focus();
441 Keyboard::magic_widget_grab_focus();
447 MidiRegionView::button_press (GdkEventButton* ev)
449 if (ev->button != 1) {
453 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
454 MouseMode m = editor->current_mouse_mode();
456 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
457 pre_press_cursor = editor->get_canvas_cursor ();
458 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
461 if (_mouse_state != SelectTouchDragging) {
463 _pressed_button = ev->button;
464 _mouse_state = Pressed;
469 _pressed_button = ev->button;
475 MidiRegionView::button_release (GdkEventButton* ev)
477 double event_x, event_y;
479 if (ev->button != 1) {
486 group->w2i(event_x, event_y);
487 group->ungrab(ev->time);
489 PublicEditor& editor = trackview.editor ();
491 if (pre_press_cursor) {
492 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
493 pre_press_cursor = 0;
496 switch (_mouse_state) {
497 case Pressed: // Clicked
499 switch (editor.current_mouse_mode()) {
501 /* no motion occured - simple click */
510 if (Keyboard::is_insert_note_event(ev)) {
512 double event_x, event_y;
516 group->w2i(event_x, event_y);
519 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
525 /* Shorten the length by 1 tick so that we can add a new note at the next
526 grid snap without it overlapping this one.
528 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
530 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
538 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
544 /* Shorten the length by 1 tick so that we can add a new note at the next
545 grid snap without it overlapping this one.
547 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
549 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
560 case SelectRectDragging:
562 editor.drags()->end_grab ((GdkEvent *) ev);
564 create_ghost_note (ev->x, ev->y);
576 MidiRegionView::motion (GdkEventMotion* ev)
578 PublicEditor& editor = trackview.editor ();
580 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
581 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
582 _mouse_state != AddDragging) {
584 create_ghost_note (ev->x, ev->y);
586 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
587 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
589 update_ghost_note (ev->x, ev->y);
591 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
593 remove_ghost_note ();
594 editor.verbose_cursor()->hide ();
596 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
598 update_ghost_note (ev->x, ev->y);
601 /* any motion immediately hides velocity text that may have been visible */
603 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
604 (*i)->hide_velocity ();
607 switch (_mouse_state) {
610 if (_pressed_button == 1) {
612 MouseMode m = editor.current_mouse_mode();
614 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
616 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
617 _mouse_state = AddDragging;
618 remove_ghost_note ();
619 editor.verbose_cursor()->hide ();
621 } else if (m == MouseObject) {
622 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
624 _mouse_state = SelectRectDragging;
626 } else if (m == MouseRange) {
627 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
628 _mouse_state = SelectVerticalDragging;
635 case SelectRectDragging:
636 case SelectVerticalDragging:
638 editor.drags()->motion_handler ((GdkEvent *) ev, false);
641 case SelectTouchDragging:
653 MidiRegionView::scroll (GdkEventScroll* ev)
655 if (_selection.empty()) {
659 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
660 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
661 it still works for zoom.
666 trackview.editor().verbose_cursor()->hide ();
668 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
669 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
671 if (ev->direction == GDK_SCROLL_UP) {
672 change_velocities (true, fine, false, together);
673 } else if (ev->direction == GDK_SCROLL_DOWN) {
674 change_velocities (false, fine, false, together);
680 MidiRegionView::key_press (GdkEventKey* ev)
682 /* since GTK bindings are generally activated on press, and since
683 detectable auto-repeat is the name of the game and only sends
684 repeated presses, carry out key actions at key press, not release.
687 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
689 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
690 _mouse_state = SelectTouchDragging;
693 } else if (ev->keyval == GDK_Escape && unmodified) {
697 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
699 bool start = (ev->keyval == GDK_comma);
700 bool end = (ev->keyval == GDK_period);
701 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
702 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
704 change_note_lengths (fine, shorter, 0.0, start, end);
708 } else if (ev->keyval == GDK_Delete && unmodified) {
713 } else if (ev->keyval == GDK_Tab) {
715 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
716 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
718 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
722 } else if (ev->keyval == GDK_ISO_Left_Tab) {
724 /* Shift-TAB generates ISO Left Tab, for some reason */
726 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
727 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
729 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
735 } else if (ev->keyval == GDK_Up) {
737 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
738 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
739 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
741 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
742 change_velocities (true, fine, allow_smush, together);
744 transpose (true, fine, allow_smush);
748 } else if (ev->keyval == GDK_Down) {
750 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
751 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
752 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
754 if (Keyboard::no_modifiers_active (ev->state)) {
755 transpose (false, fine, allow_smush);
757 change_velocities (false, fine, allow_smush, together);
762 } else if (ev->keyval == GDK_Left && unmodified) {
767 } else if (ev->keyval == GDK_Right && unmodified) {
772 } else if (ev->keyval == GDK_c && unmodified) {
781 MidiRegionView::key_release (GdkEventKey* ev)
783 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
791 MidiRegionView::channel_edit ()
794 uint8_t current_channel = 0;
796 if (_selection.empty()) {
800 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
802 current_channel = (*i)->note()->channel ();
807 MidiChannelDialog channel_dialog (current_channel);
808 int ret = channel_dialog.run ();
811 case Gtk::RESPONSE_OK:
817 uint8_t new_channel = channel_dialog.active_channel ();
819 start_note_diff_command (_("channel edit"));
821 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
822 Selection::iterator next = i;
824 change_note_channel (*i, new_channel);
832 MidiRegionView::show_list_editor ()
835 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
837 _list_editor->present ();
840 /** Add a note to the model, and the view, at a canvas (click) coordinate.
841 * \param t time in frames relative to the position of the region
842 * \param y vertical position in pixels
843 * \param length duration of the note in beats
844 * \param snap_t true to snap t to the grid, otherwise false.
847 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
849 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
850 MidiStreamView* const view = mtv->midi_view();
852 double note = view->y_to_note(y);
855 assert(note <= 127.0);
857 // Start of note in frames relative to region start
859 framecnt_t grid_frames;
860 t = snap_frame_to_grid_underneath (t, grid_frames);
864 assert (length != 0);
866 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
867 region_frames_to_region_beats(t + _region->start()),
869 (uint8_t)note, 0x40));
871 if (_model->contains (new_note)) {
875 view->update_note_range(new_note->note());
877 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
879 _model->apply_command(*trackview.session(), cmd);
881 play_midi_note (new_note);
885 MidiRegionView::clear_events()
890 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
891 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
896 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
901 _patch_changes.clear();
903 _optimization_iterator = _events.end();
907 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
911 content_connection.disconnect ();
912 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
916 if (_enable_display) {
922 MidiRegionView::start_note_diff_command (string name)
924 if (!_note_diff_command) {
925 _note_diff_command = _model->new_note_diff_command (name);
930 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
932 if (_note_diff_command) {
933 _note_diff_command->add (note);
936 _marked_for_selection.insert(note);
939 _marked_for_velocity.insert(note);
944 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
946 if (_note_diff_command && ev->note()) {
947 _note_diff_command->remove(ev->note());
952 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
953 MidiModel::NoteDiffCommand::Property property,
956 if (_note_diff_command) {
957 _note_diff_command->change (ev->note(), property, val);
962 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
963 MidiModel::NoteDiffCommand::Property property,
964 Evoral::MusicalTime val)
966 if (_note_diff_command) {
967 _note_diff_command->change (ev->note(), property, val);
972 MidiRegionView::apply_diff (bool as_subcommand)
976 if (!_note_diff_command) {
980 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
981 // Mark all selected notes for selection when model reloads
982 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
983 _marked_for_selection.insert((*i)->note());
988 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
990 _model->apply_command (*trackview.session(), _note_diff_command);
993 _note_diff_command = 0;
994 midi_view()->midi_track()->playlist_modified();
997 _marked_for_selection.clear();
1000 _marked_for_velocity.clear();
1004 MidiRegionView::abort_command()
1006 delete _note_diff_command;
1007 _note_diff_command = 0;
1012 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1014 if (_optimization_iterator != _events.end()) {
1015 ++_optimization_iterator;
1018 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1019 return *_optimization_iterator;
1022 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1023 if ((*_optimization_iterator)->note() == note) {
1024 return *_optimization_iterator;
1032 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1034 MidiModel::Notes notes;
1035 _model->get_notes (notes, op, val, chan_mask);
1037 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1038 CanvasNoteEvent* cne = find_canvas_note (*n);
1046 MidiRegionView::redisplay_model()
1048 // Don't redisplay the model if we're currently recording and displaying that
1049 if (_active_notes) {
1057 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1058 (*i)->invalidate ();
1061 MidiModel::ReadLock lock(_model->read_lock());
1063 MidiModel::Notes& notes (_model->notes());
1064 _optimization_iterator = _events.begin();
1066 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1068 boost::shared_ptr<NoteType> note (*n);
1069 CanvasNoteEvent* cne;
1072 if (note_in_region_range (note, visible)) {
1074 if ((cne = find_canvas_note (note)) != 0) {
1081 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1083 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1095 add_note (note, visible);
1100 if ((cne = find_canvas_note (note)) != 0) {
1108 /* remove note items that are no longer valid */
1110 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1111 if (!(*i)->valid ()) {
1113 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1114 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1116 gr->remove_note (*i);
1121 i = _events.erase (i);
1128 _patch_changes.clear();
1132 display_patch_changes ();
1134 _marked_for_selection.clear ();
1135 _marked_for_velocity.clear ();
1137 /* we may have caused _events to contain things out of order (e.g. if a note
1138 moved earlier or later). we don't generally need them in time order, but
1139 make a note that a sort is required for those cases that require it.
1142 _sort_needed = true;
1146 MidiRegionView::display_patch_changes ()
1148 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1149 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1151 for (uint8_t i = 0; i < 16; ++i) {
1152 if (chn_mask & (1<<i)) {
1153 display_patch_changes_on_channel (i);
1155 /* TODO gray-out patch instad of not displaying it */
1160 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1162 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1164 if ((*i)->channel() != channel) {
1168 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1170 boost::shared_ptr<MIDI::Name::Patch> patch =
1171 MIDI::Name::MidiPatchManager::instance().find_patch(
1172 _model_name, _custom_device_mode, channel, patch_key);
1175 add_canvas_patch_change (*i, patch->name());
1178 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1179 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1180 add_canvas_patch_change (*i, buf);
1186 MidiRegionView::display_sysexes()
1188 bool have_periodic_system_messages = false;
1189 bool display_periodic_messages = true;
1191 if (!Config->get_never_display_periodic_midi()) {
1193 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1194 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1195 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1198 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1199 have_periodic_system_messages = true;
1205 if (have_periodic_system_messages) {
1206 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1208 /* get an approximate value for the number of samples per video frame */
1210 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1212 /* if we are zoomed out beyond than the cutoff (i.e. more
1213 * frames per pixel than frames per 4 video frames), don't
1214 * show periodic sysex messages.
1217 if (zoom > (video_frame*4)) {
1218 display_periodic_messages = false;
1222 display_periodic_messages = false;
1225 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1227 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1228 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1230 Evoral::MusicalTime time = (*i)->time();
1234 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1235 if (!display_periodic_messages) {
1243 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1244 str << int((*i)->buffer()[b]);
1245 if (b != (*i)->size() -1) {
1249 string text = str.str();
1251 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1253 double height = midi_stream_view()->contents_height();
1255 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1256 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1258 // Show unless message is beyond the region bounds
1259 if (time - _region->start() >= _region->length() || time < _region->start()) {
1265 _sys_exes.push_back(sysex);
1269 MidiRegionView::~MidiRegionView ()
1271 in_destructor = true;
1273 trackview.editor().verbose_cursor()->hide ();
1275 note_delete_connection.disconnect ();
1277 delete _list_editor;
1279 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1281 if (_active_notes) {
1289 delete _note_diff_command;
1290 delete _step_edit_cursor;
1291 delete _temporary_note_group;
1295 MidiRegionView::region_resized (const PropertyChange& what_changed)
1297 RegionView::region_resized(what_changed);
1299 if (what_changed.contains (ARDOUR::Properties::position)) {
1300 set_duration(_region->length(), 0);
1301 if (_enable_display) {
1308 MidiRegionView::reset_width_dependent_items (double pixel_width)
1310 RegionView::reset_width_dependent_items(pixel_width);
1311 assert(_pixel_width == pixel_width);
1313 if (_enable_display) {
1317 move_step_edit_cursor (_step_edit_cursor_position);
1318 set_step_edit_cursor_width (_step_edit_cursor_width);
1322 MidiRegionView::set_height (double height)
1324 static const double FUDGE = 2.0;
1325 const double old_height = _height;
1326 RegionView::set_height(height);
1327 _height = height - FUDGE;
1329 apply_note_range(midi_stream_view()->lowest_note(),
1330 midi_stream_view()->highest_note(),
1331 height != old_height + FUDGE);
1334 name_pixbuf->raise_to_top();
1337 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1338 (*x)->set_height (midi_stream_view()->contents_height());
1341 if (_step_edit_cursor) {
1342 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1347 /** Apply the current note range from the stream view
1348 * by repositioning/hiding notes as necessary
1351 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1353 if (!_enable_display) {
1357 if (!force && _current_range_min == min && _current_range_max == max) {
1361 _current_range_min = min;
1362 _current_range_max = max;
1364 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1365 CanvasNoteEvent* event = *i;
1366 boost::shared_ptr<NoteType> note (event->note());
1368 if (note->note() < _current_range_min ||
1369 note->note() > _current_range_max) {
1375 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1377 const double y1 = midi_stream_view()->note_to_y(note->note());
1378 const double y2 = y1 + floor(midi_stream_view()->note_height());
1380 cnote->property_y1() = y1;
1381 cnote->property_y2() = y2;
1383 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1385 const double diamond_size = update_hit (chit);
1387 chit->set_height (diamond_size);
1393 MidiRegionView::add_ghost (TimeAxisView& tv)
1397 double unit_position = _region->position () / samples_per_unit;
1398 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1399 MidiGhostRegion* ghost;
1401 if (mtv && mtv->midi_view()) {
1402 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1403 to allow having midi notes on top of note lines and waveforms.
1405 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1407 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1410 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1411 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1412 ghost->add_note(note);
1416 ghost->set_height ();
1417 ghost->set_duration (_region->length() / samples_per_unit);
1418 ghosts.push_back (ghost);
1420 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1426 /** Begin tracking note state for successive calls to add_event
1429 MidiRegionView::begin_write()
1431 assert(!_active_notes);
1432 _active_notes = new CanvasNote*[128];
1433 for (unsigned i=0; i < 128; ++i) {
1434 _active_notes[i] = 0;
1439 /** Destroy note state for add_event
1442 MidiRegionView::end_write()
1444 delete[] _active_notes;
1446 _marked_for_selection.clear();
1447 _marked_for_velocity.clear();
1451 /** Resolve an active MIDI note (while recording).
1454 MidiRegionView::resolve_note(uint8_t note, double end_time)
1456 if (midi_view()->note_mode() != Sustained) {
1460 if (_active_notes && _active_notes[note]) {
1462 /* XXX is end_time really region-centric? I think so, because
1463 this is a new region that we're recording, so source zero is
1464 the same as region zero
1466 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1468 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1469 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1470 _active_notes[note] = 0;
1475 /** Extend active notes to rightmost edge of region (if length is changed)
1478 MidiRegionView::extend_active_notes()
1480 if (!_active_notes) {
1484 for (unsigned i=0; i < 128; ++i) {
1485 if (_active_notes[i]) {
1486 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1493 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1495 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1499 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1501 if (!route_ui || !route_ui->midi_track()) {
1505 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1511 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1513 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1517 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1519 if (!route_ui || !route_ui->midi_track()) {
1523 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1525 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1534 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1536 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1537 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1539 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1540 (note->note() <= midi_stream_view()->highest_note());
1545 /** Update a canvas note's size from its model note.
1546 * @param ev Canvas note to update.
1547 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1550 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1552 boost::shared_ptr<NoteType> note = ev->note();
1553 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1554 const double y1 = midi_stream_view()->note_to_y(note->note());
1556 ev->property_x1() = x;
1557 ev->property_y1() = y1;
1559 /* trim note display to not overlap the end of its region */
1561 if (note->length() > 0) {
1562 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1563 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1565 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1568 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1570 if (note->length() == 0) {
1571 if (_active_notes) {
1572 assert(note->note() < 128);
1573 // If this note is already active there's a stuck note,
1574 // finish the old note rectangle
1575 if (_active_notes[note->note()]) {
1576 CanvasNote* const old_rect = _active_notes[note->note()];
1577 boost::shared_ptr<NoteType> old_note = old_rect->note();
1578 old_rect->property_x2() = x;
1579 old_rect->property_outline_what() = (guint32) 0xF;
1581 _active_notes[note->note()] = ev;
1583 /* outline all but right edge */
1584 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1586 /* outline all edges */
1587 ev->property_outline_what() = (guint32) 0xF;
1590 if (update_ghost_regions) {
1591 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1592 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1594 gr->update_note (ev);
1601 MidiRegionView::update_hit (CanvasHit* ev)
1603 boost::shared_ptr<NoteType> note = ev->note();
1605 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1606 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1607 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1608 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1612 return diamond_size;
1615 /** Add a MIDI note to the view (with length).
1617 * If in sustained mode, notes with length 0 will be considered active
1618 * notes, and resolve_note should be called when the corresponding note off
1619 * event arrives, to properly display the note.
1622 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1624 CanvasNoteEvent* event = 0;
1626 assert(note->time() >= 0);
1627 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1629 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1631 if (midi_view()->note_mode() == Sustained) {
1633 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1635 update_note (ev_rect);
1639 MidiGhostRegion* gr;
1641 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1642 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1643 gr->add_note(ev_rect);
1647 } else if (midi_view()->note_mode() == Percussive) {
1649 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1651 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1653 update_hit (ev_diamond);
1662 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1663 note_selected(event, true);
1666 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1667 event->show_velocity();
1670 event->on_channel_selection_change(_last_channel_selection);
1671 _events.push_back(event);
1680 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1681 MidiStreamView* const view = mtv->midi_view();
1683 view->update_note_range (note->note());
1687 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1688 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1690 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1692 /* potentially extend region to hold new note */
1694 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1695 framepos_t region_end = _region->last_frame();
1697 if (end_frame > region_end) {
1698 _region->set_length (end_frame - _region->position());
1701 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1702 MidiStreamView* const view = mtv->midi_view();
1704 view->update_note_range(new_note->note());
1706 _marked_for_selection.clear ();
1709 start_note_diff_command (_("step add"));
1710 note_diff_add_note (new_note, true, false);
1713 // last_step_edit_note = new_note;
1717 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1719 change_note_lengths (false, false, beats, false, true);
1723 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1725 assert (patch->time() >= 0);
1727 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1728 const double x = trackview.editor().frame_to_pixel (region_frames);
1730 double const height = midi_stream_view()->contents_height();
1732 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1733 new CanvasPatchChange(*this, *_note_group,
1738 _custom_device_mode,
1742 // Show unless patch change is beyond the region bounds
1743 if (region_frames < 0 || region_frames >= _region->length()) {
1744 patch_change->hide();
1746 patch_change->show();
1749 _patch_changes.push_back (patch_change);
1753 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1755 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1756 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1760 if (i != _model->patch_changes().end()) {
1761 key.msb = (*i)->bank_msb ();
1762 key.lsb = (*i)->bank_lsb ();
1763 key.program_number = (*i)->program ();
1765 key.msb = key.lsb = key.program_number = 0;
1768 assert (key.is_sane());
1773 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1775 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1777 if (pc.patch()->program() != new_patch.program_number) {
1778 c->change_program (pc.patch (), new_patch.program_number);
1781 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1782 if (pc.patch()->bank() != new_bank) {
1783 c->change_bank (pc.patch (), new_bank);
1786 _model->apply_command (*trackview.session(), c);
1788 _patch_changes.clear ();
1789 display_patch_changes ();
1793 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1795 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1797 if (old_change->time() != new_change.time()) {
1798 c->change_time (old_change, new_change.time());
1801 if (old_change->channel() != new_change.channel()) {
1802 c->change_channel (old_change, new_change.channel());
1805 if (old_change->program() != new_change.program()) {
1806 c->change_program (old_change, new_change.program());
1809 if (old_change->bank() != new_change.bank()) {
1810 c->change_bank (old_change, new_change.bank());
1813 _model->apply_command (*trackview.session(), c);
1815 _patch_changes.clear ();
1816 display_patch_changes ();
1819 /** Add a patch change to the region.
1820 * @param t Time in frames relative to region position
1821 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1822 * MidiTimeAxisView::get_channel_for_add())
1825 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1827 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1829 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1830 c->add (MidiModel::PatchChangePtr (
1831 new Evoral::PatchChange<Evoral::MusicalTime> (
1832 absolute_frames_to_source_beats (_region->position() + t),
1833 mtv->get_channel_for_add(), patch.program(), patch.bank()
1838 _model->apply_command (*trackview.session(), c);
1840 _patch_changes.clear ();
1841 display_patch_changes ();
1845 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1847 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1848 c->change_time (pc.patch (), t);
1849 _model->apply_command (*trackview.session(), c);
1851 _patch_changes.clear ();
1852 display_patch_changes ();
1856 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1858 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1859 c->remove (pc->patch ());
1860 _model->apply_command (*trackview.session(), c);
1862 _patch_changes.clear ();
1863 display_patch_changes ();
1867 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1869 if (patch.patch()->program() < 127) {
1870 MIDI::Name::PatchPrimaryKey key;
1871 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1872 key.program_number++;
1873 change_patch_change (patch, key);
1878 MidiRegionView::next_patch (CanvasPatchChange& patch)
1880 if (patch.patch()->program() > 0) {
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::previous_bank (CanvasPatchChange& patch)
1891 if (patch.patch()->program() < 127) {
1892 MIDI::Name::PatchPrimaryKey key;
1893 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1896 change_patch_change (patch, key);
1901 change_patch_change (patch, key);
1908 MidiRegionView::next_bank (CanvasPatchChange& patch)
1910 if (patch.patch()->program() > 0) {
1911 MIDI::Name::PatchPrimaryKey key;
1912 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1913 if (key.lsb < 127) {
1915 change_patch_change (patch, key);
1917 if (key.msb < 127) {
1920 change_patch_change (patch, key);
1927 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1929 if (_selection.empty()) {
1933 _selection.erase (cne);
1937 MidiRegionView::delete_selection()
1939 if (_selection.empty()) {
1943 start_note_diff_command (_("delete selection"));
1945 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1946 if ((*i)->selected()) {
1947 _note_diff_command->remove((*i)->note());
1957 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1959 start_note_diff_command (_("delete note"));
1960 _note_diff_command->remove (n);
1963 trackview.editor().verbose_cursor()->hide ();
1967 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1969 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1971 Selection::iterator tmp = i;
1974 (*i)->set_selected (false);
1975 (*i)->hide_velocity ();
1976 _selection.erase (i);
1984 /* this does not change the status of this regionview w.r.t the editor
1989 SelectionCleared (this); /* EMIT SIGNAL */
1994 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1996 clear_selection_except (ev);
1998 /* don't bother with checking to see if we should remove this
1999 regionview from the editor selection, since we're about to add
2000 another note, and thus put/keep this regionview in the editor
2004 if (!ev->selected()) {
2005 add_to_selection (ev);
2010 MidiRegionView::select_all_notes ()
2014 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2015 add_to_selection (*i);
2020 MidiRegionView::select_range (framepos_t start, framepos_t end)
2024 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2025 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2026 if (t >= start && t <= end) {
2027 add_to_selection (*i);
2033 MidiRegionView::invert_selection ()
2035 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2036 if ((*i)->selected()) {
2037 remove_from_selection(*i);
2039 add_to_selection (*i);
2045 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2047 uint8_t low_note = 127;
2048 uint8_t high_note = 0;
2049 MidiModel::Notes& notes (_model->notes());
2050 _optimization_iterator = _events.begin();
2056 if (extend && _selection.empty()) {
2062 /* scan existing selection to get note range */
2064 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2065 if ((*i)->note()->note() < low_note) {
2066 low_note = (*i)->note()->note();
2068 if ((*i)->note()->note() > high_note) {
2069 high_note = (*i)->note()->note();
2073 low_note = min (low_note, notenum);
2074 high_note = max (high_note, notenum);
2077 _no_sound_notes = true;
2079 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2081 boost::shared_ptr<NoteType> note (*n);
2082 CanvasNoteEvent* cne;
2083 bool select = false;
2085 if (((1 << note->channel()) & channel_mask) != 0) {
2087 if ((note->note() >= low_note && note->note() <= high_note)) {
2090 } else if (note->note() == notenum) {
2096 if ((cne = find_canvas_note (note)) != 0) {
2097 // extend is false because we've taken care of it,
2098 // since it extends by time range, not pitch.
2099 note_selected (cne, add, false);
2103 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2107 _no_sound_notes = false;
2111 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2113 MidiModel::Notes& notes (_model->notes());
2114 _optimization_iterator = _events.begin();
2116 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2118 boost::shared_ptr<NoteType> note (*n);
2119 CanvasNoteEvent* cne;
2121 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2122 if ((cne = find_canvas_note (note)) != 0) {
2123 if (cne->selected()) {
2124 note_deselected (cne);
2126 note_selected (cne, true, false);
2134 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2137 clear_selection_except (ev);
2138 if (!_selection.empty()) {
2139 PublicEditor& editor (trackview.editor());
2140 editor.get_selection().add (this);
2146 if (!ev->selected()) {
2147 add_to_selection (ev);
2151 /* find end of latest note selected, select all between that and the start of "ev" */
2153 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2154 Evoral::MusicalTime latest = 0;
2156 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2157 if ((*i)->note()->end_time() > latest) {
2158 latest = (*i)->note()->end_time();
2160 if ((*i)->note()->time() < earliest) {
2161 earliest = (*i)->note()->time();
2165 if (ev->note()->end_time() > latest) {
2166 latest = ev->note()->end_time();
2169 if (ev->note()->time() < earliest) {
2170 earliest = ev->note()->time();
2173 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2175 /* find notes entirely within OR spanning the earliest..latest range */
2177 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2178 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2179 add_to_selection (*i);
2187 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2189 remove_from_selection (ev);
2193 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2203 // TODO: Make this faster by storing the last updated selection rect, and only
2204 // adjusting things that are in the area that appears/disappeared.
2205 // We probably need a tree to be able to find events in O(log(n)) time.
2207 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2209 /* check if any corner of the note is inside the rect
2212 1) this is computing "touched by", not "contained by" the rect.
2213 2) this does not require that events be sorted in time.
2216 const double ix1 = (*i)->x1();
2217 const double ix2 = (*i)->x2();
2218 const double iy1 = (*i)->y1();
2219 const double iy2 = (*i)->y2();
2221 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2222 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2223 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2224 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2227 if (!(*i)->selected()) {
2228 add_to_selection (*i);
2230 } else if ((*i)->selected() && !extend) {
2231 // Not inside rectangle
2232 remove_from_selection (*i);
2238 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2244 // TODO: Make this faster by storing the last updated selection rect, and only
2245 // adjusting things that are in the area that appears/disappeared.
2246 // We probably need a tree to be able to find events in O(log(n)) time.
2248 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2250 /* check if any corner of the note is inside the rect
2253 1) this is computing "touched by", not "contained by" the rect.
2254 2) this does not require that events be sorted in time.
2257 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2258 // within y- (note-) range
2259 if (!(*i)->selected()) {
2260 add_to_selection (*i);
2262 } else if ((*i)->selected() && !extend) {
2263 // Not inside rectangle
2264 remove_from_selection (*i);
2270 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2272 Selection::iterator i = _selection.find (ev);
2274 if (i != _selection.end()) {
2275 _selection.erase (i);
2278 ev->set_selected (false);
2279 ev->hide_velocity ();
2281 if (_selection.empty()) {
2282 PublicEditor& editor (trackview.editor());
2283 editor.get_selection().remove (this);
2288 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2290 bool add_mrv_selection = false;
2292 if (_selection.empty()) {
2293 add_mrv_selection = true;
2296 if (_selection.insert (ev).second) {
2297 ev->set_selected (true);
2298 play_midi_note ((ev)->note());
2301 if (add_mrv_selection) {
2302 PublicEditor& editor (trackview.editor());
2303 editor.get_selection().add (this);
2308 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2310 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2311 PossibleChord to_play;
2312 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2314 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2315 if ((*i)->note()->time() < earliest) {
2316 earliest = (*i)->note()->time();
2320 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2321 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2322 to_play.push_back ((*i)->note());
2324 (*i)->move_event(dx, dy);
2327 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2329 if (to_play.size() > 1) {
2331 PossibleChord shifted;
2333 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2334 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2335 moved_note->set_note (moved_note->note() + cumulative_dy);
2336 shifted.push_back (moved_note);
2339 play_midi_chord (shifted);
2341 } else if (!to_play.empty()) {
2343 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2344 moved_note->set_note (moved_note->note() + cumulative_dy);
2345 play_midi_note (moved_note);
2351 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2353 assert (!_selection.empty());
2355 uint8_t lowest_note_in_selection = 127;
2356 uint8_t highest_note_in_selection = 0;
2357 uint8_t highest_note_difference = 0;
2359 // find highest and lowest notes first
2361 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2362 uint8_t pitch = (*i)->note()->note();
2363 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2364 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2368 cerr << "dnote: " << (int) dnote << endl;
2369 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2370 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2371 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2372 << int(highest_note_in_selection) << endl;
2373 cerr << "selection size: " << _selection.size() << endl;
2374 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2377 // Make sure the note pitch does not exceed the MIDI standard range
2378 if (highest_note_in_selection + dnote > 127) {
2379 highest_note_difference = highest_note_in_selection - 127;
2382 start_note_diff_command (_("move notes"));
2384 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2386 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2387 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2393 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2395 uint8_t original_pitch = (*i)->note()->note();
2396 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2398 // keep notes in standard midi range
2399 clamp_to_0_127(new_pitch);
2401 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2402 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2404 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2409 // care about notes being moved beyond the upper/lower bounds on the canvas
2410 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2411 highest_note_in_selection > midi_stream_view()->highest_note()) {
2412 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2416 /** @param x Pixel relative to the region position.
2417 * @return Snapped frame relative to the region position.
2420 MidiRegionView::snap_pixel_to_frame(double x)
2422 PublicEditor& editor (trackview.editor());
2423 return snap_frame_to_frame (editor.pixel_to_frame (x));
2426 /** @param x Pixel relative to the region position.
2427 * @return Snapped pixel relative to the region position.
2430 MidiRegionView::snap_to_pixel(double x)
2432 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2436 MidiRegionView::get_position_pixels()
2438 framepos_t region_frame = get_position();
2439 return trackview.editor().frame_to_pixel(region_frame);
2443 MidiRegionView::get_end_position_pixels()
2445 framepos_t frame = get_position() + get_duration ();
2446 return trackview.editor().frame_to_pixel(frame);
2450 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2452 /* the time converter will return the frame corresponding to `beats'
2453 relative to the start of the source. The start of the source
2454 is an implied position given by region->position - region->start
2456 const framepos_t source_start = _region->position() - _region->start();
2457 return source_start + _source_relative_time_converter.to (beats);
2461 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2463 /* the `frames' argument needs to be converted into a frame count
2464 relative to the start of the source before being passed in to the
2467 const framepos_t source_start = _region->position() - _region->start();
2468 return _source_relative_time_converter.from (frames - source_start);
2472 MidiRegionView::region_beats_to_region_frames(double beats) const
2474 return _region_relative_time_converter.to(beats);
2478 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2480 return _region_relative_time_converter.from(frames);
2484 MidiRegionView::begin_resizing (bool /*at_front*/)
2486 _resize_data.clear();
2488 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2489 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2491 // only insert CanvasNotes into the map
2493 NoteResizeData *resize_data = new NoteResizeData();
2494 resize_data->canvas_note = note;
2496 // create a new SimpleRect from the note which will be the resize preview
2497 SimpleRect *resize_rect = new SimpleRect(
2498 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2500 // calculate the colors: get the color settings
2501 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2502 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2505 // make the resize preview notes more transparent and bright
2506 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2508 // calculate color based on note velocity
2509 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2510 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2514 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2515 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2517 resize_data->resize_rect = resize_rect;
2518 _resize_data.push_back(resize_data);
2523 /** Update resizing notes while user drags.
2524 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2525 * @param at_front which end of the note (true == note on, false == note off)
2526 * @param delta_x change in mouse position since the start of the drag
2527 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2528 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2529 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2530 * as the \a primary note.
2533 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2535 bool cursor_set = false;
2537 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2538 SimpleRect* resize_rect = (*i)->resize_rect;
2539 CanvasNote* canvas_note = (*i)->canvas_note;
2544 current_x = canvas_note->x1() + delta_x;
2546 current_x = primary->x1() + delta_x;
2550 current_x = canvas_note->x2() + delta_x;
2552 current_x = primary->x2() + delta_x;
2557 resize_rect->property_x1() = snap_to_pixel(current_x);
2558 resize_rect->property_x2() = canvas_note->x2();
2560 resize_rect->property_x2() = snap_to_pixel(current_x);
2561 resize_rect->property_x1() = canvas_note->x1();
2567 beats = snap_pixel_to_frame (current_x);
2568 beats = region_frames_to_region_beats (beats);
2573 if (beats < canvas_note->note()->end_time()) {
2574 len = canvas_note->note()->time() - beats;
2575 len += canvas_note->note()->length();
2580 if (beats >= canvas_note->note()->time()) {
2581 len = beats - canvas_note->note()->time();
2588 snprintf (buf, sizeof (buf), "%.3g beats", len);
2589 show_verbose_cursor (buf, 0, 0);
2598 /** Finish resizing notes when the user releases the mouse button.
2599 * Parameters the same as for \a update_resizing().
2602 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2604 start_note_diff_command (_("resize notes"));
2606 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2607 CanvasNote* canvas_note = (*i)->canvas_note;
2608 SimpleRect* resize_rect = (*i)->resize_rect;
2610 /* Get the new x position for this resize, which is in pixels relative
2611 * to the region position.
2618 current_x = canvas_note->x1() + delta_x;
2620 current_x = primary->x1() + delta_x;
2624 current_x = canvas_note->x2() + delta_x;
2626 current_x = primary->x2() + delta_x;
2630 /* Convert that to a frame within the source */
2631 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2633 /* and then to beats */
2634 current_x = region_frames_to_region_beats (current_x);
2636 if (at_front && current_x < canvas_note->note()->end_time()) {
2637 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2639 double len = canvas_note->note()->time() - current_x;
2640 len += canvas_note->note()->length();
2643 /* XXX convert to beats */
2644 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2649 double len = current_x - canvas_note->note()->time();
2652 /* XXX convert to beats */
2653 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2661 _resize_data.clear();
2666 MidiRegionView::abort_resizing ()
2668 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2669 delete (*i)->resize_rect;
2673 _resize_data.clear ();
2677 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2679 uint8_t new_velocity;
2682 new_velocity = event->note()->velocity() + velocity;
2683 clamp_to_0_127(new_velocity);
2685 new_velocity = velocity;
2688 event->set_selected (event->selected()); // change color
2690 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2694 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2699 new_note = event->note()->note() + note;
2704 clamp_to_0_127 (new_note);
2705 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2709 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2711 bool change_start = false;
2712 bool change_length = false;
2713 Evoral::MusicalTime new_start = 0;
2714 Evoral::MusicalTime new_length = 0;
2716 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2718 front_delta: if positive - move the start of the note later in time (shortening it)
2719 if negative - move the start of the note earlier in time (lengthening it)
2721 end_delta: if positive - move the end of the note later in time (lengthening it)
2722 if negative - move the end of the note earlier in time (shortening it)
2726 if (front_delta < 0) {
2728 if (event->note()->time() < -front_delta) {
2731 new_start = event->note()->time() + front_delta; // moves earlier
2734 /* start moved toward zero, so move the end point out to where it used to be.
2735 Note that front_delta is negative, so this increases the length.
2738 new_length = event->note()->length() - front_delta;
2739 change_start = true;
2740 change_length = true;
2744 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2746 if (new_pos < event->note()->end_time()) {
2747 new_start = event->note()->time() + front_delta;
2748 /* start moved toward the end, so move the end point back to where it used to be */
2749 new_length = event->note()->length() - front_delta;
2750 change_start = true;
2751 change_length = true;
2758 bool can_change = true;
2759 if (end_delta < 0) {
2760 if (event->note()->length() < -end_delta) {
2766 new_length = event->note()->length() + end_delta;
2767 change_length = true;
2772 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2775 if (change_length) {
2776 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2781 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2783 uint8_t new_channel;
2787 if (event->note()->channel() < -chn) {
2790 new_channel = event->note()->channel() + chn;
2793 new_channel = event->note()->channel() + chn;
2796 new_channel = (uint8_t) chn;
2799 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2803 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2805 Evoral::MusicalTime new_time;
2809 if (event->note()->time() < -delta) {
2812 new_time = event->note()->time() + delta;
2815 new_time = event->note()->time() + delta;
2821 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2825 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2827 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2831 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
2836 if (_selection.empty()) {
2851 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2852 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2858 start_note_diff_command (_("change velocities"));
2860 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2861 Selection::iterator next = i;
2865 if (i == _selection.begin()) {
2866 change_note_velocity (*i, delta, true);
2867 value = (*i)->note()->velocity() + delta;
2869 change_note_velocity (*i, value, false);
2873 change_note_velocity (*i, delta, true);
2881 if (!_selection.empty()) {
2883 snprintf (buf, sizeof (buf), "Vel %d",
2884 (int) (*_selection.begin())->note()->velocity());
2885 show_verbose_cursor (buf, 10, 10);
2891 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2893 if (_selection.empty()) {
2910 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2912 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2916 if ((int8_t) (*i)->note()->note() + delta > 127) {
2923 start_note_diff_command (_("transpose"));
2925 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2926 Selection::iterator next = i;
2928 change_note_note (*i, delta, true);
2936 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2942 /* grab the current grid distance */
2944 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2946 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2947 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
2957 start_note_diff_command (_("change note lengths"));
2959 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2960 Selection::iterator next = i;
2963 /* note the negation of the delta for start */
2965 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2974 MidiRegionView::nudge_notes (bool forward)
2976 if (_selection.empty()) {
2980 /* pick a note as the point along the timeline to get the nudge distance.
2981 its not necessarily the earliest note, so we may want to pull the notes out
2982 into a vector and sort before using the first one.
2985 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2987 framecnt_t distance;
2989 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2991 /* grid is off - use nudge distance */
2993 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2999 framepos_t next_pos = ref_point;
3002 if (max_framepos - 1 < next_pos) {
3006 if (next_pos == 0) {
3012 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3013 distance = ref_point - next_pos;
3016 if (distance == 0) {
3020 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3026 start_note_diff_command (_("nudge"));
3028 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3029 Selection::iterator next = i;
3031 change_note_time (*i, delta, true);
3039 MidiRegionView::change_channel(uint8_t channel)
3041 start_note_diff_command(_("change channel"));
3042 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3043 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3051 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3053 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3055 pre_enter_cursor = editor->get_canvas_cursor ();
3057 if (_mouse_state == SelectTouchDragging) {
3058 note_selected (ev, true);
3061 show_verbose_cursor (ev->note ());
3065 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3067 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3069 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3070 (*i)->hide_velocity ();
3073 editor->verbose_cursor()->hide ();
3075 if (pre_enter_cursor) {
3076 editor->set_canvas_cursor (pre_enter_cursor);
3077 pre_enter_cursor = 0;
3082 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3085 /* XXX should get patch name if we can */
3086 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3087 show_verbose_cursor (s.str(), 10, 20);
3091 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3093 trackview.editor().verbose_cursor()->hide ();
3097 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3099 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3100 Editing::MouseMode mm = editor->current_mouse_mode();
3101 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3103 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3104 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3105 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3106 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3108 if (pre_enter_cursor && can_set_cursor) {
3109 editor->set_canvas_cursor (pre_enter_cursor);
3115 MidiRegionView::set_frame_color()
3119 TimeAxisViewItem::set_frame_color ();
3126 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3127 } else if (high_enough_for_name) {
3128 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3133 if (!rect_visible) {
3134 f = UINT_RGBA_CHANGE_A (f, 0);
3137 frame->property_fill_color_rgba() = f;
3141 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3143 if (mode == ForceChannel) {
3144 mask = 0xFFFF; // Show all notes as active (below)
3147 // Update notes for selection
3148 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3149 (*i)->on_channel_selection_change(mask);
3152 _last_channel_selection = mask;
3154 _patch_changes.clear ();
3155 display_patch_changes ();
3159 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3161 _model_name = model;
3162 _custom_device_mode = custom_device_mode;
3167 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3169 if (_selection.empty()) {
3173 PublicEditor& editor (trackview.editor());
3177 /* XXX what to do ? */
3181 editor.get_cut_buffer().add (selection_as_cut_buffer());
3189 start_note_diff_command();
3191 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3198 note_diff_remove_note (*i);
3208 MidiRegionView::selection_as_cut_buffer () const
3212 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3213 NoteType* n = (*i)->note().get();
3214 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3217 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3223 /** This method handles undo */
3225 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3231 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3233 trackview.session()->begin_reversible_command (_("paste"));
3235 start_note_diff_command (_("paste"));
3237 Evoral::MusicalTime beat_delta;
3238 Evoral::MusicalTime paste_pos_beats;
3239 Evoral::MusicalTime duration;
3240 Evoral::MusicalTime end_point = 0;
3242 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3243 paste_pos_beats = absolute_frames_to_source_beats (pos);
3244 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3245 paste_pos_beats = 0;
3247 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",
3248 (*mcb.notes().begin())->time(),
3249 (*mcb.notes().rbegin())->end_time(),
3250 duration, pos, _region->position(),
3251 paste_pos_beats, beat_delta));
3255 for (int n = 0; n < (int) times; ++n) {
3257 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3259 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3260 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3262 /* make all newly added notes selected */
3264 note_diff_add_note (copied_note, true);
3265 end_point = copied_note->end_time();
3268 paste_pos_beats += duration;
3271 /* if we pasted past the current end of the region, extend the region */
3273 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3274 framepos_t region_end = _region->position() + _region->length() - 1;
3276 if (end_frame > region_end) {
3278 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3280 _region->clear_changes ();
3281 _region->set_length (end_frame - _region->position());
3282 trackview.session()->add_command (new StatefulDiffCommand (_region));
3287 trackview.session()->commit_reversible_command ();
3290 struct EventNoteTimeEarlyFirstComparator {
3291 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3292 return a->note()->time() < b->note()->time();
3297 MidiRegionView::time_sort_events ()
3299 if (!_sort_needed) {
3303 EventNoteTimeEarlyFirstComparator cmp;
3306 _sort_needed = false;
3310 MidiRegionView::goto_next_note (bool add_to_selection)
3312 bool use_next = false;
3314 if (_events.back()->selected()) {
3318 time_sort_events ();
3320 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3321 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3323 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3324 if ((*i)->selected()) {
3327 } else if (use_next) {
3328 if (channel_mask & (1 << (*i)->note()->channel())) {
3329 if (!add_to_selection) {
3332 note_selected (*i, true, false);
3339 /* use the first one */
3341 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3342 unique_select (_events.front());
3347 MidiRegionView::goto_previous_note (bool add_to_selection)
3349 bool use_next = false;
3351 if (_events.front()->selected()) {
3355 time_sort_events ();
3357 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3358 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3360 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3361 if ((*i)->selected()) {
3364 } else if (use_next) {
3365 if (channel_mask & (1 << (*i)->note()->channel())) {
3366 if (!add_to_selection) {
3369 note_selected (*i, true, false);
3376 /* use the last one */
3378 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3379 unique_select (*(_events.rbegin()));
3384 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3386 bool had_selected = false;
3388 time_sort_events ();
3390 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3391 if ((*i)->selected()) {
3392 selected.insert ((*i)->note());
3393 had_selected = true;
3397 if (allow_all_if_none_selected && !had_selected) {
3398 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3399 selected.insert ((*i)->note());
3405 MidiRegionView::update_ghost_note (double x, double y)
3407 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3412 _note_group->w2i (x, y);
3414 PublicEditor& editor = trackview.editor ();
3416 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3417 framecnt_t grid_frames;
3418 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3420 /* use region_frames... because we are converting a delta within the region
3424 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3430 /* note that this sets the time of the ghost note in beats relative to
3431 the start of the source; that is how all note times are stored.
3433 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3434 _ghost_note->note()->set_length (length);
3435 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3436 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3438 /* the ghost note does not appear in ghost regions, so pass false in here */
3439 update_note (_ghost_note, false);
3441 show_verbose_cursor (_ghost_note->note ());
3445 MidiRegionView::create_ghost_note (double x, double y)
3447 remove_ghost_note ();
3449 boost::shared_ptr<NoteType> g (new NoteType);
3450 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3451 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3452 update_ghost_note (x, y);
3453 _ghost_note->show ();
3458 show_verbose_cursor (_ghost_note->note ());
3462 MidiRegionView::snap_changed ()
3468 create_ghost_note (_last_ghost_x, _last_ghost_y);
3472 MidiRegionView::drop_down_keys ()
3474 _mouse_state = None;
3478 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3480 double note = midi_stream_view()->y_to_note(y);
3482 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3484 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3486 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3487 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3488 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3489 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3494 bool add_mrv_selection = false;
3496 if (_selection.empty()) {
3497 add_mrv_selection = true;
3500 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3501 if (_selection.insert (*i).second) {
3502 (*i)->set_selected (true);
3506 if (add_mrv_selection) {
3507 PublicEditor& editor (trackview.editor());
3508 editor.get_selection().add (this);
3513 MidiRegionView::color_handler ()
3515 RegionView::color_handler ();
3517 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3518 (*i)->set_selected ((*i)->selected()); // will change color
3521 /* XXX probably more to do here */
3525 MidiRegionView::enable_display (bool yn)
3527 RegionView::enable_display (yn);
3534 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3536 if (_step_edit_cursor == 0) {
3537 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3539 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3540 _step_edit_cursor->property_y1() = 0;
3541 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3542 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3543 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3546 move_step_edit_cursor (pos);
3547 _step_edit_cursor->show ();
3551 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3553 _step_edit_cursor_position = pos;
3555 if (_step_edit_cursor) {
3556 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3557 _step_edit_cursor->property_x1() = pixel;
3558 set_step_edit_cursor_width (_step_edit_cursor_width);
3563 MidiRegionView::hide_step_edit_cursor ()
3565 if (_step_edit_cursor) {
3566 _step_edit_cursor->hide ();
3571 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3573 _step_edit_cursor_width = beats;
3575 if (_step_edit_cursor) {
3576 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3580 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3581 * @param w Source that the data will end up in.
3584 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3586 if (!_active_notes) {
3587 /* we aren't actively being recorded to */
3591 boost::shared_ptr<MidiSource> src = w.lock ();
3592 if (!src || src != midi_region()->midi_source()) {
3593 /* recorded data was not destined for our source */
3597 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3599 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3601 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3603 framepos_t back = max_framepos;
3605 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3606 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3607 assert (ev.buffer ());
3609 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3610 frames from the start of the source, and so time_beats is in terms of the
3614 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3616 if (ev.type() == MIDI_CMD_NOTE_ON) {
3618 boost::shared_ptr<NoteType> note (
3619 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3622 add_note (note, true);
3624 /* fix up our note range */
3625 if (ev.note() < _current_range_min) {
3626 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3627 } else if (ev.note() > _current_range_max) {
3628 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3631 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3632 resolve_note (ev.note (), time_beats);
3638 midi_stream_view()->check_record_layers (region(), back);
3642 MidiRegionView::trim_front_starting ()
3644 /* Reparent the note group to the region view's parent, so that it doesn't change
3645 when the region view is trimmed.
3647 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3648 _temporary_note_group->move (group->property_x(), group->property_y());
3649 _note_group->reparent (*_temporary_note_group);
3653 MidiRegionView::trim_front_ending ()
3655 _note_group->reparent (*group);
3656 delete _temporary_note_group;
3657 _temporary_note_group = 0;
3659 if (_region->start() < 0) {
3660 /* Trim drag made start time -ve; fix this */
3661 midi_region()->fix_negative_start ();
3666 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3668 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), _model_name, _custom_device_mode, Gtk::Stock::APPLY);
3669 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3673 change_patch_change (pc->patch(), d.patch ());
3678 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3681 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3682 Evoral::midi_note_name (n->note()).c_str(),
3684 (int) n->channel() + 1,
3685 (int) n->velocity());
3687 show_verbose_cursor (buf, 10, 20);
3691 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3695 trackview.editor().get_pointer_position (wx, wy);
3700 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3702 double x1, y1, x2, y2;
3703 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3705 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3706 wy -= (y2 - y1) + 2 * yoffset;
3709 trackview.editor().verbose_cursor()->set (text, wx, wy);
3710 trackview.editor().verbose_cursor()->show ();
3713 /** @param p A session framepos.
3714 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3715 * @return p snapped to the grid subdivision underneath it.
3718 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3720 PublicEditor& editor = trackview.editor ();
3723 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3729 grid_frames = region_beats_to_region_frames (grid_beats);
3731 /* Hack so that we always snap to the note that we are over, instead of snapping
3732 to the next one if we're more than halfway through the one we're over.
3734 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3735 p -= grid_frames / 2;
3738 return snap_frame_to_frame (p);
3741 /** Called when the selection has been cleared in any MidiRegionView.
3742 * @param rv MidiRegionView that the selection was cleared in.
3745 MidiRegionView::selection_cleared (MidiRegionView* rv)
3751 /* Clear our selection in sympathy; but don't signal the fact */
3752 clear_selection (false);