2 Copyright (C) 2001-2007 Paul Davis
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <gtkmm2ext/gtk_ui.h>
29 #include <sigc++/signal.h>
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas-program-change.h"
53 #include "ghostregion.h"
54 #include "gui_thread.h"
56 #include "midi_cut_buffer.h"
57 #include "midi_list_editor.h"
58 #include "midi_region_view.h"
59 #include "midi_streamview.h"
60 #include "midi_time_axis.h"
61 #include "midi_time_axis.h"
62 #include "midi_util.h"
63 #include "note_player.h"
64 #include "public_editor.h"
65 #include "rgb_macros.h"
66 #include "selection.h"
67 #include "simpleline.h"
68 #include "streamview.h"
70 #include "mouse_cursors.h"
74 using namespace ARDOUR;
76 using namespace Editing;
77 using namespace ArdourCanvas;
78 using Gtkmm2ext::Keyboard;
80 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
81 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
82 : RegionView (parent, tv, r, spu, basic_color)
84 , _last_channel_selection(0xFFFF)
85 , _current_range_min(0)
86 , _current_range_max(0)
87 , _model_name(string())
88 , _custom_device_mode(string())
90 , _note_group(new ArdourCanvas::Group(*group))
94 , _step_edit_cursor (0)
95 , _step_edit_cursor_width (1.0)
96 , _step_edit_cursor_position (0.0)
97 , _temporary_note_group (0)
100 , _sort_needed (true)
101 , _optimization_iterator (_events.end())
103 , no_sound_notes (false)
106 , pre_enter_cursor (0)
108 _note_group->raise_to_top();
109 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
111 connect_to_diskstream ();
114 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
115 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
116 TimeAxisViewItem::Visibility visibility)
117 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
119 , _last_channel_selection(0xFFFF)
120 , _model_name(string())
121 , _custom_device_mode(string())
123 , _note_group(new ArdourCanvas::Group(*parent))
127 , _step_edit_cursor (0)
128 , _step_edit_cursor_width (1.0)
129 , _step_edit_cursor_position (0.0)
130 , _temporary_note_group (0)
133 , _sort_needed (true)
134 , _optimization_iterator (_events.end())
136 , no_sound_notes (false)
140 _note_group->raise_to_top();
141 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
143 connect_to_diskstream ();
146 MidiRegionView::MidiRegionView (const MidiRegionView& other)
147 : sigc::trackable(other)
150 , _last_channel_selection(0xFFFF)
151 , _model_name(string())
152 , _custom_device_mode(string())
154 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
158 , _step_edit_cursor (0)
159 , _step_edit_cursor_width (1.0)
160 , _step_edit_cursor_position (0.0)
161 , _temporary_note_group (0)
164 , _sort_needed (true)
165 , _optimization_iterator (_events.end())
167 , no_sound_notes (false)
174 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
175 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
180 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
181 : RegionView (other, boost::shared_ptr<Region> (region))
183 , _last_channel_selection(0xFFFF)
184 , _model_name(string())
185 , _custom_device_mode(string())
187 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
191 , _step_edit_cursor (0)
192 , _step_edit_cursor_width (1.0)
193 , _step_edit_cursor_position (0.0)
194 , _temporary_note_group (0)
197 , _sort_needed (true)
198 , _optimization_iterator (_events.end())
200 , no_sound_notes (false)
207 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
208 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
214 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
216 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
218 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
219 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
223 midi_region()->midi_source(0)->load_model();
226 _model = midi_region()->midi_source(0)->model();
227 _enable_display = false;
229 RegionView::init (basic_color, false);
231 compute_colors (basic_color);
233 set_height (trackview.current_height());
236 region_sync_changed ();
237 region_resized (ARDOUR::bounds_change);
240 reset_width_dependent_items (_pixel_width);
244 _enable_display = true;
247 display_model (_model);
251 group->raise_to_top();
252 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
254 midi_view()->signal_channel_mode_changed().connect(
255 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
257 midi_view()->signal_midi_patch_settings_changed().connect(
258 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
260 trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
262 connect_to_diskstream ();
266 MidiRegionView::connect_to_diskstream ()
268 midi_view()->midi_track()->DataRecorded.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::data_recorded, this, _1, _2), gui_context ());
272 MidiRegionView::canvas_event(GdkEvent* ev)
275 case GDK_ENTER_NOTIFY:
276 case GDK_LEAVE_NOTIFY:
277 _last_event_x = ev->crossing.x;
278 _last_event_y = ev->crossing.y;
280 case GDK_MOTION_NOTIFY:
281 _last_event_x = ev->motion.x;
282 _last_event_y = ev->motion.y;
288 if (!trackview.editor().internal_editing()) {
292 /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
293 to its items, which means that ev->type == GDK_SCROLL will never be seen
298 return scroll (&ev->scroll);
301 return key_press (&ev->key);
303 case GDK_KEY_RELEASE:
304 return key_release (&ev->key);
306 case GDK_BUTTON_PRESS:
307 return button_press (&ev->button);
309 case GDK_2BUTTON_PRESS:
312 case GDK_BUTTON_RELEASE:
313 return button_release (&ev->button);
315 case GDK_ENTER_NOTIFY:
316 return enter_notify (&ev->crossing);
318 case GDK_LEAVE_NOTIFY:
319 return leave_notify (&ev->crossing);
321 case GDK_MOTION_NOTIFY:
322 return motion (&ev->motion);
332 MidiRegionView::remove_ghost_note ()
339 MidiRegionView::enter_notify (GdkEventCrossing* ev)
341 trackview.editor().MouseModeChanged.connect (
342 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
345 Keyboard::magic_widget_grab_focus();
348 if (trackview.editor().current_mouse_mode() == MouseRange) {
349 create_ghost_note (ev->x, ev->y);
356 MidiRegionView::leave_notify (GdkEventCrossing*)
358 _mouse_mode_connection.disconnect ();
360 trackview.editor().hide_verbose_canvas_cursor ();
361 remove_ghost_note ();
366 MidiRegionView::mouse_mode_changed ()
368 if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
369 create_ghost_note (_last_event_x, _last_event_y);
371 remove_ghost_note ();
372 trackview.editor().hide_verbose_canvas_cursor ();
377 MidiRegionView::button_press (GdkEventButton* ev)
381 group->w2i (_last_x, _last_y);
383 if (_mouse_state != SelectTouchDragging && ev->button == 1) {
384 _pressed_button = ev->button;
385 _mouse_state = Pressed;
389 _pressed_button = ev->button;
395 MidiRegionView::button_release (GdkEventButton* ev)
397 double event_x, event_y;
398 framepos_t event_frame = 0;
402 group->w2i(event_x, event_y);
403 group->ungrab(ev->time);
404 event_frame = trackview.editor().pixel_to_frame(event_x);
406 if (ev->button == 3) {
408 } else if (_pressed_button != 1) {
412 switch (_mouse_state) {
413 case Pressed: // Clicked
414 switch (trackview.editor().current_mouse_mode()) {
418 maybe_select_by_position (ev, event_x, event_y);
424 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
428 create_note_at (event_x, event_y, beats, true);
436 case SelectRectDragging: // Select drag done
442 case AddDragging: // Add drag done
444 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
445 const double x = _drag_rect->property_x1();
446 const double length = trackview.editor().pixel_to_frame
447 (_drag_rect->property_x2() - _drag_rect->property_x1());
449 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), true);
455 create_ghost_note (ev->x, ev->y);
465 MidiRegionView::motion (GdkEventMotion* ev)
467 double event_x, event_y;
468 framepos_t event_frame = 0;
472 group->w2i(event_x, event_y);
474 // convert event_x to global frame
475 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
476 trackview.editor().snap_to(event_frame);
477 // convert event_frame back to local coordinates relative to position
478 event_frame -= _region->position();
481 update_ghost_note (ev->x, ev->y);
484 /* any motion immediately hides velocity text that may have been visible */
486 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
487 (*i)->hide_velocity ();
490 switch (_mouse_state) {
491 case Pressed: // Maybe start a drag, if we've moved a bit
493 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
494 /* no appreciable movement since the button was pressed */
499 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject) {
500 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
501 Gdk::Cursor(Gdk::FLEUR), ev->time);
504 _drag_start_x = event_x;
505 _drag_start_y = event_y;
507 _drag_rect = new ArdourCanvas::SimpleRect(*group);
508 _drag_rect->property_x1() = event_x;
509 _drag_rect->property_y1() = event_y;
510 _drag_rect->property_x2() = event_x;
511 _drag_rect->property_y2() = event_y;
512 _drag_rect->property_outline_what() = 0xFF;
513 _drag_rect->property_outline_color_rgba()
514 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
515 _drag_rect->property_fill_color_rgba()
516 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
518 _mouse_state = SelectRectDragging;
521 // Add note drag start
522 } else if (trackview.editor().internal_editing()) {
527 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
528 Gdk::Cursor(Gdk::FLEUR), ev->time);
531 _drag_start_x = event_x;
532 _drag_start_y = event_y;
534 _drag_rect = new ArdourCanvas::SimpleRect(*group);
535 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
537 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
538 midi_stream_view()->y_to_note(event_y));
539 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
540 _drag_rect->property_y2() = _drag_rect->property_y1()
541 + floor(midi_stream_view()->note_height());
542 _drag_rect->property_outline_what() = 0xFF;
543 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
544 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
546 _mouse_state = AddDragging;
552 case SelectRectDragging: // Select drag motion
553 case AddDragging: // Add note drag motion
557 GdkModifierType state;
558 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
563 if (_mouse_state == AddDragging)
564 event_x = trackview.editor().frame_to_pixel(event_frame);
567 if (event_x > _drag_start_x)
568 _drag_rect->property_x2() = event_x;
570 _drag_rect->property_x1() = event_x;
573 if (_drag_rect && _mouse_state == SelectRectDragging) {
574 if (event_y > _drag_start_y)
575 _drag_rect->property_y2() = event_y;
577 _drag_rect->property_y1() = event_y;
579 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
585 case SelectTouchDragging:
597 MidiRegionView::scroll (GdkEventScroll* ev)
599 if (_selection.empty()) {
603 trackview.editor().hide_verbose_canvas_cursor ();
605 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
607 if (ev->direction == GDK_SCROLL_UP) {
608 change_velocities (true, fine, false);
609 } else if (ev->direction == GDK_SCROLL_DOWN) {
610 change_velocities (false, fine, false);
616 MidiRegionView::key_press (GdkEventKey* ev)
618 /* since GTK bindings are generally activated on press, and since
619 detectable auto-repeat is the name of the game and only sends
620 repeated presses, carry out key actions at key press, not release.
623 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
624 _mouse_state = SelectTouchDragging;
627 } else if (ev->keyval == GDK_Escape) {
631 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
633 bool start = (ev->keyval == GDK_comma);
634 bool end = (ev->keyval == GDK_period);
635 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
636 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
638 change_note_lengths (fine, shorter, 0.0, start, end);
642 } else if (ev->keyval == GDK_Delete) {
647 } else if (ev->keyval == GDK_Tab) {
649 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
650 goto_previous_note ();
656 } else if (ev->keyval == GDK_Up) {
658 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
659 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
661 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
662 change_velocities (true, fine, allow_smush);
664 transpose (true, fine, allow_smush);
668 } else if (ev->keyval == GDK_Down) {
670 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
671 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
673 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
674 change_velocities (false, fine, allow_smush);
676 transpose (false, fine, allow_smush);
680 } else if (ev->keyval == GDK_Left) {
685 } else if (ev->keyval == GDK_Right) {
690 } else if (ev->keyval == GDK_Control_L) {
699 MidiRegionView::key_release (GdkEventKey* ev)
701 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
709 MidiRegionView::show_list_editor ()
712 _list_editor = new MidiListEditor (trackview.session(), midi_region());
714 _list_editor->present ();
717 /** Add a note to the model, and the view, at a canvas (click) coordinate.
718 * \param x horizontal position in pixels
719 * \param y vertical position in pixels
720 * \param length duration of the note in beats, which will be snapped to the grid
721 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
724 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
726 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
727 MidiStreamView* const view = mtv->midi_view();
729 double note = midi_stream_view()->y_to_note(y);
732 assert(note <= 127.0);
734 // Start of note in frames relative to region start
735 framepos_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
736 assert(start_frames >= 0);
739 length = frames_to_beats(
740 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
742 assert (length != 0);
745 length = frames_to_beats (beats_to_frames (length) - 1);
748 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
752 /* pick the highest selected channel, unless all channels are selected,
753 which is interpreted to mean channel 1 (zero)
756 for (uint16_t i = 0; i < 16; ++i) {
757 if (chn_mask & (1<<i)) {
767 const boost::shared_ptr<NoteType> new_note (new NoteType (channel,
768 frames_to_beats(start_frames + _region->start()), length,
769 (uint8_t)note, 0x40));
771 if (_model->contains (new_note)) {
775 view->update_note_range(new_note->note());
777 MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
779 _model->apply_command(*trackview.session(), cmd);
781 play_midi_note (new_note);
785 MidiRegionView::clear_events()
790 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
791 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
796 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
801 _pgm_changes.clear();
803 _optimization_iterator = _events.end();
807 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
811 content_connection.disconnect ();
812 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
816 if (_enable_display) {
822 MidiRegionView::start_diff_command(string name)
824 if (!_diff_command) {
825 _diff_command = _model->new_diff_command(name);
830 MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
833 _diff_command->add(note);
836 _marked_for_selection.insert(note);
839 _marked_for_velocity.insert(note);
844 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
846 if (_diff_command && ev->note()) {
847 _diff_command->remove(ev->note());
852 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
853 MidiModel::DiffCommand::Property property,
857 _diff_command->change (ev->note(), property, val);
862 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
863 MidiModel::DiffCommand::Property property,
864 Evoral::MusicalTime val)
867 _diff_command->change (ev->note(), property, val);
872 MidiRegionView::apply_diff ()
876 if (!_diff_command) {
880 if ((add_or_remove = _diff_command->adds_or_removes())) {
881 // Mark all selected notes for selection when model reloads
882 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
883 _marked_for_selection.insert((*i)->note());
887 _model->apply_command(*trackview.session(), _diff_command);
889 midi_view()->midi_track()->playlist_modified();
892 _marked_for_selection.clear();
895 _marked_for_velocity.clear();
899 MidiRegionView::apply_diff_as_subcommand()
903 if (!_diff_command) {
907 if ((add_or_remove = _diff_command->adds_or_removes())) {
908 // Mark all selected notes for selection when model reloads
909 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
910 _marked_for_selection.insert((*i)->note());
914 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
916 midi_view()->midi_track()->playlist_modified();
919 _marked_for_selection.clear();
921 _marked_for_velocity.clear();
926 MidiRegionView::abort_command()
928 delete _diff_command;
934 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
936 if (_optimization_iterator != _events.end()) {
937 ++_optimization_iterator;
940 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
941 return *_optimization_iterator;
944 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
945 if ((*_optimization_iterator)->note() == note) {
946 return *_optimization_iterator;
954 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
956 MidiModel::Notes notes;
957 _model->get_notes (notes, op, val, chan_mask);
959 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
960 CanvasNoteEvent* cne = find_canvas_note (*n);
968 MidiRegionView::redisplay_model()
970 // Don't redisplay the model if we're currently recording and displaying that
976 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
980 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
984 MidiModel::ReadLock lock(_model->read_lock());
986 MidiModel::Notes& notes (_model->notes());
987 _optimization_iterator = _events.begin();
989 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
991 boost::shared_ptr<NoteType> note (*n);
992 CanvasNoteEvent* cne;
995 if (note_in_region_range (note, visible)) {
997 if ((cne = find_canvas_note (note)) != 0) {
1004 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1006 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1018 add_note (note, visible);
1023 if ((cne = find_canvas_note (note)) != 0) {
1031 /* remove note items that are no longer valid */
1033 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1034 if (!(*i)->valid ()) {
1036 i = _events.erase (i);
1042 _pgm_changes.clear();
1046 display_program_changes();
1048 _marked_for_selection.clear ();
1049 _marked_for_velocity.clear ();
1051 /* we may have caused _events to contain things out of order (e.g. if a note
1052 moved earlier or later). we don't generally need them in time order, but
1053 make a note that a sort is required for those cases that require it.
1056 _sort_needed = true;
1060 MidiRegionView::display_program_changes()
1062 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1063 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1065 for (uint8_t i = 0; i < 16; ++i) {
1066 if (chn_mask & (1<<i)) {
1067 display_program_changes_on_channel (i);
1073 MidiRegionView::display_program_changes_on_channel(uint8_t channel)
1075 boost::shared_ptr<Evoral::Control> control =
1076 _model->control(Evoral::MIDI::ProgramChange (MidiPgmChangeAutomation, channel));
1082 Glib::Mutex::Lock lock (control->list()->lock());
1084 for (AutomationList::const_iterator event = control->list()->begin();
1085 event != control->list()->end(); ++event) {
1086 double event_time = (*event)->when;
1087 double program_number = floor((*event)->value + 0.5);
1089 // Get current value of bank select MSB at time of the program change
1090 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1091 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1093 if (msb_control != 0) {
1094 msb = uint8_t(floor(msb_control->get_double(true, event_time) + 0.5));
1097 // Get current value of bank select LSB at time of the program change
1098 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1099 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1101 if (lsb_control != 0) {
1102 lsb = uint8_t(floor(lsb_control->get_double(true, event_time) + 0.5));
1105 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1107 boost::shared_ptr<MIDI::Name::Patch> patch =
1108 MIDI::Name::MidiPatchManager::instance().find_patch(
1109 _model_name, _custom_device_mode, channel, patch_key);
1111 PCEvent program_change(event_time, uint8_t(program_number), channel);
1114 add_pgm_change(program_change, patch->name());
1117 // program_number is zero-based: convert to one-based
1118 snprintf(buf, 4, "%d", int(program_number+1));
1119 add_pgm_change(program_change, buf);
1125 MidiRegionView::display_sysexes()
1127 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1128 Evoral::MusicalTime time = (*i)->time();
1133 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1134 str << int((*i)->buffer()[b]);
1135 if (b != (*i)->size() -1) {
1139 string text = str.str();
1141 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1143 double height = midi_stream_view()->contents_height();
1145 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1146 new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1148 // Show unless program change is beyond the region bounds
1149 if (time - _region->start() >= _region->length() || time < _region->start()) {
1155 _sys_exes.push_back(sysex);
1160 MidiRegionView::~MidiRegionView ()
1162 in_destructor = true;
1164 trackview.editor().hide_verbose_canvas_cursor ();
1166 note_delete_connection.disconnect ();
1168 delete _list_editor;
1170 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1172 if (_active_notes) {
1180 delete _diff_command;
1181 delete _step_edit_cursor;
1182 delete _temporary_note_group;
1186 MidiRegionView::region_resized (const PropertyChange& what_changed)
1188 RegionView::region_resized(what_changed);
1190 if (what_changed.contains (ARDOUR::Properties::position)) {
1191 set_duration(_region->length(), 0);
1192 if (_enable_display) {
1199 MidiRegionView::reset_width_dependent_items (double pixel_width)
1201 RegionView::reset_width_dependent_items(pixel_width);
1202 assert(_pixel_width == pixel_width);
1204 if (_enable_display) {
1208 move_step_edit_cursor (_step_edit_cursor_position);
1209 set_step_edit_cursor_width (_step_edit_cursor_width);
1213 MidiRegionView::set_height (double height)
1215 static const double FUDGE = 2.0;
1216 const double old_height = _height;
1217 RegionView::set_height(height);
1218 _height = height - FUDGE;
1220 apply_note_range(midi_stream_view()->lowest_note(),
1221 midi_stream_view()->highest_note(),
1222 height != old_height + FUDGE);
1225 name_pixbuf->raise_to_top();
1228 for (PgmChanges::iterator x = _pgm_changes.begin(); x != _pgm_changes.end(); ++x) {
1229 (*x)->set_height (midi_stream_view()->contents_height());
1232 if (_step_edit_cursor) {
1233 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1238 /** Apply the current note range from the stream view
1239 * by repositioning/hiding notes as necessary
1242 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1244 if (!_enable_display) {
1248 if (!force && _current_range_min == min && _current_range_max == max) {
1252 _current_range_min = min;
1253 _current_range_max = max;
1255 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1256 CanvasNoteEvent* event = *i;
1257 boost::shared_ptr<NoteType> note (event->note());
1259 if (note->note() < _current_range_min ||
1260 note->note() > _current_range_max) {
1266 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1268 const double y1 = midi_stream_view()->note_to_y(note->note());
1269 const double y2 = y1 + floor(midi_stream_view()->note_height());
1271 cnote->property_y1() = y1;
1272 cnote->property_y2() = y2;
1274 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1276 const double diamond_size = update_hit (chit);
1278 chit->set_height (diamond_size);
1284 MidiRegionView::add_ghost (TimeAxisView& tv)
1288 double unit_position = _region->position () / samples_per_unit;
1289 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1290 MidiGhostRegion* ghost;
1292 if (mtv && mtv->midi_view()) {
1293 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1294 to allow having midi notes on top of note lines and waveforms.
1296 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1298 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1301 ghost->set_height ();
1302 ghost->set_duration (_region->length() / samples_per_unit);
1303 ghosts.push_back (ghost);
1305 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1306 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1307 ghost->add_note(note);
1311 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1317 /** Begin tracking note state for successive calls to add_event
1320 MidiRegionView::begin_write()
1322 assert(!_active_notes);
1323 _active_notes = new CanvasNote*[128];
1324 for (unsigned i=0; i < 128; ++i) {
1325 _active_notes[i] = 0;
1330 /** Destroy note state for add_event
1333 MidiRegionView::end_write()
1335 delete[] _active_notes;
1337 _marked_for_selection.clear();
1338 _marked_for_velocity.clear();
1342 /** Resolve an active MIDI note (while recording).
1345 MidiRegionView::resolve_note(uint8_t note, double end_time)
1347 if (midi_view()->note_mode() != Sustained) {
1351 if (_active_notes && _active_notes[note]) {
1352 const framepos_t end_time_frames = beats_to_frames(end_time) - _region->start();
1353 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1354 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1355 _active_notes[note] = 0;
1360 /** Extend active notes to rightmost edge of region (if length is changed)
1363 MidiRegionView::extend_active_notes()
1365 if (!_active_notes) {
1369 for (unsigned i=0; i < 128; ++i) {
1370 if (_active_notes[i]) {
1371 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1378 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1380 if (no_sound_notes || !trackview.editor().sound_notes()) {
1384 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1386 if (!route_ui || !route_ui->midi_track()) {
1390 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1396 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1398 if (no_sound_notes || !trackview.editor().sound_notes()) {
1402 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1404 if (!route_ui || !route_ui->midi_track()) {
1408 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1410 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1419 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1421 const framepos_t note_start_frames = beats_to_frames(note->time());
1423 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1424 (note_start_frames < _region->start());
1426 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1427 (note->note() <= midi_stream_view()->highest_note());
1433 MidiRegionView::update_note (CanvasNote* ev)
1435 boost::shared_ptr<NoteType> note = ev->note();
1437 const framepos_t note_start_frames = beats_to_frames(note->time());
1439 /* trim note display to not overlap the end of its region */
1440 const framepos_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length());
1442 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1443 const double y1 = midi_stream_view()->note_to_y(note->note());
1444 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1446 ev->property_x1() = x;
1447 ev->property_y1() = y1;
1448 if (note->length() > 0) {
1449 ev->property_x2() = note_endpixel;
1451 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1453 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1455 if (note->length() == 0) {
1456 if (_active_notes) {
1457 assert(note->note() < 128);
1458 // If this note is already active there's a stuck note,
1459 // finish the old note rectangle
1460 if (_active_notes[note->note()]) {
1461 CanvasNote* const old_rect = _active_notes[note->note()];
1462 boost::shared_ptr<NoteType> old_note = old_rect->note();
1463 old_rect->property_x2() = x;
1464 old_rect->property_outline_what() = (guint32) 0xF;
1466 _active_notes[note->note()] = ev;
1468 /* outline all but right edge */
1469 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1471 /* outline all edges */
1472 ev->property_outline_what() = (guint32) 0xF;
1477 MidiRegionView::update_hit (CanvasHit* ev)
1479 boost::shared_ptr<NoteType> note = ev->note();
1481 const framepos_t note_start_frames = beats_to_frames(note->time());
1482 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1483 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1484 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1488 return diamond_size;
1491 /** Add a MIDI note to the view (with length).
1493 * If in sustained mode, notes with length 0 will be considered active
1494 * notes, and resolve_note should be called when the corresponding note off
1495 * event arrives, to properly display the note.
1498 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1500 CanvasNoteEvent* event = 0;
1502 assert(note->time() >= 0);
1503 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1505 if (midi_view()->note_mode() == Sustained) {
1507 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1509 update_note (ev_rect);
1513 MidiGhostRegion* gr;
1515 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1516 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1517 gr->add_note(ev_rect);
1521 } else if (midi_view()->note_mode() == Percussive) {
1523 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1525 CanvasHit* ev_diamond = new CanvasHit(*this, *_note_group, diamond_size, note);
1527 update_hit (ev_diamond);
1536 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1537 note_selected(event, true);
1540 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1541 event->show_velocity();
1543 event->on_channel_selection_change(_last_channel_selection);
1544 _events.push_back(event);
1555 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1556 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1558 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1560 /* potentially extend region to hold new note */
1562 framepos_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1563 framepos_t region_end = _region->position() + _region->length() - 1;
1565 if (end_frame > region_end) {
1566 _region->set_length (end_frame - _region->position(), this);
1569 _marked_for_selection.clear ();
1572 start_diff_command (_("step add"));
1573 diff_add_note (new_note, true, false);
1576 // last_step_edit_note = new_note;
1580 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1582 change_note_lengths (false, false, beats, false, true);
1586 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1588 assert(program.time >= 0);
1590 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1592 double height = midi_stream_view()->contents_height();
1594 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1595 new CanvasProgramChange(*this, *_note_group,
1600 _custom_device_mode,
1601 program.time, program.channel, program.value));
1603 // Show unless program change is beyond the region bounds
1604 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1610 _pgm_changes.push_back(pgm_change);
1614 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1616 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1617 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1619 if (msb_control != 0) {
1620 msb = int(msb_control->get_double(true, time));
1623 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1624 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1626 if (lsb_control != 0) {
1627 lsb = lsb_control->get_double(true, time);
1630 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1631 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1632 double program_number = -1.0;
1633 if (program_control != 0) {
1634 program_number = program_control->get_double(true, time);
1637 key.msb = (int) floor(msb + 0.5);
1638 key.lsb = (int) floor(lsb + 0.5);
1639 key.program_number = (int) floor(program_number + 0.5);
1640 assert(key.is_sane());
1645 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1647 // TODO: Get the real event here and alter them at the original times
1648 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1649 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1650 if (msb_control != 0) {
1651 msb_control->set_double(double(new_patch.msb), true, old_program.time);
1654 // TODO: Get the real event here and alter them at the original times
1655 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1656 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1657 if (lsb_control != 0) {
1658 lsb_control->set_double(double(new_patch.lsb), true, old_program.time);
1661 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1662 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1664 assert(program_control != 0);
1665 program_control->set_double(float(new_patch.program_number), true, old_program.time);
1667 _pgm_changes.clear ();
1668 display_program_changes (); // XXX would be nice to limit to just old_program.channel
1672 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1674 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1675 alter_program_change(program_change_event, new_patch);
1679 MidiRegionView::previous_program(CanvasProgramChange& program)
1681 if (program.program() < 127) {
1682 MIDI::Name::PatchPrimaryKey key;
1683 get_patch_key_at(program.event_time(), program.channel(), key);
1684 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1686 key.program_number++;
1687 alter_program_change(program_change_event, key);
1692 MidiRegionView::next_program(CanvasProgramChange& program)
1694 if (program.program() > 0) {
1695 MIDI::Name::PatchPrimaryKey key;
1696 get_patch_key_at(program.event_time(), program.channel(), key);
1697 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1699 key.program_number--;
1700 alter_program_change(program_change_event, key);
1705 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1707 if (_selection.empty()) {
1711 if (_selection.erase (cne) > 0) {
1712 cerr << "Erased a CNE from selection\n";
1717 MidiRegionView::delete_selection()
1719 if (_selection.empty()) {
1723 start_diff_command (_("delete selection"));
1725 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1726 if ((*i)->selected()) {
1727 _diff_command->remove((*i)->note());
1737 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1739 start_diff_command (_("delete note"));
1740 _diff_command->remove (n);
1743 trackview.editor().hide_verbose_canvas_cursor ();
1747 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1749 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1750 if ((*i)->selected() && (*i) != ev) {
1751 (*i)->set_selected(false);
1752 (*i)->hide_velocity();
1760 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1762 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1765 Selection::iterator tmp = i;
1768 (*i)->set_selected (false);
1769 _selection.erase (i);
1778 /* don't bother with removing this regionview from the editor selection,
1779 since we're about to add another note, and thus put/keep this
1780 regionview in the editor selection.
1783 if (!ev->selected()) {
1784 add_to_selection (ev);
1789 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1791 uint8_t low_note = 127;
1792 uint8_t high_note = 0;
1793 MidiModel::Notes& notes (_model->notes());
1794 _optimization_iterator = _events.begin();
1800 if (extend && _selection.empty()) {
1806 /* scan existing selection to get note range */
1808 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1809 if ((*i)->note()->note() < low_note) {
1810 low_note = (*i)->note()->note();
1812 if ((*i)->note()->note() > high_note) {
1813 high_note = (*i)->note()->note();
1817 low_note = min (low_note, notenum);
1818 high_note = max (high_note, notenum);
1821 no_sound_notes = true;
1823 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1825 boost::shared_ptr<NoteType> note (*n);
1826 CanvasNoteEvent* cne;
1827 bool select = false;
1829 if (((1 << note->channel()) & channel_mask) != 0) {
1831 if ((note->note() >= low_note && note->note() <= high_note)) {
1834 } else if (note->note() == notenum) {
1840 if ((cne = find_canvas_note (note)) != 0) {
1841 // extend is false because we've taken care of it,
1842 // since it extends by time range, not pitch.
1843 note_selected (cne, add, false);
1847 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1851 no_sound_notes = false;
1855 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1857 MidiModel::Notes& notes (_model->notes());
1858 _optimization_iterator = _events.begin();
1860 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1862 boost::shared_ptr<NoteType> note (*n);
1863 CanvasNoteEvent* cne;
1865 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1866 if ((cne = find_canvas_note (note)) != 0) {
1867 if (cne->selected()) {
1868 note_deselected (cne);
1870 note_selected (cne, true, false);
1878 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1881 clear_selection_except(ev);
1886 if (!ev->selected()) {
1887 add_to_selection (ev);
1891 /* find end of latest note selected, select all between that and the start of "ev" */
1893 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
1894 Evoral::MusicalTime latest = 0;
1896 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1897 if ((*i)->note()->end_time() > latest) {
1898 latest = (*i)->note()->end_time();
1900 if ((*i)->note()->time() < earliest) {
1901 earliest = (*i)->note()->time();
1905 if (ev->note()->end_time() > latest) {
1906 latest = ev->note()->end_time();
1909 if (ev->note()->time() < earliest) {
1910 earliest = ev->note()->time();
1913 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1915 /* find notes entirely within OR spanning the earliest..latest range */
1917 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1918 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1919 add_to_selection (*i);
1923 /* if events were guaranteed to be time sorted, we could do this.
1924 but as of sept 10th 2009, they no longer are.
1927 if ((*i)->note()->time() > latest) {
1936 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1938 remove_from_selection (ev);
1942 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1952 // TODO: Make this faster by storing the last updated selection rect, and only
1953 // adjusting things that are in the area that appears/disappeared.
1954 // We probably need a tree to be able to find events in O(log(n)) time.
1956 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1958 /* check if any corner of the note is inside the rect
1961 1) this is computing "touched by", not "contained by" the rect.
1962 2) this does not require that events be sorted in time.
1965 const double ix1 = (*i)->x1();
1966 const double ix2 = (*i)->x2();
1967 const double iy1 = (*i)->y1();
1968 const double iy2 = (*i)->y2();
1970 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1971 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1972 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1973 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1976 if (!(*i)->selected()) {
1977 add_to_selection (*i);
1979 } else if ((*i)->selected()) {
1980 // Not inside rectangle
1981 remove_from_selection (*i);
1987 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1989 Selection::iterator i = _selection.find (ev);
1991 if (i != _selection.end()) {
1992 _selection.erase (i);
1995 ev->set_selected (false);
1996 ev->hide_velocity ();
1998 if (_selection.empty()) {
1999 PublicEditor& editor (trackview.editor());
2000 editor.get_selection().remove (this);
2005 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2007 bool add_mrv_selection = false;
2009 if (_selection.empty()) {
2010 add_mrv_selection = true;
2013 if (_selection.insert (ev).second) {
2014 ev->set_selected (true);
2015 play_midi_note ((ev)->note());
2018 if (add_mrv_selection) {
2019 PublicEditor& editor (trackview.editor());
2020 editor.get_selection().add (this);
2025 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2027 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2028 PossibleChord to_play;
2029 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2031 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2032 if ((*i)->note()->time() < earliest) {
2033 earliest = (*i)->note()->time();
2037 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2038 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2039 to_play.push_back ((*i)->note());
2041 (*i)->move_event(dx, dy);
2044 if (dy && !_selection.empty() && !no_sound_notes && trackview.editor().sound_notes()) {
2046 if (to_play.size() > 1) {
2048 PossibleChord shifted;
2050 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2051 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2052 moved_note->set_note (moved_note->note() + cumulative_dy);
2053 shifted.push_back (moved_note);
2056 play_midi_chord (shifted);
2058 } else if (!to_play.empty()) {
2060 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2061 moved_note->set_note (moved_note->note() + cumulative_dy);
2062 play_midi_note (moved_note);
2068 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2070 assert (!_selection.empty());
2072 uint8_t lowest_note_in_selection = 127;
2073 uint8_t highest_note_in_selection = 0;
2074 uint8_t highest_note_difference = 0;
2076 // find highest and lowest notes first
2078 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2079 uint8_t pitch = (*i)->note()->note();
2080 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2081 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2085 cerr << "dnote: " << (int) dnote << endl;
2086 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2087 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2088 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2089 << int(highest_note_in_selection) << endl;
2090 cerr << "selection size: " << _selection.size() << endl;
2091 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2094 // Make sure the note pitch does not exceed the MIDI standard range
2095 if (highest_note_in_selection + dnote > 127) {
2096 highest_note_difference = highest_note_in_selection - 127;
2099 start_diff_command(_("move notes"));
2101 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2103 Evoral::MusicalTime new_time = frames_to_beats (beats_to_frames ((*i)->note()->time()) + dt);
2109 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
2111 uint8_t original_pitch = (*i)->note()->note();
2112 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2114 // keep notes in standard midi range
2115 clamp_to_0_127(new_pitch);
2117 // keep original pitch if note is dragged outside valid midi range
2118 if ((original_pitch != 0 && new_pitch == 0)
2119 || (original_pitch != 127 && new_pitch == 127)) {
2120 new_pitch = original_pitch;
2123 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2124 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2126 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
2131 // care about notes being moved beyond the upper/lower bounds on the canvas
2132 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2133 highest_note_in_selection > midi_stream_view()->highest_note()) {
2134 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2139 MidiRegionView::snap_pixel_to_frame(double x)
2141 PublicEditor& editor = trackview.editor();
2142 // x is region relative, convert it to global absolute frames
2143 framepos_t frame = editor.pixel_to_frame(x) + _region->position();
2144 editor.snap_to(frame);
2145 return frame - _region->position(); // convert back to region relative
2149 MidiRegionView::snap_frame_to_frame(framepos_t x)
2151 PublicEditor& editor = trackview.editor();
2152 // x is region relative, convert it to global absolute frames
2153 framepos_t frame = x + _region->position();
2154 editor.snap_to(frame);
2155 return frame - _region->position(); // convert back to region relative
2159 MidiRegionView::snap_to_pixel(double x)
2161 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2165 MidiRegionView::get_position_pixels()
2167 framepos_t region_frame = get_position();
2168 return trackview.editor().frame_to_pixel(region_frame);
2172 MidiRegionView::get_end_position_pixels()
2174 framepos_t frame = get_position() + get_duration ();
2175 return trackview.editor().frame_to_pixel(frame);
2179 MidiRegionView::beats_to_frames(double beats) const
2181 return _time_converter.to(beats);
2185 MidiRegionView::frames_to_beats(framepos_t frames) const
2187 return _time_converter.from(frames);
2191 MidiRegionView::begin_resizing (bool /*at_front*/)
2193 _resize_data.clear();
2195 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2196 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2198 // only insert CanvasNotes into the map
2200 NoteResizeData *resize_data = new NoteResizeData();
2201 resize_data->canvas_note = note;
2203 // create a new SimpleRect from the note which will be the resize preview
2204 SimpleRect *resize_rect = new SimpleRect(
2205 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2207 // calculate the colors: get the color settings
2208 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2209 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2212 // make the resize preview notes more transparent and bright
2213 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2215 // calculate color based on note velocity
2216 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2217 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2221 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2222 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2224 resize_data->resize_rect = resize_rect;
2225 _resize_data.push_back(resize_data);
2230 /** Update resizing notes while user drags.
2231 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2232 * @param at_front which end of the note (true == note on, false == note off)
2233 * @param delta_x change in mouse position since the start of the drag
2234 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2235 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2236 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2237 * as the \a primary note.
2240 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2242 bool cursor_set = false;
2244 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2245 SimpleRect* resize_rect = (*i)->resize_rect;
2246 CanvasNote* canvas_note = (*i)->canvas_note;
2251 current_x = canvas_note->x1() + delta_x;
2253 current_x = primary->x1() + delta_x;
2257 current_x = canvas_note->x2() + delta_x;
2259 current_x = primary->x2() + delta_x;
2264 resize_rect->property_x1() = snap_to_pixel(current_x);
2265 resize_rect->property_x2() = canvas_note->x2();
2267 resize_rect->property_x2() = snap_to_pixel(current_x);
2268 resize_rect->property_x1() = canvas_note->x1();
2274 beats = snap_pixel_to_frame (current_x);
2275 beats = frames_to_beats (beats);
2280 if (beats < canvas_note->note()->end_time()) {
2281 len = canvas_note->note()->time() - beats;
2282 len += canvas_note->note()->length();
2287 if (beats >= canvas_note->note()->time()) {
2288 len = beats - canvas_note->note()->time();
2295 snprintf (buf, sizeof (buf), "%.3g beats", len);
2296 trackview.editor().show_verbose_canvas_cursor_with (buf);
2305 /** Finish resizing notes when the user releases the mouse button.
2306 * Parameters the same as for \a update_resizing().
2309 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2311 start_diff_command(_("resize notes"));
2313 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2314 CanvasNote* canvas_note = (*i)->canvas_note;
2315 SimpleRect* resize_rect = (*i)->resize_rect;
2320 current_x = canvas_note->x1() + delta_x;
2322 current_x = primary->x1() + delta_x;
2326 current_x = canvas_note->x2() + delta_x;
2328 current_x = primary->x2() + delta_x;
2332 current_x = snap_pixel_to_frame (current_x);
2333 current_x = frames_to_beats (current_x);
2335 if (at_front && current_x < canvas_note->note()->end_time()) {
2336 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2338 double len = canvas_note->note()->time() - current_x;
2339 len += canvas_note->note()->length();
2342 /* XXX convert to beats */
2343 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2348 double len = current_x - canvas_note->note()->time();
2351 /* XXX convert to beats */
2352 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2360 _resize_data.clear();
2365 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t channel)
2367 diff_add_change (event, MidiModel::DiffCommand::Channel, (uint8_t) channel);
2371 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2373 uint8_t new_velocity;
2376 new_velocity = event->note()->velocity() + velocity;
2377 clamp_to_0_127(new_velocity);
2379 new_velocity = velocity;
2382 event->set_selected (event->selected()); // change color
2384 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2388 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2393 new_note = event->note()->note() + note;
2398 clamp_to_0_127 (new_note);
2399 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2403 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2405 bool change_start = false;
2406 bool change_length = false;
2407 Evoral::MusicalTime new_start = 0;
2408 Evoral::MusicalTime new_length = 0;
2410 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2412 front_delta: if positive - move the start of the note later in time (shortening it)
2413 if negative - move the start of the note earlier in time (lengthening it)
2415 end_delta: if positive - move the end of the note later in time (lengthening it)
2416 if negative - move the end of the note earlier in time (shortening it)
2420 if (front_delta < 0) {
2422 if (event->note()->time() < -front_delta) {
2425 new_start = event->note()->time() + front_delta; // moves earlier
2428 /* start moved toward zero, so move the end point out to where it used to be.
2429 Note that front_delta is negative, so this increases the length.
2432 new_length = event->note()->length() - front_delta;
2433 change_start = true;
2434 change_length = true;
2438 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2440 if (new_pos < event->note()->end_time()) {
2441 new_start = event->note()->time() + front_delta;
2442 /* start moved toward the end, so move the end point back to where it used to be */
2443 new_length = event->note()->length() - front_delta;
2444 change_start = true;
2445 change_length = true;
2452 bool can_change = true;
2453 if (end_delta < 0) {
2454 if (event->note()->length() < -end_delta) {
2460 new_length = event->note()->length() + end_delta;
2461 change_length = true;
2466 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2469 if (change_length) {
2470 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2475 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2477 Evoral::MusicalTime new_time;
2481 if (event->note()->time() < -delta) {
2484 new_time = event->note()->time() + delta;
2487 new_time = event->note()->time() + delta;
2493 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2497 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2499 diff_add_change (event, MidiModel::DiffCommand::Length, t);
2503 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2507 if (_selection.empty()) {
2522 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2523 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2529 start_diff_command(_("change velocities"));
2531 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2532 Selection::iterator next = i;
2534 change_note_velocity (*i, delta, true);
2540 if (!_selection.empty()) {
2542 snprintf (buf, sizeof (buf), "Vel %d",
2543 (int) (*_selection.begin())->note()->velocity());
2544 trackview.editor().show_verbose_canvas_cursor_with (buf, 10, 10);
2550 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2552 if (_selection.empty()) {
2569 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2571 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2575 if ((int8_t) (*i)->note()->note() + delta > 127) {
2582 start_diff_command (_("transpose"));
2584 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2585 Selection::iterator next = i;
2587 change_note_note (*i, delta, true);
2595 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2601 /* grab the current grid distance */
2603 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2605 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2606 cerr << "Grid type not available as beats - TO BE FIXED\n";
2616 start_diff_command (_("change note lengths"));
2618 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2619 Selection::iterator next = i;
2622 /* note the negation of the delta for start */
2624 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2633 MidiRegionView::nudge_notes (bool forward)
2635 if (_selection.empty()) {
2639 /* pick a note as the point along the timeline to get the nudge distance.
2640 its not necessarily the earliest note, so we may want to pull the notes out
2641 into a vector and sort before using the first one.
2644 framepos_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2646 framepos_t distance;
2648 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2650 /* grid is off - use nudge distance */
2652 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2658 framepos_t next_pos = ref_point;
2661 if (max_framepos - 1 < next_pos) {
2665 if (next_pos == 0) {
2671 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2672 distance = ref_point - next_pos;
2675 if (distance == 0) {
2679 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2685 start_diff_command (_("nudge"));
2687 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2688 Selection::iterator next = i;
2690 change_note_time (*i, delta, true);
2698 MidiRegionView::change_channel(uint8_t channel)
2700 start_diff_command(_("change channel"));
2701 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2702 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2710 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2712 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2714 pre_enter_cursor = editor->get_canvas_cursor ();
2716 if (_mouse_state == SelectTouchDragging) {
2717 note_selected (ev, true);
2720 show_verbose_canvas_cursor (ev->note ());
2724 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2726 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2728 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2729 (*i)->hide_velocity ();
2732 editor->hide_verbose_canvas_cursor ();
2734 if (pre_enter_cursor) {
2735 editor->set_canvas_cursor (pre_enter_cursor);
2736 pre_enter_cursor = 0;
2741 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
2743 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2745 if (x_fraction > 0.0 && x_fraction < 0.25) {
2746 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
2747 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
2748 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
2750 if (pre_enter_cursor && can_set_cursor) {
2751 editor->set_canvas_cursor (pre_enter_cursor);
2757 MidiRegionView::set_frame_color()
2760 if (_selected && should_show_selection) {
2761 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2763 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2769 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2773 case FilterChannels:
2774 _force_channel = -1;
2777 _force_channel = mask;
2778 mask = 0xFFFF; // Show all notes as active (below)
2781 // Update notes for selection
2782 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2783 (*i)->on_channel_selection_change(mask);
2786 _last_channel_selection = mask;
2790 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2792 _model_name = model;
2793 _custom_device_mode = custom_device_mode;
2798 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2800 if (_selection.empty()) {
2804 PublicEditor& editor (trackview.editor());
2809 editor.get_cut_buffer().add (selection_as_cut_buffer());
2817 start_diff_command();
2819 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2825 diff_remove_note (*i);
2835 MidiRegionView::selection_as_cut_buffer () const
2839 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2840 NoteType* n = (*i)->note().get();
2841 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2844 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2851 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
2857 start_diff_command (_("paste"));
2859 Evoral::MusicalTime beat_delta;
2860 Evoral::MusicalTime paste_pos_beats;
2861 Evoral::MusicalTime duration;
2862 Evoral::MusicalTime end_point = 0;
2864 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2865 paste_pos_beats = frames_to_beats (pos - _region->position());
2866 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2867 paste_pos_beats = 0;
2871 for (int n = 0; n < (int) times; ++n) {
2873 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2875 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2876 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2878 /* make all newly added notes selected */
2880 diff_add_note (copied_note, true);
2881 end_point = copied_note->end_time();
2884 paste_pos_beats += duration;
2887 /* if we pasted past the current end of the region, extend the region */
2889 framepos_t end_frame = _region->position() + beats_to_frames (end_point);
2890 framepos_t region_end = _region->position() + _region->length() - 1;
2892 if (end_frame > region_end) {
2894 trackview.session()->begin_reversible_command (_("paste"));
2896 _region->clear_changes ();
2897 _region->set_length (end_frame, this);
2898 trackview.session()->add_command (new StatefulDiffCommand (_region));
2904 struct EventNoteTimeEarlyFirstComparator {
2905 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2906 return a->note()->time() < b->note()->time();
2911 MidiRegionView::time_sort_events ()
2913 if (!_sort_needed) {
2917 EventNoteTimeEarlyFirstComparator cmp;
2920 _sort_needed = false;
2924 MidiRegionView::goto_next_note ()
2926 // framepos_t pos = -1;
2927 bool use_next = false;
2929 if (_events.back()->selected()) {
2933 time_sort_events ();
2935 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2936 if ((*i)->selected()) {
2939 } else if (use_next) {
2941 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2946 /* use the first one */
2948 unique_select (_events.front());
2953 MidiRegionView::goto_previous_note ()
2955 // framepos_t pos = -1;
2956 bool use_next = false;
2958 if (_events.front()->selected()) {
2962 time_sort_events ();
2964 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2965 if ((*i)->selected()) {
2968 } else if (use_next) {
2970 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2975 /* use the last one */
2977 unique_select (*(_events.rbegin()));
2981 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2983 bool had_selected = false;
2985 time_sort_events ();
2987 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2988 if ((*i)->selected()) {
2989 selected.insert ((*i)->note());
2990 had_selected = true;
2994 if (allow_all_if_none_selected && !had_selected) {
2995 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2996 selected.insert ((*i)->note());
3002 MidiRegionView::update_ghost_note (double x, double y)
3007 _note_group->w2i (x, y);
3008 framepos_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
3009 trackview.editor().snap_to (f);
3010 f -= _region->position ();
3013 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
3018 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
3020 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
3021 _ghost_note->note()->set_length (length);
3022 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3024 update_note (_ghost_note);
3026 show_verbose_canvas_cursor (_ghost_note->note ());
3030 MidiRegionView::create_ghost_note (double x, double y)
3035 boost::shared_ptr<NoteType> g (new NoteType);
3036 _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3037 update_ghost_note (x, y);
3038 _ghost_note->show ();
3043 show_verbose_canvas_cursor (_ghost_note->note ());
3047 MidiRegionView::snap_changed ()
3053 create_ghost_note (_last_ghost_x, _last_ghost_y);
3057 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
3060 snprintf (buf, sizeof (buf), "%s (%d)\nVel %d",
3061 Evoral::midi_note_name (n->note()).c_str(),
3063 (int) n->velocity());
3064 trackview.editor().show_verbose_canvas_cursor_with (buf, 10, 20);
3068 MidiRegionView::drop_down_keys ()
3070 _mouse_state = None;
3074 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3076 double note = midi_stream_view()->y_to_note(y);
3078 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3080 cerr << "Selecting by position\n";
3082 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3084 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3085 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3086 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3087 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3092 bool add_mrv_selection = false;
3094 if (_selection.empty()) {
3095 add_mrv_selection = true;
3098 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3099 if (_selection.insert (*i).second) {
3100 (*i)->set_selected (true);
3104 if (add_mrv_selection) {
3105 PublicEditor& editor (trackview.editor());
3106 editor.get_selection().add (this);
3111 MidiRegionView::color_handler ()
3113 RegionView::color_handler ();
3115 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3116 (*i)->set_selected ((*i)->selected()); // will change color
3119 /* XXX probably more to do here */
3123 MidiRegionView::enable_display (bool yn)
3125 RegionView::enable_display (yn);
3132 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3134 if (_step_edit_cursor == 0) {
3135 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3137 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3138 _step_edit_cursor->property_y1() = 0;
3139 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3140 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3141 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3144 move_step_edit_cursor (pos);
3145 _step_edit_cursor->show ();
3149 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3151 _step_edit_cursor_position = pos;
3153 if (_step_edit_cursor) {
3154 double pixel = trackview.editor().frame_to_pixel (beats_to_frames (pos));
3155 _step_edit_cursor->property_x1() = pixel;
3156 set_step_edit_cursor_width (_step_edit_cursor_width);
3161 MidiRegionView::hide_step_edit_cursor ()
3163 if (_step_edit_cursor) {
3164 _step_edit_cursor->hide ();
3169 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3171 _step_edit_cursor_width = beats;
3173 if (_step_edit_cursor) {
3174 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (beats_to_frames (beats));
3178 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3179 * @param buf Data that has been recorded.
3180 * @param w Source that this data will end up in.
3183 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3185 if (!_active_notes) {
3186 /* we aren't actively being recorded to */
3190 boost::shared_ptr<MidiSource> src = w.lock ();
3191 if (!src || src != midi_region()->midi_source()) {
3192 /* recorded data was not destined for our source */
3196 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3197 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3199 framepos_t back = max_framepos;
3201 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3202 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3203 assert (ev.buffer ());
3205 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3207 if (ev.type() == MIDI_CMD_NOTE_ON) {
3209 boost::shared_ptr<Evoral::Note<Evoral::MusicalTime> > note (
3210 new Evoral::Note<Evoral::MusicalTime> (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3213 add_note (note, true);
3215 /* fix up our note range */
3216 if (ev.note() < _current_range_min) {
3217 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3218 } else if (ev.note() > _current_range_max) {
3219 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3222 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3223 resolve_note (ev.note (), time_beats);
3229 midi_stream_view()->check_record_layers (region(), back);
3233 MidiRegionView::trim_front_starting ()
3235 /* Reparent the note group to the region view's parent, so that it doesn't change
3236 when the region view is trimmed.
3238 _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3239 _temporary_note_group->move (group->property_x(), group->property_y());
3240 _note_group->reparent (*_temporary_note_group);
3244 MidiRegionView::trim_front_ending ()
3246 _note_group->reparent (*group);
3247 delete _temporary_note_group;
3248 _temporary_note_group = 0;