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 "note_player.h"
67 #include "public_editor.h"
68 #include "rgb_macros.h"
69 #include "selection.h"
70 #include "simpleline.h"
71 #include "streamview.h"
73 #include "mouse_cursors.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)
93 , _last_channel_selection(0xFFFF)
94 , _current_range_min(0)
95 , _current_range_max(0)
97 , _note_group(new ArdourCanvas::Group(*group))
98 , _note_diff_command (0)
100 , _step_edit_cursor (0)
101 , _step_edit_cursor_width (1.0)
102 , _step_edit_cursor_position (0.0)
103 , _channel_selection_scoped_note (0)
104 , _temporary_note_group (0)
107 , _sort_needed (true)
108 , _optimization_iterator (_events.end())
110 , _no_sound_notes (false)
113 , _pre_enter_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), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
119 connect_to_diskstream ();
121 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_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)
129 , _last_channel_selection(0xFFFF)
130 , _current_range_min(0)
131 , _current_range_max(0)
133 , _note_group(new ArdourCanvas::Group(*parent))
134 , _note_diff_command (0)
136 , _step_edit_cursor (0)
137 , _step_edit_cursor_width (1.0)
138 , _step_edit_cursor_position (0.0)
139 , _channel_selection_scoped_note (0)
140 , _temporary_note_group (0)
143 , _sort_needed (true)
144 , _optimization_iterator (_events.end())
146 , _no_sound_notes (false)
149 , _pre_enter_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), ui_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)
173 , _last_channel_selection(0xFFFF)
174 , _current_range_min(0)
175 , _current_range_max(0)
177 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
178 , _note_diff_command (0)
180 , _step_edit_cursor (0)
181 , _step_edit_cursor_width (1.0)
182 , _step_edit_cursor_position (0.0)
183 , _channel_selection_scoped_note (0)
184 , _temporary_note_group (0)
187 , _sort_needed (true)
188 , _optimization_iterator (_events.end())
190 , _no_sound_notes (false)
193 , _pre_enter_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))
207 , _last_channel_selection(0xFFFF)
208 , _current_range_min(0)
209 , _current_range_max(0)
211 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
212 , _note_diff_command (0)
214 , _step_edit_cursor (0)
215 , _step_edit_cursor_width (1.0)
216 , _step_edit_cursor_position (0.0)
217 , _channel_selection_scoped_note (0)
218 , _temporary_note_group (0)
221 , _sort_needed (true)
222 , _optimization_iterator (_events.end())
224 , _no_sound_notes (false)
227 , _pre_enter_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 ui_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 ui_bind(&MidiRegionView::snap_changed, this),
290 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
291 connect_to_diskstream ();
293 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_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 ui_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), ui_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 if (_mouse_state != SelectTouchDragging) {
434 _pressed_button = ev->button;
435 _mouse_state = Pressed;
440 _pressed_button = ev->button;
446 MidiRegionView::button_release (GdkEventButton* ev)
448 double event_x, event_y;
450 if (ev->button != 1) {
457 group->w2i(event_x, event_y);
458 group->ungrab(ev->time);
460 PublicEditor& editor = trackview.editor ();
462 switch (_mouse_state) {
463 case Pressed: // Clicked
465 switch (editor.current_mouse_mode()) {
471 if (Keyboard::is_insert_note_event(ev)) {
473 double event_x, event_y;
477 group->w2i(event_x, event_y);
480 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
486 /* Shorten the length by 1 tick so that we can add a new note at the next
487 grid snap without it overlapping this one.
489 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
491 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
499 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
505 /* Shorten the length by 1 tick so that we can add a new note at the next
506 grid snap without it overlapping this one.
508 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
510 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
521 case SelectRectDragging:
523 editor.drags()->end_grab ((GdkEvent *) ev);
525 create_ghost_note (ev->x, ev->y);
537 MidiRegionView::motion (GdkEventMotion* ev)
539 PublicEditor& editor = trackview.editor ();
541 if (!_ghost_note && editor.current_mouse_mode() != MouseDraw
542 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
543 && _mouse_state != AddDragging) {
545 create_ghost_note (ev->x, ev->y);
546 } else if (_ghost_note && editor.current_mouse_mode() != MouseDraw
547 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
549 update_ghost_note (ev->x, ev->y);
550 } else if (_ghost_note && editor.current_mouse_mode() != MouseDraw) {
552 remove_ghost_note ();
554 editor.verbose_cursor()->hide ();
555 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
556 update_ghost_note (ev->x, ev->y);
559 /* any motion immediately hides velocity text that may have been visible */
561 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
562 (*i)->hide_velocity ();
565 switch (_mouse_state) {
568 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
569 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
571 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
572 _mouse_state = SelectRectDragging;
575 } else if (editor.internal_editing()) {
577 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
578 _mouse_state = AddDragging;
580 remove_ghost_note ();
582 editor.verbose_cursor()->hide ();
589 case SelectRectDragging:
591 editor.drags()->motion_handler ((GdkEvent *) ev, false);
594 case SelectTouchDragging:
606 MidiRegionView::scroll (GdkEventScroll* ev)
608 if (_selection.empty()) {
612 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
613 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
614 it still works for zoom.
619 trackview.editor().verbose_cursor()->hide ();
621 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
623 if (ev->direction == GDK_SCROLL_UP) {
624 change_velocities (true, fine, false);
625 } else if (ev->direction == GDK_SCROLL_DOWN) {
626 change_velocities (false, fine, false);
632 MidiRegionView::key_press (GdkEventKey* ev)
634 /* since GTK bindings are generally activated on press, and since
635 detectable auto-repeat is the name of the game and only sends
636 repeated presses, carry out key actions at key press, not release.
639 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
641 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
642 _mouse_state = SelectTouchDragging;
645 } else if (ev->keyval == GDK_Escape && unmodified) {
649 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
651 bool start = (ev->keyval == GDK_comma);
652 bool end = (ev->keyval == GDK_period);
653 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
654 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
656 change_note_lengths (fine, shorter, 0.0, start, end);
660 } else if (ev->keyval == GDK_Delete && unmodified) {
665 } else if (ev->keyval == GDK_Tab) {
667 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
668 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
670 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
674 } else if (ev->keyval == GDK_ISO_Left_Tab) {
676 /* Shift-TAB generates ISO Left Tab, for some reason */
678 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
679 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
681 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
687 } else if (ev->keyval == GDK_Up) {
689 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
690 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
692 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
693 change_velocities (true, fine, allow_smush);
695 transpose (true, fine, allow_smush);
699 } else if (ev->keyval == GDK_Down) {
701 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
702 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
704 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
705 change_velocities (false, fine, allow_smush);
707 transpose (false, fine, allow_smush);
711 } else if (ev->keyval == GDK_Left && unmodified) {
716 } else if (ev->keyval == GDK_Right && unmodified) {
721 } else if (ev->keyval == GDK_c && unmodified) {
730 MidiRegionView::key_release (GdkEventKey* ev)
732 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
740 MidiRegionView::channel_edit ()
743 uint8_t current_channel = 0;
745 if (_selection.empty()) {
749 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
751 current_channel = (*i)->note()->channel ();
756 MidiChannelDialog channel_dialog (current_channel);
757 int ret = channel_dialog.run ();
760 case Gtk::RESPONSE_OK:
766 uint8_t new_channel = channel_dialog.active_channel ();
768 start_note_diff_command (_("channel edit"));
770 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
771 Selection::iterator next = i;
773 change_note_channel (*i, new_channel);
781 MidiRegionView::show_list_editor ()
784 _list_editor = new MidiListEditor (trackview.session(), midi_region());
786 _list_editor->present ();
789 /** Add a note to the model, and the view, at a canvas (click) coordinate.
790 * \param t time in frames relative to the position of the region
791 * \param y vertical position in pixels
792 * \param length duration of the note in beats
793 * \param snap_t true to snap t to the grid, otherwise false.
796 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
798 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
799 MidiStreamView* const view = mtv->midi_view();
801 double note = view->y_to_note(y);
804 assert(note <= 127.0);
806 // Start of note in frames relative to region start
808 framecnt_t grid_frames;
809 t = snap_frame_to_grid_underneath (t, grid_frames);
813 assert (length != 0);
815 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
816 region_frames_to_region_beats(t + _region->start()),
818 (uint8_t)note, 0x40));
820 if (_model->contains (new_note)) {
824 view->update_note_range(new_note->note());
826 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
828 _model->apply_command(*trackview.session(), cmd);
830 play_midi_note (new_note);
834 MidiRegionView::clear_events()
839 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
840 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
845 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
850 _patch_changes.clear();
852 _optimization_iterator = _events.end();
856 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
860 content_connection.disconnect ();
861 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
865 if (_enable_display) {
871 MidiRegionView::start_note_diff_command (string name)
873 if (!_note_diff_command) {
874 _note_diff_command = _model->new_note_diff_command (name);
879 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
881 if (_note_diff_command) {
882 _note_diff_command->add (note);
885 _marked_for_selection.insert(note);
888 _marked_for_velocity.insert(note);
893 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
895 if (_note_diff_command && ev->note()) {
896 _note_diff_command->remove(ev->note());
901 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
902 MidiModel::NoteDiffCommand::Property property,
905 if (_note_diff_command) {
906 _note_diff_command->change (ev->note(), property, val);
911 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
912 MidiModel::NoteDiffCommand::Property property,
913 Evoral::MusicalTime val)
915 if (_note_diff_command) {
916 _note_diff_command->change (ev->note(), property, val);
921 MidiRegionView::apply_diff (bool as_subcommand)
925 if (!_note_diff_command) {
929 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
930 // Mark all selected notes for selection when model reloads
931 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
932 _marked_for_selection.insert((*i)->note());
937 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
939 _model->apply_command (*trackview.session(), _note_diff_command);
942 _note_diff_command = 0;
943 midi_view()->midi_track()->playlist_modified();
946 _marked_for_selection.clear();
949 _marked_for_velocity.clear();
953 MidiRegionView::abort_command()
955 delete _note_diff_command;
956 _note_diff_command = 0;
961 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
963 if (_optimization_iterator != _events.end()) {
964 ++_optimization_iterator;
967 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
968 return *_optimization_iterator;
971 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
972 if ((*_optimization_iterator)->note() == note) {
973 return *_optimization_iterator;
981 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
983 MidiModel::Notes notes;
984 _model->get_notes (notes, op, val, chan_mask);
986 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
987 CanvasNoteEvent* cne = find_canvas_note (*n);
995 MidiRegionView::redisplay_model()
997 // Don't redisplay the model if we're currently recording and displaying that
1006 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1007 (*i)->invalidate ();
1010 MidiModel::ReadLock lock(_model->read_lock());
1012 MidiModel::Notes& notes (_model->notes());
1013 _optimization_iterator = _events.begin();
1015 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1017 boost::shared_ptr<NoteType> note (*n);
1018 CanvasNoteEvent* cne;
1021 if (note_in_region_range (note, visible)) {
1023 if ((cne = find_canvas_note (note)) != 0) {
1030 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1032 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1044 add_note (note, visible);
1049 if ((cne = find_canvas_note (note)) != 0) {
1057 /* remove note items that are no longer valid */
1059 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1060 if (!(*i)->valid ()) {
1062 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1063 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1065 gr->remove_note (*i);
1070 i = _events.erase (i);
1077 _patch_changes.clear();
1081 display_patch_changes ();
1083 _marked_for_selection.clear ();
1084 _marked_for_velocity.clear ();
1086 /* we may have caused _events to contain things out of order (e.g. if a note
1087 moved earlier or later). we don't generally need them in time order, but
1088 make a note that a sort is required for those cases that require it.
1091 _sort_needed = true;
1095 MidiRegionView::display_patch_changes ()
1097 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1098 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1100 for (uint8_t i = 0; i < 16; ++i) {
1101 if (chn_mask & (1<<i)) {
1102 display_patch_changes_on_channel (i);
1104 /* TODO gray-out patch instad of not displaying it */
1109 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1111 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1113 if ((*i)->channel() != channel) {
1117 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1119 boost::shared_ptr<MIDI::Name::Patch> patch =
1120 MIDI::Name::MidiPatchManager::instance().find_patch(
1121 _model_name, _custom_device_mode, channel, patch_key);
1124 add_canvas_patch_change (*i, patch->name());
1127 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1128 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1129 add_canvas_patch_change (*i, buf);
1135 MidiRegionView::display_sysexes()
1137 bool have_periodic_system_messages = false;
1138 bool display_periodic_messages = true;
1140 if (!Config->get_never_display_periodic_midi()) {
1142 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1143 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1144 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1147 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1148 have_periodic_system_messages = true;
1154 if (have_periodic_system_messages) {
1155 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1157 /* get an approximate value for the number of samples per video frame */
1159 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1161 /* if we are zoomed out beyond than the cutoff (i.e. more
1162 * frames per pixel than frames per 4 video frames), don't
1163 * show periodic sysex messages.
1166 if (zoom > (video_frame*4)) {
1167 display_periodic_messages = false;
1171 display_periodic_messages = false;
1174 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1176 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1177 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1179 Evoral::MusicalTime time = (*i)->time();
1183 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1184 if (!display_periodic_messages) {
1192 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1193 str << int((*i)->buffer()[b]);
1194 if (b != (*i)->size() -1) {
1198 string text = str.str();
1200 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1202 double height = midi_stream_view()->contents_height();
1204 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1205 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1207 // Show unless message is beyond the region bounds
1208 if (time - _region->start() >= _region->length() || time < _region->start()) {
1214 _sys_exes.push_back(sysex);
1218 MidiRegionView::~MidiRegionView ()
1220 in_destructor = true;
1222 trackview.editor().verbose_cursor()->hide ();
1224 note_delete_connection.disconnect ();
1226 delete _list_editor;
1228 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1230 if (_active_notes) {
1238 delete _note_diff_command;
1239 delete _step_edit_cursor;
1240 delete _temporary_note_group;
1244 MidiRegionView::region_resized (const PropertyChange& what_changed)
1246 RegionView::region_resized(what_changed);
1248 if (what_changed.contains (ARDOUR::Properties::position)) {
1249 set_duration(_region->length(), 0);
1250 if (_enable_display) {
1257 MidiRegionView::reset_width_dependent_items (double pixel_width)
1259 RegionView::reset_width_dependent_items(pixel_width);
1260 assert(_pixel_width == pixel_width);
1262 if (_enable_display) {
1266 move_step_edit_cursor (_step_edit_cursor_position);
1267 set_step_edit_cursor_width (_step_edit_cursor_width);
1271 MidiRegionView::set_height (double height)
1273 static const double FUDGE = 2.0;
1274 const double old_height = _height;
1275 RegionView::set_height(height);
1276 _height = height - FUDGE;
1278 apply_note_range(midi_stream_view()->lowest_note(),
1279 midi_stream_view()->highest_note(),
1280 height != old_height + FUDGE);
1283 name_pixbuf->raise_to_top();
1286 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1287 (*x)->set_height (midi_stream_view()->contents_height());
1290 if (_step_edit_cursor) {
1291 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1296 /** Apply the current note range from the stream view
1297 * by repositioning/hiding notes as necessary
1300 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1302 if (!_enable_display) {
1306 if (!force && _current_range_min == min && _current_range_max == max) {
1310 _current_range_min = min;
1311 _current_range_max = max;
1313 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1314 CanvasNoteEvent* event = *i;
1315 boost::shared_ptr<NoteType> note (event->note());
1317 if (note->note() < _current_range_min ||
1318 note->note() > _current_range_max) {
1324 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1326 const double y1 = midi_stream_view()->note_to_y(note->note());
1327 const double y2 = y1 + floor(midi_stream_view()->note_height());
1329 cnote->property_y1() = y1;
1330 cnote->property_y2() = y2;
1332 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1334 const double diamond_size = update_hit (chit);
1336 chit->set_height (diamond_size);
1342 MidiRegionView::add_ghost (TimeAxisView& tv)
1346 double unit_position = _region->position () / samples_per_unit;
1347 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1348 MidiGhostRegion* ghost;
1350 if (mtv && mtv->midi_view()) {
1351 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1352 to allow having midi notes on top of note lines and waveforms.
1354 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1356 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1359 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1360 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1361 ghost->add_note(note);
1365 ghost->set_height ();
1366 ghost->set_duration (_region->length() / samples_per_unit);
1367 ghosts.push_back (ghost);
1369 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1375 /** Begin tracking note state for successive calls to add_event
1378 MidiRegionView::begin_write()
1380 assert(!_active_notes);
1381 _active_notes = new CanvasNote*[128];
1382 for (unsigned i=0; i < 128; ++i) {
1383 _active_notes[i] = 0;
1388 /** Destroy note state for add_event
1391 MidiRegionView::end_write()
1393 delete[] _active_notes;
1395 _marked_for_selection.clear();
1396 _marked_for_velocity.clear();
1400 /** Resolve an active MIDI note (while recording).
1403 MidiRegionView::resolve_note(uint8_t note, double end_time)
1405 if (midi_view()->note_mode() != Sustained) {
1409 if (_active_notes && _active_notes[note]) {
1411 /* XXX is end_time really region-centric? I think so, because
1412 this is a new region that we're recording, so source zero is
1413 the same as region zero
1415 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1417 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1418 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1419 _active_notes[note] = 0;
1424 /** Extend active notes to rightmost edge of region (if length is changed)
1427 MidiRegionView::extend_active_notes()
1429 if (!_active_notes) {
1433 for (unsigned i=0; i < 128; ++i) {
1434 if (_active_notes[i]) {
1435 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1442 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1444 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1448 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1450 if (!route_ui || !route_ui->midi_track()) {
1454 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1460 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1462 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1466 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1468 if (!route_ui || !route_ui->midi_track()) {
1472 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1474 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1483 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1485 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1486 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1488 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1489 (note->note() <= midi_stream_view()->highest_note());
1494 /** Update a canvas note's size from its model note.
1495 * @param ev Canvas note to update.
1496 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1499 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1501 boost::shared_ptr<NoteType> note = ev->note();
1502 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1503 const double y1 = midi_stream_view()->note_to_y(note->note());
1505 ev->property_x1() = x;
1506 ev->property_y1() = y1;
1508 /* trim note display to not overlap the end of its region */
1510 if (note->length() > 0) {
1511 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1512 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1514 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1517 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1519 if (note->length() == 0) {
1520 if (_active_notes) {
1521 assert(note->note() < 128);
1522 // If this note is already active there's a stuck note,
1523 // finish the old note rectangle
1524 if (_active_notes[note->note()]) {
1525 CanvasNote* const old_rect = _active_notes[note->note()];
1526 boost::shared_ptr<NoteType> old_note = old_rect->note();
1527 old_rect->property_x2() = x;
1528 old_rect->property_outline_what() = (guint32) 0xF;
1530 _active_notes[note->note()] = ev;
1532 /* outline all but right edge */
1533 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1535 /* outline all edges */
1536 ev->property_outline_what() = (guint32) 0xF;
1539 if (update_ghost_regions) {
1540 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1541 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1543 gr->update_note (ev);
1550 MidiRegionView::update_hit (CanvasHit* ev)
1552 boost::shared_ptr<NoteType> note = ev->note();
1554 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1555 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1556 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1557 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1561 return diamond_size;
1564 /** Add a MIDI note to the view (with length).
1566 * If in sustained mode, notes with length 0 will be considered active
1567 * notes, and resolve_note should be called when the corresponding note off
1568 * event arrives, to properly display the note.
1571 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1573 CanvasNoteEvent* event = 0;
1575 assert(note->time() >= 0);
1576 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1578 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1580 if (midi_view()->note_mode() == Sustained) {
1582 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1584 update_note (ev_rect);
1588 MidiGhostRegion* gr;
1590 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1591 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1592 gr->add_note(ev_rect);
1596 } else if (midi_view()->note_mode() == Percussive) {
1598 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1600 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1602 update_hit (ev_diamond);
1611 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1612 note_selected(event, true);
1615 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1616 event->show_velocity();
1619 event->on_channel_selection_change(_last_channel_selection);
1620 _events.push_back(event);
1629 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1630 MidiStreamView* const view = mtv->midi_view();
1632 view->update_note_range (note->note());
1636 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1637 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1639 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1641 /* potentially extend region to hold new note */
1643 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1644 framepos_t region_end = _region->last_frame();
1646 if (end_frame > region_end) {
1647 _region->set_length (end_frame - _region->position());
1650 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1651 MidiStreamView* const view = mtv->midi_view();
1653 view->update_note_range(new_note->note());
1655 _marked_for_selection.clear ();
1658 start_note_diff_command (_("step add"));
1659 note_diff_add_note (new_note, true, false);
1662 // last_step_edit_note = new_note;
1666 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1668 change_note_lengths (false, false, beats, false, true);
1672 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1674 assert (patch->time() >= 0);
1676 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1677 const double x = trackview.editor().frame_to_pixel (region_frames);
1679 double const height = midi_stream_view()->contents_height();
1681 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1682 new CanvasPatchChange(*this, *_note_group,
1687 _custom_device_mode,
1691 // Show unless patch change is beyond the region bounds
1692 if (region_frames < 0 || region_frames >= _region->length()) {
1693 patch_change->hide();
1695 patch_change->show();
1698 _patch_changes.push_back (patch_change);
1702 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1704 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1705 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1709 if (i != _model->patch_changes().end()) {
1710 key.msb = (*i)->bank_msb ();
1711 key.lsb = (*i)->bank_lsb ();
1712 key.program_number = (*i)->program ();
1714 key.msb = key.lsb = key.program_number = 0;
1717 assert (key.is_sane());
1722 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1724 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1726 if (pc.patch()->program() != new_patch.program_number) {
1727 c->change_program (pc.patch (), new_patch.program_number);
1730 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1731 if (pc.patch()->bank() != new_bank) {
1732 c->change_bank (pc.patch (), new_bank);
1735 _model->apply_command (*trackview.session(), c);
1737 _patch_changes.clear ();
1738 display_patch_changes ();
1742 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1744 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1746 if (old_change->time() != new_change.time()) {
1747 c->change_time (old_change, new_change.time());
1750 if (old_change->channel() != new_change.channel()) {
1751 c->change_channel (old_change, new_change.channel());
1754 if (old_change->program() != new_change.program()) {
1755 c->change_program (old_change, new_change.program());
1758 if (old_change->bank() != new_change.bank()) {
1759 c->change_bank (old_change, new_change.bank());
1762 _model->apply_command (*trackview.session(), c);
1764 _patch_changes.clear ();
1765 display_patch_changes ();
1768 /** Add a patch change to the region.
1769 * @param t Time in frames relative to region position
1770 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1771 * MidiTimeAxisView::get_channel_for_add())
1774 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1776 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1778 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1779 c->add (MidiModel::PatchChangePtr (
1780 new Evoral::PatchChange<Evoral::MusicalTime> (
1781 absolute_frames_to_source_beats (_region->position() + t),
1782 mtv->get_channel_for_add(), patch.program(), patch.bank()
1787 _model->apply_command (*trackview.session(), c);
1789 _patch_changes.clear ();
1790 display_patch_changes ();
1794 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1796 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1797 c->change_time (pc.patch (), t);
1798 _model->apply_command (*trackview.session(), c);
1800 _patch_changes.clear ();
1801 display_patch_changes ();
1805 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1807 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1808 c->remove (pc->patch ());
1809 _model->apply_command (*trackview.session(), c);
1811 _patch_changes.clear ();
1812 display_patch_changes ();
1816 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1818 if (patch.patch()->program() < 127) {
1819 MIDI::Name::PatchPrimaryKey key;
1820 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1821 key.program_number++;
1822 change_patch_change (patch, key);
1827 MidiRegionView::next_patch (CanvasPatchChange& patch)
1829 if (patch.patch()->program() > 0) {
1830 MIDI::Name::PatchPrimaryKey key;
1831 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1832 key.program_number--;
1833 change_patch_change (patch, key);
1838 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1840 if (patch.patch()->program() < 127) {
1841 MIDI::Name::PatchPrimaryKey key;
1842 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1845 change_patch_change (patch, key);
1850 change_patch_change (patch, key);
1857 MidiRegionView::next_bank (CanvasPatchChange& patch)
1859 if (patch.patch()->program() > 0) {
1860 MIDI::Name::PatchPrimaryKey key;
1861 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1862 if (key.lsb < 127) {
1864 change_patch_change (patch, key);
1866 if (key.msb < 127) {
1869 change_patch_change (patch, key);
1876 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1878 if (_selection.empty()) {
1882 _selection.erase (cne);
1886 MidiRegionView::delete_selection()
1888 if (_selection.empty()) {
1892 start_note_diff_command (_("delete selection"));
1894 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1895 if ((*i)->selected()) {
1896 _note_diff_command->remove((*i)->note());
1906 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1908 start_note_diff_command (_("delete note"));
1909 _note_diff_command->remove (n);
1912 trackview.editor().verbose_cursor()->hide ();
1916 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1918 bool changed = false;
1920 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1922 Selection::iterator tmp = i;
1925 (*i)->set_selected (false);
1926 (*i)->hide_velocity ();
1927 _selection.erase (i);
1936 /* this does not change the status of this regionview w.r.t the editor
1940 if (changed && signal) {
1941 SelectionCleared (this); /* EMIT SIGNAL */
1946 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1948 clear_selection_except (ev);
1950 /* don't bother with checking to see if we should remove this
1951 regionview from the editor selection, since we're about to add
1952 another note, and thus put/keep this regionview in the editor
1956 if (!ev->selected()) {
1957 add_to_selection (ev);
1962 MidiRegionView::select_all_notes ()
1966 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1967 add_to_selection (*i);
1972 MidiRegionView::select_range (framepos_t start, framepos_t end)
1976 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1977 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
1978 if (t >= start && t <= end) {
1979 add_to_selection (*i);
1985 MidiRegionView::invert_selection ()
1987 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1988 if ((*i)->selected()) {
1989 remove_from_selection(*i);
1991 add_to_selection (*i);
1997 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1999 uint8_t low_note = 127;
2000 uint8_t high_note = 0;
2001 MidiModel::Notes& notes (_model->notes());
2002 _optimization_iterator = _events.begin();
2008 if (extend && _selection.empty()) {
2014 /* scan existing selection to get note range */
2016 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2017 if ((*i)->note()->note() < low_note) {
2018 low_note = (*i)->note()->note();
2020 if ((*i)->note()->note() > high_note) {
2021 high_note = (*i)->note()->note();
2025 low_note = min (low_note, notenum);
2026 high_note = max (high_note, notenum);
2029 _no_sound_notes = true;
2031 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2033 boost::shared_ptr<NoteType> note (*n);
2034 CanvasNoteEvent* cne;
2035 bool select = false;
2037 if (((1 << note->channel()) & channel_mask) != 0) {
2039 if ((note->note() >= low_note && note->note() <= high_note)) {
2042 } else if (note->note() == notenum) {
2048 if ((cne = find_canvas_note (note)) != 0) {
2049 // extend is false because we've taken care of it,
2050 // since it extends by time range, not pitch.
2051 note_selected (cne, add, false);
2055 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2059 _no_sound_notes = false;
2063 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2065 MidiModel::Notes& notes (_model->notes());
2066 _optimization_iterator = _events.begin();
2068 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2070 boost::shared_ptr<NoteType> note (*n);
2071 CanvasNoteEvent* cne;
2073 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2074 if ((cne = find_canvas_note (note)) != 0) {
2075 if (cne->selected()) {
2076 note_deselected (cne);
2078 note_selected (cne, true, false);
2086 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2089 clear_selection_except (ev);
2090 if (!_selection.empty()) {
2091 PublicEditor& editor (trackview.editor());
2092 editor.get_selection().add (this);
2098 if (!ev->selected()) {
2099 add_to_selection (ev);
2103 /* find end of latest note selected, select all between that and the start of "ev" */
2105 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2106 Evoral::MusicalTime latest = 0;
2108 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2109 if ((*i)->note()->end_time() > latest) {
2110 latest = (*i)->note()->end_time();
2112 if ((*i)->note()->time() < earliest) {
2113 earliest = (*i)->note()->time();
2117 if (ev->note()->end_time() > latest) {
2118 latest = ev->note()->end_time();
2121 if (ev->note()->time() < earliest) {
2122 earliest = ev->note()->time();
2125 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2127 /* find notes entirely within OR spanning the earliest..latest range */
2129 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2130 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2131 add_to_selection (*i);
2139 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2141 remove_from_selection (ev);
2145 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2155 // TODO: Make this faster by storing the last updated selection rect, and only
2156 // adjusting things that are in the area that appears/disappeared.
2157 // We probably need a tree to be able to find events in O(log(n)) time.
2159 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2161 /* check if any corner of the note is inside the rect
2164 1) this is computing "touched by", not "contained by" the rect.
2165 2) this does not require that events be sorted in time.
2168 const double ix1 = (*i)->x1();
2169 const double ix2 = (*i)->x2();
2170 const double iy1 = (*i)->y1();
2171 const double iy2 = (*i)->y2();
2173 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2174 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2175 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2176 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2179 if (!(*i)->selected()) {
2180 add_to_selection (*i);
2182 } else if ((*i)->selected() && !extend) {
2183 // Not inside rectangle
2184 remove_from_selection (*i);
2190 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2192 Selection::iterator i = _selection.find (ev);
2194 if (i != _selection.end()) {
2195 _selection.erase (i);
2198 ev->set_selected (false);
2199 ev->hide_velocity ();
2201 if (_selection.empty()) {
2202 PublicEditor& editor (trackview.editor());
2203 editor.get_selection().remove (this);
2208 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2210 bool add_mrv_selection = false;
2212 if (_selection.empty()) {
2213 add_mrv_selection = true;
2216 if (_selection.insert (ev).second) {
2217 ev->set_selected (true);
2218 play_midi_note ((ev)->note());
2221 if (add_mrv_selection) {
2222 PublicEditor& editor (trackview.editor());
2223 editor.get_selection().add (this);
2228 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2230 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2231 PossibleChord to_play;
2232 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2234 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2235 if ((*i)->note()->time() < earliest) {
2236 earliest = (*i)->note()->time();
2240 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2241 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2242 to_play.push_back ((*i)->note());
2244 (*i)->move_event(dx, dy);
2247 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2249 if (to_play.size() > 1) {
2251 PossibleChord shifted;
2253 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2254 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2255 moved_note->set_note (moved_note->note() + cumulative_dy);
2256 shifted.push_back (moved_note);
2259 play_midi_chord (shifted);
2261 } else if (!to_play.empty()) {
2263 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2264 moved_note->set_note (moved_note->note() + cumulative_dy);
2265 play_midi_note (moved_note);
2271 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2273 assert (!_selection.empty());
2275 uint8_t lowest_note_in_selection = 127;
2276 uint8_t highest_note_in_selection = 0;
2277 uint8_t highest_note_difference = 0;
2279 // find highest and lowest notes first
2281 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2282 uint8_t pitch = (*i)->note()->note();
2283 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2284 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2288 cerr << "dnote: " << (int) dnote << endl;
2289 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2290 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2291 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2292 << int(highest_note_in_selection) << endl;
2293 cerr << "selection size: " << _selection.size() << endl;
2294 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2297 // Make sure the note pitch does not exceed the MIDI standard range
2298 if (highest_note_in_selection + dnote > 127) {
2299 highest_note_difference = highest_note_in_selection - 127;
2302 start_note_diff_command (_("move notes"));
2304 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2306 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2307 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2313 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2315 uint8_t original_pitch = (*i)->note()->note();
2316 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2318 // keep notes in standard midi range
2319 clamp_to_0_127(new_pitch);
2321 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2322 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2324 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2329 // care about notes being moved beyond the upper/lower bounds on the canvas
2330 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2331 highest_note_in_selection > midi_stream_view()->highest_note()) {
2332 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2336 /** @param x Pixel relative to the region position.
2337 * @return Snapped frame relative to the region position.
2340 MidiRegionView::snap_pixel_to_frame(double x)
2342 PublicEditor& editor (trackview.editor());
2343 return snap_frame_to_frame (editor.pixel_to_frame (x));
2346 /** @param x Pixel relative to the region position.
2347 * @return Snapped pixel relative to the region position.
2350 MidiRegionView::snap_to_pixel(double x)
2352 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2356 MidiRegionView::get_position_pixels()
2358 framepos_t region_frame = get_position();
2359 return trackview.editor().frame_to_pixel(region_frame);
2363 MidiRegionView::get_end_position_pixels()
2365 framepos_t frame = get_position() + get_duration ();
2366 return trackview.editor().frame_to_pixel(frame);
2370 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2372 /* the time converter will return the frame corresponding to `beats'
2373 relative to the start of the source. The start of the source
2374 is an implied position given by region->position - region->start
2376 const framepos_t source_start = _region->position() - _region->start();
2377 return source_start + _source_relative_time_converter.to (beats);
2381 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2383 /* the `frames' argument needs to be converted into a frame count
2384 relative to the start of the source before being passed in to the
2387 const framepos_t source_start = _region->position() - _region->start();
2388 return _source_relative_time_converter.from (frames - source_start);
2392 MidiRegionView::region_beats_to_region_frames(double beats) const
2394 return _region_relative_time_converter.to(beats);
2398 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2400 return _region_relative_time_converter.from(frames);
2404 MidiRegionView::begin_resizing (bool /*at_front*/)
2406 _resize_data.clear();
2408 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2409 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2411 // only insert CanvasNotes into the map
2413 NoteResizeData *resize_data = new NoteResizeData();
2414 resize_data->canvas_note = note;
2416 // create a new SimpleRect from the note which will be the resize preview
2417 SimpleRect *resize_rect = new SimpleRect(
2418 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2420 // calculate the colors: get the color settings
2421 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2422 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2425 // make the resize preview notes more transparent and bright
2426 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2428 // calculate color based on note velocity
2429 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2430 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2434 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2435 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2437 resize_data->resize_rect = resize_rect;
2438 _resize_data.push_back(resize_data);
2443 /** Update resizing notes while user drags.
2444 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2445 * @param at_front which end of the note (true == note on, false == note off)
2446 * @param delta_x change in mouse position since the start of the drag
2447 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2448 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2449 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2450 * as the \a primary note.
2453 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2455 bool cursor_set = false;
2457 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2458 SimpleRect* resize_rect = (*i)->resize_rect;
2459 CanvasNote* canvas_note = (*i)->canvas_note;
2464 current_x = canvas_note->x1() + delta_x;
2466 current_x = primary->x1() + delta_x;
2470 current_x = canvas_note->x2() + delta_x;
2472 current_x = primary->x2() + delta_x;
2477 resize_rect->property_x1() = snap_to_pixel(current_x);
2478 resize_rect->property_x2() = canvas_note->x2();
2480 resize_rect->property_x2() = snap_to_pixel(current_x);
2481 resize_rect->property_x1() = canvas_note->x1();
2487 beats = snap_pixel_to_frame (current_x);
2488 beats = region_frames_to_region_beats (beats);
2493 if (beats < canvas_note->note()->end_time()) {
2494 len = canvas_note->note()->time() - beats;
2495 len += canvas_note->note()->length();
2500 if (beats >= canvas_note->note()->time()) {
2501 len = beats - canvas_note->note()->time();
2508 snprintf (buf, sizeof (buf), "%.3g beats", len);
2509 show_verbose_cursor (buf, 0, 0);
2518 /** Finish resizing notes when the user releases the mouse button.
2519 * Parameters the same as for \a update_resizing().
2522 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2524 start_note_diff_command (_("resize notes"));
2526 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2527 CanvasNote* canvas_note = (*i)->canvas_note;
2528 SimpleRect* resize_rect = (*i)->resize_rect;
2530 /* Get the new x position for this resize, which is in pixels relative
2531 * to the region position.
2538 current_x = canvas_note->x1() + delta_x;
2540 current_x = primary->x1() + delta_x;
2544 current_x = canvas_note->x2() + delta_x;
2546 current_x = primary->x2() + delta_x;
2550 /* Convert that to a frame within the source */
2551 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2553 /* and then to beats */
2554 current_x = region_frames_to_region_beats (current_x);
2556 if (at_front && current_x < canvas_note->note()->end_time()) {
2557 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2559 double len = canvas_note->note()->time() - current_x;
2560 len += canvas_note->note()->length();
2563 /* XXX convert to beats */
2564 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2569 double len = current_x - canvas_note->note()->time();
2572 /* XXX convert to beats */
2573 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2581 _resize_data.clear();
2586 MidiRegionView::abort_resizing ()
2588 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2589 delete (*i)->resize_rect;
2593 _resize_data.clear ();
2597 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2599 uint8_t new_velocity;
2602 new_velocity = event->note()->velocity() + velocity;
2603 clamp_to_0_127(new_velocity);
2605 new_velocity = velocity;
2608 event->set_selected (event->selected()); // change color
2610 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2614 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2619 new_note = event->note()->note() + note;
2624 clamp_to_0_127 (new_note);
2625 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2629 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2631 bool change_start = false;
2632 bool change_length = false;
2633 Evoral::MusicalTime new_start = 0;
2634 Evoral::MusicalTime new_length = 0;
2636 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2638 front_delta: if positive - move the start of the note later in time (shortening it)
2639 if negative - move the start of the note earlier in time (lengthening it)
2641 end_delta: if positive - move the end of the note later in time (lengthening it)
2642 if negative - move the end of the note earlier in time (shortening it)
2646 if (front_delta < 0) {
2648 if (event->note()->time() < -front_delta) {
2651 new_start = event->note()->time() + front_delta; // moves earlier
2654 /* start moved toward zero, so move the end point out to where it used to be.
2655 Note that front_delta is negative, so this increases the length.
2658 new_length = event->note()->length() - front_delta;
2659 change_start = true;
2660 change_length = true;
2664 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2666 if (new_pos < event->note()->end_time()) {
2667 new_start = event->note()->time() + front_delta;
2668 /* start moved toward the end, so move the end point back to where it used to be */
2669 new_length = event->note()->length() - front_delta;
2670 change_start = true;
2671 change_length = true;
2678 bool can_change = true;
2679 if (end_delta < 0) {
2680 if (event->note()->length() < -end_delta) {
2686 new_length = event->note()->length() + end_delta;
2687 change_length = true;
2692 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2695 if (change_length) {
2696 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2701 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2703 uint8_t new_channel;
2707 if (event->note()->channel() < -chn) {
2710 new_channel = event->note()->channel() + chn;
2713 new_channel = event->note()->channel() + chn;
2716 new_channel = (uint8_t) chn;
2719 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2723 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2725 Evoral::MusicalTime new_time;
2729 if (event->note()->time() < -delta) {
2732 new_time = event->note()->time() + delta;
2735 new_time = event->note()->time() + delta;
2741 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2745 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2747 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2751 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2755 if (_selection.empty()) {
2770 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2771 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2777 start_note_diff_command (_("change velocities"));
2779 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2780 Selection::iterator next = i;
2782 change_note_velocity (*i, delta, true);
2788 if (!_selection.empty()) {
2790 snprintf (buf, sizeof (buf), "Vel %d",
2791 (int) (*_selection.begin())->note()->velocity());
2792 show_verbose_cursor (buf, 10, 10);
2798 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2800 if (_selection.empty()) {
2817 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2819 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2823 if ((int8_t) (*i)->note()->note() + delta > 127) {
2830 start_note_diff_command (_("transpose"));
2832 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2833 Selection::iterator next = i;
2835 change_note_note (*i, delta, true);
2843 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2849 /* grab the current grid distance */
2851 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2853 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2854 cerr << "Grid type not available as beats - TO BE FIXED\n";
2864 start_note_diff_command (_("change note lengths"));
2866 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2867 Selection::iterator next = i;
2870 /* note the negation of the delta for start */
2872 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2881 MidiRegionView::nudge_notes (bool forward)
2883 if (_selection.empty()) {
2887 /* pick a note as the point along the timeline to get the nudge distance.
2888 its not necessarily the earliest note, so we may want to pull the notes out
2889 into a vector and sort before using the first one.
2892 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2894 framepos_t distance;
2896 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2898 /* grid is off - use nudge distance */
2900 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2906 framepos_t next_pos = ref_point;
2909 if (max_framepos - 1 < next_pos) {
2913 if (next_pos == 0) {
2919 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2920 distance = ref_point - next_pos;
2923 if (distance == 0) {
2927 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2933 start_note_diff_command (_("nudge"));
2935 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2936 Selection::iterator next = i;
2938 change_note_time (*i, delta, true);
2946 MidiRegionView::change_channel(uint8_t channel)
2948 start_note_diff_command(_("change channel"));
2949 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2950 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2958 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2960 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2962 _pre_enter_cursor = editor->get_canvas_cursor ();
2964 if (_mouse_state == SelectTouchDragging) {
2965 note_selected (ev, true);
2968 show_verbose_cursor (ev->note ());
2972 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2974 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2976 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2977 (*i)->hide_velocity ();
2980 editor->verbose_cursor()->hide ();
2982 if (_pre_enter_cursor) {
2983 editor->set_canvas_cursor (_pre_enter_cursor);
2984 _pre_enter_cursor = 0;
2989 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2992 /* XXX should get patch name if we can */
2993 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
2994 show_verbose_cursor (s.str(), 10, 20);
2998 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3000 trackview.editor().verbose_cursor()->hide ();
3004 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3006 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3008 if (x_fraction > 0.0 && x_fraction < 0.2) {
3009 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3010 } else if (x_fraction >= 0.8 && x_fraction < 1.0) {
3011 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3013 if (_pre_enter_cursor && can_set_cursor) {
3014 editor->set_canvas_cursor (_pre_enter_cursor);
3020 MidiRegionView::set_frame_color()
3024 TimeAxisViewItem::set_frame_color ();
3031 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3032 } else if (high_enough_for_name) {
3033 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3038 if (!rect_visible) {
3039 f = UINT_RGBA_CHANGE_A (f, 0);
3042 frame->property_fill_color_rgba() = f;
3046 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3050 case FilterChannels:
3051 _force_channel = -1;
3054 _force_channel = mask;
3055 mask = 0xFFFF; // Show all notes as active (below)
3058 // Update notes for selection
3059 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3060 (*i)->on_channel_selection_change(mask);
3063 _last_channel_selection = mask;
3065 _patch_changes.clear ();
3066 display_patch_changes ();
3070 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3072 _model_name = model;
3073 _custom_device_mode = custom_device_mode;
3078 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3080 if (_selection.empty()) {
3084 PublicEditor& editor (trackview.editor());
3088 /* XXX what to do ? */
3092 editor.get_cut_buffer().add (selection_as_cut_buffer());
3100 start_note_diff_command();
3102 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3109 note_diff_remove_note (*i);
3119 MidiRegionView::selection_as_cut_buffer () const
3123 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3124 NoteType* n = (*i)->note().get();
3125 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3128 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3134 /** This method handles undo */
3136 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3142 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3144 trackview.session()->begin_reversible_command (_("paste"));
3146 start_note_diff_command (_("paste"));
3148 Evoral::MusicalTime beat_delta;
3149 Evoral::MusicalTime paste_pos_beats;
3150 Evoral::MusicalTime duration;
3151 Evoral::MusicalTime end_point = 0;
3153 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3154 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3155 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3156 paste_pos_beats = 0;
3158 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",
3159 (*mcb.notes().begin())->time(),
3160 (*mcb.notes().rbegin())->end_time(),
3161 duration, pos, _region->position(),
3162 paste_pos_beats, beat_delta));
3166 for (int n = 0; n < (int) times; ++n) {
3168 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3170 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3171 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3173 /* make all newly added notes selected */
3175 note_diff_add_note (copied_note, true);
3176 end_point = copied_note->end_time();
3179 paste_pos_beats += duration;
3182 /* if we pasted past the current end of the region, extend the region */
3184 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3185 framepos_t region_end = _region->position() + _region->length() - 1;
3187 if (end_frame > region_end) {
3189 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3191 _region->clear_changes ();
3192 _region->set_length (end_frame);
3193 trackview.session()->add_command (new StatefulDiffCommand (_region));
3198 trackview.session()->commit_reversible_command ();
3201 struct EventNoteTimeEarlyFirstComparator {
3202 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3203 return a->note()->time() < b->note()->time();
3208 MidiRegionView::time_sort_events ()
3210 if (!_sort_needed) {
3214 EventNoteTimeEarlyFirstComparator cmp;
3217 _sort_needed = false;
3221 MidiRegionView::goto_next_note (bool add_to_selection)
3223 bool use_next = false;
3225 if (_events.back()->selected()) {
3229 time_sort_events ();
3231 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3232 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3234 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3235 if ((*i)->selected()) {
3238 } else if (use_next) {
3239 if (channel_mask & (1 << (*i)->note()->channel())) {
3240 if (!add_to_selection) {
3243 note_selected (*i, true, false);
3250 /* use the first one */
3252 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3253 unique_select (_events.front());
3258 MidiRegionView::goto_previous_note (bool add_to_selection)
3260 bool use_next = false;
3262 if (_events.front()->selected()) {
3266 time_sort_events ();
3268 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3269 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3271 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3272 if ((*i)->selected()) {
3275 } else if (use_next) {
3276 if (channel_mask & (1 << (*i)->note()->channel())) {
3277 if (!add_to_selection) {
3280 note_selected (*i, true, false);
3287 /* use the last one */
3289 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3290 unique_select (*(_events.rbegin()));
3295 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3297 bool had_selected = false;
3299 time_sort_events ();
3301 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3302 if ((*i)->selected()) {
3303 selected.insert ((*i)->note());
3304 had_selected = true;
3308 if (allow_all_if_none_selected && !had_selected) {
3309 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3310 selected.insert ((*i)->note());
3316 MidiRegionView::update_ghost_note (double x, double y)
3318 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3323 _note_group->w2i (x, y);
3325 PublicEditor& editor = trackview.editor ();
3327 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3328 framecnt_t grid_frames;
3329 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3331 /* use region_frames... because we are converting a delta within the region
3335 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3341 /* note that this sets the time of the ghost note in beats relative to
3342 the start of the source; that is how all note times are stored.
3344 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3345 _ghost_note->note()->set_length (length);
3346 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3347 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3349 /* the ghost note does not appear in ghost regions, so pass false in here */
3350 update_note (_ghost_note, false);
3352 show_verbose_cursor (_ghost_note->note ());
3356 MidiRegionView::create_ghost_note (double x, double y)
3358 remove_ghost_note ();
3360 boost::shared_ptr<NoteType> g (new NoteType);
3361 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3362 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3363 update_ghost_note (x, y);
3364 _ghost_note->show ();
3369 show_verbose_cursor (_ghost_note->note ());
3373 MidiRegionView::snap_changed ()
3379 create_ghost_note (_last_ghost_x, _last_ghost_y);
3383 MidiRegionView::drop_down_keys ()
3385 _mouse_state = None;
3389 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3391 double note = midi_stream_view()->y_to_note(y);
3393 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3395 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3397 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3398 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3399 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3400 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3405 bool add_mrv_selection = false;
3407 if (_selection.empty()) {
3408 add_mrv_selection = true;
3411 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3412 if (_selection.insert (*i).second) {
3413 (*i)->set_selected (true);
3417 if (add_mrv_selection) {
3418 PublicEditor& editor (trackview.editor());
3419 editor.get_selection().add (this);
3424 MidiRegionView::color_handler ()
3426 RegionView::color_handler ();
3428 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3429 (*i)->set_selected ((*i)->selected()); // will change color
3432 /* XXX probably more to do here */
3436 MidiRegionView::enable_display (bool yn)
3438 RegionView::enable_display (yn);
3445 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3447 if (_step_edit_cursor == 0) {
3448 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3450 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3451 _step_edit_cursor->property_y1() = 0;
3452 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3453 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3454 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3457 move_step_edit_cursor (pos);
3458 _step_edit_cursor->show ();
3462 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3464 _step_edit_cursor_position = pos;
3466 if (_step_edit_cursor) {
3467 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3468 _step_edit_cursor->property_x1() = pixel;
3469 set_step_edit_cursor_width (_step_edit_cursor_width);
3474 MidiRegionView::hide_step_edit_cursor ()
3476 if (_step_edit_cursor) {
3477 _step_edit_cursor->hide ();
3482 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3484 _step_edit_cursor_width = beats;
3486 if (_step_edit_cursor) {
3487 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3491 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3492 * @param w Source that the data will end up in.
3495 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3497 if (!_active_notes) {
3498 /* we aren't actively being recorded to */
3502 boost::shared_ptr<MidiSource> src = w.lock ();
3503 if (!src || src != midi_region()->midi_source()) {
3504 /* recorded data was not destined for our source */
3508 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3510 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3512 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3514 framepos_t back = max_framepos;
3516 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3517 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3518 assert (ev.buffer ());
3520 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3521 frames from the start of the source, and so time_beats is in terms of the
3525 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3527 if (ev.type() == MIDI_CMD_NOTE_ON) {
3529 boost::shared_ptr<NoteType> note (
3530 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3533 add_note (note, true);
3535 /* fix up our note range */
3536 if (ev.note() < _current_range_min) {
3537 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3538 } else if (ev.note() > _current_range_max) {
3539 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3542 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3543 resolve_note (ev.note (), time_beats);
3549 midi_stream_view()->check_record_layers (region(), back);
3553 MidiRegionView::trim_front_starting ()
3555 /* Reparent the note group to the region view's parent, so that it doesn't change
3556 when the region view is trimmed.
3558 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3559 _temporary_note_group->move (group->property_x(), group->property_y());
3560 _note_group->reparent (*_temporary_note_group);
3564 MidiRegionView::trim_front_ending ()
3566 _note_group->reparent (*group);
3567 delete _temporary_note_group;
3568 _temporary_note_group = 0;
3570 if (_region->start() < 0) {
3571 /* Trim drag made start time -ve; fix this */
3572 midi_region()->fix_negative_start ();
3577 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3579 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3580 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3584 change_patch_change (pc->patch(), d.patch ());
3589 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3592 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3593 Evoral::midi_note_name (n->note()).c_str(),
3595 (int) n->channel() + 1,
3596 (int) n->velocity());
3598 show_verbose_cursor (buf, 10, 20);
3602 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3606 trackview.editor().get_pointer_position (wx, wy);
3611 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3613 double x1, y1, x2, y2;
3614 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3616 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3617 wy -= (y2 - y1) + 2 * yoffset;
3620 trackview.editor().verbose_cursor()->set (text, wx, wy);
3621 trackview.editor().verbose_cursor()->show ();
3624 /** @param p A session framepos.
3625 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3626 * @return p snapped to the grid subdivision underneath it.
3629 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3631 PublicEditor& editor = trackview.editor ();
3634 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3640 grid_frames = region_beats_to_region_frames (grid_beats);
3642 /* Hack so that we always snap to the note that we are over, instead of snapping
3643 to the next one if we're more than halfway through the one we're over.
3645 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3646 p -= grid_frames / 2;
3649 return snap_frame_to_frame (p);
3652 /** Called when the selection has been cleared in any MidiRegionView.
3653 * @param rv MidiRegionView that the selection was cleared in.
3656 MidiRegionView::selection_cleared (MidiRegionView* rv)
3662 /* Clear our selection in sympathy; but don't signal the fact */
3663 clear_selection (false);