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 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
117 connect_to_diskstream ();
119 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
122 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
123 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
124 TimeAxisViewItem::Visibility visibility)
125 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
126 , _last_channel_selection(0xFFFF)
127 , _current_range_min(0)
128 , _current_range_max(0)
130 , _note_group(new ArdourCanvas::Group(*parent))
131 , _note_diff_command (0)
133 , _step_edit_cursor (0)
134 , _step_edit_cursor_width (1.0)
135 , _step_edit_cursor_position (0.0)
136 , _channel_selection_scoped_note (0)
137 , _temporary_note_group (0)
140 , _sort_needed (true)
141 , _optimization_iterator (_events.end())
143 , _no_sound_notes (false)
146 , pre_enter_cursor (0)
147 , pre_press_cursor (0)
149 _note_group->raise_to_top();
150 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
152 connect_to_diskstream ();
154 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
158 MidiRegionView::parameter_changed (std::string const & p)
160 if (p == "diplay-first-midi-bank-as-zero") {
161 if (_enable_display) {
167 MidiRegionView::MidiRegionView (const MidiRegionView& other)
168 : sigc::trackable(other)
170 , _last_channel_selection(0xFFFF)
171 , _current_range_min(0)
172 , _current_range_max(0)
174 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
175 , _note_diff_command (0)
177 , _step_edit_cursor (0)
178 , _step_edit_cursor_width (1.0)
179 , _step_edit_cursor_position (0.0)
180 , _channel_selection_scoped_note (0)
181 , _temporary_note_group (0)
184 , _sort_needed (true)
185 , _optimization_iterator (_events.end())
187 , _no_sound_notes (false)
190 , pre_enter_cursor (0)
191 , pre_press_cursor (0)
196 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
197 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
202 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
203 : RegionView (other, boost::shared_ptr<Region> (region))
204 , _last_channel_selection(0xFFFF)
205 , _current_range_min(0)
206 , _current_range_max(0)
208 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
209 , _note_diff_command (0)
211 , _step_edit_cursor (0)
212 , _step_edit_cursor_width (1.0)
213 , _step_edit_cursor_position (0.0)
214 , _channel_selection_scoped_note (0)
215 , _temporary_note_group (0)
218 , _sort_needed (true)
219 , _optimization_iterator (_events.end())
221 , _no_sound_notes (false)
224 , pre_enter_cursor (0)
225 , pre_press_cursor (0)
230 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
231 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
237 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
239 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
241 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
242 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
246 midi_region()->midi_source(0)->load_model();
249 _model = midi_region()->midi_source(0)->model();
250 _enable_display = false;
252 RegionView::init (basic_color, false);
254 compute_colors (basic_color);
256 set_height (trackview.current_height());
259 region_sync_changed ();
260 region_resized (ARDOUR::bounds_change);
263 reset_width_dependent_items (_pixel_width);
267 _enable_display = true;
270 display_model (_model);
274 group->raise_to_top();
275 group->signal_event().connect(
276 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
278 midi_view()->signal_channel_mode_changed().connect(
279 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
281 midi_view()->signal_midi_patch_settings_changed().connect(
282 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
284 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
285 boost::bind (&MidiRegionView::snap_changed, this),
288 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
289 connect_to_diskstream ();
291 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
294 const boost::shared_ptr<ARDOUR::MidiRegion>
295 MidiRegionView::midi_region() const
297 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
301 MidiRegionView::connect_to_diskstream ()
303 midi_view()->midi_track()->DataRecorded.connect(
304 *this, invalidator(*this),
305 boost::bind (&MidiRegionView::data_recorded, this, _1),
310 MidiRegionView::canvas_event(GdkEvent* ev)
313 case GDK_ENTER_NOTIFY:
314 case GDK_LEAVE_NOTIFY:
315 _last_event_x = ev->crossing.x;
316 _last_event_y = ev->crossing.y;
318 case GDK_MOTION_NOTIFY:
319 _last_event_x = ev->motion.x;
320 _last_event_y = ev->motion.y;
326 if (ev->type == GDK_2BUTTON_PRESS) {
327 return trackview.editor().toggle_internal_editing_from_double_click (ev);
330 if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
331 (trackview.editor().current_mouse_mode() == MouseTimeFX) ||
332 (trackview.editor().current_mouse_mode() == MouseZoom)) {
333 // handle non-draw modes elsewhere
339 return scroll (&ev->scroll);
342 return key_press (&ev->key);
344 case GDK_KEY_RELEASE:
345 return key_release (&ev->key);
347 case GDK_BUTTON_PRESS:
348 return button_press (&ev->button);
350 case GDK_BUTTON_RELEASE:
351 return button_release (&ev->button);
353 case GDK_ENTER_NOTIFY:
354 return enter_notify (&ev->crossing);
356 case GDK_LEAVE_NOTIFY:
357 return leave_notify (&ev->crossing);
359 case GDK_MOTION_NOTIFY:
360 return motion (&ev->motion);
370 MidiRegionView::remove_ghost_note ()
377 MidiRegionView::enter_notify (GdkEventCrossing* ev)
379 trackview.editor().MouseModeChanged.connect (
380 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
383 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
384 create_ghost_note (ev->x, ev->y);
387 if (!trackview.editor().internal_editing()) {
388 Keyboard::magic_widget_drop_focus();
390 Keyboard::magic_widget_grab_focus();
394 // if current operation is non-operational in a midi region, change the cursor to so indicate
395 if (trackview.editor().current_mouse_mode() == MouseGain) {
396 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
397 pre_enter_cursor = editor->get_canvas_cursor();
398 editor->set_canvas_cursor(editor->cursors()->timebar);
405 MidiRegionView::leave_notify (GdkEventCrossing*)
407 _mouse_mode_connection.disconnect ();
409 trackview.editor().verbose_cursor()->hide ();
410 remove_ghost_note ();
412 if (pre_enter_cursor) {
413 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
414 editor->set_canvas_cursor(pre_enter_cursor);
421 MidiRegionView::mouse_mode_changed ()
423 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
424 create_ghost_note (_last_event_x, _last_event_y);
426 remove_ghost_note ();
427 trackview.editor().verbose_cursor()->hide ();
430 if (!trackview.editor().internal_editing()) {
431 Keyboard::magic_widget_drop_focus();
433 Keyboard::magic_widget_grab_focus();
439 MidiRegionView::button_press (GdkEventButton* ev)
441 if (ev->button != 1) {
445 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
446 MouseMode m = editor->current_mouse_mode();
448 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
449 pre_press_cursor = editor->get_canvas_cursor ();
450 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
453 if (_mouse_state != SelectTouchDragging) {
455 _pressed_button = ev->button;
456 _mouse_state = Pressed;
461 _pressed_button = ev->button;
467 MidiRegionView::button_release (GdkEventButton* ev)
469 double event_x, event_y;
471 if (ev->button != 1) {
478 group->w2i(event_x, event_y);
479 group->ungrab(ev->time);
481 PublicEditor& editor = trackview.editor ();
483 if (pre_press_cursor) {
484 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
485 pre_press_cursor = 0;
488 switch (_mouse_state) {
489 case Pressed: // Clicked
491 switch (editor.current_mouse_mode()) {
493 /* no motion occured - simple click */
502 if (Keyboard::is_insert_note_event(ev)) {
504 double event_x, event_y;
508 group->w2i(event_x, event_y);
511 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
517 /* Shorten the length by 1 tick so that we can add a new note at the next
518 grid snap without it overlapping this one.
520 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
522 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
530 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
536 /* Shorten the length by 1 tick so that we can add a new note at the next
537 grid snap without it overlapping this one.
539 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
541 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
552 case SelectRectDragging:
554 editor.drags()->end_grab ((GdkEvent *) ev);
556 create_ghost_note (ev->x, ev->y);
568 MidiRegionView::motion (GdkEventMotion* ev)
570 PublicEditor& editor = trackview.editor ();
572 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
573 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
574 _mouse_state != AddDragging) {
576 create_ghost_note (ev->x, ev->y);
578 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
579 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
581 update_ghost_note (ev->x, ev->y);
583 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
585 remove_ghost_note ();
586 editor.verbose_cursor()->hide ();
588 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
590 update_ghost_note (ev->x, ev->y);
593 /* any motion immediately hides velocity text that may have been visible */
595 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
596 (*i)->hide_velocity ();
599 switch (_mouse_state) {
602 if (_pressed_button == 1) {
604 MouseMode m = editor.current_mouse_mode();
606 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
608 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
609 _mouse_state = AddDragging;
610 remove_ghost_note ();
611 editor.verbose_cursor()->hide ();
613 } else if (m == MouseObject) {
614 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
616 _mouse_state = SelectRectDragging;
618 } else if (m == MouseRange) {
619 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
620 _mouse_state = SelectVerticalDragging;
627 case SelectRectDragging:
628 case SelectVerticalDragging:
630 editor.drags()->motion_handler ((GdkEvent *) ev, false);
633 case SelectTouchDragging:
645 MidiRegionView::scroll (GdkEventScroll* ev)
647 if (_selection.empty()) {
651 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
652 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
653 it still works for zoom.
658 trackview.editor().verbose_cursor()->hide ();
660 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
662 if (ev->direction == GDK_SCROLL_UP) {
663 change_velocities (true, fine, false);
664 } else if (ev->direction == GDK_SCROLL_DOWN) {
665 change_velocities (false, fine, false);
671 MidiRegionView::key_press (GdkEventKey* ev)
673 /* since GTK bindings are generally activated on press, and since
674 detectable auto-repeat is the name of the game and only sends
675 repeated presses, carry out key actions at key press, not release.
678 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
680 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
681 _mouse_state = SelectTouchDragging;
684 } else if (ev->keyval == GDK_Escape && unmodified) {
688 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
690 bool start = (ev->keyval == GDK_comma);
691 bool end = (ev->keyval == GDK_period);
692 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
693 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
695 change_note_lengths (fine, shorter, 0.0, start, end);
699 } else if (ev->keyval == GDK_Delete && unmodified) {
704 } else if (ev->keyval == GDK_Tab) {
706 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
707 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
709 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
713 } else if (ev->keyval == GDK_ISO_Left_Tab) {
715 /* Shift-TAB generates ISO Left Tab, for some reason */
717 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
718 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
720 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
726 } else if (ev->keyval == GDK_Up) {
728 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
729 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
731 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
732 change_velocities (true, fine, allow_smush);
734 transpose (true, fine, allow_smush);
738 } else if (ev->keyval == GDK_Down) {
740 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
741 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
743 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
744 change_velocities (false, fine, allow_smush);
746 transpose (false, fine, allow_smush);
750 } else if (ev->keyval == GDK_Left && unmodified) {
755 } else if (ev->keyval == GDK_Right && unmodified) {
760 } else if (ev->keyval == GDK_c && unmodified) {
769 MidiRegionView::key_release (GdkEventKey* ev)
771 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
779 MidiRegionView::channel_edit ()
782 uint8_t current_channel = 0;
784 if (_selection.empty()) {
788 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
790 current_channel = (*i)->note()->channel ();
795 MidiChannelDialog channel_dialog (current_channel);
796 int ret = channel_dialog.run ();
799 case Gtk::RESPONSE_OK:
805 uint8_t new_channel = channel_dialog.active_channel ();
807 start_note_diff_command (_("channel edit"));
809 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
810 Selection::iterator next = i;
812 change_note_channel (*i, new_channel);
820 MidiRegionView::show_list_editor ()
823 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
825 _list_editor->present ();
828 /** Add a note to the model, and the view, at a canvas (click) coordinate.
829 * \param t time in frames relative to the position of the region
830 * \param y vertical position in pixels
831 * \param length duration of the note in beats
832 * \param snap_t true to snap t to the grid, otherwise false.
835 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
837 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
838 MidiStreamView* const view = mtv->midi_view();
840 double note = view->y_to_note(y);
843 assert(note <= 127.0);
845 // Start of note in frames relative to region start
847 framecnt_t grid_frames;
848 t = snap_frame_to_grid_underneath (t, grid_frames);
852 assert (length != 0);
854 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
855 region_frames_to_region_beats(t + _region->start()),
857 (uint8_t)note, 0x40));
859 if (_model->contains (new_note)) {
863 view->update_note_range(new_note->note());
865 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
867 _model->apply_command(*trackview.session(), cmd);
869 play_midi_note (new_note);
873 MidiRegionView::clear_events()
878 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
879 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
884 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
889 _patch_changes.clear();
891 _optimization_iterator = _events.end();
895 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
899 content_connection.disconnect ();
900 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
904 if (_enable_display) {
910 MidiRegionView::start_note_diff_command (string name)
912 if (!_note_diff_command) {
913 _note_diff_command = _model->new_note_diff_command (name);
918 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
920 if (_note_diff_command) {
921 _note_diff_command->add (note);
924 _marked_for_selection.insert(note);
927 _marked_for_velocity.insert(note);
932 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
934 if (_note_diff_command && ev->note()) {
935 _note_diff_command->remove(ev->note());
940 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
941 MidiModel::NoteDiffCommand::Property property,
944 if (_note_diff_command) {
945 _note_diff_command->change (ev->note(), property, val);
950 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
951 MidiModel::NoteDiffCommand::Property property,
952 Evoral::MusicalTime val)
954 if (_note_diff_command) {
955 _note_diff_command->change (ev->note(), property, val);
960 MidiRegionView::apply_diff (bool as_subcommand)
964 if (!_note_diff_command) {
968 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
969 // Mark all selected notes for selection when model reloads
970 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
971 _marked_for_selection.insert((*i)->note());
976 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
978 _model->apply_command (*trackview.session(), _note_diff_command);
981 _note_diff_command = 0;
982 midi_view()->midi_track()->playlist_modified();
985 _marked_for_selection.clear();
988 _marked_for_velocity.clear();
992 MidiRegionView::abort_command()
994 delete _note_diff_command;
995 _note_diff_command = 0;
1000 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1002 if (_optimization_iterator != _events.end()) {
1003 ++_optimization_iterator;
1006 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1007 return *_optimization_iterator;
1010 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1011 if ((*_optimization_iterator)->note() == note) {
1012 return *_optimization_iterator;
1020 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1022 MidiModel::Notes notes;
1023 _model->get_notes (notes, op, val, chan_mask);
1025 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1026 CanvasNoteEvent* cne = find_canvas_note (*n);
1034 MidiRegionView::redisplay_model()
1036 // Don't redisplay the model if we're currently recording and displaying that
1037 if (_active_notes) {
1045 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1046 (*i)->invalidate ();
1049 MidiModel::ReadLock lock(_model->read_lock());
1051 MidiModel::Notes& notes (_model->notes());
1052 _optimization_iterator = _events.begin();
1054 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1056 boost::shared_ptr<NoteType> note (*n);
1057 CanvasNoteEvent* cne;
1060 if (note_in_region_range (note, visible)) {
1062 if ((cne = find_canvas_note (note)) != 0) {
1069 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1071 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1083 add_note (note, visible);
1088 if ((cne = find_canvas_note (note)) != 0) {
1096 /* remove note items that are no longer valid */
1098 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1099 if (!(*i)->valid ()) {
1101 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1102 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1104 gr->remove_note (*i);
1109 i = _events.erase (i);
1116 _patch_changes.clear();
1120 display_patch_changes ();
1122 _marked_for_selection.clear ();
1123 _marked_for_velocity.clear ();
1125 /* we may have caused _events to contain things out of order (e.g. if a note
1126 moved earlier or later). we don't generally need them in time order, but
1127 make a note that a sort is required for those cases that require it.
1130 _sort_needed = true;
1134 MidiRegionView::display_patch_changes ()
1136 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1137 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1139 for (uint8_t i = 0; i < 16; ++i) {
1140 if (chn_mask & (1<<i)) {
1141 display_patch_changes_on_channel (i);
1143 /* TODO gray-out patch instad of not displaying it */
1148 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1150 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1152 if ((*i)->channel() != channel) {
1156 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1158 boost::shared_ptr<MIDI::Name::Patch> patch =
1159 MIDI::Name::MidiPatchManager::instance().find_patch(
1160 _model_name, _custom_device_mode, channel, patch_key);
1163 add_canvas_patch_change (*i, patch->name());
1166 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1167 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1168 add_canvas_patch_change (*i, buf);
1174 MidiRegionView::display_sysexes()
1176 bool have_periodic_system_messages = false;
1177 bool display_periodic_messages = true;
1179 if (!Config->get_never_display_periodic_midi()) {
1181 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1182 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1183 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1186 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1187 have_periodic_system_messages = true;
1193 if (have_periodic_system_messages) {
1194 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1196 /* get an approximate value for the number of samples per video frame */
1198 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1200 /* if we are zoomed out beyond than the cutoff (i.e. more
1201 * frames per pixel than frames per 4 video frames), don't
1202 * show periodic sysex messages.
1205 if (zoom > (video_frame*4)) {
1206 display_periodic_messages = false;
1210 display_periodic_messages = false;
1213 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1215 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1216 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1218 Evoral::MusicalTime time = (*i)->time();
1222 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1223 if (!display_periodic_messages) {
1231 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1232 str << int((*i)->buffer()[b]);
1233 if (b != (*i)->size() -1) {
1237 string text = str.str();
1239 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1241 double height = midi_stream_view()->contents_height();
1243 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1244 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1246 // Show unless message is beyond the region bounds
1247 if (time - _region->start() >= _region->length() || time < _region->start()) {
1253 _sys_exes.push_back(sysex);
1257 MidiRegionView::~MidiRegionView ()
1259 in_destructor = true;
1261 trackview.editor().verbose_cursor()->hide ();
1263 note_delete_connection.disconnect ();
1265 delete _list_editor;
1267 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1269 if (_active_notes) {
1277 delete _note_diff_command;
1278 delete _step_edit_cursor;
1279 delete _temporary_note_group;
1283 MidiRegionView::region_resized (const PropertyChange& what_changed)
1285 RegionView::region_resized(what_changed);
1287 if (what_changed.contains (ARDOUR::Properties::position)) {
1288 set_duration(_region->length(), 0);
1289 if (_enable_display) {
1296 MidiRegionView::reset_width_dependent_items (double pixel_width)
1298 RegionView::reset_width_dependent_items(pixel_width);
1299 assert(_pixel_width == pixel_width);
1301 if (_enable_display) {
1305 move_step_edit_cursor (_step_edit_cursor_position);
1306 set_step_edit_cursor_width (_step_edit_cursor_width);
1310 MidiRegionView::set_height (double height)
1312 static const double FUDGE = 2.0;
1313 const double old_height = _height;
1314 RegionView::set_height(height);
1315 _height = height - FUDGE;
1317 apply_note_range(midi_stream_view()->lowest_note(),
1318 midi_stream_view()->highest_note(),
1319 height != old_height + FUDGE);
1322 name_pixbuf->raise_to_top();
1325 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1326 (*x)->set_height (midi_stream_view()->contents_height());
1329 if (_step_edit_cursor) {
1330 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1335 /** Apply the current note range from the stream view
1336 * by repositioning/hiding notes as necessary
1339 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1341 if (!_enable_display) {
1345 if (!force && _current_range_min == min && _current_range_max == max) {
1349 _current_range_min = min;
1350 _current_range_max = max;
1352 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1353 CanvasNoteEvent* event = *i;
1354 boost::shared_ptr<NoteType> note (event->note());
1356 if (note->note() < _current_range_min ||
1357 note->note() > _current_range_max) {
1363 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1365 const double y1 = midi_stream_view()->note_to_y(note->note());
1366 const double y2 = y1 + floor(midi_stream_view()->note_height());
1368 cnote->property_y1() = y1;
1369 cnote->property_y2() = y2;
1371 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1373 const double diamond_size = update_hit (chit);
1375 chit->set_height (diamond_size);
1381 MidiRegionView::add_ghost (TimeAxisView& tv)
1385 double unit_position = _region->position () / samples_per_unit;
1386 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1387 MidiGhostRegion* ghost;
1389 if (mtv && mtv->midi_view()) {
1390 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1391 to allow having midi notes on top of note lines and waveforms.
1393 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1395 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1398 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1399 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1400 ghost->add_note(note);
1404 ghost->set_height ();
1405 ghost->set_duration (_region->length() / samples_per_unit);
1406 ghosts.push_back (ghost);
1408 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1414 /** Begin tracking note state for successive calls to add_event
1417 MidiRegionView::begin_write()
1419 assert(!_active_notes);
1420 _active_notes = new CanvasNote*[128];
1421 for (unsigned i=0; i < 128; ++i) {
1422 _active_notes[i] = 0;
1427 /** Destroy note state for add_event
1430 MidiRegionView::end_write()
1432 delete[] _active_notes;
1434 _marked_for_selection.clear();
1435 _marked_for_velocity.clear();
1439 /** Resolve an active MIDI note (while recording).
1442 MidiRegionView::resolve_note(uint8_t note, double end_time)
1444 if (midi_view()->note_mode() != Sustained) {
1448 if (_active_notes && _active_notes[note]) {
1450 /* XXX is end_time really region-centric? I think so, because
1451 this is a new region that we're recording, so source zero is
1452 the same as region zero
1454 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1456 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1457 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1458 _active_notes[note] = 0;
1463 /** Extend active notes to rightmost edge of region (if length is changed)
1466 MidiRegionView::extend_active_notes()
1468 if (!_active_notes) {
1472 for (unsigned i=0; i < 128; ++i) {
1473 if (_active_notes[i]) {
1474 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1481 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1483 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1487 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1489 if (!route_ui || !route_ui->midi_track()) {
1493 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1499 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1501 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1505 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1507 if (!route_ui || !route_ui->midi_track()) {
1511 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1513 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1522 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1524 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1525 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1527 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1528 (note->note() <= midi_stream_view()->highest_note());
1533 /** Update a canvas note's size from its model note.
1534 * @param ev Canvas note to update.
1535 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1538 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1540 boost::shared_ptr<NoteType> note = ev->note();
1541 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1542 const double y1 = midi_stream_view()->note_to_y(note->note());
1544 ev->property_x1() = x;
1545 ev->property_y1() = y1;
1547 /* trim note display to not overlap the end of its region */
1549 if (note->length() > 0) {
1550 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1551 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1553 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1556 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1558 if (note->length() == 0) {
1559 if (_active_notes) {
1560 assert(note->note() < 128);
1561 // If this note is already active there's a stuck note,
1562 // finish the old note rectangle
1563 if (_active_notes[note->note()]) {
1564 CanvasNote* const old_rect = _active_notes[note->note()];
1565 boost::shared_ptr<NoteType> old_note = old_rect->note();
1566 old_rect->property_x2() = x;
1567 old_rect->property_outline_what() = (guint32) 0xF;
1569 _active_notes[note->note()] = ev;
1571 /* outline all but right edge */
1572 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1574 /* outline all edges */
1575 ev->property_outline_what() = (guint32) 0xF;
1578 if (update_ghost_regions) {
1579 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1580 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1582 gr->update_note (ev);
1589 MidiRegionView::update_hit (CanvasHit* ev)
1591 boost::shared_ptr<NoteType> note = ev->note();
1593 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1594 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1595 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1596 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1600 return diamond_size;
1603 /** Add a MIDI note to the view (with length).
1605 * If in sustained mode, notes with length 0 will be considered active
1606 * notes, and resolve_note should be called when the corresponding note off
1607 * event arrives, to properly display the note.
1610 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1612 CanvasNoteEvent* event = 0;
1614 assert(note->time() >= 0);
1615 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1617 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1619 if (midi_view()->note_mode() == Sustained) {
1621 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1623 update_note (ev_rect);
1627 MidiGhostRegion* gr;
1629 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1630 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1631 gr->add_note(ev_rect);
1635 } else if (midi_view()->note_mode() == Percussive) {
1637 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1639 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1641 update_hit (ev_diamond);
1650 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1651 note_selected(event, true);
1654 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1655 event->show_velocity();
1658 event->on_channel_selection_change(_last_channel_selection);
1659 _events.push_back(event);
1668 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1669 MidiStreamView* const view = mtv->midi_view();
1671 view->update_note_range (note->note());
1675 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1676 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1678 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1680 /* potentially extend region to hold new note */
1682 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1683 framepos_t region_end = _region->last_frame();
1685 if (end_frame > region_end) {
1686 _region->set_length (end_frame - _region->position());
1689 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1690 MidiStreamView* const view = mtv->midi_view();
1692 view->update_note_range(new_note->note());
1694 _marked_for_selection.clear ();
1697 start_note_diff_command (_("step add"));
1698 note_diff_add_note (new_note, true, false);
1701 // last_step_edit_note = new_note;
1705 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1707 change_note_lengths (false, false, beats, false, true);
1711 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1713 assert (patch->time() >= 0);
1715 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1716 const double x = trackview.editor().frame_to_pixel (region_frames);
1718 double const height = midi_stream_view()->contents_height();
1720 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1721 new CanvasPatchChange(*this, *_note_group,
1726 _custom_device_mode,
1730 // Show unless patch change is beyond the region bounds
1731 if (region_frames < 0 || region_frames >= _region->length()) {
1732 patch_change->hide();
1734 patch_change->show();
1737 _patch_changes.push_back (patch_change);
1741 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1743 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1744 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1748 if (i != _model->patch_changes().end()) {
1749 key.msb = (*i)->bank_msb ();
1750 key.lsb = (*i)->bank_lsb ();
1751 key.program_number = (*i)->program ();
1753 key.msb = key.lsb = key.program_number = 0;
1756 assert (key.is_sane());
1761 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1763 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1765 if (pc.patch()->program() != new_patch.program_number) {
1766 c->change_program (pc.patch (), new_patch.program_number);
1769 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1770 if (pc.patch()->bank() != new_bank) {
1771 c->change_bank (pc.patch (), new_bank);
1774 _model->apply_command (*trackview.session(), c);
1776 _patch_changes.clear ();
1777 display_patch_changes ();
1781 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1783 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1785 if (old_change->time() != new_change.time()) {
1786 c->change_time (old_change, new_change.time());
1789 if (old_change->channel() != new_change.channel()) {
1790 c->change_channel (old_change, new_change.channel());
1793 if (old_change->program() != new_change.program()) {
1794 c->change_program (old_change, new_change.program());
1797 if (old_change->bank() != new_change.bank()) {
1798 c->change_bank (old_change, new_change.bank());
1801 _model->apply_command (*trackview.session(), c);
1803 _patch_changes.clear ();
1804 display_patch_changes ();
1807 /** Add a patch change to the region.
1808 * @param t Time in frames relative to region position
1809 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1810 * MidiTimeAxisView::get_channel_for_add())
1813 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1815 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1817 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1818 c->add (MidiModel::PatchChangePtr (
1819 new Evoral::PatchChange<Evoral::MusicalTime> (
1820 absolute_frames_to_source_beats (_region->position() + t),
1821 mtv->get_channel_for_add(), patch.program(), patch.bank()
1826 _model->apply_command (*trackview.session(), c);
1828 _patch_changes.clear ();
1829 display_patch_changes ();
1833 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1835 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1836 c->change_time (pc.patch (), t);
1837 _model->apply_command (*trackview.session(), c);
1839 _patch_changes.clear ();
1840 display_patch_changes ();
1844 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1846 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1847 c->remove (pc->patch ());
1848 _model->apply_command (*trackview.session(), c);
1850 _patch_changes.clear ();
1851 display_patch_changes ();
1855 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1857 if (patch.patch()->program() < 127) {
1858 MIDI::Name::PatchPrimaryKey key;
1859 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1860 key.program_number++;
1861 change_patch_change (patch, key);
1866 MidiRegionView::next_patch (CanvasPatchChange& patch)
1868 if (patch.patch()->program() > 0) {
1869 MIDI::Name::PatchPrimaryKey key;
1870 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1871 key.program_number--;
1872 change_patch_change (patch, key);
1877 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1879 if (patch.patch()->program() < 127) {
1880 MIDI::Name::PatchPrimaryKey key;
1881 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1884 change_patch_change (patch, key);
1889 change_patch_change (patch, key);
1896 MidiRegionView::next_bank (CanvasPatchChange& patch)
1898 if (patch.patch()->program() > 0) {
1899 MIDI::Name::PatchPrimaryKey key;
1900 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1901 if (key.lsb < 127) {
1903 change_patch_change (patch, key);
1905 if (key.msb < 127) {
1908 change_patch_change (patch, key);
1915 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1917 if (_selection.empty()) {
1921 _selection.erase (cne);
1925 MidiRegionView::delete_selection()
1927 if (_selection.empty()) {
1931 start_note_diff_command (_("delete selection"));
1933 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1934 if ((*i)->selected()) {
1935 _note_diff_command->remove((*i)->note());
1945 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1947 start_note_diff_command (_("delete note"));
1948 _note_diff_command->remove (n);
1951 trackview.editor().verbose_cursor()->hide ();
1955 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1957 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1959 Selection::iterator tmp = i;
1962 (*i)->set_selected (false);
1963 (*i)->hide_velocity ();
1964 _selection.erase (i);
1972 /* this does not change the status of this regionview w.r.t the editor
1977 SelectionCleared (this); /* EMIT SIGNAL */
1982 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1984 clear_selection_except (ev);
1986 /* don't bother with checking to see if we should remove this
1987 regionview from the editor selection, since we're about to add
1988 another note, and thus put/keep this regionview in the editor
1992 if (!ev->selected()) {
1993 add_to_selection (ev);
1998 MidiRegionView::select_all_notes ()
2002 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2003 add_to_selection (*i);
2008 MidiRegionView::select_range (framepos_t start, framepos_t end)
2012 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2013 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2014 if (t >= start && t <= end) {
2015 add_to_selection (*i);
2021 MidiRegionView::invert_selection ()
2023 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2024 if ((*i)->selected()) {
2025 remove_from_selection(*i);
2027 add_to_selection (*i);
2033 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2035 uint8_t low_note = 127;
2036 uint8_t high_note = 0;
2037 MidiModel::Notes& notes (_model->notes());
2038 _optimization_iterator = _events.begin();
2044 if (extend && _selection.empty()) {
2050 /* scan existing selection to get note range */
2052 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2053 if ((*i)->note()->note() < low_note) {
2054 low_note = (*i)->note()->note();
2056 if ((*i)->note()->note() > high_note) {
2057 high_note = (*i)->note()->note();
2061 low_note = min (low_note, notenum);
2062 high_note = max (high_note, notenum);
2065 _no_sound_notes = true;
2067 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2069 boost::shared_ptr<NoteType> note (*n);
2070 CanvasNoteEvent* cne;
2071 bool select = false;
2073 if (((1 << note->channel()) & channel_mask) != 0) {
2075 if ((note->note() >= low_note && note->note() <= high_note)) {
2078 } else if (note->note() == notenum) {
2084 if ((cne = find_canvas_note (note)) != 0) {
2085 // extend is false because we've taken care of it,
2086 // since it extends by time range, not pitch.
2087 note_selected (cne, add, false);
2091 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2095 _no_sound_notes = false;
2099 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2101 MidiModel::Notes& notes (_model->notes());
2102 _optimization_iterator = _events.begin();
2104 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2106 boost::shared_ptr<NoteType> note (*n);
2107 CanvasNoteEvent* cne;
2109 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2110 if ((cne = find_canvas_note (note)) != 0) {
2111 if (cne->selected()) {
2112 note_deselected (cne);
2114 note_selected (cne, true, false);
2122 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2125 clear_selection_except (ev);
2126 if (!_selection.empty()) {
2127 PublicEditor& editor (trackview.editor());
2128 editor.get_selection().add (this);
2134 if (!ev->selected()) {
2135 add_to_selection (ev);
2139 /* find end of latest note selected, select all between that and the start of "ev" */
2141 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2142 Evoral::MusicalTime latest = 0;
2144 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2145 if ((*i)->note()->end_time() > latest) {
2146 latest = (*i)->note()->end_time();
2148 if ((*i)->note()->time() < earliest) {
2149 earliest = (*i)->note()->time();
2153 if (ev->note()->end_time() > latest) {
2154 latest = ev->note()->end_time();
2157 if (ev->note()->time() < earliest) {
2158 earliest = ev->note()->time();
2161 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2163 /* find notes entirely within OR spanning the earliest..latest range */
2165 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2166 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2167 add_to_selection (*i);
2175 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2177 remove_from_selection (ev);
2181 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2191 // TODO: Make this faster by storing the last updated selection rect, and only
2192 // adjusting things that are in the area that appears/disappeared.
2193 // We probably need a tree to be able to find events in O(log(n)) time.
2195 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2197 /* check if any corner of the note is inside the rect
2200 1) this is computing "touched by", not "contained by" the rect.
2201 2) this does not require that events be sorted in time.
2204 const double ix1 = (*i)->x1();
2205 const double ix2 = (*i)->x2();
2206 const double iy1 = (*i)->y1();
2207 const double iy2 = (*i)->y2();
2209 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2210 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2211 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2212 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2215 if (!(*i)->selected()) {
2216 add_to_selection (*i);
2218 } else if ((*i)->selected() && !extend) {
2219 // Not inside rectangle
2220 remove_from_selection (*i);
2226 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2232 // TODO: Make this faster by storing the last updated selection rect, and only
2233 // adjusting things that are in the area that appears/disappeared.
2234 // We probably need a tree to be able to find events in O(log(n)) time.
2236 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2238 /* check if any corner of the note is inside the rect
2241 1) this is computing "touched by", not "contained by" the rect.
2242 2) this does not require that events be sorted in time.
2245 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2246 // within y- (note-) range
2247 if (!(*i)->selected()) {
2248 add_to_selection (*i);
2250 } else if ((*i)->selected() && !extend) {
2251 // Not inside rectangle
2252 remove_from_selection (*i);
2258 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2260 Selection::iterator i = _selection.find (ev);
2262 if (i != _selection.end()) {
2263 _selection.erase (i);
2266 ev->set_selected (false);
2267 ev->hide_velocity ();
2269 if (_selection.empty()) {
2270 PublicEditor& editor (trackview.editor());
2271 editor.get_selection().remove (this);
2276 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2278 bool add_mrv_selection = false;
2280 if (_selection.empty()) {
2281 add_mrv_selection = true;
2284 if (_selection.insert (ev).second) {
2285 ev->set_selected (true);
2286 play_midi_note ((ev)->note());
2289 if (add_mrv_selection) {
2290 PublicEditor& editor (trackview.editor());
2291 editor.get_selection().add (this);
2296 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2298 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2299 PossibleChord to_play;
2300 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2302 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2303 if ((*i)->note()->time() < earliest) {
2304 earliest = (*i)->note()->time();
2308 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2309 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2310 to_play.push_back ((*i)->note());
2312 (*i)->move_event(dx, dy);
2315 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2317 if (to_play.size() > 1) {
2319 PossibleChord shifted;
2321 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2322 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2323 moved_note->set_note (moved_note->note() + cumulative_dy);
2324 shifted.push_back (moved_note);
2327 play_midi_chord (shifted);
2329 } else if (!to_play.empty()) {
2331 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2332 moved_note->set_note (moved_note->note() + cumulative_dy);
2333 play_midi_note (moved_note);
2339 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2341 assert (!_selection.empty());
2343 uint8_t lowest_note_in_selection = 127;
2344 uint8_t highest_note_in_selection = 0;
2345 uint8_t highest_note_difference = 0;
2347 // find highest and lowest notes first
2349 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2350 uint8_t pitch = (*i)->note()->note();
2351 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2352 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2356 cerr << "dnote: " << (int) dnote << endl;
2357 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2358 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2359 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2360 << int(highest_note_in_selection) << endl;
2361 cerr << "selection size: " << _selection.size() << endl;
2362 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2365 // Make sure the note pitch does not exceed the MIDI standard range
2366 if (highest_note_in_selection + dnote > 127) {
2367 highest_note_difference = highest_note_in_selection - 127;
2370 start_note_diff_command (_("move notes"));
2372 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2374 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2375 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2381 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2383 uint8_t original_pitch = (*i)->note()->note();
2384 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2386 // keep notes in standard midi range
2387 clamp_to_0_127(new_pitch);
2389 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2390 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2392 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2397 // care about notes being moved beyond the upper/lower bounds on the canvas
2398 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2399 highest_note_in_selection > midi_stream_view()->highest_note()) {
2400 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2404 /** @param x Pixel relative to the region position.
2405 * @return Snapped frame relative to the region position.
2408 MidiRegionView::snap_pixel_to_frame(double x)
2410 PublicEditor& editor (trackview.editor());
2411 return snap_frame_to_frame (editor.pixel_to_frame (x));
2414 /** @param x Pixel relative to the region position.
2415 * @return Snapped pixel relative to the region position.
2418 MidiRegionView::snap_to_pixel(double x)
2420 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2424 MidiRegionView::get_position_pixels()
2426 framepos_t region_frame = get_position();
2427 return trackview.editor().frame_to_pixel(region_frame);
2431 MidiRegionView::get_end_position_pixels()
2433 framepos_t frame = get_position() + get_duration ();
2434 return trackview.editor().frame_to_pixel(frame);
2438 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2440 /* the time converter will return the frame corresponding to `beats'
2441 relative to the start of the source. The start of the source
2442 is an implied position given by region->position - region->start
2444 const framepos_t source_start = _region->position() - _region->start();
2445 return source_start + _source_relative_time_converter.to (beats);
2449 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2451 /* the `frames' argument needs to be converted into a frame count
2452 relative to the start of the source before being passed in to the
2455 const framepos_t source_start = _region->position() - _region->start();
2456 return _source_relative_time_converter.from (frames - source_start);
2460 MidiRegionView::region_beats_to_region_frames(double beats) const
2462 return _region_relative_time_converter.to(beats);
2466 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2468 return _region_relative_time_converter.from(frames);
2472 MidiRegionView::begin_resizing (bool /*at_front*/)
2474 _resize_data.clear();
2476 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2477 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2479 // only insert CanvasNotes into the map
2481 NoteResizeData *resize_data = new NoteResizeData();
2482 resize_data->canvas_note = note;
2484 // create a new SimpleRect from the note which will be the resize preview
2485 SimpleRect *resize_rect = new SimpleRect(
2486 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2488 // calculate the colors: get the color settings
2489 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2490 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2493 // make the resize preview notes more transparent and bright
2494 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2496 // calculate color based on note velocity
2497 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2498 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2502 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2503 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2505 resize_data->resize_rect = resize_rect;
2506 _resize_data.push_back(resize_data);
2511 /** Update resizing notes while user drags.
2512 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2513 * @param at_front which end of the note (true == note on, false == note off)
2514 * @param delta_x change in mouse position since the start of the drag
2515 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2516 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2517 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2518 * as the \a primary note.
2521 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2523 bool cursor_set = false;
2525 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2526 SimpleRect* resize_rect = (*i)->resize_rect;
2527 CanvasNote* canvas_note = (*i)->canvas_note;
2532 current_x = canvas_note->x1() + delta_x;
2534 current_x = primary->x1() + delta_x;
2538 current_x = canvas_note->x2() + delta_x;
2540 current_x = primary->x2() + delta_x;
2545 resize_rect->property_x1() = snap_to_pixel(current_x);
2546 resize_rect->property_x2() = canvas_note->x2();
2548 resize_rect->property_x2() = snap_to_pixel(current_x);
2549 resize_rect->property_x1() = canvas_note->x1();
2555 beats = snap_pixel_to_frame (current_x);
2556 beats = region_frames_to_region_beats (beats);
2561 if (beats < canvas_note->note()->end_time()) {
2562 len = canvas_note->note()->time() - beats;
2563 len += canvas_note->note()->length();
2568 if (beats >= canvas_note->note()->time()) {
2569 len = beats - canvas_note->note()->time();
2576 snprintf (buf, sizeof (buf), "%.3g beats", len);
2577 show_verbose_cursor (buf, 0, 0);
2586 /** Finish resizing notes when the user releases the mouse button.
2587 * Parameters the same as for \a update_resizing().
2590 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2592 start_note_diff_command (_("resize notes"));
2594 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2595 CanvasNote* canvas_note = (*i)->canvas_note;
2596 SimpleRect* resize_rect = (*i)->resize_rect;
2598 /* Get the new x position for this resize, which is in pixels relative
2599 * to the region position.
2606 current_x = canvas_note->x1() + delta_x;
2608 current_x = primary->x1() + delta_x;
2612 current_x = canvas_note->x2() + delta_x;
2614 current_x = primary->x2() + delta_x;
2618 /* Convert that to a frame within the source */
2619 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2621 /* and then to beats */
2622 current_x = region_frames_to_region_beats (current_x);
2624 if (at_front && current_x < canvas_note->note()->end_time()) {
2625 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2627 double len = canvas_note->note()->time() - current_x;
2628 len += canvas_note->note()->length();
2631 /* XXX convert to beats */
2632 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2637 double len = current_x - canvas_note->note()->time();
2640 /* XXX convert to beats */
2641 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2649 _resize_data.clear();
2654 MidiRegionView::abort_resizing ()
2656 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2657 delete (*i)->resize_rect;
2661 _resize_data.clear ();
2665 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2667 uint8_t new_velocity;
2670 new_velocity = event->note()->velocity() + velocity;
2671 clamp_to_0_127(new_velocity);
2673 new_velocity = velocity;
2676 event->set_selected (event->selected()); // change color
2678 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2682 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2687 new_note = event->note()->note() + note;
2692 clamp_to_0_127 (new_note);
2693 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2697 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2699 bool change_start = false;
2700 bool change_length = false;
2701 Evoral::MusicalTime new_start = 0;
2702 Evoral::MusicalTime new_length = 0;
2704 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2706 front_delta: if positive - move the start of the note later in time (shortening it)
2707 if negative - move the start of the note earlier in time (lengthening it)
2709 end_delta: if positive - move the end of the note later in time (lengthening it)
2710 if negative - move the end of the note earlier in time (shortening it)
2714 if (front_delta < 0) {
2716 if (event->note()->time() < -front_delta) {
2719 new_start = event->note()->time() + front_delta; // moves earlier
2722 /* start moved toward zero, so move the end point out to where it used to be.
2723 Note that front_delta is negative, so this increases the length.
2726 new_length = event->note()->length() - front_delta;
2727 change_start = true;
2728 change_length = true;
2732 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2734 if (new_pos < event->note()->end_time()) {
2735 new_start = event->note()->time() + front_delta;
2736 /* start moved toward the end, so move the end point back to where it used to be */
2737 new_length = event->note()->length() - front_delta;
2738 change_start = true;
2739 change_length = true;
2746 bool can_change = true;
2747 if (end_delta < 0) {
2748 if (event->note()->length() < -end_delta) {
2754 new_length = event->note()->length() + end_delta;
2755 change_length = true;
2760 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2763 if (change_length) {
2764 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2769 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2771 uint8_t new_channel;
2775 if (event->note()->channel() < -chn) {
2778 new_channel = event->note()->channel() + chn;
2781 new_channel = event->note()->channel() + chn;
2784 new_channel = (uint8_t) chn;
2787 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2791 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2793 Evoral::MusicalTime new_time;
2797 if (event->note()->time() < -delta) {
2800 new_time = event->note()->time() + delta;
2803 new_time = event->note()->time() + delta;
2809 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2813 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2815 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2819 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2823 if (_selection.empty()) {
2838 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2839 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2845 start_note_diff_command (_("change velocities"));
2847 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2848 Selection::iterator next = i;
2850 change_note_velocity (*i, delta, true);
2856 if (!_selection.empty()) {
2858 snprintf (buf, sizeof (buf), "Vel %d",
2859 (int) (*_selection.begin())->note()->velocity());
2860 show_verbose_cursor (buf, 10, 10);
2866 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2868 if (_selection.empty()) {
2885 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2887 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2891 if ((int8_t) (*i)->note()->note() + delta > 127) {
2898 start_note_diff_command (_("transpose"));
2900 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2901 Selection::iterator next = i;
2903 change_note_note (*i, delta, true);
2911 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2917 /* grab the current grid distance */
2919 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2921 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2922 error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg;
2932 start_note_diff_command (_("change note lengths"));
2934 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2935 Selection::iterator next = i;
2938 /* note the negation of the delta for start */
2940 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2949 MidiRegionView::nudge_notes (bool forward)
2951 if (_selection.empty()) {
2955 /* pick a note as the point along the timeline to get the nudge distance.
2956 its not necessarily the earliest note, so we may want to pull the notes out
2957 into a vector and sort before using the first one.
2960 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2962 framecnt_t distance;
2964 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2966 /* grid is off - use nudge distance */
2968 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2974 framepos_t next_pos = ref_point;
2977 if (max_framepos - 1 < next_pos) {
2981 if (next_pos == 0) {
2987 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2988 distance = ref_point - next_pos;
2991 if (distance == 0) {
2995 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3001 start_note_diff_command (_("nudge"));
3003 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3004 Selection::iterator next = i;
3006 change_note_time (*i, delta, true);
3014 MidiRegionView::change_channel(uint8_t channel)
3016 start_note_diff_command(_("change channel"));
3017 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3018 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3026 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3028 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3030 pre_enter_cursor = editor->get_canvas_cursor ();
3032 if (_mouse_state == SelectTouchDragging) {
3033 note_selected (ev, true);
3036 show_verbose_cursor (ev->note ());
3040 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3042 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3044 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3045 (*i)->hide_velocity ();
3048 editor->verbose_cursor()->hide ();
3050 if (pre_enter_cursor) {
3051 editor->set_canvas_cursor (pre_enter_cursor);
3052 pre_enter_cursor = 0;
3057 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3060 /* XXX should get patch name if we can */
3061 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3062 show_verbose_cursor (s.str(), 10, 20);
3066 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3068 trackview.editor().verbose_cursor()->hide ();
3072 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3074 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3075 Editing::MouseMode mm = editor->current_mouse_mode();
3076 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3078 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3079 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3080 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3081 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3083 if (pre_enter_cursor && can_set_cursor) {
3084 editor->set_canvas_cursor (pre_enter_cursor);
3090 MidiRegionView::set_frame_color()
3094 TimeAxisViewItem::set_frame_color ();
3101 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3102 } else if (high_enough_for_name) {
3103 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3108 if (!rect_visible) {
3109 f = UINT_RGBA_CHANGE_A (f, 0);
3112 frame->property_fill_color_rgba() = f;
3116 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3118 if (mode == ForceChannel) {
3119 mask = 0xFFFF; // Show all notes as active (below)
3122 // Update notes for selection
3123 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3124 (*i)->on_channel_selection_change(mask);
3127 _last_channel_selection = mask;
3129 _patch_changes.clear ();
3130 display_patch_changes ();
3134 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3136 _model_name = model;
3137 _custom_device_mode = custom_device_mode;
3142 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3144 if (_selection.empty()) {
3148 PublicEditor& editor (trackview.editor());
3152 /* XXX what to do ? */
3156 editor.get_cut_buffer().add (selection_as_cut_buffer());
3164 start_note_diff_command();
3166 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3173 note_diff_remove_note (*i);
3183 MidiRegionView::selection_as_cut_buffer () const
3187 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3188 NoteType* n = (*i)->note().get();
3189 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3192 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3198 /** This method handles undo */
3200 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3206 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3208 trackview.session()->begin_reversible_command (_("paste"));
3210 start_note_diff_command (_("paste"));
3212 Evoral::MusicalTime beat_delta;
3213 Evoral::MusicalTime paste_pos_beats;
3214 Evoral::MusicalTime duration;
3215 Evoral::MusicalTime end_point = 0;
3217 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3218 paste_pos_beats = absolute_frames_to_source_beats (pos);
3219 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3220 paste_pos_beats = 0;
3222 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",
3223 (*mcb.notes().begin())->time(),
3224 (*mcb.notes().rbegin())->end_time(),
3225 duration, pos, _region->position(),
3226 paste_pos_beats, beat_delta));
3230 for (int n = 0; n < (int) times; ++n) {
3232 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3234 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3235 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3237 /* make all newly added notes selected */
3239 note_diff_add_note (copied_note, true);
3240 end_point = copied_note->end_time();
3243 paste_pos_beats += duration;
3246 /* if we pasted past the current end of the region, extend the region */
3248 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3249 framepos_t region_end = _region->position() + _region->length() - 1;
3251 if (end_frame > region_end) {
3253 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3255 _region->clear_changes ();
3256 _region->set_length (end_frame - _region->position());
3257 trackview.session()->add_command (new StatefulDiffCommand (_region));
3262 trackview.session()->commit_reversible_command ();
3265 struct EventNoteTimeEarlyFirstComparator {
3266 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3267 return a->note()->time() < b->note()->time();
3272 MidiRegionView::time_sort_events ()
3274 if (!_sort_needed) {
3278 EventNoteTimeEarlyFirstComparator cmp;
3281 _sort_needed = false;
3285 MidiRegionView::goto_next_note (bool add_to_selection)
3287 bool use_next = false;
3289 if (_events.back()->selected()) {
3293 time_sort_events ();
3295 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3296 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3298 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3299 if ((*i)->selected()) {
3302 } else if (use_next) {
3303 if (channel_mask & (1 << (*i)->note()->channel())) {
3304 if (!add_to_selection) {
3307 note_selected (*i, true, false);
3314 /* use the first one */
3316 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3317 unique_select (_events.front());
3322 MidiRegionView::goto_previous_note (bool add_to_selection)
3324 bool use_next = false;
3326 if (_events.front()->selected()) {
3330 time_sort_events ();
3332 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3333 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3335 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3336 if ((*i)->selected()) {
3339 } else if (use_next) {
3340 if (channel_mask & (1 << (*i)->note()->channel())) {
3341 if (!add_to_selection) {
3344 note_selected (*i, true, false);
3351 /* use the last one */
3353 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3354 unique_select (*(_events.rbegin()));
3359 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3361 bool had_selected = false;
3363 time_sort_events ();
3365 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3366 if ((*i)->selected()) {
3367 selected.insert ((*i)->note());
3368 had_selected = true;
3372 if (allow_all_if_none_selected && !had_selected) {
3373 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3374 selected.insert ((*i)->note());
3380 MidiRegionView::update_ghost_note (double x, double y)
3382 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3387 _note_group->w2i (x, y);
3389 PublicEditor& editor = trackview.editor ();
3391 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3392 framecnt_t grid_frames;
3393 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3395 /* use region_frames... because we are converting a delta within the region
3399 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3405 /* note that this sets the time of the ghost note in beats relative to
3406 the start of the source; that is how all note times are stored.
3408 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3409 _ghost_note->note()->set_length (length);
3410 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3411 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3413 /* the ghost note does not appear in ghost regions, so pass false in here */
3414 update_note (_ghost_note, false);
3416 show_verbose_cursor (_ghost_note->note ());
3420 MidiRegionView::create_ghost_note (double x, double y)
3422 remove_ghost_note ();
3424 boost::shared_ptr<NoteType> g (new NoteType);
3425 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3426 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3427 update_ghost_note (x, y);
3428 _ghost_note->show ();
3433 show_verbose_cursor (_ghost_note->note ());
3437 MidiRegionView::snap_changed ()
3443 create_ghost_note (_last_ghost_x, _last_ghost_y);
3447 MidiRegionView::drop_down_keys ()
3449 _mouse_state = None;
3453 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3455 double note = midi_stream_view()->y_to_note(y);
3457 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3459 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3461 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3462 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3463 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3464 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3469 bool add_mrv_selection = false;
3471 if (_selection.empty()) {
3472 add_mrv_selection = true;
3475 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3476 if (_selection.insert (*i).second) {
3477 (*i)->set_selected (true);
3481 if (add_mrv_selection) {
3482 PublicEditor& editor (trackview.editor());
3483 editor.get_selection().add (this);
3488 MidiRegionView::color_handler ()
3490 RegionView::color_handler ();
3492 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3493 (*i)->set_selected ((*i)->selected()); // will change color
3496 /* XXX probably more to do here */
3500 MidiRegionView::enable_display (bool yn)
3502 RegionView::enable_display (yn);
3509 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3511 if (_step_edit_cursor == 0) {
3512 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3514 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3515 _step_edit_cursor->property_y1() = 0;
3516 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3517 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3518 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3521 move_step_edit_cursor (pos);
3522 _step_edit_cursor->show ();
3526 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3528 _step_edit_cursor_position = pos;
3530 if (_step_edit_cursor) {
3531 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3532 _step_edit_cursor->property_x1() = pixel;
3533 set_step_edit_cursor_width (_step_edit_cursor_width);
3538 MidiRegionView::hide_step_edit_cursor ()
3540 if (_step_edit_cursor) {
3541 _step_edit_cursor->hide ();
3546 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3548 _step_edit_cursor_width = beats;
3550 if (_step_edit_cursor) {
3551 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3555 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3556 * @param w Source that the data will end up in.
3559 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3561 if (!_active_notes) {
3562 /* we aren't actively being recorded to */
3566 boost::shared_ptr<MidiSource> src = w.lock ();
3567 if (!src || src != midi_region()->midi_source()) {
3568 /* recorded data was not destined for our source */
3572 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3574 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3576 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3578 framepos_t back = max_framepos;
3580 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3581 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3582 assert (ev.buffer ());
3584 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3585 frames from the start of the source, and so time_beats is in terms of the
3589 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3591 if (ev.type() == MIDI_CMD_NOTE_ON) {
3593 boost::shared_ptr<NoteType> note (
3594 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3597 add_note (note, true);
3599 /* fix up our note range */
3600 if (ev.note() < _current_range_min) {
3601 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3602 } else if (ev.note() > _current_range_max) {
3603 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3606 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3607 resolve_note (ev.note (), time_beats);
3613 midi_stream_view()->check_record_layers (region(), back);
3617 MidiRegionView::trim_front_starting ()
3619 /* Reparent the note group to the region view's parent, so that it doesn't change
3620 when the region view is trimmed.
3622 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3623 _temporary_note_group->move (group->property_x(), group->property_y());
3624 _note_group->reparent (*_temporary_note_group);
3628 MidiRegionView::trim_front_ending ()
3630 _note_group->reparent (*group);
3631 delete _temporary_note_group;
3632 _temporary_note_group = 0;
3634 if (_region->start() < 0) {
3635 /* Trim drag made start time -ve; fix this */
3636 midi_region()->fix_negative_start ();
3641 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3643 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3644 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3648 change_patch_change (pc->patch(), d.patch ());
3653 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3656 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3657 Evoral::midi_note_name (n->note()).c_str(),
3659 (int) n->channel() + 1,
3660 (int) n->velocity());
3662 show_verbose_cursor (buf, 10, 20);
3666 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3670 trackview.editor().get_pointer_position (wx, wy);
3675 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3677 double x1, y1, x2, y2;
3678 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3680 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3681 wy -= (y2 - y1) + 2 * yoffset;
3684 trackview.editor().verbose_cursor()->set (text, wx, wy);
3685 trackview.editor().verbose_cursor()->show ();
3688 /** @param p A session framepos.
3689 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3690 * @return p snapped to the grid subdivision underneath it.
3693 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3695 PublicEditor& editor = trackview.editor ();
3698 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3704 grid_frames = region_beats_to_region_frames (grid_beats);
3706 /* Hack so that we always snap to the note that we are over, instead of snapping
3707 to the next one if we're more than halfway through the one we're over.
3709 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3710 p -= grid_frames / 2;
3713 return snap_frame_to_frame (p);
3716 /** Called when the selection has been cleared in any MidiRegionView.
3717 * @param rv MidiRegionView that the selection was cleared in.
3720 MidiRegionView::selection_cleared (MidiRegionView* rv)
3726 /* Clear our selection in sympathy; but don't signal the fact */
3727 clear_selection (false);