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 (!trackview.editor().internal_editing()) {
334 return scroll (&ev->scroll);
337 return key_press (&ev->key);
339 case GDK_KEY_RELEASE:
340 return key_release (&ev->key);
342 case GDK_BUTTON_PRESS:
343 return button_press (&ev->button);
345 case GDK_2BUTTON_PRESS:
348 case GDK_BUTTON_RELEASE:
349 return button_release (&ev->button);
351 case GDK_ENTER_NOTIFY:
352 return enter_notify (&ev->crossing);
354 case GDK_LEAVE_NOTIFY:
355 return leave_notify (&ev->crossing);
357 case GDK_MOTION_NOTIFY:
358 return motion (&ev->motion);
368 MidiRegionView::remove_ghost_note ()
375 MidiRegionView::enter_notify (GdkEventCrossing* ev)
377 trackview.editor().MouseModeChanged.connect (
378 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
381 if (trackview.editor().current_mouse_mode() == MouseRange && _mouse_state != AddDragging) {
382 create_ghost_note (ev->x, ev->y);
385 if (!trackview.editor().internal_editing()) {
386 Keyboard::magic_widget_drop_focus();
388 Keyboard::magic_widget_grab_focus();
396 MidiRegionView::leave_notify (GdkEventCrossing*)
398 _mouse_mode_connection.disconnect ();
400 trackview.editor().verbose_cursor()->hide ();
401 remove_ghost_note ();
407 MidiRegionView::mouse_mode_changed ()
409 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
410 create_ghost_note (_last_event_x, _last_event_y);
412 remove_ghost_note ();
413 trackview.editor().verbose_cursor()->hide ();
416 if (!trackview.editor().internal_editing()) {
417 Keyboard::magic_widget_drop_focus();
419 Keyboard::magic_widget_grab_focus();
425 MidiRegionView::button_press (GdkEventButton* ev)
427 if (ev->button != 1) {
431 if (_mouse_state != SelectTouchDragging) {
433 _pressed_button = ev->button;
434 _mouse_state = Pressed;
439 _pressed_button = ev->button;
445 MidiRegionView::button_release (GdkEventButton* ev)
447 double event_x, event_y;
449 if (ev->button != 1) {
456 group->w2i(event_x, event_y);
457 group->ungrab(ev->time);
459 PublicEditor& editor = trackview.editor ();
461 switch (_mouse_state) {
462 case Pressed: // Clicked
464 switch (editor.current_mouse_mode()) {
470 if (Keyboard::is_insert_note_event(ev)) {
472 double event_x, event_y;
476 group->w2i(event_x, event_y);
479 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
485 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true, true);
493 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
499 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true, true);
510 case SelectRectDragging:
512 editor.drags()->end_grab ((GdkEvent *) ev);
514 create_ghost_note (ev->x, ev->y);
526 MidiRegionView::motion (GdkEventMotion* ev)
528 PublicEditor& editor = trackview.editor ();
530 if (!_ghost_note && editor.current_mouse_mode() != MouseRange
531 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
532 && _mouse_state != AddDragging) {
534 create_ghost_note (ev->x, ev->y);
535 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
536 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
538 update_ghost_note (ev->x, ev->y);
539 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
541 remove_ghost_note ();
543 editor.verbose_cursor()->hide ();
544 } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
545 update_ghost_note (ev->x, ev->y);
548 /* any motion immediately hides velocity text that may have been visible */
550 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
551 (*i)->hide_velocity ();
554 switch (_mouse_state) {
557 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
558 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
560 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
561 _mouse_state = SelectRectDragging;
564 } else if (editor.internal_editing()) {
566 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
567 _mouse_state = AddDragging;
569 remove_ghost_note ();
571 editor.verbose_cursor()->hide ();
578 case SelectRectDragging:
580 editor.drags()->motion_handler ((GdkEvent *) ev, false);
583 case SelectTouchDragging:
595 MidiRegionView::scroll (GdkEventScroll* ev)
597 if (_selection.empty()) {
601 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
602 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
603 it still works for zoom.
608 trackview.editor().verbose_cursor()->hide ();
610 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
612 if (ev->direction == GDK_SCROLL_UP) {
613 change_velocities (true, fine, false);
614 } else if (ev->direction == GDK_SCROLL_DOWN) {
615 change_velocities (false, fine, false);
621 MidiRegionView::key_press (GdkEventKey* ev)
623 /* since GTK bindings are generally activated on press, and since
624 detectable auto-repeat is the name of the game and only sends
625 repeated presses, carry out key actions at key press, not release.
628 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
630 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
631 _mouse_state = SelectTouchDragging;
634 } else if (ev->keyval == GDK_Escape && unmodified) {
638 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
640 bool start = (ev->keyval == GDK_comma);
641 bool end = (ev->keyval == GDK_period);
642 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
643 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
645 change_note_lengths (fine, shorter, 0.0, start, end);
649 } else if (ev->keyval == GDK_Delete && unmodified) {
654 } else if (ev->keyval == GDK_Tab) {
656 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
657 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
659 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
663 } else if (ev->keyval == GDK_ISO_Left_Tab) {
665 /* Shift-TAB generates ISO Left Tab, for some reason */
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));
676 } else if (ev->keyval == GDK_Up) {
678 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
679 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
681 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
682 change_velocities (true, fine, allow_smush);
684 transpose (true, fine, allow_smush);
688 } else if (ev->keyval == GDK_Down) {
690 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
691 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
693 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
694 change_velocities (false, fine, allow_smush);
696 transpose (false, fine, allow_smush);
700 } else if (ev->keyval == GDK_Left && unmodified) {
705 } else if (ev->keyval == GDK_Right && unmodified) {
710 } else if (ev->keyval == GDK_c && unmodified) {
719 MidiRegionView::key_release (GdkEventKey* ev)
721 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
729 MidiRegionView::channel_edit ()
732 uint8_t current_channel = 0;
734 if (_selection.empty()) {
738 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
740 current_channel = (*i)->note()->channel ();
745 MidiChannelDialog channel_dialog (current_channel);
746 int ret = channel_dialog.run ();
749 case Gtk::RESPONSE_OK:
755 uint8_t new_channel = channel_dialog.active_channel ();
757 start_note_diff_command (_("channel edit"));
759 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
760 Selection::iterator next = i;
762 change_note_channel (*i, new_channel);
770 MidiRegionView::show_list_editor ()
773 _list_editor = new MidiListEditor (trackview.session(), midi_region());
775 _list_editor->present ();
778 /** Add a note to the model, and the view, at a canvas (click) coordinate.
779 * \param t time in frames relative to the position of the region
780 * \param y vertical position in pixels
781 * \param length duration of the note in beats, which will be snapped to the grid
782 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
783 * \param snap_x true to snap x to the grid, otherwise false.
786 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool sh, bool snap_x)
788 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
789 MidiStreamView* const view = mtv->midi_view();
791 double note = view->y_to_note(y);
794 assert(note <= 127.0);
796 // Start of note in frames relative to region start
798 framecnt_t grid_frames;
799 t = snap_frame_to_grid_underneath (t, grid_frames);
803 assert (length != 0);
806 /* shorten the note down, but rather than using 1 frame (which
807 would be the highest resolution, use 1 tick since all
808 musical data is essentially quantized to this unit. it
809 is bigger, but not by enough to make any difference.
811 old single frame code:
813 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
815 length -= 1.0/Timecode::BBT_Time::ticks_per_beat;
818 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
819 region_frames_to_region_beats(t + _region->start()),
821 (uint8_t)note, 0x40));
823 if (_model->contains (new_note)) {
827 view->update_note_range(new_note->note());
829 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
831 _model->apply_command(*trackview.session(), cmd);
833 play_midi_note (new_note);
837 MidiRegionView::clear_events()
842 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
843 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
848 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
853 _patch_changes.clear();
855 _optimization_iterator = _events.end();
859 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
863 content_connection.disconnect ();
864 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
868 if (_enable_display) {
874 MidiRegionView::start_note_diff_command (string name)
876 if (!_note_diff_command) {
877 _note_diff_command = _model->new_note_diff_command (name);
882 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
884 if (_note_diff_command) {
885 _note_diff_command->add (note);
888 _marked_for_selection.insert(note);
891 _marked_for_velocity.insert(note);
896 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
898 if (_note_diff_command && ev->note()) {
899 _note_diff_command->remove(ev->note());
904 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
905 MidiModel::NoteDiffCommand::Property property,
908 if (_note_diff_command) {
909 _note_diff_command->change (ev->note(), property, val);
914 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
915 MidiModel::NoteDiffCommand::Property property,
916 Evoral::MusicalTime val)
918 if (_note_diff_command) {
919 _note_diff_command->change (ev->note(), property, val);
924 MidiRegionView::apply_diff (bool as_subcommand)
928 if (!_note_diff_command) {
932 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
933 // Mark all selected notes for selection when model reloads
934 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
935 _marked_for_selection.insert((*i)->note());
940 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
942 _model->apply_command (*trackview.session(), _note_diff_command);
945 _note_diff_command = 0;
946 midi_view()->midi_track()->playlist_modified();
949 _marked_for_selection.clear();
952 _marked_for_velocity.clear();
956 MidiRegionView::abort_command()
958 delete _note_diff_command;
959 _note_diff_command = 0;
964 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
966 if (_optimization_iterator != _events.end()) {
967 ++_optimization_iterator;
970 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
971 return *_optimization_iterator;
974 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
975 if ((*_optimization_iterator)->note() == note) {
976 return *_optimization_iterator;
984 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
986 MidiModel::Notes notes;
987 _model->get_notes (notes, op, val, chan_mask);
989 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
990 CanvasNoteEvent* cne = find_canvas_note (*n);
998 MidiRegionView::redisplay_model()
1000 // Don't redisplay the model if we're currently recording and displaying that
1001 if (_active_notes) {
1009 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1010 (*i)->invalidate ();
1013 MidiModel::ReadLock lock(_model->read_lock());
1015 MidiModel::Notes& notes (_model->notes());
1016 _optimization_iterator = _events.begin();
1018 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1020 boost::shared_ptr<NoteType> note (*n);
1021 CanvasNoteEvent* cne;
1024 if (note_in_region_range (note, visible)) {
1026 if ((cne = find_canvas_note (note)) != 0) {
1033 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1035 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1047 add_note (note, visible);
1052 if ((cne = find_canvas_note (note)) != 0) {
1060 /* remove note items that are no longer valid */
1062 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1063 if (!(*i)->valid ()) {
1065 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1066 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1068 gr->remove_note (*i);
1073 i = _events.erase (i);
1080 _patch_changes.clear();
1084 display_patch_changes ();
1086 _marked_for_selection.clear ();
1087 _marked_for_velocity.clear ();
1089 /* we may have caused _events to contain things out of order (e.g. if a note
1090 moved earlier or later). we don't generally need them in time order, but
1091 make a note that a sort is required for those cases that require it.
1094 _sort_needed = true;
1098 MidiRegionView::display_patch_changes ()
1100 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1101 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1103 for (uint8_t i = 0; i < 16; ++i) {
1104 if (chn_mask & (1<<i)) {
1105 display_patch_changes_on_channel (i);
1107 /* TODO gray-out patch instad of not displaying it */
1112 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1114 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1116 if ((*i)->channel() != channel) {
1120 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1122 boost::shared_ptr<MIDI::Name::Patch> patch =
1123 MIDI::Name::MidiPatchManager::instance().find_patch(
1124 _model_name, _custom_device_mode, channel, patch_key);
1127 add_canvas_patch_change (*i, patch->name());
1130 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1131 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1132 add_canvas_patch_change (*i, buf);
1138 MidiRegionView::display_sysexes()
1140 bool have_periodic_system_messages = false;
1141 bool display_periodic_messages = true;
1143 if (!Config->get_never_display_periodic_midi()) {
1145 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1146 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1147 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1150 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1151 have_periodic_system_messages = true;
1157 if (have_periodic_system_messages) {
1158 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1160 /* get an approximate value for the number of samples per video frame */
1162 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1164 /* if we are zoomed out beyond than the cutoff (i.e. more
1165 * frames per pixel than frames per 4 video frames), don't
1166 * show periodic sysex messages.
1169 if (zoom > (video_frame*4)) {
1170 display_periodic_messages = false;
1174 display_periodic_messages = false;
1177 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1179 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1180 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1182 Evoral::MusicalTime time = (*i)->time();
1186 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1187 if (!display_periodic_messages) {
1195 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1196 str << int((*i)->buffer()[b]);
1197 if (b != (*i)->size() -1) {
1201 string text = str.str();
1203 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1205 double height = midi_stream_view()->contents_height();
1207 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1208 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1210 // Show unless message is beyond the region bounds
1211 if (time - _region->start() >= _region->length() || time < _region->start()) {
1217 _sys_exes.push_back(sysex);
1221 MidiRegionView::~MidiRegionView ()
1223 in_destructor = true;
1225 trackview.editor().verbose_cursor()->hide ();
1227 note_delete_connection.disconnect ();
1229 delete _list_editor;
1231 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1233 if (_active_notes) {
1241 delete _note_diff_command;
1242 delete _step_edit_cursor;
1243 delete _temporary_note_group;
1247 MidiRegionView::region_resized (const PropertyChange& what_changed)
1249 RegionView::region_resized(what_changed);
1251 if (what_changed.contains (ARDOUR::Properties::position)) {
1252 set_duration(_region->length(), 0);
1253 if (_enable_display) {
1260 MidiRegionView::reset_width_dependent_items (double pixel_width)
1262 RegionView::reset_width_dependent_items(pixel_width);
1263 assert(_pixel_width == pixel_width);
1265 if (_enable_display) {
1269 move_step_edit_cursor (_step_edit_cursor_position);
1270 set_step_edit_cursor_width (_step_edit_cursor_width);
1274 MidiRegionView::set_height (double height)
1276 static const double FUDGE = 2.0;
1277 const double old_height = _height;
1278 RegionView::set_height(height);
1279 _height = height - FUDGE;
1281 apply_note_range(midi_stream_view()->lowest_note(),
1282 midi_stream_view()->highest_note(),
1283 height != old_height + FUDGE);
1286 name_pixbuf->raise_to_top();
1289 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1290 (*x)->set_height (midi_stream_view()->contents_height());
1293 if (_step_edit_cursor) {
1294 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1299 /** Apply the current note range from the stream view
1300 * by repositioning/hiding notes as necessary
1303 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1305 if (!_enable_display) {
1309 if (!force && _current_range_min == min && _current_range_max == max) {
1313 _current_range_min = min;
1314 _current_range_max = max;
1316 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1317 CanvasNoteEvent* event = *i;
1318 boost::shared_ptr<NoteType> note (event->note());
1320 if (note->note() < _current_range_min ||
1321 note->note() > _current_range_max) {
1327 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1329 const double y1 = midi_stream_view()->note_to_y(note->note());
1330 const double y2 = y1 + floor(midi_stream_view()->note_height());
1332 cnote->property_y1() = y1;
1333 cnote->property_y2() = y2;
1335 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1337 const double diamond_size = update_hit (chit);
1339 chit->set_height (diamond_size);
1345 MidiRegionView::add_ghost (TimeAxisView& tv)
1349 double unit_position = _region->position () / samples_per_unit;
1350 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1351 MidiGhostRegion* ghost;
1353 if (mtv && mtv->midi_view()) {
1354 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1355 to allow having midi notes on top of note lines and waveforms.
1357 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1359 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1362 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1363 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1364 ghost->add_note(note);
1368 ghost->set_height ();
1369 ghost->set_duration (_region->length() / samples_per_unit);
1370 ghosts.push_back (ghost);
1372 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1378 /** Begin tracking note state for successive calls to add_event
1381 MidiRegionView::begin_write()
1383 assert(!_active_notes);
1384 _active_notes = new CanvasNote*[128];
1385 for (unsigned i=0; i < 128; ++i) {
1386 _active_notes[i] = 0;
1391 /** Destroy note state for add_event
1394 MidiRegionView::end_write()
1396 delete[] _active_notes;
1398 _marked_for_selection.clear();
1399 _marked_for_velocity.clear();
1403 /** Resolve an active MIDI note (while recording).
1406 MidiRegionView::resolve_note(uint8_t note, double end_time)
1408 if (midi_view()->note_mode() != Sustained) {
1412 if (_active_notes && _active_notes[note]) {
1414 /* XXX is end_time really region-centric? I think so, because
1415 this is a new region that we're recording, so source zero is
1416 the same as region zero
1418 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1420 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1421 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1422 _active_notes[note] = 0;
1427 /** Extend active notes to rightmost edge of region (if length is changed)
1430 MidiRegionView::extend_active_notes()
1432 if (!_active_notes) {
1436 for (unsigned i=0; i < 128; ++i) {
1437 if (_active_notes[i]) {
1438 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1445 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1447 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1451 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1453 if (!route_ui || !route_ui->midi_track()) {
1457 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1463 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1465 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1469 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1471 if (!route_ui || !route_ui->midi_track()) {
1475 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1477 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1486 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1488 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1489 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1491 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1492 (note->note() <= midi_stream_view()->highest_note());
1497 /** Update a canvas note's size from its model note.
1498 * @param ev Canvas note to update.
1499 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1502 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1504 boost::shared_ptr<NoteType> note = ev->note();
1506 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1507 const double y1 = midi_stream_view()->note_to_y(note->note());
1509 ev->property_x1() = x;
1510 ev->property_y1() = y1;
1512 /* trim note display to not overlap the end of its region */
1514 if (note->length() > 0) {
1515 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1516 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1518 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1521 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1523 if (note->length() == 0) {
1524 if (_active_notes) {
1525 assert(note->note() < 128);
1526 // If this note is already active there's a stuck note,
1527 // finish the old note rectangle
1528 if (_active_notes[note->note()]) {
1529 CanvasNote* const old_rect = _active_notes[note->note()];
1530 boost::shared_ptr<NoteType> old_note = old_rect->note();
1531 old_rect->property_x2() = x;
1532 old_rect->property_outline_what() = (guint32) 0xF;
1534 _active_notes[note->note()] = ev;
1536 /* outline all but right edge */
1537 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1539 /* outline all edges */
1540 ev->property_outline_what() = (guint32) 0xF;
1543 if (update_ghost_regions) {
1544 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1545 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1547 gr->update_note (ev);
1554 MidiRegionView::update_hit (CanvasHit* ev)
1556 boost::shared_ptr<NoteType> note = ev->note();
1558 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1559 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1560 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1561 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1565 return diamond_size;
1568 /** Add a MIDI note to the view (with length).
1570 * If in sustained mode, notes with length 0 will be considered active
1571 * notes, and resolve_note should be called when the corresponding note off
1572 * event arrives, to properly display the note.
1575 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1577 CanvasNoteEvent* event = 0;
1579 assert(note->time() >= 0);
1580 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1582 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1584 if (midi_view()->note_mode() == Sustained) {
1586 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1588 update_note (ev_rect);
1592 MidiGhostRegion* gr;
1594 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1595 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1596 gr->add_note(ev_rect);
1600 } else if (midi_view()->note_mode() == Percussive) {
1602 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1604 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1606 update_hit (ev_diamond);
1615 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1616 note_selected(event, true);
1619 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1620 event->show_velocity();
1623 event->on_channel_selection_change(_last_channel_selection);
1624 _events.push_back(event);
1633 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1634 MidiStreamView* const view = mtv->midi_view();
1636 view->update_note_range (note->note());
1640 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1641 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1643 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1645 /* potentially extend region to hold new note */
1647 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1648 framepos_t region_end = _region->last_frame();
1650 if (end_frame > region_end) {
1651 _region->set_length (end_frame - _region->position());
1654 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1655 MidiStreamView* const view = mtv->midi_view();
1657 view->update_note_range(new_note->note());
1659 _marked_for_selection.clear ();
1662 start_note_diff_command (_("step add"));
1663 note_diff_add_note (new_note, true, false);
1666 // last_step_edit_note = new_note;
1670 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1672 change_note_lengths (false, false, beats, false, true);
1676 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1678 assert (patch->time() >= 0);
1680 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (patch->time()));
1682 double const height = midi_stream_view()->contents_height();
1684 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1685 new CanvasPatchChange(*this, *_note_group,
1690 _custom_device_mode,
1694 // Show unless patch change is beyond the region bounds
1695 if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1696 patch_change->hide();
1698 patch_change->show();
1701 _patch_changes.push_back (patch_change);
1705 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1707 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1708 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1712 if (i != _model->patch_changes().end()) {
1713 key.msb = (*i)->bank_msb ();
1714 key.lsb = (*i)->bank_lsb ();
1715 key.program_number = (*i)->program ();
1717 key.msb = key.lsb = key.program_number = 0;
1720 assert (key.is_sane());
1725 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1727 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1729 if (pc.patch()->program() != new_patch.program_number) {
1730 c->change_program (pc.patch (), new_patch.program_number);
1733 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1734 if (pc.patch()->bank() != new_bank) {
1735 c->change_bank (pc.patch (), new_bank);
1738 _model->apply_command (*trackview.session(), c);
1740 _patch_changes.clear ();
1741 display_patch_changes ();
1745 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1747 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1749 if (old_change->time() != new_change.time()) {
1750 c->change_time (old_change, new_change.time());
1753 if (old_change->channel() != new_change.channel()) {
1754 c->change_channel (old_change, new_change.channel());
1757 if (old_change->program() != new_change.program()) {
1758 c->change_program (old_change, new_change.program());
1761 if (old_change->bank() != new_change.bank()) {
1762 c->change_bank (old_change, new_change.bank());
1765 _model->apply_command (*trackview.session(), c);
1767 _patch_changes.clear ();
1768 display_patch_changes ();
1771 /** Add a patch change to the region.
1772 * @param t Time in frames relative to region position
1773 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1774 * MidiTimeAxisView::get_channel_for_add())
1777 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1779 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1781 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1782 c->add (MidiModel::PatchChangePtr (
1783 new Evoral::PatchChange<Evoral::MusicalTime> (
1784 absolute_frames_to_source_beats (_region->position() + t),
1785 mtv->get_channel_for_add(), patch.program(), patch.bank()
1790 _model->apply_command (*trackview.session(), c);
1792 _patch_changes.clear ();
1793 display_patch_changes ();
1797 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1799 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1800 c->change_time (pc.patch (), t);
1801 _model->apply_command (*trackview.session(), c);
1803 _patch_changes.clear ();
1804 display_patch_changes ();
1808 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1810 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1811 c->remove (pc->patch ());
1812 _model->apply_command (*trackview.session(), c);
1814 _patch_changes.clear ();
1815 display_patch_changes ();
1819 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1821 if (patch.patch()->program() < 127) {
1822 MIDI::Name::PatchPrimaryKey key;
1823 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1824 key.program_number++;
1825 change_patch_change (patch, key);
1830 MidiRegionView::next_patch (CanvasPatchChange& patch)
1832 if (patch.patch()->program() > 0) {
1833 MIDI::Name::PatchPrimaryKey key;
1834 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1835 key.program_number--;
1836 change_patch_change (patch, key);
1841 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1843 if (patch.patch()->program() < 127) {
1844 MIDI::Name::PatchPrimaryKey key;
1845 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1848 change_patch_change (patch, key);
1853 change_patch_change (patch, key);
1860 MidiRegionView::next_bank (CanvasPatchChange& patch)
1862 if (patch.patch()->program() > 0) {
1863 MIDI::Name::PatchPrimaryKey key;
1864 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1865 if (key.lsb < 127) {
1867 change_patch_change (patch, key);
1869 if (key.msb < 127) {
1872 change_patch_change (patch, key);
1879 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1881 if (_selection.empty()) {
1885 _selection.erase (cne);
1889 MidiRegionView::delete_selection()
1891 if (_selection.empty()) {
1895 start_note_diff_command (_("delete selection"));
1897 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1898 if ((*i)->selected()) {
1899 _note_diff_command->remove((*i)->note());
1909 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1911 start_note_diff_command (_("delete note"));
1912 _note_diff_command->remove (n);
1915 trackview.editor().verbose_cursor()->hide ();
1919 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1921 bool changed = false;
1923 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1925 Selection::iterator tmp = i;
1928 (*i)->set_selected (false);
1929 (*i)->hide_velocity ();
1930 _selection.erase (i);
1939 /* this does not change the status of this regionview w.r.t the editor
1943 if (changed && signal) {
1944 SelectionCleared (this); /* EMIT SIGNAL */
1949 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1951 clear_selection_except (ev);
1953 /* don't bother with checking to see if we should remove this
1954 regionview from the editor selection, since we're about to add
1955 another note, and thus put/keep this regionview in the editor
1959 if (!ev->selected()) {
1960 add_to_selection (ev);
1965 MidiRegionView::select_all_notes ()
1969 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1970 add_to_selection (*i);
1975 MidiRegionView::select_range (framepos_t start, framepos_t end)
1979 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1980 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
1981 if (t >= start && t <= end) {
1982 add_to_selection (*i);
1988 MidiRegionView::invert_selection ()
1990 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1991 if ((*i)->selected()) {
1992 remove_from_selection(*i);
1994 add_to_selection (*i);
2000 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2002 uint8_t low_note = 127;
2003 uint8_t high_note = 0;
2004 MidiModel::Notes& notes (_model->notes());
2005 _optimization_iterator = _events.begin();
2011 if (extend && _selection.empty()) {
2017 /* scan existing selection to get note range */
2019 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2020 if ((*i)->note()->note() < low_note) {
2021 low_note = (*i)->note()->note();
2023 if ((*i)->note()->note() > high_note) {
2024 high_note = (*i)->note()->note();
2028 low_note = min (low_note, notenum);
2029 high_note = max (high_note, notenum);
2032 _no_sound_notes = true;
2034 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2036 boost::shared_ptr<NoteType> note (*n);
2037 CanvasNoteEvent* cne;
2038 bool select = false;
2040 if (((1 << note->channel()) & channel_mask) != 0) {
2042 if ((note->note() >= low_note && note->note() <= high_note)) {
2045 } else if (note->note() == notenum) {
2051 if ((cne = find_canvas_note (note)) != 0) {
2052 // extend is false because we've taken care of it,
2053 // since it extends by time range, not pitch.
2054 note_selected (cne, add, false);
2058 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2062 _no_sound_notes = false;
2066 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2068 MidiModel::Notes& notes (_model->notes());
2069 _optimization_iterator = _events.begin();
2071 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2073 boost::shared_ptr<NoteType> note (*n);
2074 CanvasNoteEvent* cne;
2076 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2077 if ((cne = find_canvas_note (note)) != 0) {
2078 if (cne->selected()) {
2079 note_deselected (cne);
2081 note_selected (cne, true, false);
2089 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2092 clear_selection_except (ev);
2093 if (!_selection.empty()) {
2094 PublicEditor& editor (trackview.editor());
2095 editor.get_selection().add (this);
2101 if (!ev->selected()) {
2102 add_to_selection (ev);
2106 /* find end of latest note selected, select all between that and the start of "ev" */
2108 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2109 Evoral::MusicalTime latest = 0;
2111 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2112 if ((*i)->note()->end_time() > latest) {
2113 latest = (*i)->note()->end_time();
2115 if ((*i)->note()->time() < earliest) {
2116 earliest = (*i)->note()->time();
2120 if (ev->note()->end_time() > latest) {
2121 latest = ev->note()->end_time();
2124 if (ev->note()->time() < earliest) {
2125 earliest = ev->note()->time();
2128 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2130 /* find notes entirely within OR spanning the earliest..latest range */
2132 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2133 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2134 add_to_selection (*i);
2142 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2144 remove_from_selection (ev);
2148 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2158 // TODO: Make this faster by storing the last updated selection rect, and only
2159 // adjusting things that are in the area that appears/disappeared.
2160 // We probably need a tree to be able to find events in O(log(n)) time.
2162 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2164 /* check if any corner of the note is inside the rect
2167 1) this is computing "touched by", not "contained by" the rect.
2168 2) this does not require that events be sorted in time.
2171 const double ix1 = (*i)->x1();
2172 const double ix2 = (*i)->x2();
2173 const double iy1 = (*i)->y1();
2174 const double iy2 = (*i)->y2();
2176 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2177 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2178 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2179 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2182 if (!(*i)->selected()) {
2183 add_to_selection (*i);
2185 } else if ((*i)->selected() && !extend) {
2186 // Not inside rectangle
2187 remove_from_selection (*i);
2193 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2195 Selection::iterator i = _selection.find (ev);
2197 if (i != _selection.end()) {
2198 _selection.erase (i);
2201 ev->set_selected (false);
2202 ev->hide_velocity ();
2204 if (_selection.empty()) {
2205 PublicEditor& editor (trackview.editor());
2206 editor.get_selection().remove (this);
2211 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2213 bool add_mrv_selection = false;
2215 if (_selection.empty()) {
2216 add_mrv_selection = true;
2219 if (_selection.insert (ev).second) {
2220 ev->set_selected (true);
2221 play_midi_note ((ev)->note());
2224 if (add_mrv_selection) {
2225 PublicEditor& editor (trackview.editor());
2226 editor.get_selection().add (this);
2231 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2233 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2234 PossibleChord to_play;
2235 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2237 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2238 if ((*i)->note()->time() < earliest) {
2239 earliest = (*i)->note()->time();
2243 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2244 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2245 to_play.push_back ((*i)->note());
2247 (*i)->move_event(dx, dy);
2250 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2252 if (to_play.size() > 1) {
2254 PossibleChord shifted;
2256 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2257 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2258 moved_note->set_note (moved_note->note() + cumulative_dy);
2259 shifted.push_back (moved_note);
2262 play_midi_chord (shifted);
2264 } else if (!to_play.empty()) {
2266 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2267 moved_note->set_note (moved_note->note() + cumulative_dy);
2268 play_midi_note (moved_note);
2274 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2276 assert (!_selection.empty());
2278 uint8_t lowest_note_in_selection = 127;
2279 uint8_t highest_note_in_selection = 0;
2280 uint8_t highest_note_difference = 0;
2282 // find highest and lowest notes first
2284 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2285 uint8_t pitch = (*i)->note()->note();
2286 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2287 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2291 cerr << "dnote: " << (int) dnote << endl;
2292 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2293 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2294 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2295 << int(highest_note_in_selection) << endl;
2296 cerr << "selection size: " << _selection.size() << endl;
2297 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2300 // Make sure the note pitch does not exceed the MIDI standard range
2301 if (highest_note_in_selection + dnote > 127) {
2302 highest_note_difference = highest_note_in_selection - 127;
2305 start_note_diff_command (_("move notes"));
2307 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2309 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2315 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2317 uint8_t original_pitch = (*i)->note()->note();
2318 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2320 // keep notes in standard midi range
2321 clamp_to_0_127(new_pitch);
2323 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2324 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2326 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2331 // care about notes being moved beyond the upper/lower bounds on the canvas
2332 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2333 highest_note_in_selection > midi_stream_view()->highest_note()) {
2334 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2338 /** @param x Pixel relative to the region position.
2339 * @return Snapped frame relative to the region position.
2342 MidiRegionView::snap_pixel_to_frame(double x)
2344 PublicEditor& editor (trackview.editor());
2345 return snap_frame_to_frame (editor.pixel_to_frame (x));
2348 /** @param x Pixel relative to the region position.
2349 * @return Snapped pixel relative to the region position.
2352 MidiRegionView::snap_to_pixel(double x)
2354 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2358 MidiRegionView::get_position_pixels()
2360 framepos_t region_frame = get_position();
2361 return trackview.editor().frame_to_pixel(region_frame);
2365 MidiRegionView::get_end_position_pixels()
2367 framepos_t frame = get_position() + get_duration ();
2368 return trackview.editor().frame_to_pixel(frame);
2372 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2374 /* the time converter will return the frame corresponding to `beats'
2375 relative to the start of the source. The start of the source
2376 is an implied position given by region->position - region->start
2378 const framepos_t source_start = _region->position() - _region->start();
2379 return source_start + _source_relative_time_converter.to (beats);
2383 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2385 /* the `frames' argument needs to be converted into a frame count
2386 relative to the start of the source before being passed in to the
2389 const framepos_t source_start = _region->position() - _region->start();
2390 return _source_relative_time_converter.from (frames - source_start);
2394 MidiRegionView::region_beats_to_region_frames(double beats) const
2396 return _region_relative_time_converter.to(beats);
2400 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2402 return _region_relative_time_converter.from(frames);
2406 MidiRegionView::begin_resizing (bool /*at_front*/)
2408 _resize_data.clear();
2410 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2411 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2413 // only insert CanvasNotes into the map
2415 NoteResizeData *resize_data = new NoteResizeData();
2416 resize_data->canvas_note = note;
2418 // create a new SimpleRect from the note which will be the resize preview
2419 SimpleRect *resize_rect = new SimpleRect(
2420 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2422 // calculate the colors: get the color settings
2423 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2424 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2427 // make the resize preview notes more transparent and bright
2428 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2430 // calculate color based on note velocity
2431 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2432 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2436 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2437 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2439 resize_data->resize_rect = resize_rect;
2440 _resize_data.push_back(resize_data);
2445 /** Update resizing notes while user drags.
2446 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2447 * @param at_front which end of the note (true == note on, false == note off)
2448 * @param delta_x change in mouse position since the start of the drag
2449 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2450 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2451 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2452 * as the \a primary note.
2455 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2457 bool cursor_set = false;
2459 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2460 SimpleRect* resize_rect = (*i)->resize_rect;
2461 CanvasNote* canvas_note = (*i)->canvas_note;
2466 current_x = canvas_note->x1() + delta_x;
2468 current_x = primary->x1() + delta_x;
2472 current_x = canvas_note->x2() + delta_x;
2474 current_x = primary->x2() + delta_x;
2479 resize_rect->property_x1() = snap_to_pixel(current_x);
2480 resize_rect->property_x2() = canvas_note->x2();
2482 resize_rect->property_x2() = snap_to_pixel(current_x);
2483 resize_rect->property_x1() = canvas_note->x1();
2489 beats = snap_pixel_to_frame (current_x);
2490 beats = region_frames_to_region_beats (beats);
2495 if (beats < canvas_note->note()->end_time()) {
2496 len = canvas_note->note()->time() - beats;
2497 len += canvas_note->note()->length();
2502 if (beats >= canvas_note->note()->time()) {
2503 len = beats - canvas_note->note()->time();
2510 snprintf (buf, sizeof (buf), "%.3g beats", len);
2511 show_verbose_cursor (buf, 0, 0);
2520 /** Finish resizing notes when the user releases the mouse button.
2521 * Parameters the same as for \a update_resizing().
2524 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2526 start_note_diff_command (_("resize notes"));
2528 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2529 CanvasNote* canvas_note = (*i)->canvas_note;
2530 SimpleRect* resize_rect = (*i)->resize_rect;
2532 /* Get the new x position for this resize, which is in pixels relative
2533 * to the region position.
2540 current_x = canvas_note->x1() + delta_x;
2542 current_x = primary->x1() + delta_x;
2546 current_x = canvas_note->x2() + delta_x;
2548 current_x = primary->x2() + delta_x;
2552 /* Convert that to a frame within the source */
2553 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2555 /* and then to beats */
2556 current_x = region_frames_to_region_beats (current_x);
2558 if (at_front && current_x < canvas_note->note()->end_time()) {
2559 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2561 double len = canvas_note->note()->time() - current_x;
2562 len += canvas_note->note()->length();
2565 /* XXX convert to beats */
2566 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2571 double len = current_x - canvas_note->note()->time();
2574 /* XXX convert to beats */
2575 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2583 _resize_data.clear();
2588 MidiRegionView::abort_resizing ()
2590 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2591 delete (*i)->resize_rect;
2595 _resize_data.clear ();
2599 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2601 uint8_t new_velocity;
2604 new_velocity = event->note()->velocity() + velocity;
2605 clamp_to_0_127(new_velocity);
2607 new_velocity = velocity;
2610 event->set_selected (event->selected()); // change color
2612 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2616 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2621 new_note = event->note()->note() + note;
2626 clamp_to_0_127 (new_note);
2627 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2631 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2633 bool change_start = false;
2634 bool change_length = false;
2635 Evoral::MusicalTime new_start = 0;
2636 Evoral::MusicalTime new_length = 0;
2638 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2640 front_delta: if positive - move the start of the note later in time (shortening it)
2641 if negative - move the start of the note earlier in time (lengthening it)
2643 end_delta: if positive - move the end of the note later in time (lengthening it)
2644 if negative - move the end of the note earlier in time (shortening it)
2648 if (front_delta < 0) {
2650 if (event->note()->time() < -front_delta) {
2653 new_start = event->note()->time() + front_delta; // moves earlier
2656 /* start moved toward zero, so move the end point out to where it used to be.
2657 Note that front_delta is negative, so this increases the length.
2660 new_length = event->note()->length() - front_delta;
2661 change_start = true;
2662 change_length = true;
2666 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2668 if (new_pos < event->note()->end_time()) {
2669 new_start = event->note()->time() + front_delta;
2670 /* start moved toward the end, so move the end point back to where it used to be */
2671 new_length = event->note()->length() - front_delta;
2672 change_start = true;
2673 change_length = true;
2680 bool can_change = true;
2681 if (end_delta < 0) {
2682 if (event->note()->length() < -end_delta) {
2688 new_length = event->note()->length() + end_delta;
2689 change_length = true;
2694 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2697 if (change_length) {
2698 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2703 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2705 uint8_t new_channel;
2709 if (event->note()->channel() < -chn) {
2712 new_channel = event->note()->channel() + chn;
2715 new_channel = event->note()->channel() + chn;
2718 new_channel = (uint8_t) chn;
2721 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2725 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2727 Evoral::MusicalTime new_time;
2731 if (event->note()->time() < -delta) {
2734 new_time = event->note()->time() + delta;
2737 new_time = event->note()->time() + delta;
2743 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2747 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2749 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2753 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2757 if (_selection.empty()) {
2772 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2773 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2779 start_note_diff_command (_("change velocities"));
2781 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2782 Selection::iterator next = i;
2784 change_note_velocity (*i, delta, true);
2790 if (!_selection.empty()) {
2792 snprintf (buf, sizeof (buf), "Vel %d",
2793 (int) (*_selection.begin())->note()->velocity());
2794 show_verbose_cursor (buf, 10, 10);
2800 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2802 if (_selection.empty()) {
2819 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2821 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2825 if ((int8_t) (*i)->note()->note() + delta > 127) {
2832 start_note_diff_command (_("transpose"));
2834 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2835 Selection::iterator next = i;
2837 change_note_note (*i, delta, true);
2845 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2851 /* grab the current grid distance */
2853 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2855 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2856 cerr << "Grid type not available as beats - TO BE FIXED\n";
2866 start_note_diff_command (_("change note lengths"));
2868 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2869 Selection::iterator next = i;
2872 /* note the negation of the delta for start */
2874 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2883 MidiRegionView::nudge_notes (bool forward)
2885 if (_selection.empty()) {
2889 /* pick a note as the point along the timeline to get the nudge distance.
2890 its not necessarily the earliest note, so we may want to pull the notes out
2891 into a vector and sort before using the first one.
2894 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2896 framepos_t distance;
2898 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2900 /* grid is off - use nudge distance */
2902 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2908 framepos_t next_pos = ref_point;
2911 if (max_framepos - 1 < next_pos) {
2915 if (next_pos == 0) {
2921 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2922 distance = ref_point - next_pos;
2925 if (distance == 0) {
2929 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2935 start_note_diff_command (_("nudge"));
2937 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2938 Selection::iterator next = i;
2940 change_note_time (*i, delta, true);
2948 MidiRegionView::change_channel(uint8_t channel)
2950 start_note_diff_command(_("change channel"));
2951 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2952 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2960 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2962 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2964 _pre_enter_cursor = editor->get_canvas_cursor ();
2966 if (_mouse_state == SelectTouchDragging) {
2967 note_selected (ev, true);
2970 show_verbose_cursor (ev->note ());
2974 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2976 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2978 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2979 (*i)->hide_velocity ();
2982 editor->verbose_cursor()->hide ();
2984 if (_pre_enter_cursor) {
2985 editor->set_canvas_cursor (_pre_enter_cursor);
2986 _pre_enter_cursor = 0;
2991 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2994 /* XXX should get patch name if we can */
2995 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
2996 show_verbose_cursor (s.str(), 10, 20);
3000 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3002 trackview.editor().verbose_cursor()->hide ();
3006 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3008 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3010 if (x_fraction > 0.0 && x_fraction < 0.25) {
3011 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3012 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3013 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3015 if (_pre_enter_cursor && can_set_cursor) {
3016 editor->set_canvas_cursor (_pre_enter_cursor);
3022 MidiRegionView::set_frame_color()
3026 TimeAxisViewItem::set_frame_color ();
3033 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3034 } else if (high_enough_for_name) {
3035 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3040 if (!rect_visible) {
3041 f = UINT_RGBA_CHANGE_A (f, 0);
3044 frame->property_fill_color_rgba() = f;
3048 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3052 case FilterChannels:
3053 _force_channel = -1;
3056 _force_channel = mask;
3057 mask = 0xFFFF; // Show all notes as active (below)
3060 // Update notes for selection
3061 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3062 (*i)->on_channel_selection_change(mask);
3065 _last_channel_selection = mask;
3067 _patch_changes.clear ();
3068 display_patch_changes ();
3072 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3074 _model_name = model;
3075 _custom_device_mode = custom_device_mode;
3080 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3082 if (_selection.empty()) {
3086 PublicEditor& editor (trackview.editor());
3090 /* XXX what to do ? */
3094 editor.get_cut_buffer().add (selection_as_cut_buffer());
3102 start_note_diff_command();
3104 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3111 note_diff_remove_note (*i);
3121 MidiRegionView::selection_as_cut_buffer () const
3125 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3126 NoteType* n = (*i)->note().get();
3127 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3130 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3136 /** This method handles undo */
3138 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3144 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3146 trackview.session()->begin_reversible_command (_("paste"));
3148 start_note_diff_command (_("paste"));
3150 Evoral::MusicalTime beat_delta;
3151 Evoral::MusicalTime paste_pos_beats;
3152 Evoral::MusicalTime duration;
3153 Evoral::MusicalTime end_point = 0;
3155 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3156 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3157 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3158 paste_pos_beats = 0;
3160 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",
3161 (*mcb.notes().begin())->time(),
3162 (*mcb.notes().rbegin())->end_time(),
3163 duration, pos, _region->position(),
3164 paste_pos_beats, beat_delta));
3168 for (int n = 0; n < (int) times; ++n) {
3170 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3172 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3173 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3175 /* make all newly added notes selected */
3177 note_diff_add_note (copied_note, true);
3178 end_point = copied_note->end_time();
3181 paste_pos_beats += duration;
3184 /* if we pasted past the current end of the region, extend the region */
3186 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3187 framepos_t region_end = _region->position() + _region->length() - 1;
3189 if (end_frame > region_end) {
3191 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3193 _region->clear_changes ();
3194 _region->set_length (end_frame);
3195 trackview.session()->add_command (new StatefulDiffCommand (_region));
3200 trackview.session()->commit_reversible_command ();
3203 struct EventNoteTimeEarlyFirstComparator {
3204 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3205 return a->note()->time() < b->note()->time();
3210 MidiRegionView::time_sort_events ()
3212 if (!_sort_needed) {
3216 EventNoteTimeEarlyFirstComparator cmp;
3219 _sort_needed = false;
3223 MidiRegionView::goto_next_note (bool add_to_selection)
3225 bool use_next = false;
3227 if (_events.back()->selected()) {
3231 time_sort_events ();
3233 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3234 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3236 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3237 if ((*i)->selected()) {
3240 } else if (use_next) {
3241 if (channel_mask & (1 << (*i)->note()->channel())) {
3242 if (!add_to_selection) {
3245 note_selected (*i, true, false);
3252 /* use the first one */
3254 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3255 unique_select (_events.front());
3260 MidiRegionView::goto_previous_note (bool add_to_selection)
3262 bool use_next = false;
3264 if (_events.front()->selected()) {
3268 time_sort_events ();
3270 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3271 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3273 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3274 if ((*i)->selected()) {
3277 } else if (use_next) {
3278 if (channel_mask & (1 << (*i)->note()->channel())) {
3279 if (!add_to_selection) {
3282 note_selected (*i, true, false);
3289 /* use the last one */
3291 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3292 unique_select (*(_events.rbegin()));
3297 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3299 bool had_selected = false;
3301 time_sort_events ();
3303 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3304 if ((*i)->selected()) {
3305 selected.insert ((*i)->note());
3306 had_selected = true;
3310 if (allow_all_if_none_selected && !had_selected) {
3311 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3312 selected.insert ((*i)->note());
3318 MidiRegionView::update_ghost_note (double x, double y)
3320 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3325 _note_group->w2i (x, y);
3327 PublicEditor& editor = trackview.editor ();
3329 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3330 framecnt_t grid_frames;
3331 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3333 /* use region_frames... because we are converting a delta within the region
3337 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3343 /* note that this sets the time of the ghost note in beats relative to
3344 the start of the source; that is how all note times are stored.
3346 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3347 _ghost_note->note()->set_length (length);
3348 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3349 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3351 /* the ghost note does not appear in ghost regions, so pass false in here */
3352 update_note (_ghost_note, false);
3354 show_verbose_cursor (_ghost_note->note ());
3358 MidiRegionView::create_ghost_note (double x, double y)
3360 remove_ghost_note ();
3362 boost::shared_ptr<NoteType> g (new NoteType);
3363 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3364 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3365 update_ghost_note (x, y);
3366 _ghost_note->show ();
3371 show_verbose_cursor (_ghost_note->note ());
3375 MidiRegionView::snap_changed ()
3381 create_ghost_note (_last_ghost_x, _last_ghost_y);
3385 MidiRegionView::drop_down_keys ()
3387 _mouse_state = None;
3391 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3393 double note = midi_stream_view()->y_to_note(y);
3395 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3397 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3399 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3400 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3401 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3402 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3407 bool add_mrv_selection = false;
3409 if (_selection.empty()) {
3410 add_mrv_selection = true;
3413 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3414 if (_selection.insert (*i).second) {
3415 (*i)->set_selected (true);
3419 if (add_mrv_selection) {
3420 PublicEditor& editor (trackview.editor());
3421 editor.get_selection().add (this);
3426 MidiRegionView::color_handler ()
3428 RegionView::color_handler ();
3430 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3431 (*i)->set_selected ((*i)->selected()); // will change color
3434 /* XXX probably more to do here */
3438 MidiRegionView::enable_display (bool yn)
3440 RegionView::enable_display (yn);
3447 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3449 if (_step_edit_cursor == 0) {
3450 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3452 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3453 _step_edit_cursor->property_y1() = 0;
3454 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3455 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3456 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3459 move_step_edit_cursor (pos);
3460 _step_edit_cursor->show ();
3464 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3466 _step_edit_cursor_position = pos;
3468 if (_step_edit_cursor) {
3469 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3470 _step_edit_cursor->property_x1() = pixel;
3471 set_step_edit_cursor_width (_step_edit_cursor_width);
3476 MidiRegionView::hide_step_edit_cursor ()
3478 if (_step_edit_cursor) {
3479 _step_edit_cursor->hide ();
3484 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3486 _step_edit_cursor_width = beats;
3488 if (_step_edit_cursor) {
3489 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3493 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3494 * @param w Source that the data will end up in.
3497 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3499 if (!_active_notes) {
3500 /* we aren't actively being recorded to */
3504 boost::shared_ptr<MidiSource> src = w.lock ();
3505 if (!src || src != midi_region()->midi_source()) {
3506 /* recorded data was not destined for our source */
3510 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3512 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3514 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3516 framepos_t back = max_framepos;
3518 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3519 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3520 assert (ev.buffer ());
3522 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3523 frames from the start of the source, and so time_beats is in terms of the
3527 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3529 if (ev.type() == MIDI_CMD_NOTE_ON) {
3531 boost::shared_ptr<NoteType> note (
3532 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3535 add_note (note, true);
3537 /* fix up our note range */
3538 if (ev.note() < _current_range_min) {
3539 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3540 } else if (ev.note() > _current_range_max) {
3541 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3544 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3545 resolve_note (ev.note (), time_beats);
3551 midi_stream_view()->check_record_layers (region(), back);
3555 MidiRegionView::trim_front_starting ()
3557 /* Reparent the note group to the region view's parent, so that it doesn't change
3558 when the region view is trimmed.
3560 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3561 _temporary_note_group->move (group->property_x(), group->property_y());
3562 _note_group->reparent (*_temporary_note_group);
3566 MidiRegionView::trim_front_ending ()
3568 _note_group->reparent (*group);
3569 delete _temporary_note_group;
3570 _temporary_note_group = 0;
3572 if (_region->start() < 0) {
3573 /* Trim drag made start time -ve; fix this */
3574 midi_region()->fix_negative_start ();
3579 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3581 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3582 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3586 change_patch_change (pc->patch(), d.patch ());
3591 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3594 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3595 Evoral::midi_note_name (n->note()).c_str(),
3597 (int) n->channel() + 1,
3598 (int) n->velocity());
3600 show_verbose_cursor (buf, 10, 20);
3604 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3608 trackview.editor().get_pointer_position (wx, wy);
3613 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3615 double x1, y1, x2, y2;
3616 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3618 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3619 wy -= (y2 - y1) + 2 * yoffset;
3622 trackview.editor().verbose_cursor()->set (text, wx, wy);
3623 trackview.editor().verbose_cursor()->show ();
3626 /** @param p A session framepos.
3627 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3628 * @return p snapped to the grid subdivision underneath it.
3631 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3633 PublicEditor& editor = trackview.editor ();
3636 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3642 grid_frames = region_beats_to_region_frames (grid_beats);
3644 /* Hack so that we always snap to the note that we are over, instead of snapping
3645 to the next one if we're more than halfway through the one we're over.
3647 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3648 p -= grid_frames / 2;
3651 return snap_frame_to_frame (p);
3654 /** Called when the selection has been cleared in any MidiRegionView.
3655 * @param rv MidiRegionView that the selection was cleared in.
3658 MidiRegionView::selection_cleared (MidiRegionView* rv)
3664 /* Clear our selection in sympathy; but don't signal the fact */
3665 clear_selection (false);