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)
101 , _step_edit_cursor (0)
102 , _step_edit_cursor_width (1.0)
103 , _step_edit_cursor_position (0.0)
104 , _channel_selection_scoped_note (0)
105 , _temporary_note_group (0)
108 , _sort_needed (true)
109 , _optimization_iterator (_events.end())
111 , _no_sound_notes (false)
114 , _pre_enter_cursor (0)
116 _note_group->raise_to_top();
117 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
119 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
120 connect_to_diskstream ();
122 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
125 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
126 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
127 TimeAxisViewItem::Visibility visibility)
128 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
130 , _last_channel_selection(0xFFFF)
131 , _current_range_min(0)
132 , _current_range_max(0)
134 , _note_group(new ArdourCanvas::Group(*parent))
135 , _note_diff_command (0)
138 , _step_edit_cursor (0)
139 , _step_edit_cursor_width (1.0)
140 , _step_edit_cursor_position (0.0)
141 , _channel_selection_scoped_note (0)
142 , _temporary_note_group (0)
145 , _sort_needed (true)
146 , _optimization_iterator (_events.end())
148 , _no_sound_notes (false)
151 , _pre_enter_cursor (0)
153 _note_group->raise_to_top();
154 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
156 connect_to_diskstream ();
158 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
162 MidiRegionView::parameter_changed (std::string const & p)
164 if (p == "diplay-first-midi-bank-as-zero") {
165 if (_enable_display) {
171 MidiRegionView::MidiRegionView (const MidiRegionView& other)
172 : sigc::trackable(other)
175 , _last_channel_selection(0xFFFF)
176 , _current_range_min(0)
177 , _current_range_max(0)
179 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
180 , _note_diff_command (0)
183 , _step_edit_cursor (0)
184 , _step_edit_cursor_width (1.0)
185 , _step_edit_cursor_position (0.0)
186 , _channel_selection_scoped_note (0)
187 , _temporary_note_group (0)
190 , _sort_needed (true)
191 , _optimization_iterator (_events.end())
193 , _no_sound_notes (false)
196 , _pre_enter_cursor (0)
201 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
202 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
207 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
208 : RegionView (other, boost::shared_ptr<Region> (region))
210 , _last_channel_selection(0xFFFF)
211 , _current_range_min(0)
212 , _current_range_max(0)
214 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
215 , _note_diff_command (0)
218 , _step_edit_cursor (0)
219 , _step_edit_cursor_width (1.0)
220 , _step_edit_cursor_position (0.0)
221 , _channel_selection_scoped_note (0)
222 , _temporary_note_group (0)
225 , _sort_needed (true)
226 , _optimization_iterator (_events.end())
228 , _no_sound_notes (false)
231 , _pre_enter_cursor (0)
236 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
237 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
243 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
245 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
247 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
248 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
252 midi_region()->midi_source(0)->load_model();
255 _model = midi_region()->midi_source(0)->model();
256 _enable_display = false;
258 RegionView::init (basic_color, false);
260 compute_colors (basic_color);
262 set_height (trackview.current_height());
265 region_sync_changed ();
266 region_resized (ARDOUR::bounds_change);
269 reset_width_dependent_items (_pixel_width);
273 _enable_display = true;
276 display_model (_model);
280 group->raise_to_top();
281 group->signal_event().connect(
282 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
284 midi_view()->signal_channel_mode_changed().connect(
285 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
287 midi_view()->signal_midi_patch_settings_changed().connect(
288 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
290 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
291 ui_bind(&MidiRegionView::snap_changed, this),
294 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
295 connect_to_diskstream ();
297 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
300 const boost::shared_ptr<ARDOUR::MidiRegion>
301 MidiRegionView::midi_region() const
303 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
307 MidiRegionView::connect_to_diskstream ()
309 midi_view()->midi_track()->DataRecorded.connect(
310 *this, invalidator(*this),
311 ui_bind(&MidiRegionView::data_recorded, this, _1),
316 MidiRegionView::canvas_event(GdkEvent* ev)
319 case GDK_ENTER_NOTIFY:
320 case GDK_LEAVE_NOTIFY:
321 _last_event_x = ev->crossing.x;
322 _last_event_y = ev->crossing.y;
324 case GDK_MOTION_NOTIFY:
325 _last_event_x = ev->motion.x;
326 _last_event_y = ev->motion.y;
332 if (!trackview.editor().internal_editing()) {
338 return scroll (&ev->scroll);
341 return key_press (&ev->key);
343 case GDK_KEY_RELEASE:
344 return key_release (&ev->key);
346 case GDK_BUTTON_PRESS:
347 return button_press (&ev->button);
349 case GDK_2BUTTON_PRESS:
352 case GDK_BUTTON_RELEASE:
353 return button_release (&ev->button);
355 case GDK_ENTER_NOTIFY:
356 return enter_notify (&ev->crossing);
358 case GDK_LEAVE_NOTIFY:
359 return leave_notify (&ev->crossing);
361 case GDK_MOTION_NOTIFY:
362 return motion (&ev->motion);
372 MidiRegionView::remove_ghost_note ()
379 MidiRegionView::enter_notify (GdkEventCrossing* ev)
381 trackview.editor().MouseModeChanged.connect (
382 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
385 if (trackview.editor().current_mouse_mode() == MouseRange) {
386 create_ghost_note (ev->x, ev->y);
389 if (!trackview.editor().internal_editing()) {
390 Keyboard::magic_widget_drop_focus();
392 Keyboard::magic_widget_grab_focus();
400 MidiRegionView::leave_notify (GdkEventCrossing*)
402 _mouse_mode_connection.disconnect ();
404 trackview.editor().verbose_cursor()->hide ();
405 remove_ghost_note ();
411 MidiRegionView::mouse_mode_changed ()
413 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
414 create_ghost_note (_last_event_x, _last_event_y);
416 remove_ghost_note ();
417 trackview.editor().verbose_cursor()->hide ();
420 if (!trackview.editor().internal_editing()) {
421 Keyboard::magic_widget_drop_focus();
423 Keyboard::magic_widget_grab_focus();
429 MidiRegionView::button_press (GdkEventButton* ev)
431 if (ev->button != 1) {
438 group->w2i (_last_x, _last_y);
440 if (_mouse_state != SelectTouchDragging) {
442 _pressed_button = ev->button;
443 _mouse_state = Pressed;
448 _pressed_button = ev->button;
454 MidiRegionView::button_release (GdkEventButton* ev)
456 double event_x, event_y;
458 if (ev->button != 1) {
465 group->w2i(event_x, event_y);
466 group->ungrab(ev->time);
468 PublicEditor& editor = trackview.editor ();
470 switch (_mouse_state) {
471 case Pressed: // Clicked
473 switch (editor.current_mouse_mode()) {
479 if (Keyboard::is_insert_note_event(ev)) {
481 double event_x, event_y;
485 group->w2i(event_x, event_y);
488 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
494 create_note_at (event_x, event_y, beats, true, true);
502 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
508 create_note_at (event_x, event_y, beats, true, true);
519 case SelectRectDragging: // Select drag done
520 editor.drags()->end_grab ((GdkEvent *) ev);
524 case AddDragging: // Add drag done
528 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) {
530 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
532 const double x = _drag_rect->property_x1();
533 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
535 create_note_at (x, _drag_rect->property_y1(), region_frames_to_region_beats(length), true, false);
542 create_ghost_note (ev->x, ev->y);
552 MidiRegionView::motion (GdkEventMotion* ev)
554 double event_x, event_y;
555 framepos_t event_frame = 0;
559 group->w2i(event_x, event_y);
561 PublicEditor& editor = trackview.editor ();
563 // convert event_x to global frame
564 framecnt_t grid_frames;
565 event_frame = snap_frame_to_grid_underneath (editor.pixel_to_frame (event_x), grid_frames);
567 if (!_ghost_note && editor.current_mouse_mode() != MouseRange
568 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
569 && _mouse_state != AddDragging) {
571 create_ghost_note (ev->x, ev->y);
572 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
573 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
575 update_ghost_note (ev->x, ev->y);
576 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
581 editor.verbose_cursor()->hide ();
582 } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
583 update_ghost_note (ev->x, ev->y);
586 /* any motion immediately hides velocity text that may have been visible */
588 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
589 (*i)->hide_velocity ();
592 switch (_mouse_state) {
593 case Pressed: // Maybe start a drag, if we've moved a bit
595 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
596 /* no appreciable movement since the button was pressed */
600 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
601 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
603 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
604 _mouse_state = SelectRectDragging;
607 } else if (editor.internal_editing()) {
608 // Add note drag start
610 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
611 Gdk::Cursor(Gdk::FLEUR), ev->time);
615 _drag_start_x = event_x;
616 _drag_start_y = event_y;
618 _drag_rect = new ArdourCanvas::SimpleRect(*group);
619 _drag_rect->property_x1() = editor.frame_to_pixel(event_frame);
621 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
622 midi_stream_view()->y_to_note(event_y));
623 _drag_rect->property_x2() = editor.frame_to_pixel(event_frame);
625 _drag_rect->property_y2() = _drag_rect->property_y1()
626 + floor(midi_stream_view()->note_height());
627 _drag_rect->property_outline_what() = 0xFF;
628 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
629 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
631 _mouse_state = AddDragging;
636 editor.verbose_cursor()->hide ();
643 case SelectRectDragging:
644 editor.drags()->motion_handler ((GdkEvent *) ev, false);
647 case AddDragging: // Add note drag motion
652 GdkModifierType state;
653 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
658 if (_mouse_state == AddDragging) {
659 event_x = editor.frame_to_pixel(event_frame);
661 if (editor.snap_mode() == SnapNormal) {
662 /* event_frame will have been snapped to the start of the note we are under;
663 it's more intuitive if we use the end of that note here
665 event_x = editor.frame_to_pixel (event_frame + grid_frames);
667 event_x = editor.frame_to_pixel (event_frame);
674 if (event_x > _drag_start_x) {
675 _drag_rect->property_x2() = event_x;
678 _drag_rect->property_x1() = event_x;
685 case SelectTouchDragging:
697 MidiRegionView::scroll (GdkEventScroll* ev)
699 if (_selection.empty()) {
703 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
704 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
705 it still works for zoom.
710 trackview.editor().verbose_cursor()->hide ();
712 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
714 if (ev->direction == GDK_SCROLL_UP) {
715 change_velocities (true, fine, false);
716 } else if (ev->direction == GDK_SCROLL_DOWN) {
717 change_velocities (false, fine, false);
723 MidiRegionView::key_press (GdkEventKey* ev)
725 /* since GTK bindings are generally activated on press, and since
726 detectable auto-repeat is the name of the game and only sends
727 repeated presses, carry out key actions at key press, not release.
730 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
732 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
733 _mouse_state = SelectTouchDragging;
736 } else if (ev->keyval == GDK_Escape && unmodified) {
740 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
742 bool start = (ev->keyval == GDK_comma);
743 bool end = (ev->keyval == GDK_period);
744 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
745 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
747 change_note_lengths (fine, shorter, 0.0, start, end);
751 } else if (ev->keyval == GDK_Delete && unmodified) {
756 } else if (ev->keyval == GDK_Tab) {
758 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
759 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
761 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
765 } else if (ev->keyval == GDK_ISO_Left_Tab) {
767 /* Shift-TAB generates ISO Left Tab, for some reason */
769 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
770 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
772 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
778 } else if (ev->keyval == GDK_Up) {
780 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
781 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
783 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
784 change_velocities (true, fine, allow_smush);
786 transpose (true, fine, allow_smush);
790 } else if (ev->keyval == GDK_Down) {
792 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
793 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
795 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
796 change_velocities (false, fine, allow_smush);
798 transpose (false, fine, allow_smush);
802 } else if (ev->keyval == GDK_Left && unmodified) {
807 } else if (ev->keyval == GDK_Right && unmodified) {
812 } else if (ev->keyval == GDK_c && unmodified) {
821 MidiRegionView::key_release (GdkEventKey* ev)
823 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
831 MidiRegionView::channel_edit ()
834 uint8_t current_channel = 0;
836 if (_selection.empty()) {
840 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
842 current_channel = (*i)->note()->channel ();
847 MidiChannelDialog channel_dialog (current_channel);
848 int ret = channel_dialog.run ();
851 case Gtk::RESPONSE_OK:
857 uint8_t new_channel = channel_dialog.active_channel ();
859 start_note_diff_command (_("channel edit"));
861 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
862 Selection::iterator next = i;
864 change_note_channel (*i, new_channel);
872 MidiRegionView::show_list_editor ()
875 _list_editor = new MidiListEditor (trackview.session(), midi_region());
877 _list_editor->present ();
880 /** Add a note to the model, and the view, at a canvas (click) coordinate.
881 * \param x horizontal position in pixels
882 * \param y vertical position in pixels
883 * \param length duration of the note in beats, which will be snapped to the grid
884 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
885 * \param snap_x true to snap x to the grid, otherwise false.
888 MidiRegionView::create_note_at (double x, double y, double length, bool sh, bool snap_x)
890 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
891 MidiStreamView* const view = mtv->midi_view();
893 double note = view->y_to_note(y);
896 assert(note <= 127.0);
898 // Start of note in frames relative to region start
899 framepos_t start_frames = trackview.editor().pixel_to_frame (x);
901 framecnt_t grid_frames;
902 start_frames = snap_frame_to_grid_underneath (start_frames, grid_frames);
904 assert(start_frames >= 0);
907 length = region_frames_to_region_beats(
908 snap_frame_to_frame (start_frames + region_beats_to_region_frames(length)) - start_frames);
910 assert (length != 0);
913 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
916 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
917 region_frames_to_region_beats(start_frames + _region->start()), length,
918 (uint8_t)note, 0x40));
920 if (_model->contains (new_note)) {
924 view->update_note_range(new_note->note());
926 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
928 _model->apply_command(*trackview.session(), cmd);
930 play_midi_note (new_note);
934 MidiRegionView::clear_events()
939 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
940 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
945 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
950 _patch_changes.clear();
952 _optimization_iterator = _events.end();
956 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
960 content_connection.disconnect ();
961 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
965 if (_enable_display) {
971 MidiRegionView::start_note_diff_command (string name)
973 if (!_note_diff_command) {
974 _note_diff_command = _model->new_note_diff_command (name);
979 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
981 if (_note_diff_command) {
982 _note_diff_command->add (note);
985 _marked_for_selection.insert(note);
988 _marked_for_velocity.insert(note);
993 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
995 if (_note_diff_command && ev->note()) {
996 _note_diff_command->remove(ev->note());
1001 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1002 MidiModel::NoteDiffCommand::Property property,
1005 if (_note_diff_command) {
1006 _note_diff_command->change (ev->note(), property, val);
1011 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1012 MidiModel::NoteDiffCommand::Property property,
1013 Evoral::MusicalTime val)
1015 if (_note_diff_command) {
1016 _note_diff_command->change (ev->note(), property, val);
1021 MidiRegionView::apply_diff (bool as_subcommand)
1025 if (!_note_diff_command) {
1029 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1030 // Mark all selected notes for selection when model reloads
1031 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1032 _marked_for_selection.insert((*i)->note());
1036 if (as_subcommand) {
1037 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1039 _model->apply_command (*trackview.session(), _note_diff_command);
1042 _note_diff_command = 0;
1043 midi_view()->midi_track()->playlist_modified();
1045 if (add_or_remove) {
1046 _marked_for_selection.clear();
1049 _marked_for_velocity.clear();
1053 MidiRegionView::abort_command()
1055 delete _note_diff_command;
1056 _note_diff_command = 0;
1061 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1063 if (_optimization_iterator != _events.end()) {
1064 ++_optimization_iterator;
1067 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1068 return *_optimization_iterator;
1071 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1072 if ((*_optimization_iterator)->note() == note) {
1073 return *_optimization_iterator;
1081 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1083 MidiModel::Notes notes;
1084 _model->get_notes (notes, op, val, chan_mask);
1086 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1087 CanvasNoteEvent* cne = find_canvas_note (*n);
1095 MidiRegionView::redisplay_model()
1097 // Don't redisplay the model if we're currently recording and displaying that
1098 if (_active_notes) {
1106 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1107 (*i)->invalidate ();
1110 MidiModel::ReadLock lock(_model->read_lock());
1112 MidiModel::Notes& notes (_model->notes());
1113 _optimization_iterator = _events.begin();
1115 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1117 boost::shared_ptr<NoteType> note (*n);
1118 CanvasNoteEvent* cne;
1121 if (note_in_region_range (note, visible)) {
1123 if ((cne = find_canvas_note (note)) != 0) {
1130 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1132 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1144 add_note (note, visible);
1149 if ((cne = find_canvas_note (note)) != 0) {
1157 /* remove note items that are no longer valid */
1159 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1160 if (!(*i)->valid ()) {
1162 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1163 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1165 gr->remove_note (*i);
1170 i = _events.erase (i);
1177 _patch_changes.clear();
1181 display_patch_changes ();
1183 _marked_for_selection.clear ();
1184 _marked_for_velocity.clear ();
1186 /* we may have caused _events to contain things out of order (e.g. if a note
1187 moved earlier or later). we don't generally need them in time order, but
1188 make a note that a sort is required for those cases that require it.
1191 _sort_needed = true;
1195 MidiRegionView::display_patch_changes ()
1197 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1198 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1200 for (uint8_t i = 0; i < 16; ++i) {
1201 if (chn_mask & (1<<i)) {
1202 display_patch_changes_on_channel (i);
1204 /* TODO gray-out patch instad of not displaying it */
1209 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1211 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1213 if ((*i)->channel() != channel) {
1217 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1219 boost::shared_ptr<MIDI::Name::Patch> patch =
1220 MIDI::Name::MidiPatchManager::instance().find_patch(
1221 _model_name, _custom_device_mode, channel, patch_key);
1224 add_canvas_patch_change (*i, patch->name());
1227 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1228 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1229 add_canvas_patch_change (*i, buf);
1235 MidiRegionView::display_sysexes()
1237 bool have_periodic_system_messages = false;
1238 bool display_periodic_messages = true;
1240 if (!Config->get_never_display_periodic_midi()) {
1242 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1243 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1244 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1247 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1248 have_periodic_system_messages = true;
1254 if (have_periodic_system_messages) {
1255 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1257 /* get an approximate value for the number of samples per video frame */
1259 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1261 /* if we are zoomed out beyond than the cutoff (i.e. more
1262 * frames per pixel than frames per 4 video frames), don't
1263 * show periodic sysex messages.
1266 if (zoom > (video_frame*4)) {
1267 display_periodic_messages = false;
1271 display_periodic_messages = false;
1274 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1276 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1277 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1279 Evoral::MusicalTime time = (*i)->time();
1283 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1284 if (!display_periodic_messages) {
1292 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1293 str << int((*i)->buffer()[b]);
1294 if (b != (*i)->size() -1) {
1298 string text = str.str();
1300 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1302 double height = midi_stream_view()->contents_height();
1304 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1305 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1307 // Show unless message is beyond the region bounds
1308 if (time - _region->start() >= _region->length() || time < _region->start()) {
1314 _sys_exes.push_back(sysex);
1318 MidiRegionView::~MidiRegionView ()
1320 in_destructor = true;
1322 trackview.editor().verbose_cursor()->hide ();
1324 note_delete_connection.disconnect ();
1326 delete _list_editor;
1328 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1330 if (_active_notes) {
1338 delete _note_diff_command;
1339 delete _step_edit_cursor;
1340 delete _temporary_note_group;
1344 MidiRegionView::region_resized (const PropertyChange& what_changed)
1346 RegionView::region_resized(what_changed);
1348 if (what_changed.contains (ARDOUR::Properties::position)) {
1349 set_duration(_region->length(), 0);
1350 if (_enable_display) {
1357 MidiRegionView::reset_width_dependent_items (double pixel_width)
1359 RegionView::reset_width_dependent_items(pixel_width);
1360 assert(_pixel_width == pixel_width);
1362 if (_enable_display) {
1366 move_step_edit_cursor (_step_edit_cursor_position);
1367 set_step_edit_cursor_width (_step_edit_cursor_width);
1371 MidiRegionView::set_height (double height)
1373 static const double FUDGE = 2.0;
1374 const double old_height = _height;
1375 RegionView::set_height(height);
1376 _height = height - FUDGE;
1378 apply_note_range(midi_stream_view()->lowest_note(),
1379 midi_stream_view()->highest_note(),
1380 height != old_height + FUDGE);
1383 name_pixbuf->raise_to_top();
1386 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1387 (*x)->set_height (midi_stream_view()->contents_height());
1390 if (_step_edit_cursor) {
1391 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1396 /** Apply the current note range from the stream view
1397 * by repositioning/hiding notes as necessary
1400 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1402 if (!_enable_display) {
1406 if (!force && _current_range_min == min && _current_range_max == max) {
1410 _current_range_min = min;
1411 _current_range_max = max;
1413 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1414 CanvasNoteEvent* event = *i;
1415 boost::shared_ptr<NoteType> note (event->note());
1417 if (note->note() < _current_range_min ||
1418 note->note() > _current_range_max) {
1424 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1426 const double y1 = midi_stream_view()->note_to_y(note->note());
1427 const double y2 = y1 + floor(midi_stream_view()->note_height());
1429 cnote->property_y1() = y1;
1430 cnote->property_y2() = y2;
1432 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1434 const double diamond_size = update_hit (chit);
1436 chit->set_height (diamond_size);
1442 MidiRegionView::add_ghost (TimeAxisView& tv)
1446 double unit_position = _region->position () / samples_per_unit;
1447 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1448 MidiGhostRegion* ghost;
1450 if (mtv && mtv->midi_view()) {
1451 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1452 to allow having midi notes on top of note lines and waveforms.
1454 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1456 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1459 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1460 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1461 ghost->add_note(note);
1465 ghost->set_height ();
1466 ghost->set_duration (_region->length() / samples_per_unit);
1467 ghosts.push_back (ghost);
1469 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1475 /** Begin tracking note state for successive calls to add_event
1478 MidiRegionView::begin_write()
1480 assert(!_active_notes);
1481 _active_notes = new CanvasNote*[128];
1482 for (unsigned i=0; i < 128; ++i) {
1483 _active_notes[i] = 0;
1488 /** Destroy note state for add_event
1491 MidiRegionView::end_write()
1493 delete[] _active_notes;
1495 _marked_for_selection.clear();
1496 _marked_for_velocity.clear();
1500 /** Resolve an active MIDI note (while recording).
1503 MidiRegionView::resolve_note(uint8_t note, double end_time)
1505 if (midi_view()->note_mode() != Sustained) {
1509 if (_active_notes && _active_notes[note]) {
1511 /* XXX is end_time really region-centric? I think so, because
1512 this is a new region that we're recording, so source zero is
1513 the same as region zero
1515 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1517 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1518 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1519 _active_notes[note] = 0;
1524 /** Extend active notes to rightmost edge of region (if length is changed)
1527 MidiRegionView::extend_active_notes()
1529 if (!_active_notes) {
1533 for (unsigned i=0; i < 128; ++i) {
1534 if (_active_notes[i]) {
1535 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1542 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1544 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1548 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1550 if (!route_ui || !route_ui->midi_track()) {
1554 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1560 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1562 if (_no_sound_notes || !trackview.editor().sound_notes()) {
1566 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1568 if (!route_ui || !route_ui->midi_track()) {
1572 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1574 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1583 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1585 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1586 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1588 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1589 (note->note() <= midi_stream_view()->highest_note());
1594 /** Update a canvas note's size from its model note.
1595 * @param ev Canvas note to update.
1596 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1599 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1601 boost::shared_ptr<NoteType> note = ev->note();
1603 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1604 const double y1 = midi_stream_view()->note_to_y(note->note());
1606 ev->property_x1() = x;
1607 ev->property_y1() = y1;
1609 /* trim note display to not overlap the end of its region */
1611 if (note->length() > 0) {
1612 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1613 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1615 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1618 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1620 if (note->length() == 0) {
1621 if (_active_notes) {
1622 assert(note->note() < 128);
1623 // If this note is already active there's a stuck note,
1624 // finish the old note rectangle
1625 if (_active_notes[note->note()]) {
1626 CanvasNote* const old_rect = _active_notes[note->note()];
1627 boost::shared_ptr<NoteType> old_note = old_rect->note();
1628 old_rect->property_x2() = x;
1629 old_rect->property_outline_what() = (guint32) 0xF;
1631 _active_notes[note->note()] = ev;
1633 /* outline all but right edge */
1634 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1636 /* outline all edges */
1637 ev->property_outline_what() = (guint32) 0xF;
1640 if (update_ghost_regions) {
1641 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1642 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1644 gr->update_note (ev);
1651 MidiRegionView::update_hit (CanvasHit* ev)
1653 boost::shared_ptr<NoteType> note = ev->note();
1655 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1656 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1657 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1658 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1662 return diamond_size;
1665 /** Add a MIDI note to the view (with length).
1667 * If in sustained mode, notes with length 0 will be considered active
1668 * notes, and resolve_note should be called when the corresponding note off
1669 * event arrives, to properly display the note.
1672 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1674 CanvasNoteEvent* event = 0;
1676 assert(note->time() >= 0);
1677 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1679 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1681 if (midi_view()->note_mode() == Sustained) {
1683 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1685 update_note (ev_rect);
1689 MidiGhostRegion* gr;
1691 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1692 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1693 gr->add_note(ev_rect);
1697 } else if (midi_view()->note_mode() == Percussive) {
1699 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1701 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1703 update_hit (ev_diamond);
1712 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1713 note_selected(event, true);
1716 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1717 event->show_velocity();
1720 event->on_channel_selection_change(_last_channel_selection);
1721 _events.push_back(event);
1730 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1731 MidiStreamView* const view = mtv->midi_view();
1733 view->update_note_range (note->note());
1737 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1738 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1740 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1742 /* potentially extend region to hold new note */
1744 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1745 framepos_t region_end = _region->last_frame();
1747 if (end_frame > region_end) {
1748 _region->set_length (end_frame - _region->position());
1751 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1752 MidiStreamView* const view = mtv->midi_view();
1754 view->update_note_range(new_note->note());
1756 _marked_for_selection.clear ();
1759 start_note_diff_command (_("step add"));
1760 note_diff_add_note (new_note, true, false);
1763 // last_step_edit_note = new_note;
1767 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1769 change_note_lengths (false, false, beats, false, true);
1773 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1775 assert (patch->time() >= 0);
1777 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (patch->time()));
1779 double const height = midi_stream_view()->contents_height();
1781 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1782 new CanvasPatchChange(*this, *_note_group,
1787 _custom_device_mode,
1791 // Show unless patch change is beyond the region bounds
1792 if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1793 patch_change->hide();
1795 patch_change->show();
1798 _patch_changes.push_back (patch_change);
1802 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1804 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1805 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1809 if (i != _model->patch_changes().end()) {
1810 key.msb = (*i)->bank_msb ();
1811 key.lsb = (*i)->bank_lsb ();
1812 key.program_number = (*i)->program ();
1814 key.msb = key.lsb = key.program_number = 0;
1817 assert (key.is_sane());
1822 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1824 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1826 if (pc.patch()->program() != new_patch.program_number) {
1827 c->change_program (pc.patch (), new_patch.program_number);
1830 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1831 if (pc.patch()->bank() != new_bank) {
1832 c->change_bank (pc.patch (), new_bank);
1835 _model->apply_command (*trackview.session(), c);
1837 _patch_changes.clear ();
1838 display_patch_changes ();
1842 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1844 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1846 if (old_change->time() != new_change.time()) {
1847 c->change_time (old_change, new_change.time());
1850 if (old_change->channel() != new_change.channel()) {
1851 c->change_channel (old_change, new_change.channel());
1854 if (old_change->program() != new_change.program()) {
1855 c->change_program (old_change, new_change.program());
1858 if (old_change->bank() != new_change.bank()) {
1859 c->change_bank (old_change, new_change.bank());
1862 _model->apply_command (*trackview.session(), c);
1864 _patch_changes.clear ();
1865 display_patch_changes ();
1868 /** Add a patch change to the region.
1869 * @param t Time in frames relative to region position
1870 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1871 * MidiTimeAxisView::get_channel_for_add())
1874 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1876 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1878 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1879 c->add (MidiModel::PatchChangePtr (
1880 new Evoral::PatchChange<Evoral::MusicalTime> (
1881 absolute_frames_to_source_beats (_region->position() + t),
1882 mtv->get_channel_for_add(), patch.program(), patch.bank()
1887 _model->apply_command (*trackview.session(), c);
1889 _patch_changes.clear ();
1890 display_patch_changes ();
1894 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1896 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1897 c->change_time (pc.patch (), t);
1898 _model->apply_command (*trackview.session(), c);
1900 _patch_changes.clear ();
1901 display_patch_changes ();
1905 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1907 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1908 c->remove (pc->patch ());
1909 _model->apply_command (*trackview.session(), c);
1911 _patch_changes.clear ();
1912 display_patch_changes ();
1916 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1918 if (patch.patch()->program() < 127) {
1919 MIDI::Name::PatchPrimaryKey key;
1920 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1921 key.program_number++;
1922 change_patch_change (patch, key);
1927 MidiRegionView::next_patch (CanvasPatchChange& patch)
1929 if (patch.patch()->program() > 0) {
1930 MIDI::Name::PatchPrimaryKey key;
1931 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1932 key.program_number--;
1933 change_patch_change (patch, key);
1938 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1940 if (patch.patch()->program() < 127) {
1941 MIDI::Name::PatchPrimaryKey key;
1942 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1945 change_patch_change (patch, key);
1950 change_patch_change (patch, key);
1957 MidiRegionView::next_bank (CanvasPatchChange& patch)
1959 if (patch.patch()->program() > 0) {
1960 MIDI::Name::PatchPrimaryKey key;
1961 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1962 if (key.lsb < 127) {
1964 change_patch_change (patch, key);
1966 if (key.msb < 127) {
1969 change_patch_change (patch, key);
1976 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1978 if (_selection.empty()) {
1982 _selection.erase (cne);
1986 MidiRegionView::delete_selection()
1988 if (_selection.empty()) {
1992 start_note_diff_command (_("delete selection"));
1994 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1995 if ((*i)->selected()) {
1996 _note_diff_command->remove((*i)->note());
2006 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2008 start_note_diff_command (_("delete note"));
2009 _note_diff_command->remove (n);
2012 trackview.editor().verbose_cursor()->hide ();
2016 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
2018 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2020 Selection::iterator tmp = i;
2023 (*i)->set_selected (false);
2024 (*i)->hide_velocity ();
2025 _selection.erase (i);
2033 /* this does not change the status of this regionview w.r.t the editor
2038 SelectionCleared (this); /* EMIT SIGNAL */
2043 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
2045 clear_selection_except (ev);
2047 /* don't bother with checking to see if we should remove this
2048 regionview from the editor selection, since we're about to add
2049 another note, and thus put/keep this regionview in the editor
2053 if (!ev->selected()) {
2054 add_to_selection (ev);
2059 MidiRegionView::select_all_notes ()
2063 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2064 add_to_selection (*i);
2069 MidiRegionView::select_range (framepos_t start, framepos_t end)
2073 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2074 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2075 if (t >= start && t <= end) {
2076 add_to_selection (*i);
2082 MidiRegionView::invert_selection ()
2084 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2085 if ((*i)->selected()) {
2086 remove_from_selection(*i);
2088 add_to_selection (*i);
2094 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2096 uint8_t low_note = 127;
2097 uint8_t high_note = 0;
2098 MidiModel::Notes& notes (_model->notes());
2099 _optimization_iterator = _events.begin();
2105 if (extend && _selection.empty()) {
2111 /* scan existing selection to get note range */
2113 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2114 if ((*i)->note()->note() < low_note) {
2115 low_note = (*i)->note()->note();
2117 if ((*i)->note()->note() > high_note) {
2118 high_note = (*i)->note()->note();
2122 low_note = min (low_note, notenum);
2123 high_note = max (high_note, notenum);
2126 _no_sound_notes = true;
2128 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2130 boost::shared_ptr<NoteType> note (*n);
2131 CanvasNoteEvent* cne;
2132 bool select = false;
2134 if (((1 << note->channel()) & channel_mask) != 0) {
2136 if ((note->note() >= low_note && note->note() <= high_note)) {
2139 } else if (note->note() == notenum) {
2145 if ((cne = find_canvas_note (note)) != 0) {
2146 // extend is false because we've taken care of it,
2147 // since it extends by time range, not pitch.
2148 note_selected (cne, add, false);
2152 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2156 _no_sound_notes = false;
2160 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2162 MidiModel::Notes& notes (_model->notes());
2163 _optimization_iterator = _events.begin();
2165 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2167 boost::shared_ptr<NoteType> note (*n);
2168 CanvasNoteEvent* cne;
2170 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2171 if ((cne = find_canvas_note (note)) != 0) {
2172 if (cne->selected()) {
2173 note_deselected (cne);
2175 note_selected (cne, true, false);
2183 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2186 clear_selection_except (ev);
2187 if (!_selection.empty()) {
2188 PublicEditor& editor (trackview.editor());
2189 editor.get_selection().add (this);
2195 if (!ev->selected()) {
2196 add_to_selection (ev);
2200 /* find end of latest note selected, select all between that and the start of "ev" */
2202 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2203 Evoral::MusicalTime latest = 0;
2205 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2206 if ((*i)->note()->end_time() > latest) {
2207 latest = (*i)->note()->end_time();
2209 if ((*i)->note()->time() < earliest) {
2210 earliest = (*i)->note()->time();
2214 if (ev->note()->end_time() > latest) {
2215 latest = ev->note()->end_time();
2218 if (ev->note()->time() < earliest) {
2219 earliest = ev->note()->time();
2222 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2224 /* find notes entirely within OR spanning the earliest..latest range */
2226 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2227 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2228 add_to_selection (*i);
2236 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2238 remove_from_selection (ev);
2242 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2252 // TODO: Make this faster by storing the last updated selection rect, and only
2253 // adjusting things that are in the area that appears/disappeared.
2254 // We probably need a tree to be able to find events in O(log(n)) time.
2256 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2258 /* check if any corner of the note is inside the rect
2261 1) this is computing "touched by", not "contained by" the rect.
2262 2) this does not require that events be sorted in time.
2265 const double ix1 = (*i)->x1();
2266 const double ix2 = (*i)->x2();
2267 const double iy1 = (*i)->y1();
2268 const double iy2 = (*i)->y2();
2270 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2271 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2272 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2273 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2276 if (!(*i)->selected()) {
2277 add_to_selection (*i);
2279 } else if ((*i)->selected() && !extend) {
2280 // Not inside rectangle
2281 remove_from_selection (*i);
2287 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2289 Selection::iterator i = _selection.find (ev);
2291 if (i != _selection.end()) {
2292 _selection.erase (i);
2295 ev->set_selected (false);
2296 ev->hide_velocity ();
2298 if (_selection.empty()) {
2299 PublicEditor& editor (trackview.editor());
2300 editor.get_selection().remove (this);
2305 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2307 bool add_mrv_selection = false;
2309 if (_selection.empty()) {
2310 add_mrv_selection = true;
2313 if (_selection.insert (ev).second) {
2314 ev->set_selected (true);
2315 play_midi_note ((ev)->note());
2318 if (add_mrv_selection) {
2319 PublicEditor& editor (trackview.editor());
2320 editor.get_selection().add (this);
2325 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2327 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2328 PossibleChord to_play;
2329 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2331 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2332 if ((*i)->note()->time() < earliest) {
2333 earliest = (*i)->note()->time();
2337 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2338 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2339 to_play.push_back ((*i)->note());
2341 (*i)->move_event(dx, dy);
2344 if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2346 if (to_play.size() > 1) {
2348 PossibleChord shifted;
2350 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2351 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2352 moved_note->set_note (moved_note->note() + cumulative_dy);
2353 shifted.push_back (moved_note);
2356 play_midi_chord (shifted);
2358 } else if (!to_play.empty()) {
2360 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2361 moved_note->set_note (moved_note->note() + cumulative_dy);
2362 play_midi_note (moved_note);
2368 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2370 assert (!_selection.empty());
2372 uint8_t lowest_note_in_selection = 127;
2373 uint8_t highest_note_in_selection = 0;
2374 uint8_t highest_note_difference = 0;
2376 // find highest and lowest notes first
2378 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2379 uint8_t pitch = (*i)->note()->note();
2380 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2381 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2385 cerr << "dnote: " << (int) dnote << endl;
2386 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2387 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2388 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2389 << int(highest_note_in_selection) << endl;
2390 cerr << "selection size: " << _selection.size() << endl;
2391 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2394 // Make sure the note pitch does not exceed the MIDI standard range
2395 if (highest_note_in_selection + dnote > 127) {
2396 highest_note_difference = highest_note_in_selection - 127;
2399 start_note_diff_command (_("move notes"));
2401 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2403 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2409 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2411 uint8_t original_pitch = (*i)->note()->note();
2412 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2414 // keep notes in standard midi range
2415 clamp_to_0_127(new_pitch);
2417 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2418 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2420 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2425 // care about notes being moved beyond the upper/lower bounds on the canvas
2426 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2427 highest_note_in_selection > midi_stream_view()->highest_note()) {
2428 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2432 /** @param x Pixel relative to the region position.
2433 * @return Snapped frame relative to the region position.
2436 MidiRegionView::snap_pixel_to_frame(double x)
2438 PublicEditor& editor (trackview.editor());
2439 return snap_frame_to_frame (editor.pixel_to_frame (x));
2442 /** @param x Pixel relative to the region position.
2443 * @return Snapped pixel relative to the region position.
2446 MidiRegionView::snap_to_pixel(double x)
2448 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2452 MidiRegionView::get_position_pixels()
2454 framepos_t region_frame = get_position();
2455 return trackview.editor().frame_to_pixel(region_frame);
2459 MidiRegionView::get_end_position_pixels()
2461 framepos_t frame = get_position() + get_duration ();
2462 return trackview.editor().frame_to_pixel(frame);
2466 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2468 /* the time converter will return the frame corresponding to `beats'
2469 relative to the start of the source. The start of the source
2470 is an implied position given by region->position - region->start
2472 const framepos_t source_start = _region->position() - _region->start();
2473 return source_start + _source_relative_time_converter.to (beats);
2477 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2479 /* the `frames' argument needs to be converted into a frame count
2480 relative to the start of the source before being passed in to the
2483 const framepos_t source_start = _region->position() - _region->start();
2484 return _source_relative_time_converter.from (frames - source_start);
2488 MidiRegionView::region_beats_to_region_frames(double beats) const
2490 return _region_relative_time_converter.to(beats);
2494 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2496 return _region_relative_time_converter.from(frames);
2500 MidiRegionView::begin_resizing (bool /*at_front*/)
2502 _resize_data.clear();
2504 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2505 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2507 // only insert CanvasNotes into the map
2509 NoteResizeData *resize_data = new NoteResizeData();
2510 resize_data->canvas_note = note;
2512 // create a new SimpleRect from the note which will be the resize preview
2513 SimpleRect *resize_rect = new SimpleRect(
2514 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2516 // calculate the colors: get the color settings
2517 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2518 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2521 // make the resize preview notes more transparent and bright
2522 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2524 // calculate color based on note velocity
2525 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2526 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2530 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2531 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2533 resize_data->resize_rect = resize_rect;
2534 _resize_data.push_back(resize_data);
2539 /** Update resizing notes while user drags.
2540 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2541 * @param at_front which end of the note (true == note on, false == note off)
2542 * @param delta_x change in mouse position since the start of the drag
2543 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2544 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2545 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2546 * as the \a primary note.
2549 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2551 bool cursor_set = false;
2553 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2554 SimpleRect* resize_rect = (*i)->resize_rect;
2555 CanvasNote* canvas_note = (*i)->canvas_note;
2560 current_x = canvas_note->x1() + delta_x;
2562 current_x = primary->x1() + delta_x;
2566 current_x = canvas_note->x2() + delta_x;
2568 current_x = primary->x2() + delta_x;
2573 resize_rect->property_x1() = snap_to_pixel(current_x);
2574 resize_rect->property_x2() = canvas_note->x2();
2576 resize_rect->property_x2() = snap_to_pixel(current_x);
2577 resize_rect->property_x1() = canvas_note->x1();
2583 beats = snap_pixel_to_frame (current_x);
2584 beats = region_frames_to_region_beats (beats);
2589 if (beats < canvas_note->note()->end_time()) {
2590 len = canvas_note->note()->time() - beats;
2591 len += canvas_note->note()->length();
2596 if (beats >= canvas_note->note()->time()) {
2597 len = beats - canvas_note->note()->time();
2604 snprintf (buf, sizeof (buf), "%.3g beats", len);
2605 show_verbose_cursor (buf, 0, 0);
2614 /** Finish resizing notes when the user releases the mouse button.
2615 * Parameters the same as for \a update_resizing().
2618 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2620 start_note_diff_command (_("resize notes"));
2622 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2623 CanvasNote* canvas_note = (*i)->canvas_note;
2624 SimpleRect* resize_rect = (*i)->resize_rect;
2626 /* Get the new x position for this resize, which is in pixels relative
2627 * to the region position.
2634 current_x = canvas_note->x1() + delta_x;
2636 current_x = primary->x1() + delta_x;
2640 current_x = canvas_note->x2() + delta_x;
2642 current_x = primary->x2() + delta_x;
2646 /* Convert that to a frame within the source */
2647 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2649 /* and then to beats */
2650 current_x = region_frames_to_region_beats (current_x);
2652 if (at_front && current_x < canvas_note->note()->end_time()) {
2653 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2655 double len = canvas_note->note()->time() - current_x;
2656 len += canvas_note->note()->length();
2659 /* XXX convert to beats */
2660 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2665 double len = current_x - canvas_note->note()->time();
2668 /* XXX convert to beats */
2669 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2677 _resize_data.clear();
2682 MidiRegionView::abort_resizing ()
2684 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2685 delete (*i)->resize_rect;
2689 _resize_data.clear ();
2693 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2695 uint8_t new_velocity;
2698 new_velocity = event->note()->velocity() + velocity;
2699 clamp_to_0_127(new_velocity);
2701 new_velocity = velocity;
2704 event->set_selected (event->selected()); // change color
2706 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2710 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2715 new_note = event->note()->note() + note;
2720 clamp_to_0_127 (new_note);
2721 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2725 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2727 bool change_start = false;
2728 bool change_length = false;
2729 Evoral::MusicalTime new_start = 0;
2730 Evoral::MusicalTime new_length = 0;
2732 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2734 front_delta: if positive - move the start of the note later in time (shortening it)
2735 if negative - move the start of the note earlier in time (lengthening it)
2737 end_delta: if positive - move the end of the note later in time (lengthening it)
2738 if negative - move the end of the note earlier in time (shortening it)
2742 if (front_delta < 0) {
2744 if (event->note()->time() < -front_delta) {
2747 new_start = event->note()->time() + front_delta; // moves earlier
2750 /* start moved toward zero, so move the end point out to where it used to be.
2751 Note that front_delta is negative, so this increases the length.
2754 new_length = event->note()->length() - front_delta;
2755 change_start = true;
2756 change_length = true;
2760 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2762 if (new_pos < event->note()->end_time()) {
2763 new_start = event->note()->time() + front_delta;
2764 /* start moved toward the end, so move the end point back to where it used to be */
2765 new_length = event->note()->length() - front_delta;
2766 change_start = true;
2767 change_length = true;
2774 bool can_change = true;
2775 if (end_delta < 0) {
2776 if (event->note()->length() < -end_delta) {
2782 new_length = event->note()->length() + end_delta;
2783 change_length = true;
2788 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2791 if (change_length) {
2792 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2797 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2799 uint8_t new_channel;
2803 if (event->note()->channel() < -chn) {
2806 new_channel = event->note()->channel() + chn;
2809 new_channel = event->note()->channel() + chn;
2812 new_channel = (uint8_t) chn;
2815 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2819 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2821 Evoral::MusicalTime new_time;
2825 if (event->note()->time() < -delta) {
2828 new_time = event->note()->time() + delta;
2831 new_time = event->note()->time() + delta;
2837 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2841 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2843 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2847 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2851 if (_selection.empty()) {
2866 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2867 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2873 start_note_diff_command (_("change velocities"));
2875 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2876 Selection::iterator next = i;
2878 change_note_velocity (*i, delta, true);
2884 if (!_selection.empty()) {
2886 snprintf (buf, sizeof (buf), "Vel %d",
2887 (int) (*_selection.begin())->note()->velocity());
2888 show_verbose_cursor (buf, 10, 10);
2894 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2896 if (_selection.empty()) {
2913 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2915 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2919 if ((int8_t) (*i)->note()->note() + delta > 127) {
2926 start_note_diff_command (_("transpose"));
2928 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2929 Selection::iterator next = i;
2931 change_note_note (*i, delta, true);
2939 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2945 /* grab the current grid distance */
2947 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2949 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2950 cerr << "Grid type not available as beats - TO BE FIXED\n";
2960 start_note_diff_command (_("change note lengths"));
2962 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2963 Selection::iterator next = i;
2966 /* note the negation of the delta for start */
2968 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2977 MidiRegionView::nudge_notes (bool forward)
2979 if (_selection.empty()) {
2983 /* pick a note as the point along the timeline to get the nudge distance.
2984 its not necessarily the earliest note, so we may want to pull the notes out
2985 into a vector and sort before using the first one.
2988 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2990 framepos_t distance;
2992 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2994 /* grid is off - use nudge distance */
2996 distance = trackview.editor().get_nudge_distance (ref_point, unused);
3002 framepos_t next_pos = ref_point;
3005 if (max_framepos - 1 < next_pos) {
3009 if (next_pos == 0) {
3015 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
3016 distance = ref_point - next_pos;
3019 if (distance == 0) {
3023 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
3029 start_note_diff_command (_("nudge"));
3031 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3032 Selection::iterator next = i;
3034 change_note_time (*i, delta, true);
3042 MidiRegionView::change_channel(uint8_t channel)
3044 start_note_diff_command(_("change channel"));
3045 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3046 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3054 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3056 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3058 _pre_enter_cursor = editor->get_canvas_cursor ();
3060 if (_mouse_state == SelectTouchDragging) {
3061 note_selected (ev, true);
3064 show_verbose_cursor (ev->note ());
3068 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3070 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3072 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3073 (*i)->hide_velocity ();
3076 editor->verbose_cursor()->hide ();
3078 if (_pre_enter_cursor) {
3079 editor->set_canvas_cursor (_pre_enter_cursor);
3080 _pre_enter_cursor = 0;
3085 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3088 /* XXX should get patch name if we can */
3089 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3090 show_verbose_cursor (s.str(), 10, 20);
3094 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3096 trackview.editor().verbose_cursor()->hide ();
3100 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3102 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3104 if (x_fraction > 0.0 && x_fraction < 0.25) {
3105 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3106 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3107 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3109 if (_pre_enter_cursor && can_set_cursor) {
3110 editor->set_canvas_cursor (_pre_enter_cursor);
3116 MidiRegionView::set_frame_color()
3120 TimeAxisViewItem::set_frame_color ();
3127 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3128 } else if (high_enough_for_name) {
3129 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3134 if (!rect_visible) {
3135 f = UINT_RGBA_CHANGE_A (f, 0);
3138 frame->property_fill_color_rgba() = f;
3142 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3146 case FilterChannels:
3147 _force_channel = -1;
3150 _force_channel = mask;
3151 mask = 0xFFFF; // Show all notes as active (below)
3154 // Update notes for selection
3155 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3156 (*i)->on_channel_selection_change(mask);
3159 _last_channel_selection = mask;
3161 _patch_changes.clear ();
3162 display_patch_changes ();
3166 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3168 _model_name = model;
3169 _custom_device_mode = custom_device_mode;
3174 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3176 if (_selection.empty()) {
3180 PublicEditor& editor (trackview.editor());
3184 /* XXX what to do ? */
3188 editor.get_cut_buffer().add (selection_as_cut_buffer());
3196 start_note_diff_command();
3198 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3205 note_diff_remove_note (*i);
3215 MidiRegionView::selection_as_cut_buffer () const
3219 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3220 NoteType* n = (*i)->note().get();
3221 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3224 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3230 /** This method handles undo */
3232 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3238 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3240 trackview.session()->begin_reversible_command (_("paste"));
3242 start_note_diff_command (_("paste"));
3244 Evoral::MusicalTime beat_delta;
3245 Evoral::MusicalTime paste_pos_beats;
3246 Evoral::MusicalTime duration;
3247 Evoral::MusicalTime end_point = 0;
3249 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3250 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3251 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3252 paste_pos_beats = 0;
3254 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",
3255 (*mcb.notes().begin())->time(),
3256 (*mcb.notes().rbegin())->end_time(),
3257 duration, pos, _region->position(),
3258 paste_pos_beats, beat_delta));
3262 for (int n = 0; n < (int) times; ++n) {
3264 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3266 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3267 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3269 /* make all newly added notes selected */
3271 note_diff_add_note (copied_note, true);
3272 end_point = copied_note->end_time();
3275 paste_pos_beats += duration;
3278 /* if we pasted past the current end of the region, extend the region */
3280 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3281 framepos_t region_end = _region->position() + _region->length() - 1;
3283 if (end_frame > region_end) {
3285 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3287 _region->clear_changes ();
3288 _region->set_length (end_frame);
3289 trackview.session()->add_command (new StatefulDiffCommand (_region));
3294 trackview.session()->commit_reversible_command ();
3297 struct EventNoteTimeEarlyFirstComparator {
3298 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3299 return a->note()->time() < b->note()->time();
3304 MidiRegionView::time_sort_events ()
3306 if (!_sort_needed) {
3310 EventNoteTimeEarlyFirstComparator cmp;
3313 _sort_needed = false;
3317 MidiRegionView::goto_next_note (bool add_to_selection)
3319 bool use_next = false;
3321 if (_events.back()->selected()) {
3325 time_sort_events ();
3327 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3328 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3330 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3331 if ((*i)->selected()) {
3334 } else if (use_next) {
3335 if (channel_mask & (1 << (*i)->note()->channel())) {
3336 if (!add_to_selection) {
3339 note_selected (*i, true, false);
3346 /* use the first one */
3348 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3349 unique_select (_events.front());
3354 MidiRegionView::goto_previous_note (bool add_to_selection)
3356 bool use_next = false;
3358 if (_events.front()->selected()) {
3362 time_sort_events ();
3364 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3365 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3367 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3368 if ((*i)->selected()) {
3371 } else if (use_next) {
3372 if (channel_mask & (1 << (*i)->note()->channel())) {
3373 if (!add_to_selection) {
3376 note_selected (*i, true, false);
3383 /* use the last one */
3385 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3386 unique_select (*(_events.rbegin()));
3391 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3393 bool had_selected = false;
3395 time_sort_events ();
3397 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3398 if ((*i)->selected()) {
3399 selected.insert ((*i)->note());
3400 had_selected = true;
3404 if (allow_all_if_none_selected && !had_selected) {
3405 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3406 selected.insert ((*i)->note());
3412 MidiRegionView::update_ghost_note (double x, double y)
3414 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3419 _note_group->w2i (x, y);
3421 PublicEditor& editor = trackview.editor ();
3423 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3424 framecnt_t grid_frames;
3425 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3427 /* use region_frames... because we are converting a delta within the region
3430 double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3432 /* note that this sets the time of the ghost note in beats relative to
3433 the start of the source; that is how all note times are stored.
3435 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3436 _ghost_note->note()->set_length (length);
3437 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3438 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3440 /* the ghost note does not appear in ghost regions, so pass false in here */
3441 update_note (_ghost_note, false);
3443 show_verbose_cursor (_ghost_note->note ());
3447 MidiRegionView::create_ghost_note (double x, double y)
3452 boost::shared_ptr<NoteType> g (new NoteType);
3453 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3454 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3455 update_ghost_note (x, y);
3456 _ghost_note->show ();
3461 show_verbose_cursor (_ghost_note->note ());
3465 MidiRegionView::snap_changed ()
3471 create_ghost_note (_last_ghost_x, _last_ghost_y);
3475 MidiRegionView::drop_down_keys ()
3477 _mouse_state = None;
3481 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3483 double note = midi_stream_view()->y_to_note(y);
3485 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3487 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3489 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3490 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3491 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3492 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3497 bool add_mrv_selection = false;
3499 if (_selection.empty()) {
3500 add_mrv_selection = true;
3503 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3504 if (_selection.insert (*i).second) {
3505 (*i)->set_selected (true);
3509 if (add_mrv_selection) {
3510 PublicEditor& editor (trackview.editor());
3511 editor.get_selection().add (this);
3516 MidiRegionView::color_handler ()
3518 RegionView::color_handler ();
3520 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3521 (*i)->set_selected ((*i)->selected()); // will change color
3524 /* XXX probably more to do here */
3528 MidiRegionView::enable_display (bool yn)
3530 RegionView::enable_display (yn);
3537 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3539 if (_step_edit_cursor == 0) {
3540 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3542 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3543 _step_edit_cursor->property_y1() = 0;
3544 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3545 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3546 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3549 move_step_edit_cursor (pos);
3550 _step_edit_cursor->show ();
3554 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3556 _step_edit_cursor_position = pos;
3558 if (_step_edit_cursor) {
3559 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3560 _step_edit_cursor->property_x1() = pixel;
3561 set_step_edit_cursor_width (_step_edit_cursor_width);
3566 MidiRegionView::hide_step_edit_cursor ()
3568 if (_step_edit_cursor) {
3569 _step_edit_cursor->hide ();
3574 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3576 _step_edit_cursor_width = beats;
3578 if (_step_edit_cursor) {
3579 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3583 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3584 * @param w Source that the data will end up in.
3587 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3589 if (!_active_notes) {
3590 /* we aren't actively being recorded to */
3594 boost::shared_ptr<MidiSource> src = w.lock ();
3595 if (!src || src != midi_region()->midi_source()) {
3596 /* recorded data was not destined for our source */
3600 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3602 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3604 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3606 framepos_t back = max_framepos;
3608 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3609 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3610 assert (ev.buffer ());
3612 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3614 if (ev.type() == MIDI_CMD_NOTE_ON) {
3616 boost::shared_ptr<NoteType> note (
3617 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3620 add_note (note, true);
3622 /* fix up our note range */
3623 if (ev.note() < _current_range_min) {
3624 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3625 } else if (ev.note() > _current_range_max) {
3626 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3629 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3630 resolve_note (ev.note (), time_beats);
3636 midi_stream_view()->check_record_layers (region(), back);
3640 MidiRegionView::trim_front_starting ()
3642 /* Reparent the note group to the region view's parent, so that it doesn't change
3643 when the region view is trimmed.
3645 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3646 _temporary_note_group->move (group->property_x(), group->property_y());
3647 _note_group->reparent (*_temporary_note_group);
3651 MidiRegionView::trim_front_ending ()
3653 _note_group->reparent (*group);
3654 delete _temporary_note_group;
3655 _temporary_note_group = 0;
3657 if (_region->start() < 0) {
3658 /* Trim drag made start time -ve; fix this */
3659 midi_region()->fix_negative_start ();
3664 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3666 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3667 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3671 change_patch_change (pc->patch(), d.patch ());
3676 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3679 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3680 Evoral::midi_note_name (n->note()).c_str(),
3682 (int) n->channel() + 1,
3683 (int) n->velocity());
3685 show_verbose_cursor (buf, 10, 20);
3689 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3693 trackview.editor().get_pointer_position (wx, wy);
3698 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3700 double x1, y1, x2, y2;
3701 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3703 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3704 wy -= (y2 - y1) + 2 * yoffset;
3707 trackview.editor().verbose_cursor()->set (text, wx, wy);
3708 trackview.editor().verbose_cursor()->show ();
3711 /** @param p A session framepos.
3712 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3713 * @return p snapped to the grid subdivision underneath it.
3716 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3718 PublicEditor& editor = trackview.editor ();
3721 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3727 grid_frames = region_beats_to_region_frames (grid_beats);
3729 /* Hack so that we always snap to the note that we are over, instead of snapping
3730 to the next one if we're more than halfway through the one we're over.
3732 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3733 p -= grid_frames / 2;
3736 return snap_frame_to_frame (p);
3739 /** Called when the selection has been cleared in any MidiRegionView.
3740 * @param rv MidiRegionView that the selection was cleared in.
3743 MidiRegionView::selection_cleared (MidiRegionView* rv)
3749 /* Clear our selection in sympathy; but don't signal the fact */
3750 clear_selection (false);