2 Copyright (C) 2001-2011 Paul Davis
3 Author: David Robillard
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/MIDIEvent.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
48 #include "automation_region_view.h"
49 #include "automation_time_axis.h"
50 #include "canvas-hit.h"
51 #include "canvas-note.h"
52 #include "canvas_patch_change.h"
55 #include "editor_drag.h"
56 #include "ghostregion.h"
57 #include "gui_thread.h"
59 #include "midi_channel_dialog.h"
60 #include "midi_cut_buffer.h"
61 #include "midi_list_editor.h"
62 #include "midi_region_view.h"
63 #include "midi_streamview.h"
64 #include "midi_time_axis.h"
65 #include "midi_util.h"
66 #include "note_player.h"
67 #include "public_editor.h"
68 #include "rgb_macros.h"
69 #include "selection.h"
70 #include "simpleline.h"
71 #include "streamview.h"
73 #include "mouse_cursors.h"
74 #include "patch_change_dialog.h"
75 #include "verbose_cursor.h"
79 using namespace ARDOUR;
81 using namespace Editing;
82 using namespace ArdourCanvas;
83 using Gtkmm2ext::Keyboard;
85 PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
87 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
89 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
90 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
91 : RegionView (parent, tv, r, spu, basic_color)
93 , _last_channel_selection(0xFFFF)
94 , _current_range_min(0)
95 , _current_range_max(0)
97 , _note_group(new ArdourCanvas::Group(*group))
98 , _note_diff_command (0)
100 , _step_edit_cursor (0)
101 , _step_edit_cursor_width (1.0)
102 , _step_edit_cursor_position (0.0)
103 , _channel_selection_scoped_note (0)
104 , _temporary_note_group (0)
107 , _sort_needed (true)
108 , _optimization_iterator (_events.end())
110 , _no_sound_notes (false)
113 , _pre_enter_cursor (0)
115 _note_group->raise_to_top();
116 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
118 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
119 connect_to_diskstream ();
121 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
124 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
125 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
126 TimeAxisViewItem::Visibility visibility)
127 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
129 , _last_channel_selection(0xFFFF)
130 , _current_range_min(0)
131 , _current_range_max(0)
133 , _note_group(new ArdourCanvas::Group(*parent))
134 , _note_diff_command (0)
136 , _step_edit_cursor (0)
137 , _step_edit_cursor_width (1.0)
138 , _step_edit_cursor_position (0.0)
139 , _channel_selection_scoped_note (0)
140 , _temporary_note_group (0)
143 , _sort_needed (true)
144 , _optimization_iterator (_events.end())
146 , _no_sound_notes (false)
149 , _pre_enter_cursor (0)
151 _note_group->raise_to_top();
152 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
154 connect_to_diskstream ();
156 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
160 MidiRegionView::parameter_changed (std::string const & p)
162 if (p == "diplay-first-midi-bank-as-zero") {
163 if (_enable_display) {
169 MidiRegionView::MidiRegionView (const MidiRegionView& other)
170 : sigc::trackable(other)
173 , _last_channel_selection(0xFFFF)
174 , _current_range_min(0)
175 , _current_range_max(0)
177 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
178 , _note_diff_command (0)
180 , _step_edit_cursor (0)
181 , _step_edit_cursor_width (1.0)
182 , _step_edit_cursor_position (0.0)
183 , _channel_selection_scoped_note (0)
184 , _temporary_note_group (0)
187 , _sort_needed (true)
188 , _optimization_iterator (_events.end())
190 , _no_sound_notes (false)
193 , _pre_enter_cursor (0)
198 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
199 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
204 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
205 : RegionView (other, boost::shared_ptr<Region> (region))
207 , _last_channel_selection(0xFFFF)
208 , _current_range_min(0)
209 , _current_range_max(0)
211 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
212 , _note_diff_command (0)
214 , _step_edit_cursor (0)
215 , _step_edit_cursor_width (1.0)
216 , _step_edit_cursor_position (0.0)
217 , _channel_selection_scoped_note (0)
218 , _temporary_note_group (0)
221 , _sort_needed (true)
222 , _optimization_iterator (_events.end())
224 , _no_sound_notes (false)
227 , _pre_enter_cursor (0)
232 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
233 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
239 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
241 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
243 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
244 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
248 midi_region()->midi_source(0)->load_model();
251 _model = midi_region()->midi_source(0)->model();
252 _enable_display = false;
254 RegionView::init (basic_color, false);
256 compute_colors (basic_color);
258 set_height (trackview.current_height());
261 region_sync_changed ();
262 region_resized (ARDOUR::bounds_change);
265 reset_width_dependent_items (_pixel_width);
269 _enable_display = true;
272 display_model (_model);
276 group->raise_to_top();
277 group->signal_event().connect(
278 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
280 midi_view()->signal_channel_mode_changed().connect(
281 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
283 midi_view()->signal_midi_patch_settings_changed().connect(
284 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
286 trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
287 ui_bind(&MidiRegionView::snap_changed, this),
290 Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
291 connect_to_diskstream ();
293 SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), ui_bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
296 const boost::shared_ptr<ARDOUR::MidiRegion>
297 MidiRegionView::midi_region() const
299 return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
303 MidiRegionView::connect_to_diskstream ()
305 midi_view()->midi_track()->DataRecorded.connect(
306 *this, invalidator(*this),
307 ui_bind(&MidiRegionView::data_recorded, this, _1),
312 MidiRegionView::canvas_event(GdkEvent* ev)
315 case GDK_ENTER_NOTIFY:
316 case GDK_LEAVE_NOTIFY:
317 _last_event_x = ev->crossing.x;
318 _last_event_y = ev->crossing.y;
320 case GDK_MOTION_NOTIFY:
321 _last_event_x = ev->motion.x;
322 _last_event_y = ev->motion.y;
328 if (!trackview.editor().internal_editing()) {
334 return scroll (&ev->scroll);
337 return key_press (&ev->key);
339 case GDK_KEY_RELEASE:
340 return key_release (&ev->key);
342 case GDK_BUTTON_PRESS:
343 return button_press (&ev->button);
345 case GDK_2BUTTON_PRESS:
348 case GDK_BUTTON_RELEASE:
349 return button_release (&ev->button);
351 case GDK_ENTER_NOTIFY:
352 return enter_notify (&ev->crossing);
354 case GDK_LEAVE_NOTIFY:
355 return leave_notify (&ev->crossing);
357 case GDK_MOTION_NOTIFY:
358 return motion (&ev->motion);
368 MidiRegionView::remove_ghost_note ()
375 MidiRegionView::enter_notify (GdkEventCrossing* ev)
377 trackview.editor().MouseModeChanged.connect (
378 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
381 if (trackview.editor().current_mouse_mode() == MouseRange && _mouse_state != AddDragging) {
382 create_ghost_note (ev->x, ev->y);
385 if (!trackview.editor().internal_editing()) {
386 Keyboard::magic_widget_drop_focus();
388 Keyboard::magic_widget_grab_focus();
396 MidiRegionView::leave_notify (GdkEventCrossing*)
398 _mouse_mode_connection.disconnect ();
400 trackview.editor().verbose_cursor()->hide ();
401 remove_ghost_note ();
407 MidiRegionView::mouse_mode_changed ()
409 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
410 create_ghost_note (_last_event_x, _last_event_y);
412 remove_ghost_note ();
413 trackview.editor().verbose_cursor()->hide ();
416 if (!trackview.editor().internal_editing()) {
417 Keyboard::magic_widget_drop_focus();
419 Keyboard::magic_widget_grab_focus();
425 MidiRegionView::button_press (GdkEventButton* ev)
427 if (ev->button != 1) {
431 if (_mouse_state != SelectTouchDragging) {
433 _pressed_button = ev->button;
434 _mouse_state = Pressed;
439 _pressed_button = ev->button;
445 MidiRegionView::button_release (GdkEventButton* ev)
447 double event_x, event_y;
449 if (ev->button != 1) {
456 group->w2i(event_x, event_y);
457 group->ungrab(ev->time);
459 PublicEditor& editor = trackview.editor ();
461 switch (_mouse_state) {
462 case Pressed: // Clicked
464 switch (editor.current_mouse_mode()) {
470 if (Keyboard::is_insert_note_event(ev)) {
472 double event_x, event_y;
476 group->w2i(event_x, event_y);
479 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
485 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true, true);
493 Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x));
499 create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true, true);
510 case SelectRectDragging:
512 editor.drags()->end_grab ((GdkEvent *) ev);
514 create_ghost_note (ev->x, ev->y);
526 MidiRegionView::motion (GdkEventMotion* ev)
528 PublicEditor& editor = trackview.editor ();
530 if (!_ghost_note && editor.current_mouse_mode() != MouseRange
531 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
532 && _mouse_state != AddDragging) {
534 create_ghost_note (ev->x, ev->y);
535 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
536 && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
538 update_ghost_note (ev->x, ev->y);
539 } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
541 remove_ghost_note ();
543 editor.verbose_cursor()->hide ();
544 } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
545 update_ghost_note (ev->x, ev->y);
548 /* any motion immediately hides velocity text that may have been visible */
550 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
551 (*i)->hide_velocity ();
554 switch (_mouse_state) {
557 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
558 && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
560 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
561 _mouse_state = SelectRectDragging;
564 } else if (editor.internal_editing()) {
566 editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
567 _mouse_state = AddDragging;
569 remove_ghost_note ();
571 editor.verbose_cursor()->hide ();
578 case SelectRectDragging:
580 editor.drags()->motion_handler ((GdkEvent *) ev, false);
583 case SelectTouchDragging:
595 MidiRegionView::scroll (GdkEventScroll* ev)
597 if (_selection.empty()) {
601 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
602 /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
603 it still works for zoom.
608 trackview.editor().verbose_cursor()->hide ();
610 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
612 if (ev->direction == GDK_SCROLL_UP) {
613 change_velocities (true, fine, false);
614 } else if (ev->direction == GDK_SCROLL_DOWN) {
615 change_velocities (false, fine, false);
621 MidiRegionView::key_press (GdkEventKey* ev)
623 /* since GTK bindings are generally activated on press, and since
624 detectable auto-repeat is the name of the game and only sends
625 repeated presses, carry out key actions at key press, not release.
628 bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
630 if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
631 _mouse_state = SelectTouchDragging;
634 } else if (ev->keyval == GDK_Escape && unmodified) {
638 } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
640 bool start = (ev->keyval == GDK_comma);
641 bool end = (ev->keyval == GDK_period);
642 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
643 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
645 change_note_lengths (fine, shorter, 0.0, start, end);
649 } else if (ev->keyval == GDK_Delete && unmodified) {
654 } else if (ev->keyval == GDK_Tab) {
656 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
657 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
659 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
663 } else if (ev->keyval == GDK_ISO_Left_Tab) {
665 /* Shift-TAB generates ISO Left Tab, for some reason */
667 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
668 goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
670 goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
676 } else if (ev->keyval == GDK_Up) {
678 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
679 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
681 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
682 change_velocities (true, fine, allow_smush);
684 transpose (true, fine, allow_smush);
688 } else if (ev->keyval == GDK_Down) {
690 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
691 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
693 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
694 change_velocities (false, fine, allow_smush);
696 transpose (false, fine, allow_smush);
700 } else if (ev->keyval == GDK_Left && unmodified) {
705 } else if (ev->keyval == GDK_Right && unmodified) {
710 } else if (ev->keyval == GDK_c && unmodified) {
719 MidiRegionView::key_release (GdkEventKey* ev)
721 if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
729 MidiRegionView::channel_edit ()
732 uint8_t current_channel = 0;
734 if (_selection.empty()) {
738 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
740 current_channel = (*i)->note()->channel ();
745 MidiChannelDialog channel_dialog (current_channel);
746 int ret = channel_dialog.run ();
749 case Gtk::RESPONSE_OK:
755 uint8_t new_channel = channel_dialog.active_channel ();
757 start_note_diff_command (_("channel edit"));
759 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
760 Selection::iterator next = i;
762 change_note_channel (*i, new_channel);
770 MidiRegionView::show_list_editor ()
773 _list_editor = new MidiListEditor (trackview.session(), midi_region());
775 _list_editor->present ();
778 /** Add a note to the model, and the view, at a canvas (click) coordinate.
779 * \param t time in frames relative to the position of the region
780 * \param y vertical position in pixels
781 * \param length duration of the note in beats, which will be snapped to the grid
782 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
783 * \param snap_x true to snap x to the grid, otherwise false.
786 MidiRegionView::create_note_at (framepos_t t, double y, double length, bool sh, bool snap_x)
788 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
789 MidiStreamView* const view = mtv->midi_view();
791 double note = view->y_to_note(y);
794 assert(note <= 127.0);
796 // Start of note in frames relative to region start
798 framecnt_t grid_frames;
799 t = snap_frame_to_grid_underneath (t, grid_frames);
803 assert (length != 0);
806 /* shorten the note down, but rather than using 1 frame (which
807 would be the highest resolution, use 1 tick since all
808 musical data is essentially quantized to this unit. it
809 is bigger, but not by enough to make any difference.
811 old single frame code:
813 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
815 length -= 1.0/Timecode::BBT_Time::ticks_per_bar_division;
818 const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
819 region_frames_to_region_beats(t + _region->start()),
821 (uint8_t)note, 0x40));
823 if (_model->contains (new_note)) {
827 view->update_note_range(new_note->note());
829 MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
831 _model->apply_command(*trackview.session(), cmd);
833 play_midi_note (new_note);
837 MidiRegionView::clear_events()
842 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
843 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
848 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
853 _patch_changes.clear();
855 _optimization_iterator = _events.end();
859 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
863 content_connection.disconnect ();
864 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
868 if (_enable_display) {
874 MidiRegionView::start_note_diff_command (string name)
876 if (!_note_diff_command) {
877 _note_diff_command = _model->new_note_diff_command (name);
882 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
884 if (_note_diff_command) {
885 _note_diff_command->add (note);
888 _marked_for_selection.insert(note);
891 _marked_for_velocity.insert(note);
896 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
898 if (_note_diff_command && ev->note()) {
899 _note_diff_command->remove(ev->note());
904 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
905 MidiModel::NoteDiffCommand::Property property,
908 if (_note_diff_command) {
909 _note_diff_command->change (ev->note(), property, val);
914 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
915 MidiModel::NoteDiffCommand::Property property,
916 Evoral::MusicalTime val)
918 if (_note_diff_command) {
919 _note_diff_command->change (ev->note(), property, val);
924 MidiRegionView::apply_diff (bool as_subcommand)
928 if (!_note_diff_command) {
932 if ((add_or_remove = _note_diff_command->adds_or_removes())) {
933 // Mark all selected notes for selection when model reloads
934 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
935 _marked_for_selection.insert((*i)->note());
940 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
942 _model->apply_command (*trackview.session(), _note_diff_command);
945 _note_diff_command = 0;
946 midi_view()->midi_track()->playlist_modified();
949 _marked_for_selection.clear();
952 _marked_for_velocity.clear();
956 MidiRegionView::abort_command()
958 delete _note_diff_command;
959 _note_diff_command = 0;
964 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
966 if (_optimization_iterator != _events.end()) {
967 ++_optimization_iterator;
970 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
971 return *_optimization_iterator;
974 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
975 if ((*_optimization_iterator)->note() == note) {
976 return *_optimization_iterator;
984 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
986 MidiModel::Notes notes;
987 _model->get_notes (notes, op, val, chan_mask);
989 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
990 CanvasNoteEvent* cne = find_canvas_note (*n);
998 MidiRegionView::redisplay_model()
1000 // Don't redisplay the model if we're currently recording and displaying that
1001 if (_active_notes) {
1009 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1010 (*i)->invalidate ();
1013 MidiModel::ReadLock lock(_model->read_lock());
1015 MidiModel::Notes& notes (_model->notes());
1016 _optimization_iterator = _events.begin();
1018 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1020 boost::shared_ptr<NoteType> note (*n);
1021 CanvasNoteEvent* cne;
1024 if (note_in_region_range (note, visible)) {
1026 if ((cne = find_canvas_note (note)) != 0) {
1033 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1035 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1047 add_note (note, visible);
1052 if ((cne = find_canvas_note (note)) != 0) {
1060 /* remove note items that are no longer valid */
1062 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1063 if (!(*i)->valid ()) {
1065 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1066 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1068 gr->remove_note (*i);
1073 i = _events.erase (i);
1080 _patch_changes.clear();
1084 display_patch_changes ();
1086 _marked_for_selection.clear ();
1087 _marked_for_velocity.clear ();
1089 /* we may have caused _events to contain things out of order (e.g. if a note
1090 moved earlier or later). we don't generally need them in time order, but
1091 make a note that a sort is required for those cases that require it.
1094 _sort_needed = true;
1098 MidiRegionView::display_patch_changes ()
1100 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1101 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1103 for (uint8_t i = 0; i < 16; ++i) {
1104 if (chn_mask & (1<<i)) {
1105 display_patch_changes_on_channel (i);
1107 /* TODO gray-out patch instad of not displaying it */
1112 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1114 for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1116 if ((*i)->channel() != channel) {
1120 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1122 boost::shared_ptr<MIDI::Name::Patch> patch =
1123 MIDI::Name::MidiPatchManager::instance().find_patch(
1124 _model_name, _custom_device_mode, channel, patch_key);
1127 add_canvas_patch_change (*i, patch->name());
1130 /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1131 snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1132 add_canvas_patch_change (*i, buf);
1138 MidiRegionView::display_sysexes()
1140 bool have_periodic_system_messages = false;
1141 bool display_periodic_messages = true;
1143 if (!Config->get_never_display_periodic_midi()) {
1145 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1146 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1147 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1150 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1151 have_periodic_system_messages = true;
1157 if (have_periodic_system_messages) {
1158 double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1160 /* get an approximate value for the number of samples per video frame */
1162 double video_frame = trackview.session()->frame_rate() * (1.0/30);
1164 /* if we are zoomed out beyond than the cutoff (i.e. more
1165 * frames per pixel than frames per 4 video frames), don't
1166 * show periodic sysex messages.
1169 if (zoom > (video_frame*4)) {
1170 display_periodic_messages = false;
1174 display_periodic_messages = false;
1177 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1179 const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::MusicalTime> > mev =
1180 boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::MusicalTime> > (*i);
1182 Evoral::MusicalTime time = (*i)->time();
1186 if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
1187 if (!display_periodic_messages) {
1195 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1196 str << int((*i)->buffer()[b]);
1197 if (b != (*i)->size() -1) {
1201 string text = str.str();
1203 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1205 double height = midi_stream_view()->contents_height();
1207 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1208 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1210 // Show unless message is beyond the region bounds
1211 if (time - _region->start() >= _region->length() || time < _region->start()) {
1217 _sys_exes.push_back(sysex);
1221 MidiRegionView::~MidiRegionView ()
1223 in_destructor = true;
1225 trackview.editor().verbose_cursor()->hide ();
1227 note_delete_connection.disconnect ();
1229 delete _list_editor;
1231 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1233 if (_active_notes) {
1241 delete _note_diff_command;
1242 delete _step_edit_cursor;
1243 delete _temporary_note_group;
1247 MidiRegionView::region_resized (const PropertyChange& what_changed)
1249 RegionView::region_resized(what_changed);
1251 if (what_changed.contains (ARDOUR::Properties::position)) {
1252 set_duration(_region->length(), 0);
1253 if (_enable_display) {
1260 MidiRegionView::reset_width_dependent_items (double pixel_width)
1262 RegionView::reset_width_dependent_items(pixel_width);
1263 assert(_pixel_width == pixel_width);
1265 if (_enable_display) {
1269 move_step_edit_cursor (_step_edit_cursor_position);
1270 set_step_edit_cursor_width (_step_edit_cursor_width);
1274 MidiRegionView::set_height (double height)
1276 static const double FUDGE = 2.0;
1277 const double old_height = _height;
1278 RegionView::set_height(height);
1279 _height = height - FUDGE;
1281 apply_note_range(midi_stream_view()->lowest_note(),
1282 midi_stream_view()->highest_note(),
1283 height != old_height + FUDGE);
1286 name_pixbuf->raise_to_top();
1289 for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1290 (*x)->set_height (midi_stream_view()->contents_height());
1293 if (_step_edit_cursor) {
1294 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1299 /** Apply the current note range from the stream view
1300 * by repositioning/hiding notes as necessary
1303 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1305 if (!_enable_display) {
1309 if (!force && _current_range_min == min && _current_range_max == max) {
1313 _current_range_min = min;
1314 _current_range_max = max;
1316 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1317 CanvasNoteEvent* event = *i;
1318 boost::shared_ptr<NoteType> note (event->note());
1320 if (note->note() < _current_range_min ||
1321 note->note() > _current_range_max) {
1327 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1329 const double y1 = midi_stream_view()->note_to_y(note->note());
1330 const double y2 = y1 + floor(midi_stream_view()->note_height());
1332 cnote->property_y1() = y1;
1333 cnote->property_y2() = y2;
1335 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1337 const double diamond_size = update_hit (chit);
1339 chit->set_height (diamond_size);
1345 MidiRegionView::add_ghost (TimeAxisView& tv)
1349 double unit_position = _region->position () / samples_per_unit;
1350 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1351 MidiGhostRegion* ghost;
1353 if (mtv && mtv->midi_view()) {
1354 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1355 to allow having midi notes on top of note lines and waveforms.
1357 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1359 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1362 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1363 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1364 ghost->add_note(note);
1368 ghost->set_height ();
1369 ghost->set_duration (_region->length() / samples_per_unit);
1370 ghosts.push_back (ghost);
1372 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1378 /** Begin tracking note state for successive calls to add_event
1381 MidiRegionView::begin_write()
1383 assert(!_active_notes);
1384 _active_notes = new CanvasNote*[128];
1385 for (unsigned i=0; i < 128; ++i) {
1386 _active_notes[i] = 0;
1391 /** Destroy note state for add_event
1394 MidiRegionView::end_write()
1396 delete[] _active_notes;
1398 _marked_for_selection.clear();
1399 _marked_for_velocity.clear();
1403 /** Resolve an active MIDI note (while recording).
1406 MidiRegionView::resolve_note(uint8_t note, double end_time)
1408 if (midi_view()->note_mode() != Sustained) {
1412 if (_active_notes && _active_notes[note]) {
1414 /* XXX is end_time really region-centric? I think so, because
1415 this is a new region that we're recording, so source zero is
1416 the same as region zero
1418 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1420 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1421 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1422 _active_notes[note] = 0;
1427 /** Extend active notes to rightmost edge of region (if length is changed)
1430 MidiRegionView::extend_active_notes()
1432 if (!_active_notes) {
1436 for (unsigned i=0; i < 128; ++i) {
1437 if (_active_notes[i]) {
1438 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1445 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1447 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1451 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1453 if (!route_ui || !route_ui->midi_track()) {
1457 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1463 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1465 if (_no_sound_notes || !Config->get_sound_midi_notes()) {
1469 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1471 if (!route_ui || !route_ui->midi_track()) {
1475 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1477 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1486 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1488 const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1489 bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
1491 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1492 (note->note() <= midi_stream_view()->highest_note());
1497 /** Update a canvas note's size from its model note.
1498 * @param ev Canvas note to update.
1499 * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1502 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1504 boost::shared_ptr<NoteType> note = ev->note();
1506 const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1507 const double y1 = midi_stream_view()->note_to_y(note->note());
1509 ev->property_x1() = x;
1510 ev->property_y1() = y1;
1512 /* trim note display to not overlap the end of its region */
1514 if (note->length() > 0) {
1515 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1516 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1518 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1521 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1523 if (note->length() == 0) {
1524 if (_active_notes) {
1525 assert(note->note() < 128);
1526 // If this note is already active there's a stuck note,
1527 // finish the old note rectangle
1528 if (_active_notes[note->note()]) {
1529 CanvasNote* const old_rect = _active_notes[note->note()];
1530 boost::shared_ptr<NoteType> old_note = old_rect->note();
1531 old_rect->property_x2() = x;
1532 old_rect->property_outline_what() = (guint32) 0xF;
1534 _active_notes[note->note()] = ev;
1536 /* outline all but right edge */
1537 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1539 /* outline all edges */
1540 ev->property_outline_what() = (guint32) 0xF;
1543 if (update_ghost_regions) {
1544 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1545 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1547 gr->update_note (ev);
1554 MidiRegionView::update_hit (CanvasHit* ev)
1556 boost::shared_ptr<NoteType> note = ev->note();
1558 const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1559 const double x = trackview.editor().frame_to_pixel(note_start_frames);
1560 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1561 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1565 return diamond_size;
1568 /** Add a MIDI note to the view (with length).
1570 * If in sustained mode, notes with length 0 will be considered active
1571 * notes, and resolve_note should be called when the corresponding note off
1572 * event arrives, to properly display the note.
1575 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1577 CanvasNoteEvent* event = 0;
1579 assert(note->time() >= 0);
1580 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1582 //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1584 if (midi_view()->note_mode() == Sustained) {
1586 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1588 update_note (ev_rect);
1592 MidiGhostRegion* gr;
1594 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1595 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1596 gr->add_note(ev_rect);
1600 } else if (midi_view()->note_mode() == Percussive) {
1602 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1604 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1606 update_hit (ev_diamond);
1615 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1616 note_selected(event, true);
1619 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1620 event->show_velocity();
1623 event->on_channel_selection_change(_last_channel_selection);
1624 _events.push_back(event);
1633 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1634 MidiStreamView* const view = mtv->midi_view();
1636 view->update_note_range (note->note());
1640 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1641 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1643 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1645 /* potentially extend region to hold new note */
1647 framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1648 framepos_t region_end = _region->last_frame();
1650 if (end_frame > region_end) {
1651 _region->set_length (end_frame - _region->position());
1654 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1655 MidiStreamView* const view = mtv->midi_view();
1657 view->update_note_range(new_note->note());
1659 _marked_for_selection.clear ();
1662 start_note_diff_command (_("step add"));
1663 note_diff_add_note (new_note, true, false);
1666 // last_step_edit_note = new_note;
1670 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1672 change_note_lengths (false, false, beats, false, true);
1676 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1678 assert (patch->time() >= 0);
1680 framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1681 const double x = trackview.editor().frame_to_pixel (region_frames);
1683 double const height = midi_stream_view()->contents_height();
1685 boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1686 new CanvasPatchChange(*this, *_note_group,
1691 _custom_device_mode,
1695 // Show unless patch change is beyond the region bounds
1696 if (region_frames < 0 || region_frames >= _region->length()) {
1697 patch_change->hide();
1699 patch_change->show();
1702 _patch_changes.push_back (patch_change);
1706 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1708 MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1709 while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1713 if (i != _model->patch_changes().end()) {
1714 key.msb = (*i)->bank_msb ();
1715 key.lsb = (*i)->bank_lsb ();
1716 key.program_number = (*i)->program ();
1718 key.msb = key.lsb = key.program_number = 0;
1721 assert (key.is_sane());
1726 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1728 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1730 if (pc.patch()->program() != new_patch.program_number) {
1731 c->change_program (pc.patch (), new_patch.program_number);
1734 int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1735 if (pc.patch()->bank() != new_bank) {
1736 c->change_bank (pc.patch (), new_bank);
1739 _model->apply_command (*trackview.session(), c);
1741 _patch_changes.clear ();
1742 display_patch_changes ();
1746 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1748 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1750 if (old_change->time() != new_change.time()) {
1751 c->change_time (old_change, new_change.time());
1754 if (old_change->channel() != new_change.channel()) {
1755 c->change_channel (old_change, new_change.channel());
1758 if (old_change->program() != new_change.program()) {
1759 c->change_program (old_change, new_change.program());
1762 if (old_change->bank() != new_change.bank()) {
1763 c->change_bank (old_change, new_change.bank());
1766 _model->apply_command (*trackview.session(), c);
1768 _patch_changes.clear ();
1769 display_patch_changes ();
1772 /** Add a patch change to the region.
1773 * @param t Time in frames relative to region position
1774 * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1775 * MidiTimeAxisView::get_channel_for_add())
1778 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1780 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1782 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1783 c->add (MidiModel::PatchChangePtr (
1784 new Evoral::PatchChange<Evoral::MusicalTime> (
1785 absolute_frames_to_source_beats (_region->position() + t),
1786 mtv->get_channel_for_add(), patch.program(), patch.bank()
1791 _model->apply_command (*trackview.session(), c);
1793 _patch_changes.clear ();
1794 display_patch_changes ();
1798 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1800 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1801 c->change_time (pc.patch (), t);
1802 _model->apply_command (*trackview.session(), c);
1804 _patch_changes.clear ();
1805 display_patch_changes ();
1809 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1811 MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1812 c->remove (pc->patch ());
1813 _model->apply_command (*trackview.session(), c);
1815 _patch_changes.clear ();
1816 display_patch_changes ();
1820 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1822 if (patch.patch()->program() < 127) {
1823 MIDI::Name::PatchPrimaryKey key;
1824 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1825 key.program_number++;
1826 change_patch_change (patch, key);
1831 MidiRegionView::next_patch (CanvasPatchChange& patch)
1833 if (patch.patch()->program() > 0) {
1834 MIDI::Name::PatchPrimaryKey key;
1835 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1836 key.program_number--;
1837 change_patch_change (patch, key);
1842 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1844 if (patch.patch()->program() < 127) {
1845 MIDI::Name::PatchPrimaryKey key;
1846 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1849 change_patch_change (patch, key);
1854 change_patch_change (patch, key);
1861 MidiRegionView::next_bank (CanvasPatchChange& patch)
1863 if (patch.patch()->program() > 0) {
1864 MIDI::Name::PatchPrimaryKey key;
1865 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1866 if (key.lsb < 127) {
1868 change_patch_change (patch, key);
1870 if (key.msb < 127) {
1873 change_patch_change (patch, key);
1880 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1882 if (_selection.empty()) {
1886 _selection.erase (cne);
1890 MidiRegionView::delete_selection()
1892 if (_selection.empty()) {
1896 start_note_diff_command (_("delete selection"));
1898 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1899 if ((*i)->selected()) {
1900 _note_diff_command->remove((*i)->note());
1910 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1912 start_note_diff_command (_("delete note"));
1913 _note_diff_command->remove (n);
1916 trackview.editor().verbose_cursor()->hide ();
1920 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal)
1922 bool changed = false;
1924 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1926 Selection::iterator tmp = i;
1929 (*i)->set_selected (false);
1930 (*i)->hide_velocity ();
1931 _selection.erase (i);
1940 /* this does not change the status of this regionview w.r.t the editor
1944 if (changed && signal) {
1945 SelectionCleared (this); /* EMIT SIGNAL */
1950 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1952 clear_selection_except (ev);
1954 /* don't bother with checking to see if we should remove this
1955 regionview from the editor selection, since we're about to add
1956 another note, and thus put/keep this regionview in the editor
1960 if (!ev->selected()) {
1961 add_to_selection (ev);
1966 MidiRegionView::select_all_notes ()
1970 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1971 add_to_selection (*i);
1976 MidiRegionView::select_range (framepos_t start, framepos_t end)
1980 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1981 framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
1982 if (t >= start && t <= end) {
1983 add_to_selection (*i);
1989 MidiRegionView::invert_selection ()
1991 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1992 if ((*i)->selected()) {
1993 remove_from_selection(*i);
1995 add_to_selection (*i);
2001 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2003 uint8_t low_note = 127;
2004 uint8_t high_note = 0;
2005 MidiModel::Notes& notes (_model->notes());
2006 _optimization_iterator = _events.begin();
2012 if (extend && _selection.empty()) {
2018 /* scan existing selection to get note range */
2020 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2021 if ((*i)->note()->note() < low_note) {
2022 low_note = (*i)->note()->note();
2024 if ((*i)->note()->note() > high_note) {
2025 high_note = (*i)->note()->note();
2029 low_note = min (low_note, notenum);
2030 high_note = max (high_note, notenum);
2033 _no_sound_notes = true;
2035 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2037 boost::shared_ptr<NoteType> note (*n);
2038 CanvasNoteEvent* cne;
2039 bool select = false;
2041 if (((1 << note->channel()) & channel_mask) != 0) {
2043 if ((note->note() >= low_note && note->note() <= high_note)) {
2046 } else if (note->note() == notenum) {
2052 if ((cne = find_canvas_note (note)) != 0) {
2053 // extend is false because we've taken care of it,
2054 // since it extends by time range, not pitch.
2055 note_selected (cne, add, false);
2059 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2063 _no_sound_notes = false;
2067 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2069 MidiModel::Notes& notes (_model->notes());
2070 _optimization_iterator = _events.begin();
2072 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2074 boost::shared_ptr<NoteType> note (*n);
2075 CanvasNoteEvent* cne;
2077 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2078 if ((cne = find_canvas_note (note)) != 0) {
2079 if (cne->selected()) {
2080 note_deselected (cne);
2082 note_selected (cne, true, false);
2090 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2093 clear_selection_except (ev);
2094 if (!_selection.empty()) {
2095 PublicEditor& editor (trackview.editor());
2096 editor.get_selection().add (this);
2102 if (!ev->selected()) {
2103 add_to_selection (ev);
2107 /* find end of latest note selected, select all between that and the start of "ev" */
2109 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2110 Evoral::MusicalTime latest = 0;
2112 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2113 if ((*i)->note()->end_time() > latest) {
2114 latest = (*i)->note()->end_time();
2116 if ((*i)->note()->time() < earliest) {
2117 earliest = (*i)->note()->time();
2121 if (ev->note()->end_time() > latest) {
2122 latest = ev->note()->end_time();
2125 if (ev->note()->time() < earliest) {
2126 earliest = ev->note()->time();
2129 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2131 /* find notes entirely within OR spanning the earliest..latest range */
2133 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2134 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2135 add_to_selection (*i);
2143 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2145 remove_from_selection (ev);
2149 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2159 // TODO: Make this faster by storing the last updated selection rect, and only
2160 // adjusting things that are in the area that appears/disappeared.
2161 // We probably need a tree to be able to find events in O(log(n)) time.
2163 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2165 /* check if any corner of the note is inside the rect
2168 1) this is computing "touched by", not "contained by" the rect.
2169 2) this does not require that events be sorted in time.
2172 const double ix1 = (*i)->x1();
2173 const double ix2 = (*i)->x2();
2174 const double iy1 = (*i)->y1();
2175 const double iy2 = (*i)->y2();
2177 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2178 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2179 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2180 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2183 if (!(*i)->selected()) {
2184 add_to_selection (*i);
2186 } else if ((*i)->selected() && !extend) {
2187 // Not inside rectangle
2188 remove_from_selection (*i);
2194 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2196 Selection::iterator i = _selection.find (ev);
2198 if (i != _selection.end()) {
2199 _selection.erase (i);
2202 ev->set_selected (false);
2203 ev->hide_velocity ();
2205 if (_selection.empty()) {
2206 PublicEditor& editor (trackview.editor());
2207 editor.get_selection().remove (this);
2212 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2214 bool add_mrv_selection = false;
2216 if (_selection.empty()) {
2217 add_mrv_selection = true;
2220 if (_selection.insert (ev).second) {
2221 ev->set_selected (true);
2222 play_midi_note ((ev)->note());
2225 if (add_mrv_selection) {
2226 PublicEditor& editor (trackview.editor());
2227 editor.get_selection().add (this);
2232 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2234 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2235 PossibleChord to_play;
2236 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2238 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2239 if ((*i)->note()->time() < earliest) {
2240 earliest = (*i)->note()->time();
2244 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2245 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2246 to_play.push_back ((*i)->note());
2248 (*i)->move_event(dx, dy);
2251 if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
2253 if (to_play.size() > 1) {
2255 PossibleChord shifted;
2257 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2258 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2259 moved_note->set_note (moved_note->note() + cumulative_dy);
2260 shifted.push_back (moved_note);
2263 play_midi_chord (shifted);
2265 } else if (!to_play.empty()) {
2267 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2268 moved_note->set_note (moved_note->note() + cumulative_dy);
2269 play_midi_note (moved_note);
2275 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2277 assert (!_selection.empty());
2279 uint8_t lowest_note_in_selection = 127;
2280 uint8_t highest_note_in_selection = 0;
2281 uint8_t highest_note_difference = 0;
2283 // find highest and lowest notes first
2285 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2286 uint8_t pitch = (*i)->note()->note();
2287 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2288 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2292 cerr << "dnote: " << (int) dnote << endl;
2293 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2294 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2295 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2296 << int(highest_note_in_selection) << endl;
2297 cerr << "selection size: " << _selection.size() << endl;
2298 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2301 // Make sure the note pitch does not exceed the MIDI standard range
2302 if (highest_note_in_selection + dnote > 127) {
2303 highest_note_difference = highest_note_in_selection - 127;
2306 start_note_diff_command (_("move notes"));
2308 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2310 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2316 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2318 uint8_t original_pitch = (*i)->note()->note();
2319 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2321 // keep notes in standard midi range
2322 clamp_to_0_127(new_pitch);
2324 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2325 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2327 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2332 // care about notes being moved beyond the upper/lower bounds on the canvas
2333 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2334 highest_note_in_selection > midi_stream_view()->highest_note()) {
2335 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2339 /** @param x Pixel relative to the region position.
2340 * @return Snapped frame relative to the region position.
2343 MidiRegionView::snap_pixel_to_frame(double x)
2345 PublicEditor& editor (trackview.editor());
2346 return snap_frame_to_frame (editor.pixel_to_frame (x));
2349 /** @param x Pixel relative to the region position.
2350 * @return Snapped pixel relative to the region position.
2353 MidiRegionView::snap_to_pixel(double x)
2355 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2359 MidiRegionView::get_position_pixels()
2361 framepos_t region_frame = get_position();
2362 return trackview.editor().frame_to_pixel(region_frame);
2366 MidiRegionView::get_end_position_pixels()
2368 framepos_t frame = get_position() + get_duration ();
2369 return trackview.editor().frame_to_pixel(frame);
2373 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2375 /* the time converter will return the frame corresponding to `beats'
2376 relative to the start of the source. The start of the source
2377 is an implied position given by region->position - region->start
2379 const framepos_t source_start = _region->position() - _region->start();
2380 return source_start + _source_relative_time_converter.to (beats);
2384 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2386 /* the `frames' argument needs to be converted into a frame count
2387 relative to the start of the source before being passed in to the
2390 const framepos_t source_start = _region->position() - _region->start();
2391 return _source_relative_time_converter.from (frames - source_start);
2395 MidiRegionView::region_beats_to_region_frames(double beats) const
2397 return _region_relative_time_converter.to(beats);
2401 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2403 return _region_relative_time_converter.from(frames);
2407 MidiRegionView::begin_resizing (bool /*at_front*/)
2409 _resize_data.clear();
2411 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2412 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2414 // only insert CanvasNotes into the map
2416 NoteResizeData *resize_data = new NoteResizeData();
2417 resize_data->canvas_note = note;
2419 // create a new SimpleRect from the note which will be the resize preview
2420 SimpleRect *resize_rect = new SimpleRect(
2421 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2423 // calculate the colors: get the color settings
2424 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2425 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2428 // make the resize preview notes more transparent and bright
2429 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2431 // calculate color based on note velocity
2432 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2433 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2437 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2438 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2440 resize_data->resize_rect = resize_rect;
2441 _resize_data.push_back(resize_data);
2446 /** Update resizing notes while user drags.
2447 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2448 * @param at_front which end of the note (true == note on, false == note off)
2449 * @param delta_x change in mouse position since the start of the drag
2450 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2451 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2452 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2453 * as the \a primary note.
2456 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2458 bool cursor_set = false;
2460 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2461 SimpleRect* resize_rect = (*i)->resize_rect;
2462 CanvasNote* canvas_note = (*i)->canvas_note;
2467 current_x = canvas_note->x1() + delta_x;
2469 current_x = primary->x1() + delta_x;
2473 current_x = canvas_note->x2() + delta_x;
2475 current_x = primary->x2() + delta_x;
2480 resize_rect->property_x1() = snap_to_pixel(current_x);
2481 resize_rect->property_x2() = canvas_note->x2();
2483 resize_rect->property_x2() = snap_to_pixel(current_x);
2484 resize_rect->property_x1() = canvas_note->x1();
2490 beats = snap_pixel_to_frame (current_x);
2491 beats = region_frames_to_region_beats (beats);
2496 if (beats < canvas_note->note()->end_time()) {
2497 len = canvas_note->note()->time() - beats;
2498 len += canvas_note->note()->length();
2503 if (beats >= canvas_note->note()->time()) {
2504 len = beats - canvas_note->note()->time();
2511 snprintf (buf, sizeof (buf), "%.3g beats", len);
2512 show_verbose_cursor (buf, 0, 0);
2521 /** Finish resizing notes when the user releases the mouse button.
2522 * Parameters the same as for \a update_resizing().
2525 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2527 start_note_diff_command (_("resize notes"));
2529 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2530 CanvasNote* canvas_note = (*i)->canvas_note;
2531 SimpleRect* resize_rect = (*i)->resize_rect;
2533 /* Get the new x position for this resize, which is in pixels relative
2534 * to the region position.
2541 current_x = canvas_note->x1() + delta_x;
2543 current_x = primary->x1() + delta_x;
2547 current_x = canvas_note->x2() + delta_x;
2549 current_x = primary->x2() + delta_x;
2553 /* Convert that to a frame within the source */
2554 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2556 /* and then to beats */
2557 current_x = region_frames_to_region_beats (current_x);
2559 if (at_front && current_x < canvas_note->note()->end_time()) {
2560 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2562 double len = canvas_note->note()->time() - current_x;
2563 len += canvas_note->note()->length();
2566 /* XXX convert to beats */
2567 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2572 double len = current_x - canvas_note->note()->time();
2575 /* XXX convert to beats */
2576 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2584 _resize_data.clear();
2589 MidiRegionView::abort_resizing ()
2591 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2592 delete (*i)->resize_rect;
2596 _resize_data.clear ();
2600 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2602 uint8_t new_velocity;
2605 new_velocity = event->note()->velocity() + velocity;
2606 clamp_to_0_127(new_velocity);
2608 new_velocity = velocity;
2611 event->set_selected (event->selected()); // change color
2613 note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2617 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2622 new_note = event->note()->note() + note;
2627 clamp_to_0_127 (new_note);
2628 note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2632 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2634 bool change_start = false;
2635 bool change_length = false;
2636 Evoral::MusicalTime new_start = 0;
2637 Evoral::MusicalTime new_length = 0;
2639 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2641 front_delta: if positive - move the start of the note later in time (shortening it)
2642 if negative - move the start of the note earlier in time (lengthening it)
2644 end_delta: if positive - move the end of the note later in time (lengthening it)
2645 if negative - move the end of the note earlier in time (shortening it)
2649 if (front_delta < 0) {
2651 if (event->note()->time() < -front_delta) {
2654 new_start = event->note()->time() + front_delta; // moves earlier
2657 /* start moved toward zero, so move the end point out to where it used to be.
2658 Note that front_delta is negative, so this increases the length.
2661 new_length = event->note()->length() - front_delta;
2662 change_start = true;
2663 change_length = true;
2667 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2669 if (new_pos < event->note()->end_time()) {
2670 new_start = event->note()->time() + front_delta;
2671 /* start moved toward the end, so move the end point back to where it used to be */
2672 new_length = event->note()->length() - front_delta;
2673 change_start = true;
2674 change_length = true;
2681 bool can_change = true;
2682 if (end_delta < 0) {
2683 if (event->note()->length() < -end_delta) {
2689 new_length = event->note()->length() + end_delta;
2690 change_length = true;
2695 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2698 if (change_length) {
2699 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2704 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2706 uint8_t new_channel;
2710 if (event->note()->channel() < -chn) {
2713 new_channel = event->note()->channel() + chn;
2716 new_channel = event->note()->channel() + chn;
2719 new_channel = (uint8_t) chn;
2722 note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2726 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2728 Evoral::MusicalTime new_time;
2732 if (event->note()->time() < -delta) {
2735 new_time = event->note()->time() + delta;
2738 new_time = event->note()->time() + delta;
2744 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2748 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2750 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2754 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2758 if (_selection.empty()) {
2773 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2774 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2780 start_note_diff_command (_("change velocities"));
2782 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2783 Selection::iterator next = i;
2785 change_note_velocity (*i, delta, true);
2791 if (!_selection.empty()) {
2793 snprintf (buf, sizeof (buf), "Vel %d",
2794 (int) (*_selection.begin())->note()->velocity());
2795 show_verbose_cursor (buf, 10, 10);
2801 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2803 if (_selection.empty()) {
2820 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2822 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2826 if ((int8_t) (*i)->note()->note() + delta > 127) {
2833 start_note_diff_command (_("transpose"));
2835 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2836 Selection::iterator next = i;
2838 change_note_note (*i, delta, true);
2846 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2852 /* grab the current grid distance */
2854 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2856 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2857 cerr << "Grid type not available as beats - TO BE FIXED\n";
2867 start_note_diff_command (_("change note lengths"));
2869 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2870 Selection::iterator next = i;
2873 /* note the negation of the delta for start */
2875 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2884 MidiRegionView::nudge_notes (bool forward)
2886 if (_selection.empty()) {
2890 /* pick a note as the point along the timeline to get the nudge distance.
2891 its not necessarily the earliest note, so we may want to pull the notes out
2892 into a vector and sort before using the first one.
2895 framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2897 framepos_t distance;
2899 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2901 /* grid is off - use nudge distance */
2903 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2909 framepos_t next_pos = ref_point;
2912 if (max_framepos - 1 < next_pos) {
2916 if (next_pos == 0) {
2922 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2923 distance = ref_point - next_pos;
2926 if (distance == 0) {
2930 Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2936 start_note_diff_command (_("nudge"));
2938 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2939 Selection::iterator next = i;
2941 change_note_time (*i, delta, true);
2949 MidiRegionView::change_channel(uint8_t channel)
2951 start_note_diff_command(_("change channel"));
2952 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2953 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2961 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2963 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2965 _pre_enter_cursor = editor->get_canvas_cursor ();
2967 if (_mouse_state == SelectTouchDragging) {
2968 note_selected (ev, true);
2971 show_verbose_cursor (ev->note ());
2975 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2977 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2979 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2980 (*i)->hide_velocity ();
2983 editor->verbose_cursor()->hide ();
2985 if (_pre_enter_cursor) {
2986 editor->set_canvas_cursor (_pre_enter_cursor);
2987 _pre_enter_cursor = 0;
2992 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2995 /* XXX should get patch name if we can */
2996 s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
2997 show_verbose_cursor (s.str(), 10, 20);
3001 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3003 trackview.editor().verbose_cursor()->hide ();
3007 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3009 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3011 if (x_fraction > 0.0 && x_fraction < 0.25) {
3012 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3013 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3014 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3016 if (_pre_enter_cursor && can_set_cursor) {
3017 editor->set_canvas_cursor (_pre_enter_cursor);
3023 MidiRegionView::set_frame_color()
3027 TimeAxisViewItem::set_frame_color ();
3034 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3035 } else if (high_enough_for_name) {
3036 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3041 if (!rect_visible) {
3042 f = UINT_RGBA_CHANGE_A (f, 0);
3045 frame->property_fill_color_rgba() = f;
3049 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3053 case FilterChannels:
3054 _force_channel = -1;
3057 _force_channel = mask;
3058 mask = 0xFFFF; // Show all notes as active (below)
3061 // Update notes for selection
3062 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3063 (*i)->on_channel_selection_change(mask);
3066 _last_channel_selection = mask;
3068 _patch_changes.clear ();
3069 display_patch_changes ();
3073 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3075 _model_name = model;
3076 _custom_device_mode = custom_device_mode;
3081 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3083 if (_selection.empty()) {
3087 PublicEditor& editor (trackview.editor());
3091 /* XXX what to do ? */
3095 editor.get_cut_buffer().add (selection_as_cut_buffer());
3103 start_note_diff_command();
3105 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3112 note_diff_remove_note (*i);
3122 MidiRegionView::selection_as_cut_buffer () const
3126 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3127 NoteType* n = (*i)->note().get();
3128 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3131 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3137 /** This method handles undo */
3139 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3145 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3147 trackview.session()->begin_reversible_command (_("paste"));
3149 start_note_diff_command (_("paste"));
3151 Evoral::MusicalTime beat_delta;
3152 Evoral::MusicalTime paste_pos_beats;
3153 Evoral::MusicalTime duration;
3154 Evoral::MusicalTime end_point = 0;
3156 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3157 paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3158 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3159 paste_pos_beats = 0;
3161 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",
3162 (*mcb.notes().begin())->time(),
3163 (*mcb.notes().rbegin())->end_time(),
3164 duration, pos, _region->position(),
3165 paste_pos_beats, beat_delta));
3169 for (int n = 0; n < (int) times; ++n) {
3171 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3173 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3174 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3176 /* make all newly added notes selected */
3178 note_diff_add_note (copied_note, true);
3179 end_point = copied_note->end_time();
3182 paste_pos_beats += duration;
3185 /* if we pasted past the current end of the region, extend the region */
3187 framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3188 framepos_t region_end = _region->position() + _region->length() - 1;
3190 if (end_frame > region_end) {
3192 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3194 _region->clear_changes ();
3195 _region->set_length (end_frame);
3196 trackview.session()->add_command (new StatefulDiffCommand (_region));
3201 trackview.session()->commit_reversible_command ();
3204 struct EventNoteTimeEarlyFirstComparator {
3205 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3206 return a->note()->time() < b->note()->time();
3211 MidiRegionView::time_sort_events ()
3213 if (!_sort_needed) {
3217 EventNoteTimeEarlyFirstComparator cmp;
3220 _sort_needed = false;
3224 MidiRegionView::goto_next_note (bool add_to_selection)
3226 bool use_next = false;
3228 if (_events.back()->selected()) {
3232 time_sort_events ();
3234 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3235 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3237 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3238 if ((*i)->selected()) {
3241 } else if (use_next) {
3242 if (channel_mask & (1 << (*i)->note()->channel())) {
3243 if (!add_to_selection) {
3246 note_selected (*i, true, false);
3253 /* use the first one */
3255 if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) {
3256 unique_select (_events.front());
3261 MidiRegionView::goto_previous_note (bool add_to_selection)
3263 bool use_next = false;
3265 if (_events.front()->selected()) {
3269 time_sort_events ();
3271 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3272 uint16_t const channel_mask = mtv->channel_selector().get_selected_channels ();
3274 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3275 if ((*i)->selected()) {
3278 } else if (use_next) {
3279 if (channel_mask & (1 << (*i)->note()->channel())) {
3280 if (!add_to_selection) {
3283 note_selected (*i, true, false);
3290 /* use the last one */
3292 if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) {
3293 unique_select (*(_events.rbegin()));
3298 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3300 bool had_selected = false;
3302 time_sort_events ();
3304 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3305 if ((*i)->selected()) {
3306 selected.insert ((*i)->note());
3307 had_selected = true;
3311 if (allow_all_if_none_selected && !had_selected) {
3312 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3313 selected.insert ((*i)->note());
3319 MidiRegionView::update_ghost_note (double x, double y)
3321 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3326 _note_group->w2i (x, y);
3328 PublicEditor& editor = trackview.editor ();
3330 framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3331 framecnt_t grid_frames;
3332 framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3334 /* use region_frames... because we are converting a delta within the region
3338 double length = editor.get_grid_type_as_beats (success, unsnapped_frame);
3344 /* note that this sets the time of the ghost note in beats relative to
3345 the start of the source; that is how all note times are stored.
3347 _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3348 _ghost_note->note()->set_length (length);
3349 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3350 _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3352 /* the ghost note does not appear in ghost regions, so pass false in here */
3353 update_note (_ghost_note, false);
3355 show_verbose_cursor (_ghost_note->note ());
3359 MidiRegionView::create_ghost_note (double x, double y)
3361 remove_ghost_note ();
3363 boost::shared_ptr<NoteType> g (new NoteType);
3364 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3365 _ghost_note->property_outline_color_rgba() = 0x000000aa;
3366 update_ghost_note (x, y);
3367 _ghost_note->show ();
3372 show_verbose_cursor (_ghost_note->note ());
3376 MidiRegionView::snap_changed ()
3382 create_ghost_note (_last_ghost_x, _last_ghost_y);
3386 MidiRegionView::drop_down_keys ()
3388 _mouse_state = None;
3392 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3394 double note = midi_stream_view()->y_to_note(y);
3396 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3398 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3400 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3401 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3402 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3403 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3408 bool add_mrv_selection = false;
3410 if (_selection.empty()) {
3411 add_mrv_selection = true;
3414 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3415 if (_selection.insert (*i).second) {
3416 (*i)->set_selected (true);
3420 if (add_mrv_selection) {
3421 PublicEditor& editor (trackview.editor());
3422 editor.get_selection().add (this);
3427 MidiRegionView::color_handler ()
3429 RegionView::color_handler ();
3431 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3432 (*i)->set_selected ((*i)->selected()); // will change color
3435 /* XXX probably more to do here */
3439 MidiRegionView::enable_display (bool yn)
3441 RegionView::enable_display (yn);
3448 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3450 if (_step_edit_cursor == 0) {
3451 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3453 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3454 _step_edit_cursor->property_y1() = 0;
3455 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3456 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3457 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3460 move_step_edit_cursor (pos);
3461 _step_edit_cursor->show ();
3465 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3467 _step_edit_cursor_position = pos;
3469 if (_step_edit_cursor) {
3470 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3471 _step_edit_cursor->property_x1() = pixel;
3472 set_step_edit_cursor_width (_step_edit_cursor_width);
3477 MidiRegionView::hide_step_edit_cursor ()
3479 if (_step_edit_cursor) {
3480 _step_edit_cursor->hide ();
3485 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3487 _step_edit_cursor_width = beats;
3489 if (_step_edit_cursor) {
3490 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3494 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3495 * @param w Source that the data will end up in.
3498 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3500 if (!_active_notes) {
3501 /* we aren't actively being recorded to */
3505 boost::shared_ptr<MidiSource> src = w.lock ();
3506 if (!src || src != midi_region()->midi_source()) {
3507 /* recorded data was not destined for our source */
3511 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3513 boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3515 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3517 framepos_t back = max_framepos;
3519 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3520 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3521 assert (ev.buffer ());
3523 /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
3524 frames from the start of the source, and so time_beats is in terms of the
3528 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3530 if (ev.type() == MIDI_CMD_NOTE_ON) {
3532 boost::shared_ptr<NoteType> note (
3533 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3536 add_note (note, true);
3538 /* fix up our note range */
3539 if (ev.note() < _current_range_min) {
3540 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3541 } else if (ev.note() > _current_range_max) {
3542 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3545 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3546 resolve_note (ev.note (), time_beats);
3552 midi_stream_view()->check_record_layers (region(), back);
3556 MidiRegionView::trim_front_starting ()
3558 /* Reparent the note group to the region view's parent, so that it doesn't change
3559 when the region view is trimmed.
3561 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3562 _temporary_note_group->move (group->property_x(), group->property_y());
3563 _note_group->reparent (*_temporary_note_group);
3567 MidiRegionView::trim_front_ending ()
3569 _note_group->reparent (*group);
3570 delete _temporary_note_group;
3571 _temporary_note_group = 0;
3573 if (_region->start() < 0) {
3574 /* Trim drag made start time -ve; fix this */
3575 midi_region()->fix_negative_start ();
3580 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3582 PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3583 if (d.run () != Gtk::RESPONSE_ACCEPT) {
3587 change_patch_change (pc->patch(), d.patch ());
3592 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3595 snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3596 Evoral::midi_note_name (n->note()).c_str(),
3598 (int) n->channel() + 1,
3599 (int) n->velocity());
3601 show_verbose_cursor (buf, 10, 20);
3605 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3609 trackview.editor().get_pointer_position (wx, wy);
3614 /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3616 double x1, y1, x2, y2;
3617 trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3619 if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3620 wy -= (y2 - y1) + 2 * yoffset;
3623 trackview.editor().verbose_cursor()->set (text, wx, wy);
3624 trackview.editor().verbose_cursor()->show ();
3627 /** @param p A session framepos.
3628 * @param grid_frames Filled in with the number of frames that a grid interval is at p.
3629 * @return p snapped to the grid subdivision underneath it.
3632 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3634 PublicEditor& editor = trackview.editor ();
3637 Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3643 grid_frames = region_beats_to_region_frames (grid_beats);
3645 /* Hack so that we always snap to the note that we are over, instead of snapping
3646 to the next one if we're more than halfway through the one we're over.
3648 if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3649 p -= grid_frames / 2;
3652 return snap_frame_to_frame (p);
3655 /** Called when the selection has been cleared in any MidiRegionView.
3656 * @param rv MidiRegionView that the selection was cleared in.
3659 MidiRegionView::selection_cleared (MidiRegionView* rv)
3665 /* Clear our selection in sympathy; but don't signal the fact */
3666 clear_selection (false);