2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include "gtkmm2ext/gtk_ui.h"
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "automation_region_view.h"
49 #include "automation_time_axis.h"
50 #include "canvas-hit.h"
51 #include "canvas-note.h"
52 #include "canvas_patch_change.h"
55 #include "editor_drag.h"
56 #include "ghostregion.h"
57 #include "gui_thread.h"
59 #include "midi_channel_dialog.h"
60 #include "midi_cut_buffer.h"
61 #include "midi_list_editor.h"
62 #include "midi_region_view.h"
63 #include "midi_streamview.h"
64 #include "midi_time_axis.h"
65 #include "midi_util.h"
66 #include "mouse_cursors.h"
67 #include "note_player.h"
68 #include "public_editor.h"
69 #include "rgb_macros.h"
70 #include "selection.h"
71 #include "simpleline.h"
72 #include "streamview.h"
74 #include "patch_change_dialog.h"
75 #include "verbose_cursor.h"
79 using namespace ARDOUR;
81 using namespace Editing;
82 using namespace ArdourCanvas;
83 using Gtkmm2ext::Keyboard;
85 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
87 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
89 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
90 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
91 : RegionView (parent, tv, r, spu, basic_color)
93 , _last_channel_selection(0xFFFF)
94 , _current_range_min(0)
95 , _current_range_max(0)
97 , _note_group(new ArdourCanvas::Group(*group))
98 , _note_diff_command (0)
100 , _step_edit_cursor (0)
101 , _step_edit_cursor_width (1.0)
102 , _step_edit_cursor_position (0.0)
103 , _channel_selection_scoped_note (0)
104 , _temporary_note_group (0)
107 , _sort_needed (true)
108 , _optimization_iterator (_events.end())
110 , _no_sound_notes (false)
113 , pre_enter_cursor (0)
114 , pre_press_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), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
120 connect_to_diskstream ();
122 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::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)
137 , _step_edit_cursor (0)
138 , _step_edit_cursor_width (1.0)
139 , _step_edit_cursor_position (0.0)
140 , _channel_selection_scoped_note (0)
141 , _temporary_note_group (0)
144 , _sort_needed (true)
145 , _optimization_iterator (_events.end())
147 , _no_sound_notes (false)
150 , pre_enter_cursor (0)
151 , pre_press_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), boost::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)
182 , _step_edit_cursor (0)
183 , _step_edit_cursor_width (1.0)
184 , _step_edit_cursor_position (0.0)
185 , _channel_selection_scoped_note (0)
186 , _temporary_note_group (0)
189 , _sort_needed (true)
190 , _optimization_iterator (_events.end())
192 , _no_sound_notes (false)
195 , pre_enter_cursor (0)
196 , pre_press_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)
217 , _step_edit_cursor (0)
218 , _step_edit_cursor_width (1.0)
219 , _step_edit_cursor_position (0.0)
220 , _channel_selection_scoped_note (0)
221 , _temporary_note_group (0)
224 , _sort_needed (true)
225 , _optimization_iterator (_events.end())
227 , _no_sound_notes (false)
230 , pre_enter_cursor (0)
231 , pre_press_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 boost::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 boost::bind (&MidiRegionView::snap_changed, this),
294 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
295 connect_to_diskstream ();
297 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::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 boost::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 (ev->type == GDK_2BUTTON_PRESS) {
333 return trackview.editor().toggle_internal_editing_from_double_click (ev);
336 if (!trackview.editor().internal_editing()) {
342 return scroll (&ev->scroll);
345 return key_press (&ev->key);
347 case GDK_KEY_RELEASE:
348 return key_release (&ev->key);
350 case GDK_BUTTON_PRESS:
351 return button_press (&ev->button);
353 case GDK_BUTTON_RELEASE:
354 return button_release (&ev->button);
356 case GDK_ENTER_NOTIFY:
357 return enter_notify (&ev->crossing);
359 case GDK_LEAVE_NOTIFY:
360 return leave_notify (&ev->crossing);
362 case GDK_MOTION_NOTIFY:
363 return motion (&ev->motion);
373 MidiRegionView::remove_ghost_note ()
380 MidiRegionView::enter_notify (GdkEventCrossing* ev)
382 trackview.editor().MouseModeChanged.connect (
383 _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
386 if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
387 create_ghost_note (ev->x, ev->y);
390 if (!trackview.editor().internal_editing()) {
391 Keyboard::magic_widget_drop_focus();
393 Keyboard::magic_widget_grab_focus();
401 MidiRegionView::leave_notify (GdkEventCrossing*)
403 _mouse_mode_connection.disconnect ();
405 trackview.editor().verbose_cursor()->hide ();
406 remove_ghost_note ();
412 MidiRegionView::mouse_mode_changed ()
414 if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
415 create_ghost_note (_last_event_x, _last_event_y);
417 remove_ghost_note ();
418 trackview.editor().verbose_cursor()->hide ();
421 if (!trackview.editor().internal_editing()) {
422 Keyboard::magic_widget_drop_focus();
424 Keyboard::magic_widget_grab_focus();
430 MidiRegionView::button_press (GdkEventButton* ev)
432 if (ev->button != 1) {
436 Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
437 MouseMode m = editor->current_mouse_mode();
439 if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
440 pre_press_cursor = editor->get_canvas_cursor ();
441 editor->set_canvas_cursor (editor->cursors()->midi_pencil);
444 if (_mouse_state != SelectTouchDragging) {
446 _pressed_button = ev->button;
447 _mouse_state = Pressed;
452 _pressed_button = ev->button;
458 MidiRegionView::button_release (GdkEventButton* ev)
460 double event_x, event_y;
462 if (ev->button != 1) {
469 group->w2i(event_x, event_y);
470 group->ungrab(ev->time);
472 PublicEditor& editor = trackview.editor ();
474 if (pre_press_cursor) {
475 dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
476 pre_press_cursor = 0;
479 switch (_mouse_state) {
480 case Pressed: // Clicked
482 switch (editor.current_mouse_mode()) {
484 /* no motion occured - simple click */
493 if (Keyboard::is_insert_note_event(ev)) {
495 double event_x, event_y;
499 group->w2i(event_x, event_y);
502 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
508 /* Shorten the length by 1 tick so that we can add a new note at the next
509 grid snap without it overlapping this one.
511 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
513 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
521 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
527 /* Shorten the length by 1 tick so that we can add a new note at the next
528 grid snap without it overlapping this one.
530 beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat;
532 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true);
543 case SelectRectDragging:
545 editor.drags()->end_grab ((GdkEvent *) ev);
547 create_ghost_note (ev->x, ev->y);
559 MidiRegionView::motion (GdkEventMotion* ev)
561 PublicEditor& editor = trackview.editor ();
563 if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
564 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
565 _mouse_state != AddDragging) {
567 create_ghost_note (ev->x, ev->y);
569 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
570 Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
572 update_ghost_note (ev->x, ev->y);
574 } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
576 remove_ghost_note ();
577 editor.verbose_cursor()->hide ();
579 } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
581 update_ghost_note (ev->x, ev->y);
584 /* any motion immediately hides velocity text that may have been visible */
586 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
587 (*i)->hide_velocity ();
590 switch (_mouse_state) {
593 if (_pressed_button == 1) {
595 MouseMode m = editor.current_mouse_mode();
597 if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
599 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
600 _mouse_state = AddDragging;
601 remove_ghost_note ();
602 editor.verbose_cursor()->hide ();
604 } else if (m == MouseObject) {
606 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
607 _mouse_state = SelectRectDragging;
609 } else if (m == MouseRange) {
610 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
611 _mouse_state = SelectVerticalDragging;
618 case SelectRectDragging:
619 case SelectVerticalDragging:
621 editor.drags()->motion_handler ((GdkEvent *) ev, false);
624 case SelectTouchDragging:
636 MidiRegionView::scroll (GdkEventScroll* ev)
638 if (_selection.empty()) {
642 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
643 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
644 it still works for zoom.
649 trackview.editor().verbose_cursor()->hide ();
651 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
653 if (ev->direction == GDK_SCROLL_UP) {
654 change_velocities (true, fine, false);
655 } else if (ev->direction == GDK_SCROLL_DOWN) {
656 change_velocities (false, fine, false);
662 MidiRegionView::key_press (GdkEventKey* ev)
664 /* since GTK bindings are generally activated on press, and since
665 detectable auto-repeat is the name of the game and only sends
666 repeated presses, carry out key actions at key press, not release.
669 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
671 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
672 _mouse_state = SelectTouchDragging;
675 } else if (ev->keyval == GDK_Escape && unmodified) {
679 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
681 bool start = (ev->keyval == GDK_comma);
682 bool end = (ev->keyval == GDK_period);
683 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
684 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
686 change_note_lengths (fine, shorter, 0.0, start, end);
690 } else if (ev->keyval == GDK_Delete && unmodified) {
695 } else if (ev->keyval == GDK_Tab) {
697 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
698 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
700 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
704 } else if (ev->keyval == GDK_ISO_Left_Tab) {
706 /* Shift-TAB generates ISO Left Tab, for some reason */
708 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
709 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
711 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
717 } else if (ev->keyval == GDK_Up) {
719 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
720 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
722 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
723 change_velocities (true, fine, allow_smush);
725 transpose (true, fine, allow_smush);
729 } else if (ev->keyval == GDK_Down) {
731 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
732 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
734 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
735 change_velocities (false, fine, allow_smush);
737 transpose (false, fine, allow_smush);
741 } else if (ev->keyval == GDK_Left && unmodified) {
746 } else if (ev->keyval == GDK_Right && unmodified) {
751 } else if (ev->keyval == GDK_c && unmodified) {
760 MidiRegionView::key_release (GdkEventKey* ev)
762 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
770 MidiRegionView::channel_edit ()
773 uint8_t current_channel = 0;
775 if (_selection.empty()) {
779 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
781 current_channel = (*i)->note()->channel ();
786 MidiChannelDialog channel_dialog (current_channel);
787 int ret = channel_dialog.run ();
790 case Gtk::RESPONSE_OK:
796 uint8_t new_channel = channel_dialog.active_channel ();
798 start_note_diff_command (_("channel edit"));
800 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
801 Selection::iterator next = i;
803 change_note_channel (*i, new_channel);
811 MidiRegionView::show_list_editor ()
814 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
816 _list_editor->present ();
819 /** Add a note to the model, and the view, at a canvas (click) coordinate.
820 * \param t time in frames relative to the position of the region
821 * \param y vertical position in pixels
822 * \param length duration of the note in beats
823 * \param snap_t true to snap t to the grid, otherwise false.
826 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t)
828 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
829 MidiStreamView* const view = mtv->midi_view();
831 double note = view->y_to_note(y);
834 assert(note <= 127.0);
836 // Start of note in frames relative to region start
838 framecnt_t grid_frames;
839 t = snap_frame_to_grid_underneath (t, grid_frames);
843 assert (length != 0);
845 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
846 region_frames_to_region_beats(t + _region->start()),
848 (uint8_t)note, 0x40));
850 if (_model->contains (new_note)) {
854 view->update_note_range(new_note->note());
856 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
858 _model->apply_command(*trackview.session(), cmd);
860 play_midi_note (new_note);
864 MidiRegionView::clear_events()
869 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
870 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
875 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
880 _patch_changes.clear();
882 _optimization_iterator = _events.end();
886 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
890 content_connection.disconnect ();
891 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
895 if (_enable_display) {
901 MidiRegionView::start_note_diff_command (string name)
903 if (!_note_diff_command) {
904 _note_diff_command = _model->new_note_diff_command (name);
909 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
911 if (_note_diff_command) {
912 _note_diff_command->add (note);
915 _marked_for_selection.insert(note);
918 _marked_for_velocity.insert(note);
923 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
925 if (_note_diff_command && ev->note()) {
926 _note_diff_command->remove(ev->note());
931 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
932 MidiModel::NoteDiffCommand::Property property,
935 if (_note_diff_command) {
936 _note_diff_command->change (ev->note(), property, val);
941 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
942 MidiModel::NoteDiffCommand::Property property,
943 Evoral::MusicalTime val)
945 if (_note_diff_command) {
946 _note_diff_command->change (ev->note(), property, val);
951 MidiRegionView::apply_diff (bool as_subcommand)
955 if (!_note_diff_command) {
959 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
960 // Mark all selected notes for selection when model reloads
961 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
962 _marked_for_selection.insert((*i)->note());
967 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
969 _model->apply_command (*trackview.session(), _note_diff_command);
972 _note_diff_command = 0;
973 midi_view()->midi_track()->playlist_modified();
976 _marked_for_selection.clear();
979 _marked_for_velocity.clear();
983 MidiRegionView::abort_command()
985 delete _note_diff_command;
986 _note_diff_command = 0;
991 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
993 if (_optimization_iterator != _events.end()) {
994 ++_optimization_iterator;
997 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
998 return *_optimization_iterator;
1001 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1002 if ((*_optimization_iterator)->note() == note) {
1003 return *_optimization_iterator;
1011 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1013 MidiModel::Notes notes;
1014 _model->get_notes (notes, op, val, chan_mask);
1016 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1017 CanvasNoteEvent* cne = find_canvas_note (*n);
1025 MidiRegionView::redisplay_model()
1027 // Don't redisplay the model if we're currently recording and displaying that
1028 if (_active_notes) {
1036 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1037 (*i)->invalidate ();
1040 MidiModel::ReadLock lock(_model->read_lock());
1042 MidiModel::Notes& notes (_model->notes());
1043 _optimization_iterator = _events.begin();
1045 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1047 boost::shared_ptr<NoteType> note (*n);
1048 CanvasNoteEvent* cne;
1051 if (note_in_region_range (note, visible)) {
1053 if ((cne = find_canvas_note (note)) != 0) {
1060 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1062 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1074 add_note (note, visible);
1079 if ((cne = find_canvas_note (note)) != 0) {
1087 /* remove note items that are no longer valid */
1089 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1090 if (!(*i)->valid ()) {
1092 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1093 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1095 gr->remove_note (*i);
1100 i = _events.erase (i);
1107 _patch_changes.clear();
1111 display_patch_changes ();
1113 _marked_for_selection.clear ();
1114 _marked_for_velocity.clear ();
1116 /* we may have caused _events to contain things out of order (e.g. if a note
1117 moved earlier or later). we don't generally need them in time order, but
1118 make a note that a sort is required for those cases that require it.
1121 _sort_needed = true;
1125 MidiRegionView::display_patch_changes ()
1127 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1128 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1130 for (uint8_t i = 0; i < 16; ++i) {
1131 if (chn_mask & (1<<i)) {
1132 display_patch_changes_on_channel (i);
1134 /* TODO gray-out patch instad of not displaying it */
1139 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1141 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1143 if ((*i)->channel() != channel) {
1147 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1149 boost::shared_ptr<MIDI::Name::Patch> patch =
1150 MIDI::Name::MidiPatchManager::instance().find_patch(
1151 _model_name, _custom_device_mode, channel, patch_key);
1154 add_canvas_patch_change (*i, patch->name());
1157 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1158 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1159 add_canvas_patch_change (*i, buf);
1165 MidiRegionView::display_sysexes()
1167 bool have_periodic_system_messages = false;
1168 bool display_periodic_messages = true;
1170 if (!Config->get_never_display_periodic_midi()) {
1172 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1173 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1174 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1177 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1178 have_periodic_system_messages = true;
1184 if (have_periodic_system_messages) {
1185 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1187 /* get an approximate value for the number of samples per video frame */
1189 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1191 /* if we are zoomed out beyond than the cutoff (i.e. more
1192 * frames per pixel than frames per 4 video frames), don't
1193 * show periodic sysex messages.
1196 if (zoom > (video_frame*4)) {
1197 display_periodic_messages = false;
1201 display_periodic_messages = false;
1204 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1206 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1207 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1209 Evoral::MusicalTime time = (*i)->time();
1213 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1214 if (!display_periodic_messages) {
1222 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1223 str << int((*i)->buffer()[b]);
1224 if (b != (*i)->size() -1) {
1228 string text = str.str();
1230 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1232 double height = midi_stream_view()->contents_height();
1234 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1235 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1237 // Show unless message is beyond the region bounds
1238 if (time - _region->start() >= _region->length() || time < _region->start()) {
1244 _sys_exes.push_back(sysex);
1248 MidiRegionView::~MidiRegionView ()
1250 in_destructor = true;
1252 trackview.editor().verbose_cursor()->hide ();
1254 note_delete_connection.disconnect ();
1256 delete _list_editor;
1258 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1260 if (_active_notes) {
1268 delete _note_diff_command;
1269 delete _step_edit_cursor;
1270 delete _temporary_note_group;
1274 MidiRegionView::region_resized (const PropertyChange& what_changed)
1276 RegionView::region_resized(what_changed);
1278 if (what_changed.contains (ARDOUR::Properties::position)) {
1279 set_duration(_region->length(), 0);
1280 if (_enable_display) {
1287 MidiRegionView::reset_width_dependent_items (double pixel_width)
1289 RegionView::reset_width_dependent_items(pixel_width);
1290 assert(_pixel_width == pixel_width);
1292 if (_enable_display) {
1296 move_step_edit_cursor (_step_edit_cursor_position);
1297 set_step_edit_cursor_width (_step_edit_cursor_width);
1301 MidiRegionView::set_height (double height)
1303 static const double FUDGE = 2.0;
1304 const double old_height = _height;
1305 RegionView::set_height(height);
1306 _height = height - FUDGE;
1308 apply_note_range(midi_stream_view()->lowest_note(),
1309 midi_stream_view()->highest_note(),
1310 height != old_height + FUDGE);
1313 name_pixbuf->raise_to_top();
1316 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1317 (*x)->set_height (midi_stream_view()->contents_height());
1320 if (_step_edit_cursor) {
1321 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1326 /** Apply the current note range from the stream view
1327 * by repositioning/hiding notes as necessary
1330 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1332 if (!_enable_display) {
1336 if (!force && _current_range_min == min && _current_range_max == max) {
1340 _current_range_min = min;
1341 _current_range_max = max;
1343 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1344 CanvasNoteEvent* event = *i;
1345 boost::shared_ptr<NoteType> note (event->note());
1347 if (note->note() < _current_range_min ||
1348 note->note() > _current_range_max) {
1354 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1356 const double y1 = midi_stream_view()->note_to_y(note->note());
1357 const double y2 = y1 + floor(midi_stream_view()->note_height());
1359 cnote->property_y1() = y1;
1360 cnote->property_y2() = y2;
1362 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1364 const double diamond_size = update_hit (chit);
1366 chit->set_height (diamond_size);
1372 MidiRegionView::add_ghost (TimeAxisView& tv)
1376 double unit_position = _region->position () / samples_per_unit;
1377 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1378 MidiGhostRegion* ghost;
1380 if (mtv && mtv->midi_view()) {
1381 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1382 to allow having midi notes on top of note lines and waveforms.
1384 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1386 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1389 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1390 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1391 ghost->add_note(note);
1395 ghost->set_height ();
1396 ghost->set_duration (_region->length() / samples_per_unit);
1397 ghosts.push_back (ghost);
1399 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
1405 /** Begin tracking note state for successive calls to add_event
1408 MidiRegionView::begin_write()
1410 assert(!_active_notes);
1411 _active_notes = new CanvasNote*[128];
1412 for (unsigned i=0; i < 128; ++i) {
1413 _active_notes[i] = 0;
1418 /** Destroy note state for add_event
1421 MidiRegionView::end_write()
1423 delete[] _active_notes;
1425 _marked_for_selection.clear();
1426 _marked_for_velocity.clear();
1430 /** Resolve an active MIDI note (while recording).
1433 MidiRegionView::resolve_note(uint8_t note, double end_time)
1435 if (midi_view()->note_mode() != Sustained) {
1439 if (_active_notes && _active_notes[note]) {
1441 /* XXX is end_time really region-centric? I think so, because
1442 this is a new region that we're recording, so source zero is
1443 the same as region zero
1445 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1447 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1448 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1449 _active_notes[note] = 0;
1454 /** Extend active notes to rightmost edge of region (if length is changed)
1457 MidiRegionView::extend_active_notes()
1459 if (!_active_notes) {
1463 for (unsigned i=0; i < 128; ++i) {
1464 if (_active_notes[i]) {
1465 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1472 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1474 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1478 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1480 if (!route_ui || !route_ui->midi_track()) {
1484 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1490 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1492 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1496 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1498 if (!route_ui || !route_ui->midi_track()) {
1502 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1504 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1513 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1515 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1516 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1518 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1519 (note->note() <= midi_stream_view()->highest_note());
1524 /** Update a canvas note's size from its model note.
1525 * @param ev Canvas note to update.
1526 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1529 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1531 boost::shared_ptr<NoteType> note = ev->note();
1532 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1533 const double y1 = midi_stream_view()->note_to_y(note->note());
1535 ev->property_x1() = x;
1536 ev->property_y1() = y1;
1538 /* trim note display to not overlap the end of its region */
1540 if (note->length() > 0) {
1541 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1542 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1544 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1547 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1549 if (note->length() == 0) {
1550 if (_active_notes) {
1551 assert(note->note() < 128);
1552 // If this note is already active there's a stuck note,
1553 // finish the old note rectangle
1554 if (_active_notes[note->note()]) {
1555 CanvasNote* const old_rect = _active_notes[note->note()];
1556 boost::shared_ptr<NoteType> old_note = old_rect->note();
1557 old_rect->property_x2() = x;
1558 old_rect->property_outline_what() = (guint32) 0xF;
1560 _active_notes[note->note()] = ev;
1562 /* outline all but right edge */
1563 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1565 /* outline all edges */
1566 ev->property_outline_what() = (guint32) 0xF;
1569 if (update_ghost_regions) {
1570 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1571 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1573 gr->update_note (ev);
1580 MidiRegionView::update_hit (CanvasHit* ev)
1582 boost::shared_ptr<NoteType> note = ev->note();
1584 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1585 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1586 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1587 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1591 return diamond_size;
1594 /** Add a MIDI note to the view (with length).
1596 * If in sustained mode, notes with length 0 will be considered active
1597 * notes, and resolve_note should be called when the corresponding note off
1598 * event arrives, to properly display the note.
1601 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1603 CanvasNoteEvent* event = 0;
1605 assert(note->time() >= 0);
1606 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1608 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1610 if (midi_view()->note_mode() == Sustained) {
1612 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1614 update_note (ev_rect);
1618 MidiGhostRegion* gr;
1620 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1621 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1622 gr->add_note(ev_rect);
1626 } else if (midi_view()->note_mode() == Percussive) {
1628 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1630 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1632 update_hit (ev_diamond);
1641 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1642 note_selected(event, true);
1645 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1646 event->show_velocity();
1649 event->on_channel_selection_change(_last_channel_selection);
1650 _events.push_back(event);
1659 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1660 MidiStreamView* const view = mtv->midi_view();
1662 view->update_note_range (note->note());
1666 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1667 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1669 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1671 /* potentially extend region to hold new note */
1673 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1674 framepos_t region_end = _region->last_frame();
1676 if (end_frame > region_end) {
1677 _region->set_length (end_frame - _region->position());
1680 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1681 MidiStreamView* const view = mtv->midi_view();
1683 view->update_note_range(new_note->note());
1685 _marked_for_selection.clear ();
1688 start_note_diff_command (_("step add"));
1689 note_diff_add_note (new_note, true, false);
1692 // last_step_edit_note = new_note;
1696 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1698 change_note_lengths (false, false, beats, false, true);
1702 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1704 assert (patch->time() >= 0);
1706 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1707 const double x = trackview.editor().frame_to_pixel (region_frames);
1709 double const height = midi_stream_view()->contents_height();
1711 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1712 new CanvasPatchChange(*this, *_note_group,
1717 _custom_device_mode,
1721 // Show unless patch change is beyond the region bounds
1722 if (region_frames < 0 || region_frames >= _region->length()) {
1723 patch_change->hide();
1725 patch_change->show();
1728 _patch_changes.push_back (patch_change);
1732 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1734 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1735 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1739 if (i != _model->patch_changes().end()) {
1740 key.msb = (*i)->bank_msb ();
1741 key.lsb = (*i)->bank_lsb ();
1742 key.program_number = (*i)->program ();
1744 key.msb = key.lsb = key.program_number = 0;
1747 assert (key.is_sane());
1752 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1754 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1756 if (pc.patch()->program() != new_patch.program_number) {
1757 c->change_program (pc.patch (), new_patch.program_number);
1760 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1761 if (pc.patch()->bank() != new_bank) {
1762 c->change_bank (pc.patch (), new_bank);
1765 _model->apply_command (*trackview.session(), c);
1767 _patch_changes.clear ();
1768 display_patch_changes ();
1772 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1774 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1776 if (old_change->time() != new_change.time()) {
1777 c->change_time (old_change, new_change.time());
1780 if (old_change->channel() != new_change.channel()) {
1781 c->change_channel (old_change, new_change.channel());
1784 if (old_change->program() != new_change.program()) {
1785 c->change_program (old_change, new_change.program());
1788 if (old_change->bank() != new_change.bank()) {
1789 c->change_bank (old_change, new_change.bank());
1792 _model->apply_command (*trackview.session(), c);
1794 _patch_changes.clear ();
1795 display_patch_changes ();
1798 /** Add a patch change to the region.
1799 * @param t Time in frames relative to region position
1800 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1801 * MidiTimeAxisView::get_channel_for_add())
1804 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1806 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1808 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1809 c->add (MidiModel::PatchChangePtr (
1810 new Evoral::PatchChange<Evoral::MusicalTime> (
1811 absolute_frames_to_source_beats (_region->position() + t),
1812 mtv->get_channel_for_add(), patch.program(), patch.bank()
1817 _model->apply_command (*trackview.session(), c);
1819 _patch_changes.clear ();
1820 display_patch_changes ();
1824 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1826 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1827 c->change_time (pc.patch (), t);
1828 _model->apply_command (*trackview.session(), c);
1830 _patch_changes.clear ();
1831 display_patch_changes ();
1835 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1837 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1838 c->remove (pc->patch ());
1839 _model->apply_command (*trackview.session(), c);
1841 _patch_changes.clear ();
1842 display_patch_changes ();
1846 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1848 if (patch.patch()->program() < 127) {
1849 MIDI::Name::PatchPrimaryKey key;
1850 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1851 key.program_number++;
1852 change_patch_change (patch, key);
1857 MidiRegionView::next_patch (CanvasPatchChange& patch)
1859 if (patch.patch()->program() > 0) {
1860 MIDI::Name::PatchPrimaryKey key;
1861 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1862 key.program_number--;
1863 change_patch_change (patch, key);
1868 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1870 if (patch.patch()->program() < 127) {
1871 MIDI::Name::PatchPrimaryKey key;
1872 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1875 change_patch_change (patch, key);
1880 change_patch_change (patch, key);
1887 MidiRegionView::next_bank (CanvasPatchChange& patch)
1889 if (patch.patch()->program() > 0) {
1890 MIDI::Name::PatchPrimaryKey key;
1891 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1892 if (key.lsb < 127) {
1894 change_patch_change (patch, key);
1896 if (key.msb < 127) {
1899 change_patch_change (patch, key);
1906 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1908 if (_selection.empty()) {
1912 _selection.erase (cne);
1916 MidiRegionView::delete_selection()
1918 if (_selection.empty()) {
1922 start_note_diff_command (_("delete selection"));
1924 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1925 if ((*i)->selected()) {
1926 _note_diff_command->remove((*i)->note());
1936 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1938 start_note_diff_command (_("delete note"));
1939 _note_diff_command->remove (n);
1942 trackview.editor().verbose_cursor()->hide ();
1946 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1948 bool changed = false;
1950 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1952 Selection::iterator tmp = i;
1955 (*i)->set_selected (false);
1956 (*i)->hide_velocity ();
1957 _selection.erase (i);
1966 /* this does not change the status of this regionview w.r.t the editor
1970 if (changed && signal) {
1971 SelectionCleared (this); /* EMIT SIGNAL */
1976 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1978 clear_selection_except (ev);
1980 /* don't bother with checking to see if we should remove this
1981 regionview from the editor selection, since we're about to add
1982 another note, and thus put/keep this regionview in the editor
1986 if (!ev->selected()) {
1987 add_to_selection (ev);
1992 MidiRegionView::select_all_notes ()
1996 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1997 add_to_selection (*i);
2002 MidiRegionView::select_range (framepos_t start, framepos_t end)
2006 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2007 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
2008 if (t >= start && t <= end) {
2009 add_to_selection (*i);
2015 MidiRegionView::invert_selection ()
2017 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2018 if ((*i)->selected()) {
2019 remove_from_selection(*i);
2021 add_to_selection (*i);
2027 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2029 uint8_t low_note = 127;
2030 uint8_t high_note = 0;
2031 MidiModel::Notes& notes (_model->notes());
2032 _optimization_iterator = _events.begin();
2038 if (extend && _selection.empty()) {
2044 /* scan existing selection to get note range */
2046 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2047 if ((*i)->note()->note() < low_note) {
2048 low_note = (*i)->note()->note();
2050 if ((*i)->note()->note() > high_note) {
2051 high_note = (*i)->note()->note();
2055 low_note = min (low_note, notenum);
2056 high_note = max (high_note, notenum);
2059 _no_sound_notes = true;
2061 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2063 boost::shared_ptr<NoteType> note (*n);
2064 CanvasNoteEvent* cne;
2065 bool select = false;
2067 if (((1 << note->channel()) & channel_mask) != 0) {
2069 if ((note->note() >= low_note && note->note() <= high_note)) {
2072 } else if (note->note() == notenum) {
2078 if ((cne = find_canvas_note (note)) != 0) {
2079 // extend is false because we've taken care of it,
2080 // since it extends by time range, not pitch.
2081 note_selected (cne, add, false);
2085 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2089 _no_sound_notes = false;
2093 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2095 MidiModel::Notes& notes (_model->notes());
2096 _optimization_iterator = _events.begin();
2098 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2100 boost::shared_ptr<NoteType> note (*n);
2101 CanvasNoteEvent* cne;
2103 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2104 if ((cne = find_canvas_note (note)) != 0) {
2105 if (cne->selected()) {
2106 note_deselected (cne);
2108 note_selected (cne, true, false);
2116 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2119 clear_selection_except (ev);
2120 if (!_selection.empty()) {
2121 PublicEditor& editor (trackview.editor());
2122 editor.get_selection().add (this);
2128 if (!ev->selected()) {
2129 add_to_selection (ev);
2133 /* find end of latest note selected, select all between that and the start of "ev" */
2135 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2136 Evoral::MusicalTime latest = 0;
2138 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2139 if ((*i)->note()->end_time() > latest) {
2140 latest = (*i)->note()->end_time();
2142 if ((*i)->note()->time() < earliest) {
2143 earliest = (*i)->note()->time();
2147 if (ev->note()->end_time() > latest) {
2148 latest = ev->note()->end_time();
2151 if (ev->note()->time() < earliest) {
2152 earliest = ev->note()->time();
2155 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2157 /* find notes entirely within OR spanning the earliest..latest range */
2159 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2160 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2161 add_to_selection (*i);
2169 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2171 remove_from_selection (ev);
2175 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2185 // TODO: Make this faster by storing the last updated selection rect, and only
2186 // adjusting things that are in the area that appears/disappeared.
2187 // We probably need a tree to be able to find events in O(log(n)) time.
2189 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2191 /* check if any corner of the note is inside the rect
2194 1) this is computing "touched by", not "contained by" the rect.
2195 2) this does not require that events be sorted in time.
2198 const double ix1 = (*i)->x1();
2199 const double ix2 = (*i)->x2();
2200 const double iy1 = (*i)->y1();
2201 const double iy2 = (*i)->y2();
2203 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2204 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2205 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2206 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2209 if (!(*i)->selected()) {
2210 add_to_selection (*i);
2212 } else if ((*i)->selected() && !extend) {
2213 // Not inside rectangle
2214 remove_from_selection (*i);
2220 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2226 // TODO: Make this faster by storing the last updated selection rect, and only
2227 // adjusting things that are in the area that appears/disappeared.
2228 // We probably need a tree to be able to find events in O(log(n)) time.
2230 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2232 /* check if any corner of the note is inside the rect
2235 1) this is computing "touched by", not "contained by" the rect.
2236 2) this does not require that events be sorted in time.
2239 if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) {
2240 // within y- (note-) range
2241 if (!(*i)->selected()) {
2242 add_to_selection (*i);
2244 } else if ((*i)->selected() && !extend) {
2245 // Not inside rectangle
2246 remove_from_selection (*i);
2252 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2254 Selection::iterator i = _selection.find (ev);
2256 if (i != _selection.end()) {
2257 _selection.erase (i);
2260 ev->set_selected (false);
2261 ev->hide_velocity ();
2263 if (_selection.empty()) {
2264 PublicEditor& editor (trackview.editor());
2265 editor.get_selection().remove (this);
2270 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2272 bool add_mrv_selection = false;
2274 if (_selection.empty()) {
2275 add_mrv_selection = true;
2278 if (_selection.insert (ev).second) {
2279 ev->set_selected (true);
2280 play_midi_note ((ev)->note());
2283 if (add_mrv_selection) {
2284 PublicEditor& editor (trackview.editor());
2285 editor.get_selection().add (this);
2290 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2292 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2293 PossibleChord to_play;
2294 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2296 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2297 if ((*i)->note()->time() < earliest) {
2298 earliest = (*i)->note()->time();
2302 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2303 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2304 to_play.push_back ((*i)->note());
2306 (*i)->move_event(dx, dy);
2309 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2311 if (to_play.size() > 1) {
2313 PossibleChord shifted;
2315 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2316 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2317 moved_note->set_note (moved_note->note() + cumulative_dy);
2318 shifted.push_back (moved_note);
2321 play_midi_chord (shifted);
2323 } else if (!to_play.empty()) {
2325 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2326 moved_note->set_note (moved_note->note() + cumulative_dy);
2327 play_midi_note (moved_note);
2333 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2335 assert (!_selection.empty());
2337 uint8_t lowest_note_in_selection = 127;
2338 uint8_t highest_note_in_selection = 0;
2339 uint8_t highest_note_difference = 0;
2341 // find highest and lowest notes first
2343 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2344 uint8_t pitch = (*i)->note()->note();
2345 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2346 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2350 cerr << "dnote: " << (int) dnote << endl;
2351 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2352 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2353 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2354 << int(highest_note_in_selection) << endl;
2355 cerr << "selection size: " << _selection.size() << endl;
2356 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2359 // Make sure the note pitch does not exceed the MIDI standard range
2360 if (highest_note_in_selection + dnote > 127) {
2361 highest_note_difference = highest_note_in_selection - 127;
2364 start_note_diff_command (_("move notes"));
2366 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2368 framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
2369 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames);
2375 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2377 uint8_t original_pitch = (*i)->note()->note();
2378 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2380 // keep notes in standard midi range
2381 clamp_to_0_127(new_pitch);
2383 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2384 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2386 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2391 // care about notes being moved beyond the upper/lower bounds on the canvas
2392 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2393 highest_note_in_selection > midi_stream_view()->highest_note()) {
2394 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2398 /** @param x Pixel relative to the region position.
2399 * @return Snapped frame relative to the region position.
2402 MidiRegionView::snap_pixel_to_frame(double x)
2404 PublicEditor& editor (trackview.editor());
2405 return snap_frame_to_frame (editor.pixel_to_frame (x));
2408 /** @param x Pixel relative to the region position.
2409 * @return Snapped pixel relative to the region position.
2412 MidiRegionView::snap_to_pixel(double x)
2414 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2418 MidiRegionView::get_position_pixels()
2420 framepos_t region_frame = get_position();
2421 return trackview.editor().frame_to_pixel(region_frame);
2425 MidiRegionView::get_end_position_pixels()
2427 framepos_t frame = get_position() + get_duration ();
2428 return trackview.editor().frame_to_pixel(frame);
2432 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2434 /* the time converter will return the frame corresponding to `beats'
2435 relative to the start of the source. The start of the source
2436 is an implied position given by region->position - region->start
2438 const framepos_t source_start = _region->position() - _region->start();
2439 return source_start + _source_relative_time_converter.to (beats);
2443 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2445 /* the `frames' argument needs to be converted into a frame count
2446 relative to the start of the source before being passed in to the
2449 const framepos_t source_start = _region->position() - _region->start();
2450 return _source_relative_time_converter.from (frames - source_start);
2454 MidiRegionView::region_beats_to_region_frames(double beats) const
2456 return _region_relative_time_converter.to(beats);
2460 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2462 return _region_relative_time_converter.from(frames);
2466 MidiRegionView::begin_resizing (bool /*at_front*/)
2468 _resize_data.clear();
2470 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2471 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2473 // only insert CanvasNotes into the map
2475 NoteResizeData *resize_data = new NoteResizeData();
2476 resize_data->canvas_note = note;
2478 // create a new SimpleRect from the note which will be the resize preview
2479 SimpleRect *resize_rect = new SimpleRect(
2480 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2482 // calculate the colors: get the color settings
2483 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2484 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2487 // make the resize preview notes more transparent and bright
2488 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2490 // calculate color based on note velocity
2491 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2492 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2496 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2497 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2499 resize_data->resize_rect = resize_rect;
2500 _resize_data.push_back(resize_data);
2505 /** Update resizing notes while user drags.
2506 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2507 * @param at_front which end of the note (true == note on, false == note off)
2508 * @param delta_x change in mouse position since the start of the drag
2509 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2510 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2511 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2512 * as the \a primary note.
2515 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2517 bool cursor_set = false;
2519 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2520 SimpleRect* resize_rect = (*i)->resize_rect;
2521 CanvasNote* canvas_note = (*i)->canvas_note;
2526 current_x = canvas_note->x1() + delta_x;
2528 current_x = primary->x1() + delta_x;
2532 current_x = canvas_note->x2() + delta_x;
2534 current_x = primary->x2() + delta_x;
2539 resize_rect->property_x1() = snap_to_pixel(current_x);
2540 resize_rect->property_x2() = canvas_note->x2();
2542 resize_rect->property_x2() = snap_to_pixel(current_x);
2543 resize_rect->property_x1() = canvas_note->x1();
2549 beats = snap_pixel_to_frame (current_x);
2550 beats = region_frames_to_region_beats (beats);
2555 if (beats < canvas_note->note()->end_time()) {
2556 len = canvas_note->note()->time() - beats;
2557 len += canvas_note->note()->length();
2562 if (beats >= canvas_note->note()->time()) {
2563 len = beats - canvas_note->note()->time();
2570 snprintf (buf, sizeof (buf), "%.3g beats", len);
2571 show_verbose_cursor (buf, 0, 0);
2580 /** Finish resizing notes when the user releases the mouse button.
2581 * Parameters the same as for \a update_resizing().
2584 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2586 start_note_diff_command (_("resize notes"));
2588 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2589 CanvasNote* canvas_note = (*i)->canvas_note;
2590 SimpleRect* resize_rect = (*i)->resize_rect;
2592 /* Get the new x position for this resize, which is in pixels relative
2593 * to the region position.
2600 current_x = canvas_note->x1() + delta_x;
2602 current_x = primary->x1() + delta_x;
2606 current_x = canvas_note->x2() + delta_x;
2608 current_x = primary->x2() + delta_x;
2612 /* Convert that to a frame within the source */
2613 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2615 /* and then to beats */
2616 current_x = region_frames_to_region_beats (current_x);
2618 if (at_front && current_x < canvas_note->note()->end_time()) {
2619 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2621 double len = canvas_note->note()->time() - current_x;
2622 len += canvas_note->note()->length();
2625 /* XXX convert to beats */
2626 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2631 double len = current_x - canvas_note->note()->time();
2634 /* XXX convert to beats */
2635 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2643 _resize_data.clear();
2648 MidiRegionView::abort_resizing ()
2650 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2651 delete (*i)->resize_rect;
2655 _resize_data.clear ();
2659 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2661 uint8_t new_velocity;
2664 new_velocity = event->note()->velocity() + velocity;
2665 clamp_to_0_127(new_velocity);
2667 new_velocity = velocity;
2670 event->set_selected (event->selected()); // change color
2672 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2676 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2681 new_note = event->note()->note() + note;
2686 clamp_to_0_127 (new_note);
2687 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2691 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2693 bool change_start = false;
2694 bool change_length = false;
2695 Evoral::MusicalTime new_start = 0;
2696 Evoral::MusicalTime new_length = 0;
2698 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2700 front_delta: if positive - move the start of the note later in time (shortening it)
2701 if negative - move the start of the note earlier in time (lengthening it)
2703 end_delta: if positive - move the end of the note later in time (lengthening it)
2704 if negative - move the end of the note earlier in time (shortening it)
2708 if (front_delta < 0) {
2710 if (event->note()->time() < -front_delta) {
2713 new_start = event->note()->time() + front_delta; // moves earlier
2716 /* start moved toward zero, so move the end point out to where it used to be.
2717 Note that front_delta is negative, so this increases the length.
2720 new_length = event->note()->length() - front_delta;
2721 change_start = true;
2722 change_length = true;
2726 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2728 if (new_pos < event->note()->end_time()) {
2729 new_start = event->note()->time() + front_delta;
2730 /* start moved toward the end, so move the end point back to where it used to be */
2731 new_length = event->note()->length() - front_delta;
2732 change_start = true;
2733 change_length = true;
2740 bool can_change = true;
2741 if (end_delta < 0) {
2742 if (event->note()->length() < -end_delta) {
2748 new_length = event->note()->length() + end_delta;
2749 change_length = true;
2754 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2757 if (change_length) {
2758 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2763 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2765 uint8_t new_channel;
2769 if (event->note()->channel() < -chn) {
2772 new_channel = event->note()->channel() + chn;
2775 new_channel = event->note()->channel() + chn;
2778 new_channel = (uint8_t) chn;
2781 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2785 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2787 Evoral::MusicalTime new_time;
2791 if (event->note()->time() < -delta) {
2794 new_time = event->note()->time() + delta;
2797 new_time = event->note()->time() + delta;
2803 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2807 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2809 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2813 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2817 if (_selection.empty()) {
2832 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2833 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2839 start_note_diff_command (_("change velocities"));
2841 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2842 Selection::iterator next = i;
2844 change_note_velocity (*i, delta, true);
2850 if (!_selection.empty()) {
2852 snprintf (buf, sizeof (buf), "Vel %d",
2853 (int) (*_selection.begin())->note()->velocity());
2854 show_verbose_cursor (buf, 10, 10);
2860 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2862 if (_selection.empty()) {
2879 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2881 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2885 if ((int8_t) (*i)->note()->note() + delta > 127) {
2892 start_note_diff_command (_("transpose"));
2894 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2895 Selection::iterator next = i;
2897 change_note_note (*i, delta, true);
2905 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2911 /* grab the current grid distance */
2913 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2915 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2916 cerr << "Grid type not available as beats - TO BE FIXED\n";
2926 start_note_diff_command (_("change note lengths"));
2928 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2929 Selection::iterator next = i;
2932 /* note the negation of the delta for start */
2934 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2943 MidiRegionView::nudge_notes (bool forward)
2945 if (_selection.empty()) {
2949 /* pick a note as the point along the timeline to get the nudge distance.
2950 its not necessarily the earliest note, so we may want to pull the notes out
2951 into a vector and sort before using the first one.
2954 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2956 framecnt_t distance;
2958 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2960 /* grid is off - use nudge distance */
2962 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2968 framepos_t next_pos = ref_point;
2971 if (max_framepos - 1 < next_pos) {
2975 if (next_pos == 0) {
2981 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2982 distance = ref_point - next_pos;
2985 if (distance == 0) {
2989 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2995 start_note_diff_command (_("nudge"));
2997 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2998 Selection::iterator next = i;
3000 change_note_time (*i, delta, true);
3008 MidiRegionView::change_channel(uint8_t channel)
3010 start_note_diff_command(_("change channel"));
3011 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3012 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3020 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
3022 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3024 pre_enter_cursor = editor->get_canvas_cursor ();
3026 if (_mouse_state == SelectTouchDragging) {
3027 note_selected (ev, true);
3030 show_verbose_cursor (ev->note ());
3034 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
3036 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3038 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3039 (*i)->hide_velocity ();
3042 editor->verbose_cursor()->hide ();
3044 if (pre_enter_cursor) {
3045 editor->set_canvas_cursor (pre_enter_cursor);
3046 pre_enter_cursor = 0;
3051 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
3054 /* XXX should get patch name if we can */
3055 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3056 show_verbose_cursor (s.str(), 10, 20);
3060 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3062 trackview.editor().verbose_cursor()->hide ();
3066 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3068 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3069 Editing::MouseMode mm = editor->current_mouse_mode();
3070 bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
3072 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3073 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3074 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3075 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3077 if (pre_enter_cursor && can_set_cursor) {
3078 editor->set_canvas_cursor (pre_enter_cursor);
3084 MidiRegionView::set_frame_color()
3088 TimeAxisViewItem::set_frame_color ();
3095 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3096 } else if (high_enough_for_name) {
3097 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3102 if (!rect_visible) {
3103 f = UINT_RGBA_CHANGE_A (f, 0);
3106 frame->property_fill_color_rgba() = f;
3110 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3114 case FilterChannels:
3115 _force_channel = -1;
3118 _force_channel = mask;
3119 mask = 0xFFFF; // Show all notes as active (below)
3122 // Update notes for selection
3123 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3124 (*i)->on_channel_selection_change(mask);
3127 _last_channel_selection = mask;
3129 _patch_changes.clear ();
3130 display_patch_changes ();
3134 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3136 _model_name = model;
3137 _custom_device_mode = custom_device_mode;
3142 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3144 if (_selection.empty()) {
3148 PublicEditor& editor (trackview.editor());
3152 /* XXX what to do ? */
3156 editor.get_cut_buffer().add (selection_as_cut_buffer());
3164 start_note_diff_command();
3166 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3173 note_diff_remove_note (*i);
3183 MidiRegionView::selection_as_cut_buffer () const
3187 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3188 NoteType* n = (*i)->note().get();
3189 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3192 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3198 /** This method handles undo */
3200 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3206 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3208 trackview.session()->begin_reversible_command (_("paste"));
3210 start_note_diff_command (_("paste"));
3212 Evoral::MusicalTime beat_delta;
3213 Evoral::MusicalTime paste_pos_beats;
3214 Evoral::MusicalTime duration;
3215 Evoral::MusicalTime end_point = 0;
3217 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3218 paste_pos_beats = absolute_frames_to_source_beats (pos);
3219 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3220 paste_pos_beats = 0;
3222 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",
3223 (*mcb.notes().begin())->time(),
3224 (*mcb.notes().rbegin())->end_time(),
3225 duration, pos, _region->position(),
3226 paste_pos_beats, beat_delta));
3230 for (int n = 0; n < (int) times; ++n) {
3232 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3234 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3235 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3237 /* make all newly added notes selected */
3239 note_diff_add_note (copied_note, true);
3240 end_point = copied_note->end_time();
3243 paste_pos_beats += duration;
3246 /* if we pasted past the current end of the region, extend the region */
3248 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3249 framepos_t region_end = _region->position() + _region->length() - 1;
3251 if (end_frame > region_end) {
3253 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3255 _region->clear_changes ();
3256 _region->set_length (end_frame - _region->position());
3257 trackview.session()->add_command (new StatefulDiffCommand (_region));
3262 trackview.session()->commit_reversible_command ();
3265 struct EventNoteTimeEarlyFirstComparator {
3266 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3267 return a->note()->time() < b->note()->time();
3272 MidiRegionView::time_sort_events ()
3274 if (!_sort_needed) {
3278 EventNoteTimeEarlyFirstComparator cmp;
3281 _sort_needed = false;
3285 MidiRegionView::goto_next_note (bool add_to_selection)
3287 bool use_next = false;
3289 if (_events.back()->selected()) {
3293 time_sort_events ();
3295 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3296 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3298 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3299 if ((*i)->selected()) {
3302 } else if (use_next) {
3303 if (channel_mask & (1 << (*i)->note()->channel())) {
3304 if (!add_to_selection) {
3307 note_selected (*i, true, false);
3314 /* use the first one */
3316 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3317 unique_select (_events.front());
3322 MidiRegionView::goto_previous_note (bool add_to_selection)
3324 bool use_next = false;
3326 if (_events.front()->selected()) {
3330 time_sort_events ();
3332 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3333 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3335 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3336 if ((*i)->selected()) {
3339 } else if (use_next) {
3340 if (channel_mask & (1 << (*i)->note()->channel())) {
3341 if (!add_to_selection) {
3344 note_selected (*i, true, false);
3351 /* use the last one */
3353 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3354 unique_select (*(_events.rbegin()));
3359 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3361 bool had_selected = false;
3363 time_sort_events ();
3365 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3366 if ((*i)->selected()) {
3367 selected.insert ((*i)->note());
3368 had_selected = true;
3372 if (allow_all_if_none_selected && !had_selected) {
3373 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3374 selected.insert ((*i)->note());
3380 MidiRegionView::update_ghost_note (double x, double y)
3382 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3387 _note_group->w2i (x, y);
3389 PublicEditor& editor = trackview.editor ();
3391 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3392 framecnt_t grid_frames;
3393 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3395 /* use region_frames... because we are converting a delta within the region
3399 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3405 /* note that this sets the time of the ghost note in beats relative to
3406 the start of the source; that is how all note times are stored.
3408 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3409 _ghost_note->note()->set_length (length);
3410 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3411 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3413 /* the ghost note does not appear in ghost regions, so pass false in here */
3414 update_note (_ghost_note, false);
3416 show_verbose_cursor (_ghost_note->note ());
3420 MidiRegionView::create_ghost_note (double x, double y)
3422 remove_ghost_note ();
3424 boost::shared_ptr<NoteType> g (new NoteType);
3425 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3426 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3427 update_ghost_note (x, y);
3428 _ghost_note->show ();
3433 show_verbose_cursor (_ghost_note->note ());
3437 MidiRegionView::snap_changed ()
3443 create_ghost_note (_last_ghost_x, _last_ghost_y);
3447 MidiRegionView::drop_down_keys ()
3449 _mouse_state = None;
3453 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3455 double note = midi_stream_view()->y_to_note(y);
3457 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3459 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3461 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3462 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3463 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3464 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3469 bool add_mrv_selection = false;
3471 if (_selection.empty()) {
3472 add_mrv_selection = true;
3475 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3476 if (_selection.insert (*i).second) {
3477 (*i)->set_selected (true);
3481 if (add_mrv_selection) {
3482 PublicEditor& editor (trackview.editor());
3483 editor.get_selection().add (this);
3488 MidiRegionView::color_handler ()
3490 RegionView::color_handler ();
3492 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3493 (*i)->set_selected ((*i)->selected()); // will change color
3496 /* XXX probably more to do here */
3500 MidiRegionView::enable_display (bool yn)
3502 RegionView::enable_display (yn);
3509 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3511 if (_step_edit_cursor == 0) {
3512 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3514 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3515 _step_edit_cursor->property_y1() = 0;
3516 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3517 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3518 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3521 move_step_edit_cursor (pos);
3522 _step_edit_cursor->show ();
3526 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3528 _step_edit_cursor_position = pos;
3530 if (_step_edit_cursor) {
3531 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3532 _step_edit_cursor->property_x1() = pixel;
3533 set_step_edit_cursor_width (_step_edit_cursor_width);
3538 MidiRegionView::hide_step_edit_cursor ()
3540 if (_step_edit_cursor) {
3541 _step_edit_cursor->hide ();
3546 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3548 _step_edit_cursor_width = beats;
3550 if (_step_edit_cursor) {
3551 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3555 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3556 * @param w Source that the data will end up in.
3559 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3561 if (!_active_notes) {
3562 /* we aren't actively being recorded to */
3566 boost::shared_ptr<MidiSource> src = w.lock ();
3567 if (!src || src != midi_region()->midi_source()) {
3568 /* recorded data was not destined for our source */
3572 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3574 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3576 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3578 framepos_t back = max_framepos;
3580 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3581 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3582 assert (ev.buffer ());
3584 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3585 frames from the start of the source, and so time_beats is in terms of the
3589 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3591 if (ev.type() == MIDI_CMD_NOTE_ON) {
3593 boost::shared_ptr<NoteType> note (
3594 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3597 add_note (note, true);
3599 /* fix up our note range */
3600 if (ev.note() < _current_range_min) {
3601 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3602 } else if (ev.note() > _current_range_max) {
3603 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3606 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3607 resolve_note (ev.note (), time_beats);
3613 midi_stream_view()->check_record_layers (region(), back);
3617 MidiRegionView::trim_front_starting ()
3619 /* Reparent the note group to the region view's parent, so that it doesn't change
3620 when the region view is trimmed.
3622 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3623 _temporary_note_group->move (group->property_x(), group->property_y());
3624 _note_group->reparent (*_temporary_note_group);
3628 MidiRegionView::trim_front_ending ()
3630 _note_group->reparent (*group);
3631 delete _temporary_note_group;
3632 _temporary_note_group = 0;
3634 if (_region->start() < 0) {
3635 /* Trim drag made start time -ve; fix this */
3636 midi_region()->fix_negative_start ();
3641 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3643 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3644 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3648 change_patch_change (pc->patch(), d.patch ());
3653 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3656 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3657 Evoral::midi_note_name (n->note()).c_str(),
3659 (int) n->channel() + 1,
3660 (int) n->velocity());
3662 show_verbose_cursor (buf, 10, 20);
3666 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3670 trackview.editor().get_pointer_position (wx, wy);
3675 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3677 double x1, y1, x2, y2;
3678 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3680 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3681 wy -= (y2 - y1) + 2 * yoffset;
3684 trackview.editor().verbose_cursor()->set (text, wx, wy);
3685 trackview.editor().verbose_cursor()->show ();
3688 /** @param p A session framepos.
3689 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3690 * @return p snapped to the grid subdivision underneath it.
3693 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3695 PublicEditor& editor = trackview.editor ();
3698 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3704 grid_frames = region_beats_to_region_frames (grid_beats);
3706 /* Hack so that we always snap to the note that we are over, instead of snapping
3707 to the next one if we're more than halfway through the one we're over.
3709 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3710 p -= grid_frames / 2;
3713 return snap_frame_to_frame (p);
3716 /** Called when the selection has been cleared in any MidiRegionView.
3717 * @param rv MidiRegionView that the selection was cleared in.
3720 MidiRegionView::selection_cleared (MidiRegionView* rv)
3726 /* Clear our selection in sympathy; but don't signal the fact */
3727 clear_selection (false);