2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include "gtkmm2ext/gtk_ui.h"
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "automation_region_view.h"
49 #include "automation_time_axis.h"
50 #include "canvas-hit.h"
51 #include "canvas-note.h"
52 #include "canvas_patch_change.h"
55 #include "editor_drag.h"
56 #include "ghostregion.h"
57 #include "gui_thread.h"
59 #include "midi_channel_dialog.h"
60 #include "midi_cut_buffer.h"
61 #include "midi_list_editor.h"
62 #include "midi_region_view.h"
63 #include "midi_streamview.h"
64 #include "midi_time_axis.h"
65 #include "midi_util.h"
66 #include "mouse_cursors.h"
67 #include "note_player.h"
68 #include "public_editor.h"
69 #include "rgb_macros.h"
70 #include "selection.h"
71 #include "simpleline.h"
72 #include "streamview.h"
74 #include "patch_change_dialog.h"
75 #include "verbose_cursor.h"
79 using namespace ARDOUR;
81 using namespace Editing;
82 using namespace ArdourCanvas;
83 using Gtkmm2ext::Keyboard;
85 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
87 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
89 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
90 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
91 : RegionView (parent, tv, r, spu, basic_color)
92 , _last_channel_selection(0xFFFF)
93 , _current_range_min(0)
94 , _current_range_max(0)
96 , _note_group(new ArdourCanvas::Group(*group))
97 , _note_diff_command (0)
99 , _step_edit_cursor (0)
100 , _step_edit_cursor_width (1.0)
101 , _step_edit_cursor_position (0.0)
102 , _channel_selection_scoped_note (0)
103 , _temporary_note_group (0)
106 , _sort_needed (true)
107 , _optimization_iterator (_events.end())
109 , _no_sound_notes (false)
112 , pre_enter_cursor (0)
113 , pre_press_cursor (0)
115 _note_group->raise_to_top();
116 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
118 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
119 connect_to_diskstream ();
121 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
124 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
125 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
126 TimeAxisViewItem::Visibility visibility)
127 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
128 , _last_channel_selection(0xFFFF)
129 , _current_range_min(0)
130 , _current_range_max(0)
132 , _note_group(new ArdourCanvas::Group(*parent))
133 , _note_diff_command (0)
135 , _step_edit_cursor (0)
136 , _step_edit_cursor_width (1.0)
137 , _step_edit_cursor_position (0.0)
138 , _channel_selection_scoped_note (0)
139 , _temporary_note_group (0)
142 , _sort_needed (true)
143 , _optimization_iterator (_events.end())
145 , _no_sound_notes (false)
148 , pre_enter_cursor (0)
149 , pre_press_cursor (0)
151 _note_group->raise_to_top();
152 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
154 connect_to_diskstream ();
156 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
160 MidiRegionView::parameter_changed (std::string const & p)
162 if (p == "diplay-first-midi-bank-as-zero") {
163 if (_enable_display) {
169 MidiRegionView::MidiRegionView (const MidiRegionView& other)
170 : sigc::trackable(other)
172 , _last_channel_selection(0xFFFF)
173 , _current_range_min(0)
174 , _current_range_max(0)
176 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
177 , _note_diff_command (0)
179 , _step_edit_cursor (0)
180 , _step_edit_cursor_width (1.0)
181 , _step_edit_cursor_position (0.0)
182 , _channel_selection_scoped_note (0)
183 , _temporary_note_group (0)
186 , _sort_needed (true)
187 , _optimization_iterator (_events.end())
189 , _no_sound_notes (false)
192 , pre_enter_cursor (0)
193 , pre_press_cursor (0)
198 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
199 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
204 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
205 : RegionView (other, boost::shared_ptr<Region> (region))
206 , _last_channel_selection(0xFFFF)
207 , _current_range_min(0)
208 , _current_range_max(0)
210 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
211 , _note_diff_command (0)
213 , _step_edit_cursor (0)
214 , _step_edit_cursor_width (1.0)
215 , _step_edit_cursor_position (0.0)
216 , _channel_selection_scoped_note (0)
217 , _temporary_note_group (0)
220 , _sort_needed (true)
221 , _optimization_iterator (_events.end())
223 , _no_sound_notes (false)
226 , pre_enter_cursor (0)
227 , pre_press_cursor (0)
232 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
233 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
239 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
241 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
243 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
244 boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
248 midi_region()->midi_source(0)->load_model();
251 _model = midi_region()->midi_source(0)->model();
252 _enable_display = false;
254 RegionView::init (basic_color, false);
256 compute_colors (basic_color);
258 set_height (trackview.current_height());
261 region_sync_changed ();
262 region_resized (ARDOUR::bounds_change);
265 reset_width_dependent_items (_pixel_width);
269 _enable_display = true;
272 display_model (_model);
276 group->raise_to_top();
277 group->signal_event().connect(
278 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
280 midi_view()->signal_channel_mode_changed().connect(
281 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
283 midi_view()->signal_midi_patch_settings_changed().connect(
284 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
286 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
287 boost::bind (&MidiRegionView::snap_changed, this),
290 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
291 connect_to_diskstream ();
293 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
296 const boost::shared_ptr<ARDOUR::MidiRegion>
297 MidiRegionView::midi_region() const
299 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
303 MidiRegionView::connect_to_diskstream ()
305 midi_view()->midi_track()->DataRecorded.connect(
306 *this, invalidator(*this),
307 boost::bind (&MidiRegionView::data_recorded, this, _1),
312 MidiRegionView::canvas_event(GdkEvent* ev)
315 case GDK_ENTER_NOTIFY:
316 case GDK_LEAVE_NOTIFY:
317 _last_event_x = ev->crossing.x;
318 _last_event_y = ev->crossing.y;
320 case GDK_MOTION_NOTIFY:
321 _last_event_x = ev->motion.x;
322 _last_event_y = ev->motion.y;
328 if (ev->type == GDK_2BUTTON_PRESS) {
329 return trackview.editor().toggle_internal_editing_from_double_click (ev);
332 if (!trackview.editor().internal_editing()) {
338 return scroll (&ev->scroll);
341 return key_press (&ev->key);
343 case GDK_KEY_RELEASE:
344 return key_release (&ev->key);
346 case GDK_BUTTON_PRESS:
347 return button_press (&ev->button);
349 case GDK_BUTTON_RELEASE:
350 return button_release (&ev->button);
352 case GDK_ENTER_NOTIFY:
353 return enter_notify (&ev->crossing);
355 case GDK_LEAVE_NOTIFY:
356 return leave_notify (&ev->crossing);
358 case GDK_MOTION_NOTIFY:
359 return motion (&ev->motion);
369 MidiRegionView::remove_ghost_note ()
376 MidiRegionView::enter_notify (GdkEventCrossing* ev)
378 trackview.editor().MouseModeChanged.connect (
379 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
382 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
383 create_ghost_note (ev->x, ev->y);
386 if (!trackview.editor().internal_editing()) {
387 Keyboard::magic_widget_drop_focus();
389 Keyboard::magic_widget_grab_focus();
397 MidiRegionView::leave_notify (GdkEventCrossing*)
399 _mouse_mode_connection.disconnect ();
401 trackview.editor().verbose_cursor()->hide ();
402 remove_ghost_note ();
408 MidiRegionView::mouse_mode_changed ()
410 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
411 create_ghost_note (_last_event_x, _last_event_y);
413 remove_ghost_note ();
414 trackview.editor().verbose_cursor()->hide ();
417 if (!trackview.editor().internal_editing()) {
418 Keyboard::magic_widget_drop_focus();
420 Keyboard::magic_widget_grab_focus();
426 MidiRegionView::button_press (GdkEventButton* ev)
428 if (ev->button != 1) {
432 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
433 MouseMode m = editor->current_mouse_mode();
435 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
436 pre_press_cursor = editor->get_canvas_cursor ();
437 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
440 if (_mouse_state != SelectTouchDragging) {
442 _pressed_button = ev->button;
443 _mouse_state = Pressed;
448 _pressed_button = ev->button;
454 MidiRegionView::button_release (GdkEventButton* ev)
456 double event_x, event_y;
458 if (ev->button != 1) {
465 group->w2i(event_x, event_y);
466 group->ungrab(ev->time);
468 PublicEditor& editor = trackview.editor ();
470 if (pre_press_cursor) {
471 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
472 pre_press_cursor = 0;
475 switch (_mouse_state) {
476 case Pressed: // Clicked
478 switch (editor.current_mouse_mode()) {
480 /* no motion occured - simple click */
489 if (Keyboard::is_insert_note_event(ev)) {
491 double event_x, event_y;
495 group->w2i(event_x, event_y);
498 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
504 /* Shorten the length by 1 tick so that we can add a new note at the next
505 grid snap without it overlapping this one.
507 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
509 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
517 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
523 /* Shorten the length by 1 tick so that we can add a new note at the next
524 grid snap without it overlapping this one.
526 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
528 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
539 case SelectRectDragging:
541 editor.drags()->end_grab ((GdkEvent *) ev);
543 create_ghost_note (ev->x, ev->y);
555 MidiRegionView::motion (GdkEventMotion* ev)
557 PublicEditor& editor = trackview.editor ();
559 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
560 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
561 _mouse_state != AddDragging) {
563 create_ghost_note (ev->x, ev->y);
565 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
566 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
568 update_ghost_note (ev->x, ev->y);
570 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
572 remove_ghost_note ();
573 editor.verbose_cursor()->hide ();
575 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
577 update_ghost_note (ev->x, ev->y);
580 /* any motion immediately hides velocity text that may have been visible */
582 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
583 (*i)->hide_velocity ();
586 switch (_mouse_state) {
589 if (_pressed_button == 1) {
591 MouseMode m = editor.current_mouse_mode();
593 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
595 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
596 _mouse_state = AddDragging;
597 remove_ghost_note ();
598 editor.verbose_cursor()->hide ();
600 } else if (m == MouseObject) {
602 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
603 _mouse_state = SelectRectDragging;
605 } else if (m == MouseRange) {
606 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
607 _mouse_state = SelectVerticalDragging;
614 case SelectRectDragging:
615 case SelectVerticalDragging:
617 editor.drags()->motion_handler ((GdkEvent *) ev, false);
620 case SelectTouchDragging:
632 MidiRegionView::scroll (GdkEventScroll* ev)
634 if (_selection.empty()) {
638 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
639 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
640 it still works for zoom.
645 trackview.editor().verbose_cursor()->hide ();
647 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
649 if (ev->direction == GDK_SCROLL_UP) {
650 change_velocities (true, fine, false);
651 } else if (ev->direction == GDK_SCROLL_DOWN) {
652 change_velocities (false, fine, false);
658 MidiRegionView::key_press (GdkEventKey* ev)
660 /* since GTK bindings are generally activated on press, and since
661 detectable auto-repeat is the name of the game and only sends
662 repeated presses, carry out key actions at key press, not release.
665 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
667 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
668 _mouse_state = SelectTouchDragging;
671 } else if (ev->keyval == GDK_Escape && unmodified) {
675 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
677 bool start = (ev->keyval == GDK_comma);
678 bool end = (ev->keyval == GDK_period);
679 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
680 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
682 change_note_lengths (fine, shorter, 0.0, start, end);
686 } else if (ev->keyval == GDK_Delete && unmodified) {
691 } else if (ev->keyval == GDK_Tab) {
693 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
694 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
696 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
700 } else if (ev->keyval == GDK_ISO_Left_Tab) {
702 /* Shift-TAB generates ISO Left Tab, for some reason */
704 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
705 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
707 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
713 } else if (ev->keyval == GDK_Up) {
715 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
716 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
718 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
719 change_velocities (true, fine, allow_smush);
721 transpose (true, fine, allow_smush);
725 } else if (ev->keyval == GDK_Down) {
727 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
728 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
730 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
731 change_velocities (false, fine, allow_smush);
733 transpose (false, fine, allow_smush);
737 } else if (ev->keyval == GDK_Left && unmodified) {
742 } else if (ev->keyval == GDK_Right && unmodified) {
747 } else if (ev->keyval == GDK_c && unmodified) {
756 MidiRegionView::key_release (GdkEventKey* ev)
758 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
766 MidiRegionView::channel_edit ()
769 uint8_t current_channel = 0;
771 if (_selection.empty()) {
775 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
777 current_channel = (*i)->note()->channel ();
782 MidiChannelDialog channel_dialog (current_channel);
783 int ret = channel_dialog.run ();
786 case Gtk::RESPONSE_OK:
792 uint8_t new_channel = channel_dialog.active_channel ();
794 start_note_diff_command (_("channel edit"));
796 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
797 Selection::iterator next = i;
799 change_note_channel (*i, new_channel);
807 MidiRegionView::show_list_editor ()
810 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
812 _list_editor->present ();
815 /** Add a note to the model, and the view, at a canvas (click) coordinate.
816 * \param t time in frames relative to the position of the region
817 * \param y vertical position in pixels
818 * \param length duration of the note in beats
819 * \param snap_t true to snap t to the grid, otherwise false.
822 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
824 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
825 MidiStreamView* const view = mtv->midi_view();
827 double note = view->y_to_note(y);
830 assert(note <= 127.0);
832 // Start of note in frames relative to region start
834 framecnt_t grid_frames;
835 t = snap_frame_to_grid_underneath (t, grid_frames);
839 assert (length != 0);
841 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
842 region_frames_to_region_beats(t + _region->start()),
844 (uint8_t)note, 0x40));
846 if (_model->contains (new_note)) {
850 view->update_note_range(new_note->note());
852 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
854 _model->apply_command(*trackview.session(), cmd);
856 play_midi_note (new_note);
860 MidiRegionView::clear_events()
865 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
866 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
871 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
876 _patch_changes.clear();
878 _optimization_iterator = _events.end();
882 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
886 content_connection.disconnect ();
887 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
891 if (_enable_display) {
897 MidiRegionView::start_note_diff_command (string name)
899 if (!_note_diff_command) {
900 _note_diff_command = _model->new_note_diff_command (name);
905 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
907 if (_note_diff_command) {
908 _note_diff_command->add (note);
911 _marked_for_selection.insert(note);
914 _marked_for_velocity.insert(note);
919 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
921 if (_note_diff_command && ev->note()) {
922 _note_diff_command->remove(ev->note());
927 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
928 MidiModel::NoteDiffCommand::Property property,
931 if (_note_diff_command) {
932 _note_diff_command->change (ev->note(), property, val);
937 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
938 MidiModel::NoteDiffCommand::Property property,
939 Evoral::MusicalTime val)
941 if (_note_diff_command) {
942 _note_diff_command->change (ev->note(), property, val);
947 MidiRegionView::apply_diff (bool as_subcommand)
951 if (!_note_diff_command) {
955 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
956 // Mark all selected notes for selection when model reloads
957 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
958 _marked_for_selection.insert((*i)->note());
963 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
965 _model->apply_command (*trackview.session(), _note_diff_command);
968 _note_diff_command = 0;
969 midi_view()->midi_track()->playlist_modified();
972 _marked_for_selection.clear();
975 _marked_for_velocity.clear();
979 MidiRegionView::abort_command()
981 delete _note_diff_command;
982 _note_diff_command = 0;
987 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
989 if (_optimization_iterator != _events.end()) {
990 ++_optimization_iterator;
993 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
994 return *_optimization_iterator;
997 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
998 if ((*_optimization_iterator)->note() == note) {
999 return *_optimization_iterator;
1007 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1009 MidiModel::Notes notes;
1010 _model->get_notes (notes, op, val, chan_mask);
1012 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1013 CanvasNoteEvent* cne = find_canvas_note (*n);
1021 MidiRegionView::redisplay_model()
1023 // Don't redisplay the model if we're currently recording and displaying that
1024 if (_active_notes) {
1032 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1033 (*i)->invalidate ();
1036 MidiModel::ReadLock lock(_model->read_lock());
1038 MidiModel::Notes& notes (_model->notes());
1039 _optimization_iterator = _events.begin();
1041 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1043 boost::shared_ptr<NoteType> note (*n);
1044 CanvasNoteEvent* cne;
1047 if (note_in_region_range (note, visible)) {
1049 if ((cne = find_canvas_note (note)) != 0) {
1056 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1058 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1070 add_note (note, visible);
1075 if ((cne = find_canvas_note (note)) != 0) {
1083 /* remove note items that are no longer valid */
1085 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1086 if (!(*i)->valid ()) {
1088 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1089 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1091 gr->remove_note (*i);
1096 i = _events.erase (i);
1103 _patch_changes.clear();
1107 display_patch_changes ();
1109 _marked_for_selection.clear ();
1110 _marked_for_velocity.clear ();
1112 /* we may have caused _events to contain things out of order (e.g. if a note
1113 moved earlier or later). we don't generally need them in time order, but
1114 make a note that a sort is required for those cases that require it.
1117 _sort_needed = true;
1121 MidiRegionView::display_patch_changes ()
1123 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1124 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1126 for (uint8_t i = 0; i < 16; ++i) {
1127 if (chn_mask & (1<<i)) {
1128 display_patch_changes_on_channel (i);
1130 /* TODO gray-out patch instad of not displaying it */
1135 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1137 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1139 if ((*i)->channel() != channel) {
1143 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1145 boost::shared_ptr<MIDI::Name::Patch> patch =
1146 MIDI::Name::MidiPatchManager::instance().find_patch(
1147 _model_name, _custom_device_mode, channel, patch_key);
1150 add_canvas_patch_change (*i, patch->name());
1153 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1154 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1155 add_canvas_patch_change (*i, buf);
1161 MidiRegionView::display_sysexes()
1163 bool have_periodic_system_messages = false;
1164 bool display_periodic_messages = true;
1166 if (!Config->get_never_display_periodic_midi()) {
1168 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1169 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1170 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1173 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1174 have_periodic_system_messages = true;
1180 if (have_periodic_system_messages) {
1181 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1183 /* get an approximate value for the number of samples per video frame */
1185 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1187 /* if we are zoomed out beyond than the cutoff (i.e. more
1188 * frames per pixel than frames per 4 video frames), don't
1189 * show periodic sysex messages.
1192 if (zoom > (video_frame*4)) {
1193 display_periodic_messages = false;
1197 display_periodic_messages = false;
1200 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1202 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1203 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1205 Evoral::MusicalTime time = (*i)->time();
1209 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1210 if (!display_periodic_messages) {
1218 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1219 str << int((*i)->buffer()[b]);
1220 if (b != (*i)->size() -1) {
1224 string text = str.str();
1226 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1228 double height = midi_stream_view()->contents_height();
1230 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1231 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1233 // Show unless message is beyond the region bounds
1234 if (time - _region->start() >= _region->length() || time < _region->start()) {
1240 _sys_exes.push_back(sysex);
1244 MidiRegionView::~MidiRegionView ()
1246 in_destructor = true;
1248 trackview.editor().verbose_cursor()->hide ();
1250 note_delete_connection.disconnect ();
1252 delete _list_editor;
1254 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1256 if (_active_notes) {
1264 delete _note_diff_command;
1265 delete _step_edit_cursor;
1266 delete _temporary_note_group;
1270 MidiRegionView::region_resized (const PropertyChange& what_changed)
1272 RegionView::region_resized(what_changed);
1274 if (what_changed.contains (ARDOUR::Properties::position)) {
1275 set_duration(_region->length(), 0);
1276 if (_enable_display) {
1283 MidiRegionView::reset_width_dependent_items (double pixel_width)
1285 RegionView::reset_width_dependent_items(pixel_width);
1286 assert(_pixel_width == pixel_width);
1288 if (_enable_display) {
1292 move_step_edit_cursor (_step_edit_cursor_position);
1293 set_step_edit_cursor_width (_step_edit_cursor_width);
1297 MidiRegionView::set_height (double height)
1299 static const double FUDGE = 2.0;
1300 const double old_height = _height;
1301 RegionView::set_height(height);
1302 _height = height - FUDGE;
1304 apply_note_range(midi_stream_view()->lowest_note(),
1305 midi_stream_view()->highest_note(),
1306 height != old_height + FUDGE);
1309 name_pixbuf->raise_to_top();
1312 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1313 (*x)->set_height (midi_stream_view()->contents_height());
1316 if (_step_edit_cursor) {
1317 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1322 /** Apply the current note range from the stream view
1323 * by repositioning/hiding notes as necessary
1326 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1328 if (!_enable_display) {
1332 if (!force && _current_range_min == min && _current_range_max == max) {
1336 _current_range_min = min;
1337 _current_range_max = max;
1339 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1340 CanvasNoteEvent* event = *i;
1341 boost::shared_ptr<NoteType> note (event->note());
1343 if (note->note() < _current_range_min ||
1344 note->note() > _current_range_max) {
1350 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1352 const double y1 = midi_stream_view()->note_to_y(note->note());
1353 const double y2 = y1 + floor(midi_stream_view()->note_height());
1355 cnote->property_y1() = y1;
1356 cnote->property_y2() = y2;
1358 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1360 const double diamond_size = update_hit (chit);
1362 chit->set_height (diamond_size);
1368 MidiRegionView::add_ghost (TimeAxisView& tv)
1372 double unit_position = _region->position () / samples_per_unit;
1373 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1374 MidiGhostRegion* ghost;
1376 if (mtv && mtv->midi_view()) {
1377 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1378 to allow having midi notes on top of note lines and waveforms.
1380 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1382 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1385 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1386 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1387 ghost->add_note(note);
1391 ghost->set_height ();
1392 ghost->set_duration (_region->length() / samples_per_unit);
1393 ghosts.push_back (ghost);
1395 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1401 /** Begin tracking note state for successive calls to add_event
1404 MidiRegionView::begin_write()
1406 assert(!_active_notes);
1407 _active_notes = new CanvasNote*[128];
1408 for (unsigned i=0; i < 128; ++i) {
1409 _active_notes[i] = 0;
1414 /** Destroy note state for add_event
1417 MidiRegionView::end_write()
1419 delete[] _active_notes;
1421 _marked_for_selection.clear();
1422 _marked_for_velocity.clear();
1426 /** Resolve an active MIDI note (while recording).
1429 MidiRegionView::resolve_note(uint8_t note, double end_time)
1431 if (midi_view()->note_mode() != Sustained) {
1435 if (_active_notes && _active_notes[note]) {
1437 /* XXX is end_time really region-centric? I think so, because
1438 this is a new region that we're recording, so source zero is
1439 the same as region zero
1441 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1443 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1444 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1445 _active_notes[note] = 0;
1450 /** Extend active notes to rightmost edge of region (if length is changed)
1453 MidiRegionView::extend_active_notes()
1455 if (!_active_notes) {
1459 for (unsigned i=0; i < 128; ++i) {
1460 if (_active_notes[i]) {
1461 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1468 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1470 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1474 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1476 if (!route_ui || !route_ui->midi_track()) {
1480 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1486 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1488 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1492 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1494 if (!route_ui || !route_ui->midi_track()) {
1498 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1500 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1509 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1511 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1512 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1514 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1515 (note->note() <= midi_stream_view()->highest_note());
1520 /** Update a canvas note's size from its model note.
1521 * @param ev Canvas note to update.
1522 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1525 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1527 boost::shared_ptr<NoteType> note = ev->note();
1528 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1529 const double y1 = midi_stream_view()->note_to_y(note->note());
1531 ev->property_x1() = x;
1532 ev->property_y1() = y1;
1534 /* trim note display to not overlap the end of its region */
1536 if (note->length() > 0) {
1537 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1538 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1540 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1543 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1545 if (note->length() == 0) {
1546 if (_active_notes) {
1547 assert(note->note() < 128);
1548 // If this note is already active there's a stuck note,
1549 // finish the old note rectangle
1550 if (_active_notes[note->note()]) {
1551 CanvasNote* const old_rect = _active_notes[note->note()];
1552 boost::shared_ptr<NoteType> old_note = old_rect->note();
1553 old_rect->property_x2() = x;
1554 old_rect->property_outline_what() = (guint32) 0xF;
1556 _active_notes[note->note()] = ev;
1558 /* outline all but right edge */
1559 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1561 /* outline all edges */
1562 ev->property_outline_what() = (guint32) 0xF;
1565 if (update_ghost_regions) {
1566 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1567 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1569 gr->update_note (ev);
1576 MidiRegionView::update_hit (CanvasHit* ev)
1578 boost::shared_ptr<NoteType> note = ev->note();
1580 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1581 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1582 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1583 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1587 return diamond_size;
1590 /** Add a MIDI note to the view (with length).
1592 * If in sustained mode, notes with length 0 will be considered active
1593 * notes, and resolve_note should be called when the corresponding note off
1594 * event arrives, to properly display the note.
1597 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1599 CanvasNoteEvent* event = 0;
1601 assert(note->time() >= 0);
1602 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1604 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1606 if (midi_view()->note_mode() == Sustained) {
1608 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1610 update_note (ev_rect);
1614 MidiGhostRegion* gr;
1616 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1617 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1618 gr->add_note(ev_rect);
1622 } else if (midi_view()->note_mode() == Percussive) {
1624 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1626 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1628 update_hit (ev_diamond);
1637 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1638 note_selected(event, true);
1641 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1642 event->show_velocity();
1645 event->on_channel_selection_change(_last_channel_selection);
1646 _events.push_back(event);
1655 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1656 MidiStreamView* const view = mtv->midi_view();
1658 view->update_note_range (note->note());
1662 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1663 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1665 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1667 /* potentially extend region to hold new note */
1669 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1670 framepos_t region_end = _region->last_frame();
1672 if (end_frame > region_end) {
1673 _region->set_length (end_frame - _region->position());
1676 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1677 MidiStreamView* const view = mtv->midi_view();
1679 view->update_note_range(new_note->note());
1681 _marked_for_selection.clear ();
1684 start_note_diff_command (_("step add"));
1685 note_diff_add_note (new_note, true, false);
1688 // last_step_edit_note = new_note;
1692 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1694 change_note_lengths (false, false, beats, false, true);
1698 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1700 assert (patch->time() >= 0);
1702 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1703 const double x = trackview.editor().frame_to_pixel (region_frames);
1705 double const height = midi_stream_view()->contents_height();
1707 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1708 new CanvasPatchChange(*this, *_note_group,
1713 _custom_device_mode,
1717 // Show unless patch change is beyond the region bounds
1718 if (region_frames < 0 || region_frames >= _region->length()) {
1719 patch_change->hide();
1721 patch_change->show();
1724 _patch_changes.push_back (patch_change);
1728 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1730 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1731 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1735 if (i != _model->patch_changes().end()) {
1736 key.msb = (*i)->bank_msb ();
1737 key.lsb = (*i)->bank_lsb ();
1738 key.program_number = (*i)->program ();
1740 key.msb = key.lsb = key.program_number = 0;
1743 assert (key.is_sane());
1748 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1750 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1752 if (pc.patch()->program() != new_patch.program_number) {
1753 c->change_program (pc.patch (), new_patch.program_number);
1756 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1757 if (pc.patch()->bank() != new_bank) {
1758 c->change_bank (pc.patch (), new_bank);
1761 _model->apply_command (*trackview.session(), c);
1763 _patch_changes.clear ();
1764 display_patch_changes ();
1768 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1770 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1772 if (old_change->time() != new_change.time()) {
1773 c->change_time (old_change, new_change.time());
1776 if (old_change->channel() != new_change.channel()) {
1777 c->change_channel (old_change, new_change.channel());
1780 if (old_change->program() != new_change.program()) {
1781 c->change_program (old_change, new_change.program());
1784 if (old_change->bank() != new_change.bank()) {
1785 c->change_bank (old_change, new_change.bank());
1788 _model->apply_command (*trackview.session(), c);
1790 _patch_changes.clear ();
1791 display_patch_changes ();
1794 /** Add a patch change to the region.
1795 * @param t Time in frames relative to region position
1796 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1797 * MidiTimeAxisView::get_channel_for_add())
1800 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1802 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1804 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1805 c->add (MidiModel::PatchChangePtr (
1806 new Evoral::PatchChange<Evoral::MusicalTime> (
1807 absolute_frames_to_source_beats (_region->position() + t),
1808 mtv->get_channel_for_add(), patch.program(), patch.bank()
1813 _model->apply_command (*trackview.session(), c);
1815 _patch_changes.clear ();
1816 display_patch_changes ();
1820 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1822 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1823 c->change_time (pc.patch (), t);
1824 _model->apply_command (*trackview.session(), c);
1826 _patch_changes.clear ();
1827 display_patch_changes ();
1831 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1833 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1834 c->remove (pc->patch ());
1835 _model->apply_command (*trackview.session(), c);
1837 _patch_changes.clear ();
1838 display_patch_changes ();
1842 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1844 if (patch.patch()->program() < 127) {
1845 MIDI::Name::PatchPrimaryKey key;
1846 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1847 key.program_number++;
1848 change_patch_change (patch, key);
1853 MidiRegionView::next_patch (CanvasPatchChange& patch)
1855 if (patch.patch()->program() > 0) {
1856 MIDI::Name::PatchPrimaryKey key;
1857 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1858 key.program_number--;
1859 change_patch_change (patch, key);
1864 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1866 if (patch.patch()->program() < 127) {
1867 MIDI::Name::PatchPrimaryKey key;
1868 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1871 change_patch_change (patch, key);
1876 change_patch_change (patch, key);
1883 MidiRegionView::next_bank (CanvasPatchChange& patch)
1885 if (patch.patch()->program() > 0) {
1886 MIDI::Name::PatchPrimaryKey key;
1887 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1888 if (key.lsb < 127) {
1890 change_patch_change (patch, key);
1892 if (key.msb < 127) {
1895 change_patch_change (patch, key);
1902 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1904 if (_selection.empty()) {
1908 _selection.erase (cne);
1912 MidiRegionView::delete_selection()
1914 if (_selection.empty()) {
1918 start_note_diff_command (_("delete selection"));
1920 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1921 if ((*i)->selected()) {
1922 _note_diff_command->remove((*i)->note());
1932 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1934 start_note_diff_command (_("delete note"));
1935 _note_diff_command->remove (n);
1938 trackview.editor().verbose_cursor()->hide ();
1942 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1944 bool changed = false;
1946 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1948 Selection::iterator tmp = i;
1951 (*i)->set_selected (false);
1952 (*i)->hide_velocity ();
1953 _selection.erase (i);
1962 /* this does not change the status of this regionview w.r.t the editor
1966 if (changed && signal) {
1967 SelectionCleared (this); /* EMIT SIGNAL */
1972 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1974 clear_selection_except (ev);
1976 /* don't bother with checking to see if we should remove this
1977 regionview from the editor selection, since we're about to add
1978 another note, and thus put/keep this regionview in the editor
1982 if (!ev->selected()) {
1983 add_to_selection (ev);
1988 MidiRegionView::select_all_notes ()
1992 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1993 add_to_selection (*i);
1998 MidiRegionView::select_range (framepos_t start, framepos_t end)
2002 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2003 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2004 if (t >= start && t <= end) {
2005 add_to_selection (*i);
2011 MidiRegionView::invert_selection ()
2013 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2014 if ((*i)->selected()) {
2015 remove_from_selection(*i);
2017 add_to_selection (*i);
2023 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2025 uint8_t low_note = 127;
2026 uint8_t high_note = 0;
2027 MidiModel::Notes& notes (_model->notes());
2028 _optimization_iterator = _events.begin();
2034 if (extend && _selection.empty()) {
2040 /* scan existing selection to get note range */
2042 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2043 if ((*i)->note()->note() < low_note) {
2044 low_note = (*i)->note()->note();
2046 if ((*i)->note()->note() > high_note) {
2047 high_note = (*i)->note()->note();
2051 low_note = min (low_note, notenum);
2052 high_note = max (high_note, notenum);
2055 _no_sound_notes = true;
2057 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2059 boost::shared_ptr<NoteType> note (*n);
2060 CanvasNoteEvent* cne;
2061 bool select = false;
2063 if (((1 << note->channel()) & channel_mask) != 0) {
2065 if ((note->note() >= low_note && note->note() <= high_note)) {
2068 } else if (note->note() == notenum) {
2074 if ((cne = find_canvas_note (note)) != 0) {
2075 // extend is false because we've taken care of it,
2076 // since it extends by time range, not pitch.
2077 note_selected (cne, add, false);
2081 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2085 _no_sound_notes = false;
2089 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2091 MidiModel::Notes& notes (_model->notes());
2092 _optimization_iterator = _events.begin();
2094 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2096 boost::shared_ptr<NoteType> note (*n);
2097 CanvasNoteEvent* cne;
2099 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2100 if ((cne = find_canvas_note (note)) != 0) {
2101 if (cne->selected()) {
2102 note_deselected (cne);
2104 note_selected (cne, true, false);
2112 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2115 clear_selection_except (ev);
2116 if (!_selection.empty()) {
2117 PublicEditor& editor (trackview.editor());
2118 editor.get_selection().add (this);
2124 if (!ev->selected()) {
2125 add_to_selection (ev);
2129 /* find end of latest note selected, select all between that and the start of "ev" */
2131 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2132 Evoral::MusicalTime latest = 0;
2134 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2135 if ((*i)->note()->end_time() > latest) {
2136 latest = (*i)->note()->end_time();
2138 if ((*i)->note()->time() < earliest) {
2139 earliest = (*i)->note()->time();
2143 if (ev->note()->end_time() > latest) {
2144 latest = ev->note()->end_time();
2147 if (ev->note()->time() < earliest) {
2148 earliest = ev->note()->time();
2151 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2153 /* find notes entirely within OR spanning the earliest..latest range */
2155 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2156 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2157 add_to_selection (*i);
2165 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2167 remove_from_selection (ev);
2171 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2181 // TODO: Make this faster by storing the last updated selection rect, and only
2182 // adjusting things that are in the area that appears/disappeared.
2183 // We probably need a tree to be able to find events in O(log(n)) time.
2185 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2187 /* check if any corner of the note is inside the rect
2190 1) this is computing "touched by", not "contained by" the rect.
2191 2) this does not require that events be sorted in time.
2194 const double ix1 = (*i)->x1();
2195 const double ix2 = (*i)->x2();
2196 const double iy1 = (*i)->y1();
2197 const double iy2 = (*i)->y2();
2199 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2200 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2201 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2202 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2205 if (!(*i)->selected()) {
2206 add_to_selection (*i);
2208 } else if ((*i)->selected() && !extend) {
2209 // Not inside rectangle
2210 remove_from_selection (*i);
2216 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2222 // TODO: Make this faster by storing the last updated selection rect, and only
2223 // adjusting things that are in the area that appears/disappeared.
2224 // We probably need a tree to be able to find events in O(log(n)) time.
2226 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2228 /* check if any corner of the note is inside the rect
2231 1) this is computing "touched by", not "contained by" the rect.
2232 2) this does not require that events be sorted in time.
2235 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2236 // within y- (note-) range
2237 if (!(*i)->selected()) {
2238 add_to_selection (*i);
2240 } else if ((*i)->selected() && !extend) {
2241 // Not inside rectangle
2242 remove_from_selection (*i);
2248 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2250 Selection::iterator i = _selection.find (ev);
2252 if (i != _selection.end()) {
2253 _selection.erase (i);
2256 ev->set_selected (false);
2257 ev->hide_velocity ();
2259 if (_selection.empty()) {
2260 PublicEditor& editor (trackview.editor());
2261 editor.get_selection().remove (this);
2266 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2268 bool add_mrv_selection = false;
2270 if (_selection.empty()) {
2271 add_mrv_selection = true;
2274 if (_selection.insert (ev).second) {
2275 ev->set_selected (true);
2276 play_midi_note ((ev)->note());
2279 if (add_mrv_selection) {
2280 PublicEditor& editor (trackview.editor());
2281 editor.get_selection().add (this);
2286 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2288 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2289 PossibleChord to_play;
2290 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2292 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2293 if ((*i)->note()->time() < earliest) {
2294 earliest = (*i)->note()->time();
2298 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2299 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2300 to_play.push_back ((*i)->note());
2302 (*i)->move_event(dx, dy);
2305 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2307 if (to_play.size() > 1) {
2309 PossibleChord shifted;
2311 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2312 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2313 moved_note->set_note (moved_note->note() + cumulative_dy);
2314 shifted.push_back (moved_note);
2317 play_midi_chord (shifted);
2319 } else if (!to_play.empty()) {
2321 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2322 moved_note->set_note (moved_note->note() + cumulative_dy);
2323 play_midi_note (moved_note);
2329 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2331 assert (!_selection.empty());
2333 uint8_t lowest_note_in_selection = 127;
2334 uint8_t highest_note_in_selection = 0;
2335 uint8_t highest_note_difference = 0;
2337 // find highest and lowest notes first
2339 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2340 uint8_t pitch = (*i)->note()->note();
2341 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2342 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2346 cerr << "dnote: " << (int) dnote << endl;
2347 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2348 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2349 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2350 << int(highest_note_in_selection) << endl;
2351 cerr << "selection size: " << _selection.size() << endl;
2352 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2355 // Make sure the note pitch does not exceed the MIDI standard range
2356 if (highest_note_in_selection + dnote > 127) {
2357 highest_note_difference = highest_note_in_selection - 127;
2360 start_note_diff_command (_("move notes"));
2362 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2364 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2365 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2371 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2373 uint8_t original_pitch = (*i)->note()->note();
2374 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2376 // keep notes in standard midi range
2377 clamp_to_0_127(new_pitch);
2379 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2380 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2382 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2387 // care about notes being moved beyond the upper/lower bounds on the canvas
2388 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2389 highest_note_in_selection > midi_stream_view()->highest_note()) {
2390 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2394 /** @param x Pixel relative to the region position.
2395 * @return Snapped frame relative to the region position.
2398 MidiRegionView::snap_pixel_to_frame(double x)
2400 PublicEditor& editor (trackview.editor());
2401 return snap_frame_to_frame (editor.pixel_to_frame (x));
2404 /** @param x Pixel relative to the region position.
2405 * @return Snapped pixel relative to the region position.
2408 MidiRegionView::snap_to_pixel(double x)
2410 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2414 MidiRegionView::get_position_pixels()
2416 framepos_t region_frame = get_position();
2417 return trackview.editor().frame_to_pixel(region_frame);
2421 MidiRegionView::get_end_position_pixels()
2423 framepos_t frame = get_position() + get_duration ();
2424 return trackview.editor().frame_to_pixel(frame);
2428 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2430 /* the time converter will return the frame corresponding to `beats'
2431 relative to the start of the source. The start of the source
2432 is an implied position given by region->position - region->start
2434 const framepos_t source_start = _region->position() - _region->start();
2435 return source_start + _source_relative_time_converter.to (beats);
2439 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2441 /* the `frames' argument needs to be converted into a frame count
2442 relative to the start of the source before being passed in to the
2445 const framepos_t source_start = _region->position() - _region->start();
2446 return _source_relative_time_converter.from (frames - source_start);
2450 MidiRegionView::region_beats_to_region_frames(double beats) const
2452 return _region_relative_time_converter.to(beats);
2456 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2458 return _region_relative_time_converter.from(frames);
2462 MidiRegionView::begin_resizing (bool /*at_front*/)
2464 _resize_data.clear();
2466 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2467 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2469 // only insert CanvasNotes into the map
2471 NoteResizeData *resize_data = new NoteResizeData();
2472 resize_data->canvas_note = note;
2474 // create a new SimpleRect from the note which will be the resize preview
2475 SimpleRect *resize_rect = new SimpleRect(
2476 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2478 // calculate the colors: get the color settings
2479 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2480 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2483 // make the resize preview notes more transparent and bright
2484 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2486 // calculate color based on note velocity
2487 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2488 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2492 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2493 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2495 resize_data->resize_rect = resize_rect;
2496 _resize_data.push_back(resize_data);
2501 /** Update resizing notes while user drags.
2502 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2503 * @param at_front which end of the note (true == note on, false == note off)
2504 * @param delta_x change in mouse position since the start of the drag
2505 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2506 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2507 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2508 * as the \a primary note.
2511 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2513 bool cursor_set = false;
2515 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2516 SimpleRect* resize_rect = (*i)->resize_rect;
2517 CanvasNote* canvas_note = (*i)->canvas_note;
2522 current_x = canvas_note->x1() + delta_x;
2524 current_x = primary->x1() + delta_x;
2528 current_x = canvas_note->x2() + delta_x;
2530 current_x = primary->x2() + delta_x;
2535 resize_rect->property_x1() = snap_to_pixel(current_x);
2536 resize_rect->property_x2() = canvas_note->x2();
2538 resize_rect->property_x2() = snap_to_pixel(current_x);
2539 resize_rect->property_x1() = canvas_note->x1();
2545 beats = snap_pixel_to_frame (current_x);
2546 beats = region_frames_to_region_beats (beats);
2551 if (beats < canvas_note->note()->end_time()) {
2552 len = canvas_note->note()->time() - beats;
2553 len += canvas_note->note()->length();
2558 if (beats >= canvas_note->note()->time()) {
2559 len = beats - canvas_note->note()->time();
2566 snprintf (buf, sizeof (buf), "%.3g beats", len);
2567 show_verbose_cursor (buf, 0, 0);
2576 /** Finish resizing notes when the user releases the mouse button.
2577 * Parameters the same as for \a update_resizing().
2580 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2582 start_note_diff_command (_("resize notes"));
2584 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2585 CanvasNote* canvas_note = (*i)->canvas_note;
2586 SimpleRect* resize_rect = (*i)->resize_rect;
2588 /* Get the new x position for this resize, which is in pixels relative
2589 * to the region position.
2596 current_x = canvas_note->x1() + delta_x;
2598 current_x = primary->x1() + delta_x;
2602 current_x = canvas_note->x2() + delta_x;
2604 current_x = primary->x2() + delta_x;
2608 /* Convert that to a frame within the source */
2609 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2611 /* and then to beats */
2612 current_x = region_frames_to_region_beats (current_x);
2614 if (at_front && current_x < canvas_note->note()->end_time()) {
2615 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2617 double len = canvas_note->note()->time() - current_x;
2618 len += canvas_note->note()->length();
2621 /* XXX convert to beats */
2622 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2627 double len = current_x - canvas_note->note()->time();
2630 /* XXX convert to beats */
2631 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2639 _resize_data.clear();
2644 MidiRegionView::abort_resizing ()
2646 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2647 delete (*i)->resize_rect;
2651 _resize_data.clear ();
2655 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2657 uint8_t new_velocity;
2660 new_velocity = event->note()->velocity() + velocity;
2661 clamp_to_0_127(new_velocity);
2663 new_velocity = velocity;
2666 event->set_selected (event->selected()); // change color
2668 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2672 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2677 new_note = event->note()->note() + note;
2682 clamp_to_0_127 (new_note);
2683 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2687 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2689 bool change_start = false;
2690 bool change_length = false;
2691 Evoral::MusicalTime new_start = 0;
2692 Evoral::MusicalTime new_length = 0;
2694 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2696 front_delta: if positive - move the start of the note later in time (shortening it)
2697 if negative - move the start of the note earlier in time (lengthening it)
2699 end_delta: if positive - move the end of the note later in time (lengthening it)
2700 if negative - move the end of the note earlier in time (shortening it)
2704 if (front_delta < 0) {
2706 if (event->note()->time() < -front_delta) {
2709 new_start = event->note()->time() + front_delta; // moves earlier
2712 /* start moved toward zero, so move the end point out to where it used to be.
2713 Note that front_delta is negative, so this increases the length.
2716 new_length = event->note()->length() - front_delta;
2717 change_start = true;
2718 change_length = true;
2722 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2724 if (new_pos < event->note()->end_time()) {
2725 new_start = event->note()->time() + front_delta;
2726 /* start moved toward the end, so move the end point back to where it used to be */
2727 new_length = event->note()->length() - front_delta;
2728 change_start = true;
2729 change_length = true;
2736 bool can_change = true;
2737 if (end_delta < 0) {
2738 if (event->note()->length() < -end_delta) {
2744 new_length = event->note()->length() + end_delta;
2745 change_length = true;
2750 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2753 if (change_length) {
2754 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2759 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2761 uint8_t new_channel;
2765 if (event->note()->channel() < -chn) {
2768 new_channel = event->note()->channel() + chn;
2771 new_channel = event->note()->channel() + chn;
2774 new_channel = (uint8_t) chn;
2777 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2781 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2783 Evoral::MusicalTime new_time;
2787 if (event->note()->time() < -delta) {
2790 new_time = event->note()->time() + delta;
2793 new_time = event->note()->time() + delta;
2799 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2803 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2805 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2809 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2813 if (_selection.empty()) {
2828 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2829 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2835 start_note_diff_command (_("change velocities"));
2837 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2838 Selection::iterator next = i;
2840 change_note_velocity (*i, delta, true);
2846 if (!_selection.empty()) {
2848 snprintf (buf, sizeof (buf), "Vel %d",
2849 (int) (*_selection.begin())->note()->velocity());
2850 show_verbose_cursor (buf, 10, 10);
2856 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2858 if (_selection.empty()) {
2875 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2877 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2881 if ((int8_t) (*i)->note()->note() + delta > 127) {
2888 start_note_diff_command (_("transpose"));
2890 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2891 Selection::iterator next = i;
2893 change_note_note (*i, delta, true);
2901 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2907 /* grab the current grid distance */
2909 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2911 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2912 cerr << "Grid type not available as beats - TO BE FIXED\n";
2922 start_note_diff_command (_("change note lengths"));
2924 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2925 Selection::iterator next = i;
2928 /* note the negation of the delta for start */
2930 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2939 MidiRegionView::nudge_notes (bool forward)
2941 if (_selection.empty()) {
2945 /* pick a note as the point along the timeline to get the nudge distance.
2946 its not necessarily the earliest note, so we may want to pull the notes out
2947 into a vector and sort before using the first one.
2950 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2952 framecnt_t distance;
2954 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2956 /* grid is off - use nudge distance */
2958 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2964 framepos_t next_pos = ref_point;
2967 if (max_framepos - 1 < next_pos) {
2971 if (next_pos == 0) {
2977 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2978 distance = ref_point - next_pos;
2981 if (distance == 0) {
2985 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2991 start_note_diff_command (_("nudge"));
2993 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2994 Selection::iterator next = i;
2996 change_note_time (*i, delta, true);
3004 MidiRegionView::change_channel(uint8_t channel)
3006 start_note_diff_command(_("change channel"));
3007 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3008 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3016 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3018 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3020 pre_enter_cursor = editor->get_canvas_cursor ();
3022 if (_mouse_state == SelectTouchDragging) {
3023 note_selected (ev, true);
3026 show_verbose_cursor (ev->note ());
3030 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3032 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3034 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3035 (*i)->hide_velocity ();
3038 editor->verbose_cursor()->hide ();
3040 if (pre_enter_cursor) {
3041 editor->set_canvas_cursor (pre_enter_cursor);
3042 pre_enter_cursor = 0;
3047 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3050 /* XXX should get patch name if we can */
3051 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3052 show_verbose_cursor (s.str(), 10, 20);
3056 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3058 trackview.editor().verbose_cursor()->hide ();
3062 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3064 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3065 Editing::MouseMode mm = editor->current_mouse_mode();
3066 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3068 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3069 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3070 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3071 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3073 if (pre_enter_cursor && can_set_cursor) {
3074 editor->set_canvas_cursor (pre_enter_cursor);
3080 MidiRegionView::set_frame_color()
3084 TimeAxisViewItem::set_frame_color ();
3091 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3092 } else if (high_enough_for_name) {
3093 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3098 if (!rect_visible) {
3099 f = UINT_RGBA_CHANGE_A (f, 0);
3102 frame->property_fill_color_rgba() = f;
3106 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3108 if (mode == ForceChannel) {
3109 mask = 0xFFFF; // Show all notes as active (below)
3112 // Update notes for selection
3113 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3114 (*i)->on_channel_selection_change(mask);
3117 _last_channel_selection = mask;
3119 _patch_changes.clear ();
3120 display_patch_changes ();
3124 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3126 _model_name = model;
3127 _custom_device_mode = custom_device_mode;
3132 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3134 if (_selection.empty()) {
3138 PublicEditor& editor (trackview.editor());
3142 /* XXX what to do ? */
3146 editor.get_cut_buffer().add (selection_as_cut_buffer());
3154 start_note_diff_command();
3156 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3163 note_diff_remove_note (*i);
3173 MidiRegionView::selection_as_cut_buffer () const
3177 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3178 NoteType* n = (*i)->note().get();
3179 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3182 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3188 /** This method handles undo */
3190 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3196 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3198 trackview.session()->begin_reversible_command (_("paste"));
3200 start_note_diff_command (_("paste"));
3202 Evoral::MusicalTime beat_delta;
3203 Evoral::MusicalTime paste_pos_beats;
3204 Evoral::MusicalTime duration;
3205 Evoral::MusicalTime end_point = 0;
3207 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3208 paste_pos_beats = absolute_frames_to_source_beats (pos);
3209 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3210 paste_pos_beats = 0;
3212 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",
3213 (*mcb.notes().begin())->time(),
3214 (*mcb.notes().rbegin())->end_time(),
3215 duration, pos, _region->position(),
3216 paste_pos_beats, beat_delta));
3220 for (int n = 0; n < (int) times; ++n) {
3222 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3224 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3225 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3227 /* make all newly added notes selected */
3229 note_diff_add_note (copied_note, true);
3230 end_point = copied_note->end_time();
3233 paste_pos_beats += duration;
3236 /* if we pasted past the current end of the region, extend the region */
3238 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3239 framepos_t region_end = _region->position() + _region->length() - 1;
3241 if (end_frame > region_end) {
3243 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3245 _region->clear_changes ();
3246 _region->set_length (end_frame - _region->position());
3247 trackview.session()->add_command (new StatefulDiffCommand (_region));
3252 trackview.session()->commit_reversible_command ();
3255 struct EventNoteTimeEarlyFirstComparator {
3256 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3257 return a->note()->time() < b->note()->time();
3262 MidiRegionView::time_sort_events ()
3264 if (!_sort_needed) {
3268 EventNoteTimeEarlyFirstComparator cmp;
3271 _sort_needed = false;
3275 MidiRegionView::goto_next_note (bool add_to_selection)
3277 bool use_next = false;
3279 if (_events.back()->selected()) {
3283 time_sort_events ();
3285 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3286 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3288 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3289 if ((*i)->selected()) {
3292 } else if (use_next) {
3293 if (channel_mask & (1 << (*i)->note()->channel())) {
3294 if (!add_to_selection) {
3297 note_selected (*i, true, false);
3304 /* use the first one */
3306 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3307 unique_select (_events.front());
3312 MidiRegionView::goto_previous_note (bool add_to_selection)
3314 bool use_next = false;
3316 if (_events.front()->selected()) {
3320 time_sort_events ();
3322 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3323 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3325 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3326 if ((*i)->selected()) {
3329 } else if (use_next) {
3330 if (channel_mask & (1 << (*i)->note()->channel())) {
3331 if (!add_to_selection) {
3334 note_selected (*i, true, false);
3341 /* use the last one */
3343 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3344 unique_select (*(_events.rbegin()));
3349 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3351 bool had_selected = false;
3353 time_sort_events ();
3355 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3356 if ((*i)->selected()) {
3357 selected.insert ((*i)->note());
3358 had_selected = true;
3362 if (allow_all_if_none_selected && !had_selected) {
3363 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3364 selected.insert ((*i)->note());
3370 MidiRegionView::update_ghost_note (double x, double y)
3372 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3377 _note_group->w2i (x, y);
3379 PublicEditor& editor = trackview.editor ();
3381 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3382 framecnt_t grid_frames;
3383 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3385 /* use region_frames... because we are converting a delta within the region
3389 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3395 /* note that this sets the time of the ghost note in beats relative to
3396 the start of the source; that is how all note times are stored.
3398 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3399 _ghost_note->note()->set_length (length);
3400 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3401 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3403 /* the ghost note does not appear in ghost regions, so pass false in here */
3404 update_note (_ghost_note, false);
3406 show_verbose_cursor (_ghost_note->note ());
3410 MidiRegionView::create_ghost_note (double x, double y)
3412 remove_ghost_note ();
3414 boost::shared_ptr<NoteType> g (new NoteType);
3415 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3416 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3417 update_ghost_note (x, y);
3418 _ghost_note->show ();
3423 show_verbose_cursor (_ghost_note->note ());
3427 MidiRegionView::snap_changed ()
3433 create_ghost_note (_last_ghost_x, _last_ghost_y);
3437 MidiRegionView::drop_down_keys ()
3439 _mouse_state = None;
3443 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3445 double note = midi_stream_view()->y_to_note(y);
3447 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3449 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3451 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3452 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3453 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3454 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3459 bool add_mrv_selection = false;
3461 if (_selection.empty()) {
3462 add_mrv_selection = true;
3465 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3466 if (_selection.insert (*i).second) {
3467 (*i)->set_selected (true);
3471 if (add_mrv_selection) {
3472 PublicEditor& editor (trackview.editor());
3473 editor.get_selection().add (this);
3478 MidiRegionView::color_handler ()
3480 RegionView::color_handler ();
3482 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3483 (*i)->set_selected ((*i)->selected()); // will change color
3486 /* XXX probably more to do here */
3490 MidiRegionView::enable_display (bool yn)
3492 RegionView::enable_display (yn);
3499 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3501 if (_step_edit_cursor == 0) {
3502 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3504 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3505 _step_edit_cursor->property_y1() = 0;
3506 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3507 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3508 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3511 move_step_edit_cursor (pos);
3512 _step_edit_cursor->show ();
3516 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3518 _step_edit_cursor_position = pos;
3520 if (_step_edit_cursor) {
3521 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3522 _step_edit_cursor->property_x1() = pixel;
3523 set_step_edit_cursor_width (_step_edit_cursor_width);
3528 MidiRegionView::hide_step_edit_cursor ()
3530 if (_step_edit_cursor) {
3531 _step_edit_cursor->hide ();
3536 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3538 _step_edit_cursor_width = beats;
3540 if (_step_edit_cursor) {
3541 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3545 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3546 * @param w Source that the data will end up in.
3549 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3551 if (!_active_notes) {
3552 /* we aren't actively being recorded to */
3556 boost::shared_ptr<MidiSource> src = w.lock ();
3557 if (!src || src != midi_region()->midi_source()) {
3558 /* recorded data was not destined for our source */
3562 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3564 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3566 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3568 framepos_t back = max_framepos;
3570 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3571 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3572 assert (ev.buffer ());
3574 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3575 frames from the start of the source, and so time_beats is in terms of the
3579 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3581 if (ev.type() == MIDI_CMD_NOTE_ON) {
3583 boost::shared_ptr<NoteType> note (
3584 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3587 add_note (note, true);
3589 /* fix up our note range */
3590 if (ev.note() < _current_range_min) {
3591 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3592 } else if (ev.note() > _current_range_max) {
3593 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3596 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3597 resolve_note (ev.note (), time_beats);
3603 midi_stream_view()->check_record_layers (region(), back);
3607 MidiRegionView::trim_front_starting ()
3609 /* Reparent the note group to the region view's parent, so that it doesn't change
3610 when the region view is trimmed.
3612 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3613 _temporary_note_group->move (group->property_x(), group->property_y());
3614 _note_group->reparent (*_temporary_note_group);
3618 MidiRegionView::trim_front_ending ()
3620 _note_group->reparent (*group);
3621 delete _temporary_note_group;
3622 _temporary_note_group = 0;
3624 if (_region->start() < 0) {
3625 /* Trim drag made start time -ve; fix this */
3626 midi_region()->fix_negative_start ();
3631 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3633 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3634 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3638 change_patch_change (pc->patch(), d.patch ());
3643 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3646 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3647 Evoral::midi_note_name (n->note()).c_str(),
3649 (int) n->channel() + 1,
3650 (int) n->velocity());
3652 show_verbose_cursor (buf, 10, 20);
3656 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3660 trackview.editor().get_pointer_position (wx, wy);
3665 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3667 double x1, y1, x2, y2;
3668 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3670 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3671 wy -= (y2 - y1) + 2 * yoffset;
3674 trackview.editor().verbose_cursor()->set (text, wx, wy);
3675 trackview.editor().verbose_cursor()->show ();
3678 /** @param p A session framepos.
3679 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3680 * @return p snapped to the grid subdivision underneath it.
3683 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3685 PublicEditor& editor = trackview.editor ();
3688 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3694 grid_frames = region_beats_to_region_frames (grid_beats);
3696 /* Hack so that we always snap to the note that we are over, instead of snapping
3697 to the next one if we're more than halfway through the one we're over.
3699 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3700 p -= grid_frames / 2;
3703 return snap_frame_to_frame (p);
3706 /** Called when the selection has been cleared in any MidiRegionView.
3707 * @param rv MidiRegionView that the selection was cleared in.
3710 MidiRegionView::selection_cleared (MidiRegionView* rv)
3716 /* Clear our selection in sympathy; but don't signal the fact */
3717 clear_selection (false);