2 Copyright (C) 2001-2007 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/Control.hpp"
45 #include "evoral/midi_util.h"
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas_patch_change.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
57 #include "midi_cut_buffer.h"
58 #include "midi_list_editor.h"
59 #include "midi_region_view.h"
60 #include "midi_streamview.h"
61 #include "midi_time_axis.h"
62 #include "midi_util.h"
63 #include "note_player.h"
64 #include "public_editor.h"
65 #include "rgb_macros.h"
66 #include "selection.h"
67 #include "simpleline.h"
68 #include "streamview.h"
70 #include "mouse_cursors.h"
71 #include "patch_change_dialog.h"
75 using namespace ARDOUR;
77 using namespace Editing;
78 using namespace ArdourCanvas;
79 using Gtkmm2ext::Keyboard;
81 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
82 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
83 : RegionView (parent, tv, r, spu, basic_color)
85 , _last_channel_selection(0xFFFF)
86 , _current_range_min(0)
87 , _current_range_max(0)
88 , _model_name(string())
89 , _custom_device_mode(string())
91 , _note_group(new ArdourCanvas::Group(*group))
92 , _note_diff_command (0)
95 , _step_edit_cursor (0)
96 , _step_edit_cursor_width (1.0)
97 , _step_edit_cursor_position (0.0)
98 , _channel_selection_scoped_note (0)
99 , _temporary_note_group (0)
102 , _sort_needed (true)
103 , _optimization_iterator (_events.end())
105 , no_sound_notes (false)
108 , pre_enter_cursor (0)
110 _note_group->raise_to_top();
111 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
113 connect_to_diskstream ();
116 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
117 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
118 TimeAxisViewItem::Visibility visibility)
119 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
121 , _last_channel_selection(0xFFFF)
122 , _model_name(string())
123 , _custom_device_mode(string())
125 , _note_group(new ArdourCanvas::Group(*parent))
126 , _note_diff_command (0)
129 , _step_edit_cursor (0)
130 , _step_edit_cursor_width (1.0)
131 , _step_edit_cursor_position (0.0)
132 , _channel_selection_scoped_note (0)
133 , _temporary_note_group (0)
136 , _sort_needed (true)
137 , _optimization_iterator (_events.end())
139 , no_sound_notes (false)
143 _note_group->raise_to_top();
144 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
146 connect_to_diskstream ();
149 MidiRegionView::MidiRegionView (const MidiRegionView& other)
150 : sigc::trackable(other)
153 , _last_channel_selection(0xFFFF)
154 , _model_name(string())
155 , _custom_device_mode(string())
157 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
158 , _note_diff_command (0)
161 , _step_edit_cursor (0)
162 , _step_edit_cursor_width (1.0)
163 , _step_edit_cursor_position (0.0)
164 , _channel_selection_scoped_note (0)
165 , _temporary_note_group (0)
168 , _sort_needed (true)
169 , _optimization_iterator (_events.end())
171 , no_sound_notes (false)
178 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
179 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
184 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
185 : RegionView (other, boost::shared_ptr<Region> (region))
187 , _last_channel_selection(0xFFFF)
188 , _model_name(string())
189 , _custom_device_mode(string())
191 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
192 , _note_diff_command (0)
195 , _step_edit_cursor (0)
196 , _step_edit_cursor_width (1.0)
197 , _step_edit_cursor_position (0.0)
198 , _channel_selection_scoped_note (0)
199 , _temporary_note_group (0)
202 , _sort_needed (true)
203 , _optimization_iterator (_events.end())
205 , no_sound_notes (false)
212 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
213 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
219 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
221 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
223 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
224 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
228 midi_region()->midi_source(0)->load_model();
231 _model = midi_region()->midi_source(0)->model();
232 _enable_display = false;
234 RegionView::init (basic_color, false);
236 compute_colors (basic_color);
238 set_height (trackview.current_height());
241 region_sync_changed ();
242 region_resized (ARDOUR::bounds_change);
245 reset_width_dependent_items (_pixel_width);
249 _enable_display = true;
252 display_model (_model);
256 group->raise_to_top();
257 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
259 midi_view()->signal_channel_mode_changed().connect(
260 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
262 midi_view()->signal_midi_patch_settings_changed().connect(
263 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
265 trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
267 connect_to_diskstream ();
271 MidiRegionView::connect_to_diskstream ()
273 midi_view()->midi_track()->DataRecorded.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::data_recorded, this, _1, _2), gui_context ());
277 MidiRegionView::canvas_event(GdkEvent* ev)
280 case GDK_ENTER_NOTIFY:
281 case GDK_LEAVE_NOTIFY:
282 _last_event_x = ev->crossing.x;
283 _last_event_y = ev->crossing.y;
285 case GDK_MOTION_NOTIFY:
286 _last_event_x = ev->motion.x;
287 _last_event_y = ev->motion.y;
293 if (!trackview.editor().internal_editing()) {
299 return scroll (&ev->scroll);
302 return key_press (&ev->key);
304 case GDK_KEY_RELEASE:
305 return key_release (&ev->key);
307 case GDK_BUTTON_PRESS:
308 return button_press (&ev->button);
310 case GDK_2BUTTON_PRESS:
313 case GDK_BUTTON_RELEASE:
314 return button_release (&ev->button);
316 case GDK_ENTER_NOTIFY:
317 return enter_notify (&ev->crossing);
319 case GDK_LEAVE_NOTIFY:
320 return leave_notify (&ev->crossing);
322 case GDK_MOTION_NOTIFY:
323 return motion (&ev->motion);
333 MidiRegionView::remove_ghost_note ()
340 MidiRegionView::enter_notify (GdkEventCrossing* ev)
342 trackview.editor().MouseModeChanged.connect (
343 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
346 Keyboard::magic_widget_grab_focus();
349 if (trackview.editor().current_mouse_mode() == MouseRange) {
350 create_ghost_note (ev->x, ev->y);
357 MidiRegionView::leave_notify (GdkEventCrossing*)
359 _mouse_mode_connection.disconnect ();
361 trackview.editor().hide_verbose_canvas_cursor ();
362 remove_ghost_note ();
367 MidiRegionView::mouse_mode_changed ()
369 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
370 create_ghost_note (_last_event_x, _last_event_y);
372 remove_ghost_note ();
373 trackview.editor().hide_verbose_canvas_cursor ();
378 MidiRegionView::button_press (GdkEventButton* ev)
380 if (ev->button != 1) {
387 group->w2i (_last_x, _last_y);
389 if (_mouse_state != SelectTouchDragging) {
391 _pressed_button = ev->button;
392 _mouse_state = Pressed;
397 _pressed_button = ev->button;
403 MidiRegionView::button_release (GdkEventButton* ev)
405 double event_x, event_y;
406 framepos_t event_frame = 0;
408 if (ev->button != 1) {
415 group->w2i(event_x, event_y);
416 group->ungrab(ev->time);
418 event_frame = trackview.editor().pixel_to_frame(event_x);
420 switch (_mouse_state) {
421 case Pressed: // Clicked
423 switch (trackview.editor().current_mouse_mode()) {
429 if (Keyboard::is_insert_note_event(ev)){
431 double event_x, event_y;
435 group->w2i(event_x, event_y);
438 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
444 create_note_at (event_x, event_y, beats, true);
452 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
458 create_note_at (event_x, event_y, beats, true);
469 case SelectRectDragging: // Select drag done
476 case AddDragging: // Add drag done
480 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange){
482 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
484 const double x = _drag_rect->property_x1();
485 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
487 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), true);
494 create_ghost_note (ev->x, ev->y);
504 MidiRegionView::motion (GdkEventMotion* ev)
506 double event_x, event_y;
507 framepos_t event_frame = 0;
511 group->w2i(event_x, event_y);
513 // convert event_x to global frame
514 event_frame = snap_pixel_to_frame (event_x);
516 if (!_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
517 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
518 && _mouse_state != AddDragging){
520 create_ghost_note (ev->x, ev->y);
522 else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange
523 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())){
525 update_ghost_note (ev->x, ev->y);
527 else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange){
532 trackview.editor().hide_verbose_canvas_cursor ();
534 else if (_ghost_note && trackview.editor().current_mouse_mode() == MouseRange) {
535 update_ghost_note (ev->x, ev->y);
538 /* any motion immediately hides velocity text that may have been visible */
540 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
541 (*i)->hide_velocity ();
544 switch (_mouse_state) {
545 case Pressed: // Maybe start a drag, if we've moved a bit
547 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
548 /* no appreciable movement since the button was pressed */
553 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject
554 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
556 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
557 Gdk::Cursor(Gdk::FLEUR), ev->time);
561 _drag_start_x = event_x;
562 _drag_start_y = event_y;
564 _drag_rect = new ArdourCanvas::SimpleRect(*group);
565 _drag_rect->property_x1() = event_x;
566 _drag_rect->property_y1() = event_y;
567 _drag_rect->property_x2() = event_x;
568 _drag_rect->property_y2() = event_y;
569 _drag_rect->property_outline_what() = 0xFF;
570 _drag_rect->property_outline_color_rgba()
571 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
572 _drag_rect->property_fill_color_rgba()
573 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
575 _mouse_state = SelectRectDragging;
578 // Add note drag start
579 } else if (trackview.editor().internal_editing()) {
584 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
585 Gdk::Cursor(Gdk::FLEUR), ev->time);
589 _drag_start_x = event_x;
590 _drag_start_y = event_y;
592 _drag_rect = new ArdourCanvas::SimpleRect(*group);
593 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
595 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
596 midi_stream_view()->y_to_note(event_y));
597 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
598 _drag_rect->property_y2() = _drag_rect->property_y1()
599 + floor(midi_stream_view()->note_height());
600 _drag_rect->property_outline_what() = 0xFF;
601 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
602 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
604 _mouse_state = AddDragging;
611 trackview.editor().hide_verbose_canvas_cursor ();
619 case SelectRectDragging: // Select drag motion
620 case AddDragging: // Add note drag motion
625 GdkModifierType state;
626 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
631 if (_mouse_state == AddDragging){
632 event_x = trackview.editor().frame_to_pixel(event_frame);
637 if (event_x > _drag_start_x){
638 _drag_rect->property_x2() = event_x;
641 _drag_rect->property_x1() = event_x;
645 if (_drag_rect && _mouse_state == SelectRectDragging) {
647 if (event_y > _drag_start_y){
648 _drag_rect->property_y2() = event_y;
651 _drag_rect->property_y1() = event_y;
654 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
660 case SelectTouchDragging:
672 MidiRegionView::scroll (GdkEventScroll* ev)
674 if (_selection.empty()) {
678 trackview.editor().hide_verbose_canvas_cursor ();
680 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
682 if (ev->direction == GDK_SCROLL_UP) {
683 change_velocities (true, fine, false);
684 } else if (ev->direction == GDK_SCROLL_DOWN) {
685 change_velocities (false, fine, false);
691 MidiRegionView::key_press (GdkEventKey* ev)
693 /* since GTK bindings are generally activated on press, and since
694 detectable auto-repeat is the name of the game and only sends
695 repeated presses, carry out key actions at key press, not release.
698 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
699 _mouse_state = SelectTouchDragging;
702 } else if (ev->keyval == GDK_Escape) {
706 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
708 bool start = (ev->keyval == GDK_comma);
709 bool end = (ev->keyval == GDK_period);
710 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
711 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
713 change_note_lengths (fine, shorter, 0.0, start, end);
717 } else if (ev->keyval == GDK_Delete) {
722 } else if (ev->keyval == GDK_Tab) {
724 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
725 goto_previous_note ();
731 } else if (ev->keyval == GDK_Up) {
733 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
734 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
736 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
737 change_velocities (true, fine, allow_smush);
739 transpose (true, fine, allow_smush);
743 } else if (ev->keyval == GDK_Down) {
745 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
746 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
748 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
749 change_velocities (false, fine, allow_smush);
751 transpose (false, fine, allow_smush);
755 } else if (ev->keyval == GDK_Left) {
760 } else if (ev->keyval == GDK_Right) {
765 } else if (ev->keyval == GDK_Control_L) {
774 MidiRegionView::key_release (GdkEventKey* ev)
776 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
784 MidiRegionView::show_list_editor ()
787 _list_editor = new MidiListEditor (trackview.session(), midi_region());
789 _list_editor->present ();
792 /** Add a note to the model, and the view, at a canvas (click) coordinate.
793 * \param x horizontal position in pixels
794 * \param y vertical position in pixels
795 * \param length duration of the note in beats, which will be snapped to the grid
796 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
799 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
801 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
802 MidiStreamView* const view = mtv->midi_view();
804 double note = view->y_to_note(y);
807 assert(note <= 127.0);
809 // Start of note in frames relative to region start
810 framepos_t const start_frames = snap_pixel_to_frame (x);
811 assert(start_frames >= 0);
814 length = frames_to_beats(
815 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
817 assert (length != 0);
820 length = frames_to_beats (beats_to_frames (length) - 1);
823 const boost::shared_ptr<NoteType> new_note (new NoteType (get_channel_for_add (),
824 frames_to_beats(start_frames + _region->start()), length,
825 (uint8_t)note, 0x40));
827 if (_model->contains (new_note)) {
831 view->update_note_range(new_note->note());
833 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
835 _model->apply_command(*trackview.session(), cmd);
837 play_midi_note (new_note);
841 MidiRegionView::clear_events()
846 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
847 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
852 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
857 _patch_changes.clear();
859 _optimization_iterator = _events.end();
863 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
867 content_connection.disconnect ();
868 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
872 if (_enable_display) {
878 MidiRegionView::start_note_diff_command (string name)
880 if (!_note_diff_command) {
881 _note_diff_command = _model->new_note_diff_command (name);
886 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
888 if (_note_diff_command) {
889 _note_diff_command->add (note);
892 _marked_for_selection.insert(note);
895 _marked_for_velocity.insert(note);
900 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
902 if (_note_diff_command && ev->note()) {
903 _note_diff_command->remove(ev->note());
908 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
909 MidiModel::NoteDiffCommand::Property property,
912 if (_note_diff_command) {
913 _note_diff_command->change (ev->note(), property, val);
918 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
919 MidiModel::NoteDiffCommand::Property property,
920 Evoral::MusicalTime val)
922 if (_note_diff_command) {
923 _note_diff_command->change (ev->note(), property, val);
928 MidiRegionView::apply_diff (bool as_subcommand)
932 if (!_note_diff_command) {
936 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
937 // Mark all selected notes for selection when model reloads
938 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
939 _marked_for_selection.insert((*i)->note());
944 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
946 _model->apply_command (*trackview.session(), _note_diff_command);
949 _note_diff_command = 0;
950 midi_view()->midi_track()->playlist_modified();
953 _marked_for_selection.clear();
956 _marked_for_velocity.clear();
960 MidiRegionView::abort_command()
962 delete _note_diff_command;
963 _note_diff_command = 0;
968 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
970 if (_optimization_iterator != _events.end()) {
971 ++_optimization_iterator;
974 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
975 return *_optimization_iterator;
978 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
979 if ((*_optimization_iterator)->note() == note) {
980 return *_optimization_iterator;
988 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
990 MidiModel::Notes notes;
991 _model->get_notes (notes, op, val, chan_mask);
993 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
994 CanvasNoteEvent* cne = find_canvas_note (*n);
1002 MidiRegionView::redisplay_model()
1004 // Don't redisplay the model if we're currently recording and displaying that
1005 if (_active_notes) {
1013 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1014 (*i)->invalidate ();
1017 MidiModel::ReadLock lock(_model->read_lock());
1019 MidiModel::Notes& notes (_model->notes());
1020 _optimization_iterator = _events.begin();
1022 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1024 boost::shared_ptr<NoteType> note (*n);
1025 CanvasNoteEvent* cne;
1028 if (note_in_region_range (note, visible)) {
1030 if ((cne = find_canvas_note (note)) != 0) {
1037 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1039 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1051 add_note (note, visible);
1056 if ((cne = find_canvas_note (note)) != 0) {
1064 /* remove note items that are no longer valid */
1066 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1067 if (!(*i)->valid ()) {
1069 i = _events.erase (i);
1075 _patch_changes.clear();
1079 display_patch_changes ();
1081 _marked_for_selection.clear ();
1082 _marked_for_velocity.clear ();
1084 /* we may have caused _events to contain things out of order (e.g. if a note
1085 moved earlier or later). we don't generally need them in time order, but
1086 make a note that a sort is required for those cases that require it.
1089 _sort_needed = true;
1093 MidiRegionView::display_patch_changes ()
1095 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1096 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1098 for (uint8_t i = 0; i < 16; ++i) {
1099 if (chn_mask & (1<<i)) {
1100 display_patch_changes_on_channel (i);
1106 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1108 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1110 if ((*i)->channel() != channel) {
1114 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1116 boost::shared_ptr<MIDI::Name::Patch> patch =
1117 MIDI::Name::MidiPatchManager::instance().find_patch(
1118 _model_name, _custom_device_mode, channel, patch_key);
1121 add_canvas_patch_change (*i, patch->name());
1124 /* program and bank numbers are zero-based: convert to one-based */
1125 snprintf (buf, 16, "%d\n%d", (*i)->program() + 1, (*i)->bank() + 1);
1126 add_canvas_patch_change (*i, buf);
1132 MidiRegionView::display_sysexes()
1134 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1135 Evoral::MusicalTime time = (*i)->time();
1140 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1141 str << int((*i)->buffer()[b]);
1142 if (b != (*i)->size() -1) {
1146 string text = str.str();
1148 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1150 double height = midi_stream_view()->contents_height();
1152 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1153 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1155 // Show unless patch change is beyond the region bounds
1156 if (time - _region->start() >= _region->length() || time < _region->start()) {
1162 _sys_exes.push_back(sysex);
1167 MidiRegionView::~MidiRegionView ()
1169 in_destructor = true;
1171 trackview.editor().hide_verbose_canvas_cursor ();
1173 note_delete_connection.disconnect ();
1175 delete _list_editor;
1177 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1179 if (_active_notes) {
1187 delete _note_diff_command;
1188 delete _step_edit_cursor;
1189 delete _temporary_note_group;
1193 MidiRegionView::region_resized (const PropertyChange& what_changed)
1195 RegionView::region_resized(what_changed);
1197 if (what_changed.contains (ARDOUR::Properties::position)) {
1198 set_duration(_region->length(), 0);
1199 if (_enable_display) {
1206 MidiRegionView::reset_width_dependent_items (double pixel_width)
1208 RegionView::reset_width_dependent_items(pixel_width);
1209 assert(_pixel_width == pixel_width);
1211 if (_enable_display) {
1215 move_step_edit_cursor (_step_edit_cursor_position);
1216 set_step_edit_cursor_width (_step_edit_cursor_width);
1220 MidiRegionView::set_height (double height)
1222 static const double FUDGE = 2.0;
1223 const double old_height = _height;
1224 RegionView::set_height(height);
1225 _height = height - FUDGE;
1227 apply_note_range(midi_stream_view()->lowest_note(),
1228 midi_stream_view()->highest_note(),
1229 height != old_height + FUDGE);
1232 name_pixbuf->raise_to_top();
1235 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1236 (*x)->set_height (midi_stream_view()->contents_height());
1239 if (_step_edit_cursor) {
1240 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1245 /** Apply the current note range from the stream view
1246 * by repositioning/hiding notes as necessary
1249 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1251 if (!_enable_display) {
1255 if (!force && _current_range_min == min && _current_range_max == max) {
1259 _current_range_min = min;
1260 _current_range_max = max;
1262 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1263 CanvasNoteEvent* event = *i;
1264 boost::shared_ptr<NoteType> note (event->note());
1266 if (note->note() < _current_range_min ||
1267 note->note() > _current_range_max) {
1273 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1275 const double y1 = midi_stream_view()->note_to_y(note->note());
1276 const double y2 = y1 + floor(midi_stream_view()->note_height());
1278 cnote->property_y1() = y1;
1279 cnote->property_y2() = y2;
1281 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1283 const double diamond_size = update_hit (chit);
1285 chit->set_height (diamond_size);
1291 MidiRegionView::add_ghost (TimeAxisView& tv)
1295 double unit_position = _region->position () / samples_per_unit;
1296 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1297 MidiGhostRegion* ghost;
1299 if (mtv && mtv->midi_view()) {
1300 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1301 to allow having midi notes on top of note lines and waveforms.
1303 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1305 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1308 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1309 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1310 ghost->add_note(note);
1314 ghost->set_height ();
1315 ghost->set_duration (_region->length() / samples_per_unit);
1316 ghosts.push_back (ghost);
1318 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1324 /** Begin tracking note state for successive calls to add_event
1327 MidiRegionView::begin_write()
1329 assert(!_active_notes);
1330 _active_notes = new CanvasNote*[128];
1331 for (unsigned i=0; i < 128; ++i) {
1332 _active_notes[i] = 0;
1337 /** Destroy note state for add_event
1340 MidiRegionView::end_write()
1342 delete[] _active_notes;
1344 _marked_for_selection.clear();
1345 _marked_for_velocity.clear();
1349 /** Resolve an active MIDI note (while recording).
1352 MidiRegionView::resolve_note(uint8_t note, double end_time)
1354 if (midi_view()->note_mode() != Sustained) {
1358 if (_active_notes && _active_notes[note]) {
1360 const framepos_t end_time_frames = beats_to_frames(end_time);
1362 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1363 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1364 _active_notes[note] = 0;
1369 /** Extend active notes to rightmost edge of region (if length is changed)
1372 MidiRegionView::extend_active_notes()
1374 if (!_active_notes) {
1378 for (unsigned i=0; i < 128; ++i) {
1379 if (_active_notes[i]) {
1380 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1387 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1389 if (no_sound_notes || !trackview.editor().sound_notes()) {
1393 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1395 if (!route_ui || !route_ui->midi_track()) {
1399 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1405 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1407 if (no_sound_notes || !trackview.editor().sound_notes()) {
1411 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1413 if (!route_ui || !route_ui->midi_track()) {
1417 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1419 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1428 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1430 const framepos_t note_start_frames = beats_to_frames(note->time());
1432 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1433 (note_start_frames < _region->start());
1435 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1436 (note->note() <= midi_stream_view()->highest_note());
1441 /** Update a canvas note's size from its model note.
1442 * @param ev Canvas note to update.
1443 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1446 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1448 boost::shared_ptr<NoteType> note = ev->note();
1450 const framepos_t note_start_frames = beats_to_frames(note->time());
1452 /* trim note display to not overlap the end of its region */
1453 const framepos_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length());
1455 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1456 const double y1 = midi_stream_view()->note_to_y(note->note());
1457 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1459 ev->property_x1() = x;
1460 ev->property_y1() = y1;
1462 if (note->length() > 0) {
1463 ev->property_x2() = note_endpixel;
1465 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1468 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1470 if (note->length() == 0) {
1471 if (_active_notes) {
1472 assert(note->note() < 128);
1473 // If this note is already active there's a stuck note,
1474 // finish the old note rectangle
1475 if (_active_notes[note->note()]) {
1476 CanvasNote* const old_rect = _active_notes[note->note()];
1477 boost::shared_ptr<NoteType> old_note = old_rect->note();
1478 old_rect->property_x2() = x;
1479 old_rect->property_outline_what() = (guint32) 0xF;
1481 _active_notes[note->note()] = ev;
1483 /* outline all but right edge */
1484 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1486 /* outline all edges */
1487 ev->property_outline_what() = (guint32) 0xF;
1490 if (update_ghost_regions) {
1491 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1492 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1494 gr->update_note (ev);
1501 MidiRegionView::update_hit (CanvasHit* ev)
1503 boost::shared_ptr<NoteType> note = ev->note();
1505 const framepos_t note_start_frames = beats_to_frames(note->time());
1506 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1507 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1508 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1512 return diamond_size;
1515 /** Add a MIDI note to the view (with length).
1517 * If in sustained mode, notes with length 0 will be considered active
1518 * notes, and resolve_note should be called when the corresponding note off
1519 * event arrives, to properly display the note.
1522 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1524 CanvasNoteEvent* event = 0;
1526 assert(note->time() >= 0);
1527 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1529 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1531 if (midi_view()->note_mode() == Sustained) {
1533 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1535 update_note (ev_rect);
1539 MidiGhostRegion* gr;
1541 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1542 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1543 gr->add_note(ev_rect);
1547 } else if (midi_view()->note_mode() == Percussive) {
1549 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1551 CanvasHit* ev_diamond = new CanvasHit(*this, *_note_group, diamond_size, note);
1553 update_hit (ev_diamond);
1562 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1563 note_selected(event, true);
1566 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1567 event->show_velocity();
1570 event->on_channel_selection_change(_last_channel_selection);
1571 _events.push_back(event);
1580 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1581 MidiStreamView* const view = mtv->midi_view();
1583 view->update_note_range(note->note());
1587 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1588 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1590 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1592 /* potentially extend region to hold new note */
1594 framepos_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1595 framepos_t region_end = _region->position() + _region->length() - 1;
1597 if (end_frame > region_end) {
1598 _region->set_length (end_frame - _region->position(), this);
1601 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1602 MidiStreamView* const view = mtv->midi_view();
1604 view->update_note_range(new_note->note());
1606 _marked_for_selection.clear ();
1609 start_note_diff_command (_("step add"));
1610 note_diff_add_note (new_note, true, false);
1613 // last_step_edit_note = new_note;
1617 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1619 change_note_lengths (false, false, beats, false, true);
1623 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1625 assert (patch->time() >= 0);
1627 const double x = trackview.editor().frame_to_pixel (beats_to_frames (patch->time()));
1629 double const height = midi_stream_view()->contents_height();
1631 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1632 new CanvasPatchChange(*this, *_note_group,
1637 _custom_device_mode,
1641 // Show unless patch change is beyond the region bounds
1642 if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1643 patch_change->hide();
1645 patch_change->show();
1648 _patch_changes.push_back (patch_change);
1652 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1654 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1655 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1659 if (i != _model->patch_changes().end()) {
1660 key.msb = (*i)->bank_msb ();
1661 key.lsb = (*i)->bank_lsb ();
1662 key.program_number = (*i)->program ();
1664 key.msb = key.lsb = key.program_number = 0;
1667 assert (key.is_sane());
1672 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1674 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1676 if (pc.patch()->program() != new_patch.program_number) {
1677 c->change_program (pc.patch (), new_patch.program_number);
1680 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1681 if (pc.patch()->bank() != new_bank) {
1682 c->change_bank (pc.patch (), new_bank);
1685 _model->apply_command (*trackview.session(), c);
1687 _patch_changes.clear ();
1688 display_patch_changes ();
1692 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1694 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1696 if (old_change->time() != new_change.time()) {
1697 c->change_time (old_change, new_change.time());
1700 if (old_change->channel() != new_change.channel()) {
1701 c->change_channel (old_change, new_change.channel());
1704 if (old_change->program() != new_change.program()) {
1705 c->change_program (old_change, new_change.program());
1708 if (old_change->bank() != new_change.bank()) {
1709 c->change_bank (old_change, new_change.bank());
1712 _model->apply_command (*trackview.session(), c);
1714 _patch_changes.clear ();
1715 display_patch_changes ();
1718 /** Add a patch change to the region.
1719 * @param t Time in frames relative to region position
1720 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1721 * get_channel_for_add())
1724 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1726 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1727 c->add (MidiModel::PatchChangePtr (
1728 new Evoral::PatchChange<Evoral::MusicalTime> (
1729 frames_to_beats (t + midi_region()->start()), get_channel_for_add(), patch.program(), patch.bank()
1733 _model->apply_command (*trackview.session(), c);
1735 _patch_changes.clear ();
1736 display_patch_changes ();
1740 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1742 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1743 c->change_time (pc.patch (), t);
1744 _model->apply_command (*trackview.session(), c);
1746 _patch_changes.clear ();
1747 display_patch_changes ();
1751 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1753 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1754 c->remove (pc->patch ());
1755 _model->apply_command (*trackview.session(), c);
1757 _patch_changes.clear ();
1758 display_patch_changes ();
1762 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1764 if (patch.patch()->program() < 127) {
1765 MIDI::Name::PatchPrimaryKey key;
1766 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1767 key.program_number++;
1768 change_patch_change (patch, key);
1773 MidiRegionView::next_patch (CanvasPatchChange& patch)
1775 if (patch.patch()->program() > 0) {
1776 MIDI::Name::PatchPrimaryKey key;
1777 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1778 key.program_number--;
1779 change_patch_change (patch, key);
1784 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1786 if (_selection.empty()) {
1790 if (_selection.erase (cne) > 0) {
1791 cerr << "Erased a CNE from selection\n";
1796 MidiRegionView::delete_selection()
1798 if (_selection.empty()) {
1802 start_note_diff_command (_("delete selection"));
1804 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1805 if ((*i)->selected()) {
1806 _note_diff_command->remove((*i)->note());
1816 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1818 start_note_diff_command (_("delete note"));
1819 _note_diff_command->remove (n);
1822 trackview.editor().hide_verbose_canvas_cursor ();
1826 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1828 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1829 if ((*i)->selected() && (*i) != ev) {
1830 (*i)->set_selected(false);
1831 (*i)->hide_velocity();
1839 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1841 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1844 Selection::iterator tmp = i;
1847 (*i)->set_selected (false);
1848 _selection.erase (i);
1857 /* don't bother with removing this regionview from the editor selection,
1858 since we're about to add another note, and thus put/keep this
1859 regionview in the editor selection.
1862 if (!ev->selected()) {
1863 add_to_selection (ev);
1868 MidiRegionView::select_all_notes ()
1872 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1873 add_to_selection (*i);
1878 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1880 uint8_t low_note = 127;
1881 uint8_t high_note = 0;
1882 MidiModel::Notes& notes (_model->notes());
1883 _optimization_iterator = _events.begin();
1889 if (extend && _selection.empty()) {
1895 /* scan existing selection to get note range */
1897 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1898 if ((*i)->note()->note() < low_note) {
1899 low_note = (*i)->note()->note();
1901 if ((*i)->note()->note() > high_note) {
1902 high_note = (*i)->note()->note();
1906 low_note = min (low_note, notenum);
1907 high_note = max (high_note, notenum);
1910 no_sound_notes = true;
1912 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1914 boost::shared_ptr<NoteType> note (*n);
1915 CanvasNoteEvent* cne;
1916 bool select = false;
1918 if (((1 << note->channel()) & channel_mask) != 0) {
1920 if ((note->note() >= low_note && note->note() <= high_note)) {
1923 } else if (note->note() == notenum) {
1929 if ((cne = find_canvas_note (note)) != 0) {
1930 // extend is false because we've taken care of it,
1931 // since it extends by time range, not pitch.
1932 note_selected (cne, add, false);
1936 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1940 no_sound_notes = false;
1944 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1946 MidiModel::Notes& notes (_model->notes());
1947 _optimization_iterator = _events.begin();
1949 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1951 boost::shared_ptr<NoteType> note (*n);
1952 CanvasNoteEvent* cne;
1954 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1955 if ((cne = find_canvas_note (note)) != 0) {
1956 if (cne->selected()) {
1957 note_deselected (cne);
1959 note_selected (cne, true, false);
1967 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1970 clear_selection_except(ev);
1975 if (!ev->selected()) {
1976 add_to_selection (ev);
1980 /* find end of latest note selected, select all between that and the start of "ev" */
1982 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
1983 Evoral::MusicalTime latest = 0;
1985 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1986 if ((*i)->note()->end_time() > latest) {
1987 latest = (*i)->note()->end_time();
1989 if ((*i)->note()->time() < earliest) {
1990 earliest = (*i)->note()->time();
1994 if (ev->note()->end_time() > latest) {
1995 latest = ev->note()->end_time();
1998 if (ev->note()->time() < earliest) {
1999 earliest = ev->note()->time();
2002 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2004 /* find notes entirely within OR spanning the earliest..latest range */
2006 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2007 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2008 add_to_selection (*i);
2012 /* if events were guaranteed to be time sorted, we could do this.
2013 but as of sept 10th 2009, they no longer are.
2016 if ((*i)->note()->time() > latest) {
2025 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2027 remove_from_selection (ev);
2031 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
2041 // TODO: Make this faster by storing the last updated selection rect, and only
2042 // adjusting things that are in the area that appears/disappeared.
2043 // We probably need a tree to be able to find events in O(log(n)) time.
2045 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2047 /* check if any corner of the note is inside the rect
2050 1) this is computing "touched by", not "contained by" the rect.
2051 2) this does not require that events be sorted in time.
2054 const double ix1 = (*i)->x1();
2055 const double ix2 = (*i)->x2();
2056 const double iy1 = (*i)->y1();
2057 const double iy2 = (*i)->y2();
2059 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2060 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2061 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2062 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2065 if (!(*i)->selected()) {
2066 add_to_selection (*i);
2068 } else if ((*i)->selected()) {
2069 // Not inside rectangle
2070 remove_from_selection (*i);
2076 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2078 Selection::iterator i = _selection.find (ev);
2080 if (i != _selection.end()) {
2081 _selection.erase (i);
2084 ev->set_selected (false);
2085 ev->hide_velocity ();
2087 if (_selection.empty()) {
2088 PublicEditor& editor (trackview.editor());
2089 editor.get_selection().remove (this);
2094 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2096 bool add_mrv_selection = false;
2098 if (_selection.empty()) {
2099 add_mrv_selection = true;
2102 if (_selection.insert (ev).second) {
2103 ev->set_selected (true);
2104 play_midi_note ((ev)->note());
2107 if (add_mrv_selection) {
2108 PublicEditor& editor (trackview.editor());
2109 editor.get_selection().add (this);
2114 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2116 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2117 PossibleChord to_play;
2118 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2120 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2121 if ((*i)->note()->time() < earliest) {
2122 earliest = (*i)->note()->time();
2126 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2127 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2128 to_play.push_back ((*i)->note());
2130 (*i)->move_event(dx, dy);
2133 if (dy && !_selection.empty() && !no_sound_notes && trackview.editor().sound_notes()) {
2135 if (to_play.size() > 1) {
2137 PossibleChord shifted;
2139 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2140 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2141 moved_note->set_note (moved_note->note() + cumulative_dy);
2142 shifted.push_back (moved_note);
2145 play_midi_chord (shifted);
2147 } else if (!to_play.empty()) {
2149 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2150 moved_note->set_note (moved_note->note() + cumulative_dy);
2151 play_midi_note (moved_note);
2157 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2159 assert (!_selection.empty());
2161 uint8_t lowest_note_in_selection = 127;
2162 uint8_t highest_note_in_selection = 0;
2163 uint8_t highest_note_difference = 0;
2165 // find highest and lowest notes first
2167 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2168 uint8_t pitch = (*i)->note()->note();
2169 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2170 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2174 cerr << "dnote: " << (int) dnote << endl;
2175 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2176 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2177 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2178 << int(highest_note_in_selection) << endl;
2179 cerr << "selection size: " << _selection.size() << endl;
2180 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2183 // Make sure the note pitch does not exceed the MIDI standard range
2184 if (highest_note_in_selection + dnote > 127) {
2185 highest_note_difference = highest_note_in_selection - 127;
2188 start_note_diff_command (_("move notes"));
2190 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2192 Evoral::MusicalTime new_time = frames_to_beats (beats_to_frames ((*i)->note()->time()) + dt);
2198 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2200 uint8_t original_pitch = (*i)->note()->note();
2201 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2203 // keep notes in standard midi range
2204 clamp_to_0_127(new_pitch);
2206 // keep original pitch if note is dragged outside valid midi range
2207 if ((original_pitch != 0 && new_pitch == 0)
2208 || (original_pitch != 127 && new_pitch == 127)) {
2209 new_pitch = original_pitch;
2212 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2213 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2215 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2220 // care about notes being moved beyond the upper/lower bounds on the canvas
2221 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2222 highest_note_in_selection > midi_stream_view()->highest_note()) {
2223 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2228 MidiRegionView::snap_pixel_to_frame(double x)
2230 PublicEditor& editor (trackview.editor());
2231 return snap_frame_to_frame (editor.pixel_to_frame (x));
2234 /** Snap a frame offset within our region using the current snap settings.
2235 * @param x Frame offset from this region's position.
2236 * @return Snapped frame offset from this region's position.
2239 MidiRegionView::snap_frame_to_frame (frameoffset_t x)
2241 PublicEditor& editor = trackview.editor();
2243 /* x is region relative, convert it to global absolute frames */
2244 framepos_t const session_frame = x + _region->position();
2246 /* try a snap in either direction */
2247 framepos_t frame = session_frame;
2248 editor.snap_to (frame, 0);
2250 /* if we went off the beginning of the region, snap forwards */
2251 if (frame < _region->position ()) {
2252 frame = session_frame;
2253 editor.snap_to (frame, 1);
2256 /* back to region relative */
2257 return frame - _region->position();
2261 MidiRegionView::snap_to_pixel(double x)
2263 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2267 MidiRegionView::get_position_pixels()
2269 framepos_t region_frame = get_position();
2270 return trackview.editor().frame_to_pixel(region_frame);
2274 MidiRegionView::get_end_position_pixels()
2276 framepos_t frame = get_position() + get_duration ();
2277 return trackview.editor().frame_to_pixel(frame);
2281 MidiRegionView::beats_to_frames(double beats) const
2283 return _time_converter.to(beats);
2287 MidiRegionView::frames_to_beats(framepos_t frames) const
2289 return _time_converter.from(frames);
2293 MidiRegionView::begin_resizing (bool /*at_front*/)
2295 _resize_data.clear();
2297 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2298 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2300 // only insert CanvasNotes into the map
2302 NoteResizeData *resize_data = new NoteResizeData();
2303 resize_data->canvas_note = note;
2305 // create a new SimpleRect from the note which will be the resize preview
2306 SimpleRect *resize_rect = new SimpleRect(
2307 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2309 // calculate the colors: get the color settings
2310 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2311 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2314 // make the resize preview notes more transparent and bright
2315 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2317 // calculate color based on note velocity
2318 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2319 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2323 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2324 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2326 resize_data->resize_rect = resize_rect;
2327 _resize_data.push_back(resize_data);
2332 /** Update resizing notes while user drags.
2333 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2334 * @param at_front which end of the note (true == note on, false == note off)
2335 * @param delta_x change in mouse position since the start of the drag
2336 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2337 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2338 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2339 * as the \a primary note.
2342 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2344 bool cursor_set = false;
2346 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2347 SimpleRect* resize_rect = (*i)->resize_rect;
2348 CanvasNote* canvas_note = (*i)->canvas_note;
2353 current_x = canvas_note->x1() + delta_x;
2355 current_x = primary->x1() + delta_x;
2359 current_x = canvas_note->x2() + delta_x;
2361 current_x = primary->x2() + delta_x;
2366 resize_rect->property_x1() = snap_to_pixel(current_x);
2367 resize_rect->property_x2() = canvas_note->x2();
2369 resize_rect->property_x2() = snap_to_pixel(current_x);
2370 resize_rect->property_x1() = canvas_note->x1();
2376 beats = snap_pixel_to_frame (current_x);
2377 beats = frames_to_beats (beats);
2382 if (beats < canvas_note->note()->end_time()) {
2383 len = canvas_note->note()->time() - beats;
2384 len += canvas_note->note()->length();
2389 if (beats >= canvas_note->note()->time()) {
2390 len = beats - canvas_note->note()->time();
2397 snprintf (buf, sizeof (buf), "%.3g beats", len);
2398 trackview.editor().show_verbose_canvas_cursor_with (buf);
2407 /** Finish resizing notes when the user releases the mouse button.
2408 * Parameters the same as for \a update_resizing().
2411 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2413 start_note_diff_command (_("resize notes"));
2415 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2416 CanvasNote* canvas_note = (*i)->canvas_note;
2417 SimpleRect* resize_rect = (*i)->resize_rect;
2422 current_x = canvas_note->x1() + delta_x;
2424 current_x = primary->x1() + delta_x;
2428 current_x = canvas_note->x2() + delta_x;
2430 current_x = primary->x2() + delta_x;
2434 current_x = snap_pixel_to_frame (current_x);
2435 current_x = frames_to_beats (current_x);
2437 if (at_front && current_x < canvas_note->note()->end_time()) {
2438 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2440 double len = canvas_note->note()->time() - current_x;
2441 len += canvas_note->note()->length();
2444 /* XXX convert to beats */
2445 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2450 double len = current_x - canvas_note->note()->time();
2453 /* XXX convert to beats */
2454 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2462 _resize_data.clear();
2467 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t channel)
2469 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, (uint8_t) channel);
2473 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2475 uint8_t new_velocity;
2478 new_velocity = event->note()->velocity() + velocity;
2479 clamp_to_0_127(new_velocity);
2481 new_velocity = velocity;
2484 event->set_selected (event->selected()); // change color
2486 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2490 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2495 new_note = event->note()->note() + note;
2500 clamp_to_0_127 (new_note);
2501 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2505 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2507 bool change_start = false;
2508 bool change_length = false;
2509 Evoral::MusicalTime new_start = 0;
2510 Evoral::MusicalTime new_length = 0;
2512 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2514 front_delta: if positive - move the start of the note later in time (shortening it)
2515 if negative - move the start of the note earlier in time (lengthening it)
2517 end_delta: if positive - move the end of the note later in time (lengthening it)
2518 if negative - move the end of the note earlier in time (shortening it)
2522 if (front_delta < 0) {
2524 if (event->note()->time() < -front_delta) {
2527 new_start = event->note()->time() + front_delta; // moves earlier
2530 /* start moved toward zero, so move the end point out to where it used to be.
2531 Note that front_delta is negative, so this increases the length.
2534 new_length = event->note()->length() - front_delta;
2535 change_start = true;
2536 change_length = true;
2540 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2542 if (new_pos < event->note()->end_time()) {
2543 new_start = event->note()->time() + front_delta;
2544 /* start moved toward the end, so move the end point back to where it used to be */
2545 new_length = event->note()->length() - front_delta;
2546 change_start = true;
2547 change_length = true;
2554 bool can_change = true;
2555 if (end_delta < 0) {
2556 if (event->note()->length() < -end_delta) {
2562 new_length = event->note()->length() + end_delta;
2563 change_length = true;
2568 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2571 if (change_length) {
2572 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2577 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2579 Evoral::MusicalTime new_time;
2583 if (event->note()->time() < -delta) {
2586 new_time = event->note()->time() + delta;
2589 new_time = event->note()->time() + delta;
2595 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2599 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2601 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2605 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2609 if (_selection.empty()) {
2624 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2625 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2631 start_note_diff_command (_("change velocities"));
2633 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2634 Selection::iterator next = i;
2636 change_note_velocity (*i, delta, true);
2642 if (!_selection.empty()) {
2644 snprintf (buf, sizeof (buf), "Vel %d",
2645 (int) (*_selection.begin())->note()->velocity());
2646 trackview.editor().show_verbose_canvas_cursor_with (buf, 10, 10);
2652 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2654 if (_selection.empty()) {
2671 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2673 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2677 if ((int8_t) (*i)->note()->note() + delta > 127) {
2684 start_note_diff_command (_("transpose"));
2686 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2687 Selection::iterator next = i;
2689 change_note_note (*i, delta, true);
2697 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2703 /* grab the current grid distance */
2705 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2707 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2708 cerr << "Grid type not available as beats - TO BE FIXED\n";
2718 start_note_diff_command (_("change note lengths"));
2720 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2721 Selection::iterator next = i;
2724 /* note the negation of the delta for start */
2726 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2735 MidiRegionView::nudge_notes (bool forward)
2737 if (_selection.empty()) {
2741 /* pick a note as the point along the timeline to get the nudge distance.
2742 its not necessarily the earliest note, so we may want to pull the notes out
2743 into a vector and sort before using the first one.
2746 framepos_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2748 framepos_t distance;
2750 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2752 /* grid is off - use nudge distance */
2754 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2760 framepos_t next_pos = ref_point;
2763 if (max_framepos - 1 < next_pos) {
2767 if (next_pos == 0) {
2773 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2774 distance = ref_point - next_pos;
2777 if (distance == 0) {
2781 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2787 start_note_diff_command (_("nudge"));
2789 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2790 Selection::iterator next = i;
2792 change_note_time (*i, delta, true);
2800 MidiRegionView::change_channel(uint8_t channel)
2802 start_note_diff_command(_("change channel"));
2803 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2804 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2812 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2814 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2816 pre_enter_cursor = editor->get_canvas_cursor ();
2818 if (_mouse_state == SelectTouchDragging) {
2819 note_selected (ev, true);
2822 show_verbose_canvas_cursor (ev->note ());
2826 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2828 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2830 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2831 (*i)->hide_velocity ();
2834 editor->hide_verbose_canvas_cursor ();
2836 if (pre_enter_cursor) {
2837 editor->set_canvas_cursor (pre_enter_cursor);
2838 pre_enter_cursor = 0;
2843 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2846 s << ((int) ev->patch()->program() + 1) << ":" << (ev->patch()->bank() + 1);
2847 trackview.editor().show_verbose_canvas_cursor_with (s.str().c_str(), 10, 20);
2851 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
2853 trackview.editor().hide_verbose_canvas_cursor ();
2857 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
2859 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2861 if (x_fraction > 0.0 && x_fraction < 0.25) {
2862 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
2863 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
2864 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
2866 if (pre_enter_cursor && can_set_cursor) {
2867 editor->set_canvas_cursor (pre_enter_cursor);
2873 MidiRegionView::set_frame_color()
2877 TimeAxisViewItem::set_frame_color ();
2884 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2885 } else if (high_enough_for_name) {
2886 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2891 if (!rect_visible) {
2892 f = UINT_RGBA_CHANGE_A (f, 0);
2895 frame->property_fill_color_rgba() = f;
2899 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2903 case FilterChannels:
2904 _force_channel = -1;
2907 _force_channel = mask;
2908 mask = 0xFFFF; // Show all notes as active (below)
2911 // Update notes for selection
2912 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2913 (*i)->on_channel_selection_change(mask);
2916 _last_channel_selection = mask;
2918 _patch_changes.clear ();
2919 display_patch_changes ();
2923 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2925 _model_name = model;
2926 _custom_device_mode = custom_device_mode;
2931 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2933 if (_selection.empty()) {
2937 PublicEditor& editor (trackview.editor());
2942 editor.get_cut_buffer().add (selection_as_cut_buffer());
2950 start_note_diff_command();
2952 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2958 note_diff_remove_note (*i);
2968 MidiRegionView::selection_as_cut_buffer () const
2972 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2973 NoteType* n = (*i)->note().get();
2974 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2977 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2983 /** This method handles undo */
2985 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
2991 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
2993 trackview.session()->begin_reversible_command (_("paste"));
2995 start_note_diff_command (_("paste"));
2997 Evoral::MusicalTime beat_delta;
2998 Evoral::MusicalTime paste_pos_beats;
2999 Evoral::MusicalTime duration;
3000 Evoral::MusicalTime end_point = 0;
3002 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3003 paste_pos_beats = frames_to_beats (pos - _region->position());
3004 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3005 paste_pos_beats = 0;
3007 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",
3008 (*mcb.notes().begin())->time(),
3009 (*mcb.notes().rbegin())->end_time(),
3010 duration, pos, _region->position(),
3011 paste_pos_beats, beat_delta));
3015 for (int n = 0; n < (int) times; ++n) {
3017 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3019 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3020 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3022 /* make all newly added notes selected */
3024 note_diff_add_note (copied_note, true);
3025 end_point = copied_note->end_time();
3028 paste_pos_beats += duration;
3031 /* if we pasted past the current end of the region, extend the region */
3033 framepos_t end_frame = _region->position() + beats_to_frames (end_point);
3034 framepos_t region_end = _region->position() + _region->length() - 1;
3036 if (end_frame > region_end) {
3038 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3040 _region->clear_changes ();
3041 _region->set_length (end_frame, this);
3042 trackview.session()->add_command (new StatefulDiffCommand (_region));
3047 trackview.session()->commit_reversible_command ();
3050 struct EventNoteTimeEarlyFirstComparator {
3051 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3052 return a->note()->time() < b->note()->time();
3057 MidiRegionView::time_sort_events ()
3059 if (!_sort_needed) {
3063 EventNoteTimeEarlyFirstComparator cmp;
3066 _sort_needed = false;
3070 MidiRegionView::goto_next_note ()
3072 // framepos_t pos = -1;
3073 bool use_next = false;
3075 if (_events.back()->selected()) {
3079 time_sort_events ();
3081 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3082 if ((*i)->selected()) {
3085 } else if (use_next) {
3087 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3092 /* use the first one */
3094 unique_select (_events.front());
3099 MidiRegionView::goto_previous_note ()
3101 // framepos_t pos = -1;
3102 bool use_next = false;
3104 if (_events.front()->selected()) {
3108 time_sort_events ();
3110 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3111 if ((*i)->selected()) {
3114 } else if (use_next) {
3116 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
3121 /* use the last one */
3123 unique_select (*(_events.rbegin()));
3127 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3129 bool had_selected = false;
3131 time_sort_events ();
3133 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3134 if ((*i)->selected()) {
3135 selected.insert ((*i)->note());
3136 had_selected = true;
3140 if (allow_all_if_none_selected && !had_selected) {
3141 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3142 selected.insert ((*i)->note());
3148 MidiRegionView::update_ghost_note (double x, double y)
3153 _note_group->w2i (x, y);
3154 framepos_t const f = snap_pixel_to_frame (x);
3157 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
3163 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
3165 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
3166 _ghost_note->note()->set_length (length);
3167 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3169 /* the ghost note does not appear in ghost regions, so pass false in here */
3170 update_note (_ghost_note, false);
3172 show_verbose_canvas_cursor (_ghost_note->note ());
3176 MidiRegionView::create_ghost_note (double x, double y)
3181 boost::shared_ptr<NoteType> g (new NoteType);
3182 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3183 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3184 update_ghost_note (x, y);
3185 _ghost_note->show ();
3190 show_verbose_canvas_cursor (_ghost_note->note ());
3194 MidiRegionView::snap_changed ()
3200 create_ghost_note (_last_ghost_x, _last_ghost_y);
3204 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
3207 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3208 Evoral::midi_note_name (n->note()).c_str(),
3210 (int) n->channel() + 1,
3211 (int) n->velocity());
3212 trackview.editor().show_verbose_canvas_cursor_with (buf, 10, 20);
3216 MidiRegionView::drop_down_keys ()
3218 _mouse_state = None;
3222 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3224 double note = midi_stream_view()->y_to_note(y);
3226 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3228 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3230 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3231 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3232 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3233 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3238 bool add_mrv_selection = false;
3240 if (_selection.empty()) {
3241 add_mrv_selection = true;
3244 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3245 if (_selection.insert (*i).second) {
3246 (*i)->set_selected (true);
3250 if (add_mrv_selection) {
3251 PublicEditor& editor (trackview.editor());
3252 editor.get_selection().add (this);
3257 MidiRegionView::color_handler ()
3259 RegionView::color_handler ();
3261 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3262 (*i)->set_selected ((*i)->selected()); // will change color
3265 /* XXX probably more to do here */
3269 MidiRegionView::enable_display (bool yn)
3271 RegionView::enable_display (yn);
3278 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3280 if (_step_edit_cursor == 0) {
3281 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3283 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3284 _step_edit_cursor->property_y1() = 0;
3285 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3286 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3287 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3290 move_step_edit_cursor (pos);
3291 _step_edit_cursor->show ();
3295 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3297 _step_edit_cursor_position = pos;
3299 if (_step_edit_cursor) {
3300 double pixel = trackview.editor().frame_to_pixel (beats_to_frames (pos));
3301 _step_edit_cursor->property_x1() = pixel;
3302 set_step_edit_cursor_width (_step_edit_cursor_width);
3307 MidiRegionView::hide_step_edit_cursor ()
3309 if (_step_edit_cursor) {
3310 _step_edit_cursor->hide ();
3315 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3317 _step_edit_cursor_width = beats;
3319 if (_step_edit_cursor) {
3320 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (beats_to_frames (beats));
3324 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3325 * @param buf Data that has been recorded.
3326 * @param w Source that this data will end up in.
3329 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3331 if (!_active_notes) {
3332 /* we aren't actively being recorded to */
3336 boost::shared_ptr<MidiSource> src = w.lock ();
3337 if (!src || src != midi_region()->midi_source()) {
3338 /* recorded data was not destined for our source */
3342 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3343 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3345 framepos_t back = max_framepos;
3347 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3348 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3349 assert (ev.buffer ());
3351 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3353 if (ev.type() == MIDI_CMD_NOTE_ON) {
3355 boost::shared_ptr<NoteType> note (
3356 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3359 add_note (note, true);
3361 /* fix up our note range */
3362 if (ev.note() < _current_range_min) {
3363 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3364 } else if (ev.note() > _current_range_max) {
3365 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3368 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3369 resolve_note (ev.note (), time_beats);
3375 midi_stream_view()->check_record_layers (region(), back);
3379 MidiRegionView::trim_front_starting ()
3381 /* Reparent the note group to the region view's parent, so that it doesn't change
3382 when the region view is trimmed.
3384 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3385 _temporary_note_group->move (group->property_x(), group->property_y());
3386 _note_group->reparent (*_temporary_note_group);
3390 MidiRegionView::trim_front_ending ()
3392 _note_group->reparent (*group);
3393 delete _temporary_note_group;
3394 _temporary_note_group = 0;
3396 if (_region->start() < 0) {
3397 /* Trim drag made start time -ve; fix this */
3398 midi_region()->fix_negative_start ();
3402 /** @return channel (counted from 0) to add an event to, based on the current setting
3403 * of the channel selector.
3406 MidiRegionView::get_channel_for_add () const
3408 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3409 uint16_t const chn_mask = mtv->channel_selector().get_selected_channels();
3411 uint8_t channel = 0;
3413 /* pick the highest selected channel, unless all channels are selected,
3414 which is interpreted to mean channel 1 (zero)
3417 for (uint16_t i = 0; i < 16; ++i) {
3418 if (chn_mask & (1<<i)) {
3424 if (chn_cnt == 16) {
3432 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3434 PatchChangeDialog d (&_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3435 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3439 change_patch_change (pc->patch(), d.patch ());