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"
52 #include "ghostregion.h"
53 #include "gui_thread.h"
55 #include "midi_cut_buffer.h"
56 #include "midi_list_editor.h"
57 #include "midi_region_view.h"
58 #include "midi_streamview.h"
59 #include "midi_time_axis.h"
60 #include "midi_time_axis.h"
61 #include "midi_util.h"
62 #include "public_editor.h"
63 #include "rgb_macros.h"
64 #include "selection.h"
65 #include "simpleline.h"
66 #include "streamview.h"
71 using namespace ARDOUR;
73 using namespace Editing;
74 using namespace ArdourCanvas;
75 using Gtkmm2ext::Keyboard;
77 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
78 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
79 : RegionView (parent, tv, r, spu, basic_color)
81 , _last_channel_selection(0xFFFF)
82 , _current_range_min(0)
83 , _current_range_max(0)
84 , _model_name(string())
85 , _custom_device_mode(string())
87 , _note_group(new ArdourCanvas::Group(*parent))
91 , _step_edit_cursor (0)
92 , _step_edit_cursor_width (1.0)
96 , _optimization_iterator (_events.end())
98 , no_sound_notes (false)
100 _note_group->raise_to_top();
101 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
104 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
105 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
106 TimeAxisViewItem::Visibility visibility)
107 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
109 , _last_channel_selection(0xFFFF)
110 , _model_name(string())
111 , _custom_device_mode(string())
113 , _note_group(new ArdourCanvas::Group(*parent))
119 , _sort_needed (true)
120 , _optimization_iterator (_events.end())
122 , no_sound_notes (false)
124 _note_group->raise_to_top();
125 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
128 MidiRegionView::MidiRegionView (const MidiRegionView& other)
129 : sigc::trackable(other)
132 , _last_channel_selection(0xFFFF)
133 , _model_name(string())
134 , _custom_device_mode(string())
136 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
142 , _sort_needed (true)
143 , _optimization_iterator (_events.end())
145 , no_sound_notes (false)
150 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
151 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
156 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
157 : RegionView (other, boost::shared_ptr<Region> (region))
159 , _last_channel_selection(0xFFFF)
160 , _model_name(string())
161 , _custom_device_mode(string())
163 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
169 , _sort_needed (true)
170 , _optimization_iterator (_events.end())
172 , no_sound_notes (false)
177 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
178 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
184 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
186 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
188 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
189 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
193 midi_region()->midi_source(0)->load_model();
196 _model = midi_region()->midi_source(0)->model();
197 _enable_display = false;
199 RegionView::init (basic_color, false);
201 compute_colors (basic_color);
203 set_height (trackview.current_height());
206 region_sync_changed ();
207 region_resized (ARDOUR::bounds_change);
210 reset_width_dependent_items (_pixel_width);
214 _enable_display = true;
217 display_model (_model);
221 group->raise_to_top();
222 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
224 midi_view()->signal_channel_mode_changed().connect(
225 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
227 midi_view()->signal_midi_patch_settings_changed().connect(
228 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
230 trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
234 MidiRegionView::canvas_event(GdkEvent* ev)
236 if (!trackview.editor().internal_editing()) {
240 /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
241 to its items, which means that ev->type == GDK_SCROLL will never be seen
246 return scroll (&ev->scroll);
249 return key_press (&ev->key);
251 case GDK_KEY_RELEASE:
252 return key_release (&ev->key);
254 case GDK_BUTTON_PRESS:
255 return button_press (&ev->button);
257 case GDK_2BUTTON_PRESS:
260 case GDK_BUTTON_RELEASE:
261 return button_release (&ev->button);
263 case GDK_ENTER_NOTIFY:
264 return enter_notify (&ev->crossing);
266 case GDK_LEAVE_NOTIFY:
267 return leave_notify (&ev->crossing);
269 case GDK_MOTION_NOTIFY:
270 return motion (&ev->motion);
280 MidiRegionView::enter_notify (GdkEventCrossing* ev)
282 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
284 Keyboard::magic_widget_grab_focus();
287 if (trackview.editor().current_mouse_mode() == MouseRange) {
288 create_ghost_note (ev->x, ev->y);
295 MidiRegionView::leave_notify (GdkEventCrossing* ev)
297 trackview.editor().hide_verbose_canvas_cursor ();
304 MidiRegionView::button_press (GdkEventButton* ev)
308 group->w2i (_last_x, _last_y);
310 if (_mouse_state != SelectTouchDragging && ev->button == 1) {
311 _pressed_button = ev->button;
312 _mouse_state = Pressed;
316 _pressed_button = ev->button;
322 MidiRegionView::button_release (GdkEventButton* ev)
324 double event_x, event_y;
325 nframes64_t event_frame = 0;
329 group->w2i(event_x, event_y);
330 group->ungrab(ev->time);
331 event_frame = trackview.editor().pixel_to_frame(event_x);
333 if (ev->button == 3) {
335 } else if (_pressed_button != 1) {
339 switch (_mouse_state) {
340 case Pressed: // Clicked
341 switch (trackview.editor().current_mouse_mode()) {
345 maybe_select_by_position (ev, event_x, event_y);
351 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
355 create_note_at (event_x, event_y, beats, true);
363 case SelectRectDragging: // Select drag done
369 case AddDragging: // Add drag done
371 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
372 const double x = _drag_rect->property_x1();
373 const double length = trackview.editor().pixel_to_frame
374 (_drag_rect->property_x2() - _drag_rect->property_x1());
376 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), false);
382 create_ghost_note (ev->x, ev->y);
392 MidiRegionView::motion (GdkEventMotion* ev)
394 double event_x, event_y;
395 nframes64_t event_frame = 0;
399 group->w2i(event_x, event_y);
401 // convert event_x to global frame
402 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
403 trackview.editor().snap_to(event_frame);
404 // convert event_frame back to local coordinates relative to position
405 event_frame -= _region->position();
408 update_ghost_note (ev->x, ev->y);
411 /* any motion immediately hides velocity text that may have been visible */
413 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
414 (*i)->hide_velocity ();
417 switch (_mouse_state) {
418 case Pressed: // Maybe start a drag, if we've moved a bit
420 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
421 /* no appreciable movement since the button was pressed */
426 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject) {
427 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
428 Gdk::Cursor(Gdk::FLEUR), ev->time);
431 _drag_start_x = event_x;
432 _drag_start_y = event_y;
434 _drag_rect = new ArdourCanvas::SimpleRect(*group);
435 _drag_rect->property_x1() = event_x;
436 _drag_rect->property_y1() = event_y;
437 _drag_rect->property_x2() = event_x;
438 _drag_rect->property_y2() = event_y;
439 _drag_rect->property_outline_what() = 0xFF;
440 _drag_rect->property_outline_color_rgba()
441 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
442 _drag_rect->property_fill_color_rgba()
443 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
445 _mouse_state = SelectRectDragging;
448 // Add note drag start
449 } else if (trackview.editor().internal_editing()) {
454 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
455 Gdk::Cursor(Gdk::FLEUR), ev->time);
458 _drag_start_x = event_x;
459 _drag_start_y = event_y;
461 _drag_rect = new ArdourCanvas::SimpleRect(*group);
462 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
464 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
465 midi_stream_view()->y_to_note(event_y));
466 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
467 _drag_rect->property_y2() = _drag_rect->property_y1()
468 + floor(midi_stream_view()->note_height());
469 _drag_rect->property_outline_what() = 0xFF;
470 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
471 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
473 _mouse_state = AddDragging;
479 case SelectRectDragging: // Select drag motion
480 case AddDragging: // Add note drag motion
484 GdkModifierType state;
485 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
490 if (_mouse_state == AddDragging)
491 event_x = trackview.editor().frame_to_pixel(event_frame);
494 if (event_x > _drag_start_x)
495 _drag_rect->property_x2() = event_x;
497 _drag_rect->property_x1() = event_x;
500 if (_drag_rect && _mouse_state == SelectRectDragging) {
501 if (event_y > _drag_start_y)
502 _drag_rect->property_y2() = event_y;
504 _drag_rect->property_y1() = event_y;
506 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
512 case SelectTouchDragging:
524 MidiRegionView::scroll (GdkEventScroll* ev)
526 if (_selection.empty()) {
530 trackview.editor().hide_verbose_canvas_cursor ();
532 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
534 if (ev->direction == GDK_SCROLL_UP) {
535 change_velocities (true, fine, false);
536 } else if (ev->direction == GDK_SCROLL_DOWN) {
537 change_velocities (false, fine, false);
543 MidiRegionView::key_press (GdkEventKey* ev)
545 /* since GTK bindings are generally activated on press, and since
546 detectable auto-repeat is the name of the game and only sends
547 repeated presses, carry out key actions at key press, not release.
550 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
551 _mouse_state = SelectTouchDragging;
554 } else if (ev->keyval == GDK_Escape) {
558 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
560 bool start = (ev->keyval == GDK_comma);
561 bool end = (ev->keyval == GDK_period);
562 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
563 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
565 change_note_lengths (fine, shorter, start, end);
569 } else if (ev->keyval == GDK_Delete) {
574 } else if (ev->keyval == GDK_Tab) {
576 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
577 goto_previous_note ();
583 } else if (ev->keyval == GDK_Up) {
585 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
586 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
588 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
589 change_velocities (true, fine, allow_smush);
591 transpose (true, fine, allow_smush);
595 } else if (ev->keyval == GDK_Down) {
597 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
598 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
600 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
601 change_velocities (false, fine, allow_smush);
603 transpose (false, fine, allow_smush);
607 } else if (ev->keyval == GDK_Left) {
612 } else if (ev->keyval == GDK_Right) {
617 } else if (ev->keyval == GDK_Control_L) {
626 MidiRegionView::key_release (GdkEventKey* ev)
628 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
636 MidiRegionView::show_list_editor ()
639 _list_editor = new MidiListEditor (trackview.session(), midi_region());
641 _list_editor->present ();
644 /** Add a note to the model, and the view, at a canvas (click) coordinate.
645 * \param x horizontal position in pixels
646 * \param y vertical position in pixels
647 * \param length duration of the note in beats, which will be snapped to the grid
648 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
651 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
653 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
654 MidiStreamView* const view = mtv->midi_view();
656 double note = midi_stream_view()->y_to_note(y);
659 assert(note <= 127.0);
661 // Start of note in frames relative to region start
662 nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
663 assert(start_frames >= 0);
666 length = frames_to_beats(
667 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
669 assert (length != 0);
672 length = frames_to_beats (beats_to_frames (length) - 1);
675 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
679 /* pick the highest selected channel, unless all channels are selected,
680 which is interpreted to mean channel 1 (zero)
683 for (uint16_t i = 0; i < 16; ++i) {
684 if (chn_mask & (1<<i)) {
694 const boost::shared_ptr<NoteType> new_note (new NoteType (channel,
695 frames_to_beats(start_frames + _region->start()), length,
696 (uint8_t)note, 0x40));
698 if (_model->contains (new_note)) {
702 view->update_note_range(new_note->note());
704 MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
706 _model->apply_command(*trackview.session(), cmd);
708 play_midi_note (new_note);
712 MidiRegionView::clear_events()
717 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
718 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
723 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
728 _pgm_changes.clear();
730 _optimization_iterator = _events.end();
735 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
738 content_connection.disconnect ();
739 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
743 if (_enable_display) {
749 MidiRegionView::start_diff_command(string name)
751 if (!_diff_command) {
752 _diff_command = _model->new_diff_command(name);
757 MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
760 _diff_command->add(note);
763 _marked_for_selection.insert(note);
766 _marked_for_velocity.insert(note);
771 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
773 if (_diff_command && ev->note()) {
774 _diff_command->remove(ev->note());
779 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
780 MidiModel::DiffCommand::Property property,
784 _diff_command->change (ev->note(), property, val);
789 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
790 MidiModel::DiffCommand::Property property,
791 Evoral::MusicalTime val)
794 _diff_command->change (ev->note(), property, val);
799 MidiRegionView::apply_diff ()
803 if (!_diff_command) {
807 if ((add_or_remove = _diff_command->adds_or_removes())) {
808 // Mark all selected notes for selection when model reloads
809 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
810 _marked_for_selection.insert((*i)->note());
814 _model->apply_command(*trackview.session(), _diff_command);
816 midi_view()->midi_track()->playlist_modified();
819 _marked_for_selection.clear();
822 _marked_for_velocity.clear();
826 MidiRegionView::apply_diff_as_subcommand()
830 if (!_diff_command) {
834 if ((add_or_remove = _diff_command->adds_or_removes())) {
835 // Mark all selected notes for selection when model reloads
836 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
837 _marked_for_selection.insert((*i)->note());
841 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
843 midi_view()->midi_track()->playlist_modified();
846 _marked_for_selection.clear();
848 _marked_for_velocity.clear();
853 MidiRegionView::abort_command()
855 delete _diff_command;
861 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
863 if (_optimization_iterator != _events.end()) {
864 ++_optimization_iterator;
867 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
868 return *_optimization_iterator;
871 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
872 if ((*_optimization_iterator)->note() == note) {
873 return *_optimization_iterator;
881 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
883 MidiModel::Notes notes;
884 _model->get_notes (notes, op, val, chan_mask);
886 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
887 CanvasNoteEvent* cne = find_canvas_note (*n);
895 MidiRegionView::redisplay_model()
897 // Don't redisplay the model if we're currently recording and displaying that
903 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
907 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
911 MidiModel::ReadLock lock(_model->read_lock());
913 MidiModel::Notes& notes (_model->notes());
914 _optimization_iterator = _events.begin();
916 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
918 boost::shared_ptr<NoteType> note (*n);
919 CanvasNoteEvent* cne;
922 if (note_in_region_range (note, visible)) {
924 if ((cne = find_canvas_note (note)) != 0) {
931 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
933 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
945 add_note (note, visible);
950 if ((cne = find_canvas_note (note)) != 0) {
958 /* remove note items that are no longer valid */
960 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
961 if (!(*i)->valid ()) {
963 i = _events.erase (i);
969 _pgm_changes.clear();
973 display_program_changes();
975 _marked_for_selection.clear ();
976 _marked_for_velocity.clear ();
978 /* we may have caused _events to contain things out of order (e.g. if a note
979 moved earlier or later). we don't generally need them in time order, but
980 make a note that a sort is required for those cases that require it.
987 MidiRegionView::display_program_changes()
989 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
990 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
992 for (uint8_t i = 0; i < 16; ++i) {
993 if (chn_mask & (1<<i)) {
994 display_program_changes_on_channel (i);
1000 MidiRegionView::display_program_changes_on_channel(uint8_t channel)
1002 boost::shared_ptr<Evoral::Control> control =
1003 _model->control(Evoral::MIDI::ProgramChange (MidiPgmChangeAutomation, channel));
1009 Glib::Mutex::Lock lock (control->list()->lock());
1011 for (AutomationList::const_iterator event = control->list()->begin();
1012 event != control->list()->end(); ++event) {
1013 double event_time = (*event)->when;
1014 double program_number = floor((*event)->value + 0.5);
1016 // Get current value of bank select MSB at time of the program change
1017 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1018 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1020 if (msb_control != 0) {
1021 msb = uint8_t(floor(msb_control->get_double(true, event_time) + 0.5));
1024 // Get current value of bank select LSB at time of the program change
1025 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1026 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1028 if (lsb_control != 0) {
1029 lsb = uint8_t(floor(lsb_control->get_double(true, event_time) + 0.5));
1032 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1034 boost::shared_ptr<MIDI::Name::Patch> patch =
1035 MIDI::Name::MidiPatchManager::instance().find_patch(
1036 _model_name, _custom_device_mode, channel, patch_key);
1038 PCEvent program_change(event_time, uint8_t(program_number), channel);
1041 add_pgm_change(program_change, patch->name());
1044 // program_number is zero-based: convert to one-based
1045 snprintf(buf, 4, "%d", int(program_number+1));
1046 add_pgm_change(program_change, buf);
1052 MidiRegionView::display_sysexes()
1054 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1055 Evoral::MusicalTime time = (*i)->time();
1060 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1061 str << int((*i)->buffer()[b]);
1062 if (b != (*i)->size() -1) {
1066 string text = str.str();
1068 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1070 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1072 double height = midi_stream_view()->contents_height();
1074 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1075 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1077 // Show unless program change is beyond the region bounds
1078 if (time - _region->start() >= _region->length() || time < _region->start()) {
1084 _sys_exes.push_back(sysex);
1089 MidiRegionView::~MidiRegionView ()
1091 in_destructor = true;
1093 trackview.editor().hide_verbose_canvas_cursor ();
1095 note_delete_connection.disconnect ();
1097 delete _list_editor;
1099 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1101 if (_active_notes) {
1109 delete _diff_command;
1110 delete _step_edit_cursor;
1114 MidiRegionView::region_resized (const PropertyChange& what_changed)
1116 RegionView::region_resized(what_changed);
1118 if (what_changed.contains (ARDOUR::Properties::position)) {
1119 set_duration(_region->length(), 0);
1120 if (_enable_display) {
1127 MidiRegionView::reset_width_dependent_items (double pixel_width)
1129 RegionView::reset_width_dependent_items(pixel_width);
1130 assert(_pixel_width == pixel_width);
1132 if (_enable_display) {
1138 MidiRegionView::set_height (double height)
1140 static const double FUDGE = 2.0;
1141 const double old_height = _height;
1142 RegionView::set_height(height);
1143 _height = height - FUDGE;
1145 apply_note_range(midi_stream_view()->lowest_note(),
1146 midi_stream_view()->highest_note(),
1147 height != old_height + FUDGE);
1150 name_pixbuf->raise_to_top();
1153 for (PgmChanges::iterator x = _pgm_changes.begin(); x != _pgm_changes.end(); ++x) {
1154 (*x)->set_height (midi_stream_view()->contents_height());
1157 if (_step_edit_cursor) {
1158 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1163 /** Apply the current note range from the stream view
1164 * by repositioning/hiding notes as necessary
1167 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1169 if (!_enable_display) {
1173 if (!force && _current_range_min == min && _current_range_max == max) {
1177 _current_range_min = min;
1178 _current_range_max = max;
1180 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1181 CanvasNoteEvent* event = *i;
1182 boost::shared_ptr<NoteType> note (event->note());
1184 if (note->note() < _current_range_min ||
1185 note->note() > _current_range_max) {
1191 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1193 const double y1 = midi_stream_view()->note_to_y(note->note());
1194 const double y2 = y1 + floor(midi_stream_view()->note_height());
1196 cnote->property_y1() = y1;
1197 cnote->property_y2() = y2;
1199 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1201 double x = trackview.editor().frame_to_pixel(
1202 beats_to_frames(note->time()) - _region->start());
1203 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1204 double y = midi_stream_view()->note_to_y(event->note()->note())
1205 + ((diamond_size-2.0) / 4.0);
1207 chit->set_height (diamond_size);
1208 chit->move (x - chit->x1(), y - chit->y1());
1215 MidiRegionView::add_ghost (TimeAxisView& tv)
1219 double unit_position = _region->position () / samples_per_unit;
1220 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1221 MidiGhostRegion* ghost;
1223 if (mtv && mtv->midi_view()) {
1224 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1225 to allow having midi notes on top of note lines and waveforms.
1227 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1229 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1232 ghost->set_height ();
1233 ghost->set_duration (_region->length() / samples_per_unit);
1234 ghosts.push_back (ghost);
1236 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1237 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1238 ghost->add_note(note);
1242 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1248 /** Begin tracking note state for successive calls to add_event
1251 MidiRegionView::begin_write()
1253 assert(!_active_notes);
1254 _active_notes = new CanvasNote*[128];
1255 for (unsigned i=0; i < 128; ++i) {
1256 _active_notes[i] = 0;
1261 /** Destroy note state for add_event
1264 MidiRegionView::end_write()
1266 delete[] _active_notes;
1268 _marked_for_selection.clear();
1269 _marked_for_velocity.clear();
1273 /** Resolve an active MIDI note (while recording).
1276 MidiRegionView::resolve_note(uint8_t note, double end_time)
1278 if (midi_view()->note_mode() != Sustained) {
1282 if (_active_notes && _active_notes[note]) {
1283 const nframes64_t end_time_frames = beats_to_frames(end_time);
1284 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1285 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1286 _active_notes[note] = 0;
1291 /** Extend active notes to rightmost edge of region (if length is changed)
1294 MidiRegionView::extend_active_notes()
1296 if (!_active_notes) {
1300 for (unsigned i=0; i < 128; ++i) {
1301 if (_active_notes[i]) {
1302 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1308 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1310 if (no_sound_notes || !trackview.editor().sound_notes()) {
1314 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1317 route_ui->midi_track()->write_immediate_event(
1318 note->on_event().size(), note->on_event().buffer());
1320 const double note_length_beats = (note->off_event().time() - note->on_event().time());
1321 nframes_t note_length_ms = beats_to_frames(note_length_beats)
1322 * (1000 / (double)route_ui->session()->nominal_frame_rate());
1323 Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1324 note_length_ms, G_PRIORITY_DEFAULT);
1328 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1330 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1333 route_ui->midi_track()->write_immediate_event(
1334 note->off_event().size(), note->off_event().buffer());
1340 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1342 const nframes64_t note_start_frames = beats_to_frames(note->time());
1344 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1345 (note_start_frames < _region->start());
1347 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1348 (note->note() <= midi_stream_view()->highest_note());
1354 MidiRegionView::update_note (CanvasNote* ev)
1356 boost::shared_ptr<NoteType> note = ev->note();
1358 const nframes64_t note_start_frames = beats_to_frames(note->time());
1360 /* trim note display to not overlap the end of its region */
1361 const nframes64_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length());
1363 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1364 const double y1 = midi_stream_view()->note_to_y(note->note());
1365 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1367 ev->property_x1() = x;
1368 ev->property_y1() = y1;
1369 if (note->length() > 0) {
1370 ev->property_x2() = note_endpixel;
1372 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1374 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1376 if (note->length() == 0) {
1377 if (_active_notes) {
1378 assert(note->note() < 128);
1379 // If this note is already active there's a stuck note,
1380 // finish the old note rectangle
1381 if (_active_notes[note->note()]) {
1382 CanvasNote* const old_rect = _active_notes[note->note()];
1383 boost::shared_ptr<NoteType> old_note = old_rect->note();
1384 old_rect->property_x2() = x;
1385 old_rect->property_outline_what() = (guint32) 0xF;
1387 _active_notes[note->note()] = ev;
1389 /* outline all but right edge */
1390 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1392 /* outline all edges */
1393 ev->property_outline_what() = (guint32) 0xF;
1398 MidiRegionView::update_hit (CanvasHit* ev)
1400 boost::shared_ptr<NoteType> note = ev->note();
1402 const nframes64_t note_start_frames = beats_to_frames(note->time());
1403 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1404 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1405 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1410 /** Add a MIDI note to the view (with length).
1412 * If in sustained mode, notes with length 0 will be considered active
1413 * notes, and resolve_note should be called when the corresponding note off
1414 * event arrives, to properly display the note.
1417 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1419 CanvasNoteEvent* event = 0;
1421 assert(note->time() >= 0);
1422 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1424 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1426 if (midi_view()->note_mode() == Sustained) {
1428 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1430 update_note (ev_rect);
1434 MidiGhostRegion* gr;
1436 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1437 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1438 gr->add_note(ev_rect);
1442 } else if (midi_view()->note_mode() == Percussive) {
1444 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1446 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1448 update_hit (ev_diamond);
1457 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1458 note_selected(event, true);
1461 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1462 event->show_velocity();
1464 event->on_channel_selection_change(_last_channel_selection);
1465 _events.push_back(event);
1476 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1477 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1479 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1481 /* potentially extend region to hold new note */
1483 nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1484 nframes64_t region_end = _region->position() + _region->length() - 1;
1486 if (end_frame > region_end) {
1487 _region->set_length (end_frame - _region->position(), this);
1490 start_diff_command (_("step add"));
1491 diff_add_note (new_note, true, false);
1494 // last_step_edit_note = new_note;
1498 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1500 assert(program.time >= 0);
1502 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1503 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1505 double height = midi_stream_view()->contents_height();
1507 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1508 new CanvasProgramChange(*this, *group,
1513 _custom_device_mode,
1514 program.time, program.channel, program.value));
1516 // Show unless program change is beyond the region bounds
1517 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1523 _pgm_changes.push_back(pgm_change);
1527 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1529 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1530 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1532 if (msb_control != 0) {
1533 msb = int(msb_control->get_double(true, time));
1536 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1537 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1539 if (lsb_control != 0) {
1540 lsb = lsb_control->get_double(true, time);
1543 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1544 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1545 double program_number = -1.0;
1546 if (program_control != 0) {
1547 program_number = program_control->get_double(true, time);
1550 key.msb = (int) floor(msb + 0.5);
1551 key.lsb = (int) floor(lsb + 0.5);
1552 key.program_number = (int) floor(program_number + 0.5);
1553 assert(key.is_sane());
1558 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1560 // TODO: Get the real event here and alter them at the original times
1561 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1562 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1563 if (msb_control != 0) {
1564 msb_control->set_double(double(new_patch.msb), true, old_program.time);
1567 // TODO: Get the real event here and alter them at the original times
1568 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1569 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1570 if (lsb_control != 0) {
1571 lsb_control->set_double(double(new_patch.lsb), true, old_program.time);
1574 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1575 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1577 assert(program_control != 0);
1578 program_control->set_double(float(new_patch.program_number), true, old_program.time);
1580 _pgm_changes.clear ();
1581 display_program_changes (); // XXX would be nice to limit to just old_program.channel
1585 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1587 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1588 alter_program_change(program_change_event, new_patch);
1592 MidiRegionView::previous_program(CanvasProgramChange& program)
1594 if (program.program() < 127) {
1595 MIDI::Name::PatchPrimaryKey key;
1596 get_patch_key_at(program.event_time(), program.channel(), key);
1597 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1599 key.program_number++;
1600 alter_program_change(program_change_event, key);
1605 MidiRegionView::next_program(CanvasProgramChange& program)
1607 if (program.program() > 0) {
1608 MIDI::Name::PatchPrimaryKey key;
1609 get_patch_key_at(program.event_time(), program.channel(), key);
1610 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1612 key.program_number--;
1613 alter_program_change(program_change_event, key);
1618 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1620 if (_selection.empty()) {
1624 if (_selection.erase (cne) > 0) {
1625 cerr << "Erased a CNE from selection\n";
1630 MidiRegionView::delete_selection()
1632 if (_selection.empty()) {
1636 start_diff_command (_("delete selection"));
1638 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1639 if ((*i)->selected()) {
1640 _diff_command->remove((*i)->note());
1650 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1652 start_diff_command (_("delete note"));
1653 _diff_command->remove (n);
1656 trackview.editor().hide_verbose_canvas_cursor ();
1660 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1662 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1663 if ((*i)->selected() && (*i) != ev) {
1664 (*i)->set_selected(false);
1665 (*i)->hide_velocity();
1673 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1675 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1678 Selection::iterator tmp = i;
1681 (*i)->set_selected (false);
1682 _selection.erase (i);
1691 /* don't bother with removing this regionview from the editor selection,
1692 since we're about to add another note, and thus put/keep this
1693 regionview in the editor selection.
1696 if (!ev->selected()) {
1697 add_to_selection (ev);
1702 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1704 uint8_t low_note = 127;
1705 uint8_t high_note = 0;
1706 MidiModel::Notes& notes (_model->notes());
1707 _optimization_iterator = _events.begin();
1713 if (extend && _selection.empty()) {
1719 /* scan existing selection to get note range */
1721 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1722 if ((*i)->note()->note() < low_note) {
1723 low_note = (*i)->note()->note();
1725 if ((*i)->note()->note() > high_note) {
1726 high_note = (*i)->note()->note();
1730 low_note = min (low_note, notenum);
1731 high_note = max (high_note, notenum);
1734 no_sound_notes = true;
1736 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1738 boost::shared_ptr<NoteType> note (*n);
1739 CanvasNoteEvent* cne;
1740 bool select = false;
1742 if (((1 << note->channel()) & channel_mask) != 0) {
1744 if ((note->note() >= low_note && note->note() <= high_note)) {
1747 } else if (note->note() == notenum) {
1753 if ((cne = find_canvas_note (note)) != 0) {
1754 // extend is false because we've taken care of it,
1755 // since it extends by time range, not pitch.
1756 note_selected (cne, add, false);
1760 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1764 no_sound_notes = false;
1768 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1770 MidiModel::Notes& notes (_model->notes());
1771 _optimization_iterator = _events.begin();
1773 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1775 boost::shared_ptr<NoteType> note (*n);
1776 CanvasNoteEvent* cne;
1778 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1779 if ((cne = find_canvas_note (note)) != 0) {
1780 if (cne->selected()) {
1781 note_deselected (cne);
1783 note_selected (cne, true, false);
1791 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1794 clear_selection_except(ev);
1799 if (!ev->selected()) {
1800 add_to_selection (ev);
1804 /* find end of latest note selected, select all between that and the start of "ev" */
1806 Evoral::MusicalTime earliest = DBL_MAX;
1807 Evoral::MusicalTime latest = 0;
1809 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1810 if ((*i)->note()->end_time() > latest) {
1811 latest = (*i)->note()->end_time();
1813 if ((*i)->note()->time() < earliest) {
1814 earliest = (*i)->note()->time();
1818 if (ev->note()->end_time() > latest) {
1819 latest = ev->note()->end_time();
1822 if (ev->note()->time() < earliest) {
1823 earliest = ev->note()->time();
1826 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1828 /* find notes entirely within OR spanning the earliest..latest range */
1830 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1831 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1832 add_to_selection (*i);
1836 /* if events were guaranteed to be time sorted, we could do this.
1837 but as of sept 10th 2009, they no longer are.
1840 if ((*i)->note()->time() > latest) {
1849 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1851 remove_from_selection (ev);
1855 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1865 // TODO: Make this faster by storing the last updated selection rect, and only
1866 // adjusting things that are in the area that appears/disappeared.
1867 // We probably need a tree to be able to find events in O(log(n)) time.
1869 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1871 /* check if any corner of the note is inside the rect
1874 1) this is computing "touched by", not "contained by" the rect.
1875 2) this does not require that events be sorted in time.
1878 const double ix1 = (*i)->x1();
1879 const double ix2 = (*i)->x2();
1880 const double iy1 = (*i)->y1();
1881 const double iy2 = (*i)->y2();
1883 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1884 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1885 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1886 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1889 if (!(*i)->selected()) {
1890 add_to_selection (*i);
1892 } else if ((*i)->selected()) {
1893 // Not inside rectangle
1894 remove_from_selection (*i);
1900 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1902 Selection::iterator i = _selection.find (ev);
1904 if (i != _selection.end()) {
1905 _selection.erase (i);
1908 ev->set_selected (false);
1909 ev->hide_velocity ();
1911 if (_selection.empty()) {
1912 PublicEditor& editor (trackview.editor());
1913 editor.get_selection().remove (this);
1918 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1920 bool add_mrv_selection = false;
1922 if (_selection.empty()) {
1923 add_mrv_selection = true;
1926 if (_selection.insert (ev).second) {
1927 ev->set_selected (true);
1928 play_midi_note ((ev)->note());
1931 if (add_mrv_selection) {
1932 PublicEditor& editor (trackview.editor());
1933 editor.get_selection().add (this);
1938 MidiRegionView::move_selection(double dx, double dy)
1940 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1941 (*i)->move_event(dx, dy);
1946 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1948 assert (!_selection.empty());
1950 uint8_t lowest_note_in_selection = 127;
1951 uint8_t highest_note_in_selection = 0;
1952 uint8_t highest_note_difference = 0;
1954 // find highest and lowest notes first
1956 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1957 uint8_t pitch = (*i)->note()->note();
1958 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
1959 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1963 cerr << "dnote: " << (int) dnote << endl;
1964 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1965 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1966 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1967 << int(highest_note_in_selection) << endl;
1968 cerr << "selection size: " << _selection.size() << endl;
1969 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1972 // Make sure the note pitch does not exceed the MIDI standard range
1973 if (highest_note_in_selection + dnote > 127) {
1974 highest_note_difference = highest_note_in_selection - 127;
1977 start_diff_command(_("move notes"));
1979 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1981 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1984 start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1986 start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1989 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1995 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1997 uint8_t original_pitch = (*i)->note()->note();
1998 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2000 // keep notes in standard midi range
2001 clamp_to_0_127(new_pitch);
2003 // keep original pitch if note is dragged outside valid midi range
2004 if ((original_pitch != 0 && new_pitch == 0)
2005 || (original_pitch != 127 && new_pitch == 127)) {
2006 new_pitch = original_pitch;
2009 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2010 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2012 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
2017 // care about notes being moved beyond the upper/lower bounds on the canvas
2018 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2019 highest_note_in_selection > midi_stream_view()->highest_note()) {
2020 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2025 MidiRegionView::snap_pixel_to_frame(double x)
2027 PublicEditor& editor = trackview.editor();
2028 // x is region relative, convert it to global absolute frames
2029 nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
2030 editor.snap_to(frame);
2031 return frame - _region->position(); // convert back to region relative
2035 MidiRegionView::snap_frame_to_frame(nframes64_t x)
2037 PublicEditor& editor = trackview.editor();
2038 // x is region relative, convert it to global absolute frames
2039 nframes64_t frame = x + _region->position();
2040 editor.snap_to(frame);
2041 return frame - _region->position(); // convert back to region relative
2045 MidiRegionView::snap_to_pixel(double x)
2047 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2051 MidiRegionView::get_position_pixels()
2053 nframes64_t region_frame = get_position();
2054 return trackview.editor().frame_to_pixel(region_frame);
2058 MidiRegionView::get_end_position_pixels()
2060 nframes64_t frame = get_position() + get_duration ();
2061 return trackview.editor().frame_to_pixel(frame);
2065 MidiRegionView::beats_to_frames(double beats) const
2067 return _time_converter.to(beats);
2071 MidiRegionView::frames_to_beats(nframes64_t frames) const
2073 return _time_converter.from(frames);
2077 MidiRegionView::begin_resizing (bool /*at_front*/)
2079 _resize_data.clear();
2081 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2082 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2084 // only insert CanvasNotes into the map
2086 NoteResizeData *resize_data = new NoteResizeData();
2087 resize_data->canvas_note = note;
2089 // create a new SimpleRect from the note which will be the resize preview
2090 SimpleRect *resize_rect = new SimpleRect(
2091 *group, note->x1(), note->y1(), note->x2(), note->y2());
2093 // calculate the colors: get the color settings
2094 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2095 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2098 // make the resize preview notes more transparent and bright
2099 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2101 // calculate color based on note velocity
2102 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2103 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2107 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2108 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2110 resize_data->resize_rect = resize_rect;
2111 _resize_data.push_back(resize_data);
2116 /** Update resizing notes while user drags.
2117 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2118 * @param at_front which end of the note (true == note on, false == note off)
2119 * @param delta_x change in mouse position since the start of the drag
2120 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2121 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2122 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2123 * as the \a primary note.
2126 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2128 bool cursor_set = false;
2130 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2131 SimpleRect* resize_rect = (*i)->resize_rect;
2132 CanvasNote* canvas_note = (*i)->canvas_note;
2137 current_x = canvas_note->x1() + delta_x;
2139 current_x = primary->x1() + delta_x;
2143 current_x = canvas_note->x2() + delta_x;
2145 current_x = primary->x2() + delta_x;
2150 resize_rect->property_x1() = snap_to_pixel(current_x);
2151 resize_rect->property_x2() = canvas_note->x2();
2153 resize_rect->property_x2() = snap_to_pixel(current_x);
2154 resize_rect->property_x1() = canvas_note->x1();
2160 beats = snap_pixel_to_frame (current_x);
2161 beats = frames_to_beats (beats);
2166 if (beats < canvas_note->note()->end_time()) {
2167 len = canvas_note->note()->time() - beats;
2168 len += canvas_note->note()->length();
2173 if (beats >= canvas_note->note()->end_time()) {
2174 len = beats - canvas_note->note()->time();
2181 snprintf (buf, sizeof (buf), "%.3g beats", len);
2182 trackview.editor().show_verbose_canvas_cursor_with (buf);
2191 /** Finish resizing notes when the user releases the mouse button.
2192 * Parameters the same as for \a update_resizing().
2195 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2197 start_diff_command(_("resize notes"));
2199 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2200 CanvasNote* canvas_note = (*i)->canvas_note;
2201 SimpleRect* resize_rect = (*i)->resize_rect;
2206 current_x = canvas_note->x1() + delta_x;
2208 current_x = primary->x1() + delta_x;
2212 current_x = canvas_note->x2() + delta_x;
2214 current_x = primary->x2() + delta_x;
2218 current_x = snap_pixel_to_frame (current_x);
2219 current_x = frames_to_beats (current_x);
2221 if (at_front && current_x < canvas_note->note()->end_time()) {
2222 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2224 double len = canvas_note->note()->time() - current_x;
2225 len += canvas_note->note()->length();
2228 /* XXX convert to beats */
2229 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2234 double len = current_x - canvas_note->note()->time();
2237 /* XXX convert to beats */
2238 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2246 _resize_data.clear();
2251 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2253 uint8_t new_velocity;
2256 new_velocity = event->note()->velocity() + velocity;
2257 clamp_to_0_127(new_velocity);
2259 new_velocity = velocity;
2262 event->set_selected (event->selected()); // change color
2264 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2268 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2273 new_note = event->note()->note() + note;
2278 clamp_to_0_127 (new_note);
2279 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2283 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2285 bool change_start = false;
2286 bool change_length = false;
2287 Evoral::MusicalTime new_start = 0;
2288 Evoral::MusicalTime new_length = 0;
2290 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2292 front_delta: if positive - move the start of the note later in time (shortening it)
2293 if negative - move the start of the note earlier in time (lengthening it)
2295 end_delta: if positive - move the end of the note later in time (lengthening it)
2296 if negative - move the end of the note earlier in time (shortening it)
2300 if (front_delta < 0) {
2302 if (event->note()->time() < -front_delta) {
2305 new_start = event->note()->time() + front_delta; // moves earlier
2308 /* start moved toward zero, so move the end point out to where it used to be.
2309 Note that front_delta is negative, so this increases the length.
2312 new_length = event->note()->length() - front_delta;
2313 change_start = true;
2314 change_length = true;
2318 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2320 if (new_pos < event->note()->end_time()) {
2321 new_start = event->note()->time() + front_delta;
2322 /* start moved toward the end, so move the end point back to where it used to be */
2323 new_length = event->note()->length() - front_delta;
2324 change_start = true;
2325 change_length = true;
2332 bool can_change = true;
2333 if (end_delta < 0) {
2334 if (event->note()->length() < -end_delta) {
2340 new_length = event->note()->length() + end_delta;
2341 change_length = true;
2346 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2349 if (change_length) {
2350 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2355 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2357 Evoral::MusicalTime new_time;
2361 if (event->note()->time() < -delta) {
2364 new_time = event->note()->time() + delta;
2367 new_time = event->note()->time() + delta;
2373 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2377 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2381 if (_selection.empty()) {
2396 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2397 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2403 start_diff_command(_("change velocities"));
2405 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2406 Selection::iterator next = i;
2408 change_note_velocity (*i, delta, true);
2414 if (!_selection.empty()) {
2416 snprintf (buf, sizeof (buf), "Vel %d",
2417 (int) (*_selection.begin())->note()->velocity());
2418 trackview.editor().show_verbose_canvas_cursor_with (buf);
2424 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2426 if (_selection.empty()) {
2443 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2445 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2449 if ((int8_t) (*i)->note()->note() + delta > 127) {
2456 start_diff_command (_("transpose"));
2458 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2459 Selection::iterator next = i;
2461 change_note_note (*i, delta, true);
2469 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2471 Evoral::MusicalTime delta;
2476 /* grab the current grid distance */
2478 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2480 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2481 cerr << "Grid type not available as beats - TO BE FIXED\n";
2490 start_diff_command (_("change note lengths"));
2492 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2493 Selection::iterator next = i;
2496 /* note the negation of the delta for start */
2498 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2507 MidiRegionView::nudge_notes (bool forward)
2509 if (_selection.empty()) {
2513 /* pick a note as the point along the timeline to get the nudge distance.
2514 its not necessarily the earliest note, so we may want to pull the notes out
2515 into a vector and sort before using the first one.
2518 nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2520 nframes64_t distance;
2522 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2524 /* grid is off - use nudge distance */
2526 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2532 nframes64_t next_pos = ref_point;
2535 /* XXX need check on max_frames, but that needs max_frames64 or something */
2538 if (next_pos == 0) {
2544 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2545 distance = ref_point - next_pos;
2548 if (distance == 0) {
2552 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2558 start_diff_command (_("nudge"));
2560 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2561 Selection::iterator next = i;
2563 change_note_time (*i, delta, true);
2571 MidiRegionView::change_channel(uint8_t channel)
2573 start_diff_command(_("change channel"));
2574 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2575 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2583 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2585 if (_mouse_state == SelectTouchDragging) {
2586 note_selected (ev, true);
2589 show_verbose_canvas_cursor (ev->note ());
2593 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2595 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2596 (*i)->hide_velocity ();
2599 trackview.editor().hide_verbose_canvas_cursor ();
2603 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2605 boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2607 display_model(msrc->model());
2611 MidiRegionView::set_frame_color()
2614 if (_selected && should_show_selection) {
2615 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2617 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2623 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2627 case FilterChannels:
2628 _force_channel = -1;
2631 _force_channel = mask;
2632 mask = 0xFFFF; // Show all notes as active (below)
2635 // Update notes for selection
2636 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2637 (*i)->on_channel_selection_change(mask);
2640 _last_channel_selection = mask;
2644 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2646 _model_name = model;
2647 _custom_device_mode = custom_device_mode;
2652 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2654 if (_selection.empty()) {
2658 PublicEditor& editor (trackview.editor());
2663 editor.get_cut_buffer().add (selection_as_cut_buffer());
2671 start_diff_command();
2673 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2679 diff_remove_note (*i);
2689 MidiRegionView::selection_as_cut_buffer () const
2693 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2694 NoteType* n = (*i)->note().get();
2695 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2698 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2705 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2711 start_diff_command (_("paste"));
2713 Evoral::MusicalTime beat_delta;
2714 Evoral::MusicalTime paste_pos_beats;
2715 Evoral::MusicalTime duration;
2716 Evoral::MusicalTime end_point = 0;
2718 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2719 paste_pos_beats = frames_to_beats (pos - _region->position());
2720 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2721 paste_pos_beats = 0;
2723 _selection.clear ();
2725 for (int n = 0; n < (int) times; ++n) {
2727 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2729 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2730 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2732 /* make all newly added notes selected */
2734 diff_add_note (copied_note, true);
2735 end_point = copied_note->end_time();
2738 paste_pos_beats += duration;
2741 /* if we pasted past the current end of the region, extend the region */
2743 nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2744 nframes64_t region_end = _region->position() + _region->length() - 1;
2746 if (end_frame > region_end) {
2748 trackview.session()->begin_reversible_command (_("paste"));
2750 _region->clear_history ();
2751 _region->set_length (end_frame, this);
2752 trackview.session()->add_command (new StatefulDiffCommand (_region));
2758 struct EventNoteTimeEarlyFirstComparator {
2759 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2760 return a->note()->time() < b->note()->time();
2765 MidiRegionView::time_sort_events ()
2767 if (!_sort_needed) {
2771 EventNoteTimeEarlyFirstComparator cmp;
2774 _sort_needed = false;
2778 MidiRegionView::goto_next_note ()
2780 // nframes64_t pos = -1;
2781 bool use_next = false;
2783 if (_events.back()->selected()) {
2787 time_sort_events ();
2789 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2790 if ((*i)->selected()) {
2793 } else if (use_next) {
2795 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2800 /* use the first one */
2802 unique_select (_events.front());
2807 MidiRegionView::goto_previous_note ()
2809 // nframes64_t pos = -1;
2810 bool use_next = false;
2812 if (_events.front()->selected()) {
2816 time_sort_events ();
2818 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2819 if ((*i)->selected()) {
2822 } else if (use_next) {
2824 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2829 /* use the last one */
2831 unique_select (*(_events.rbegin()));
2835 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2837 bool had_selected = false;
2839 time_sort_events ();
2841 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2842 if ((*i)->selected()) {
2843 selected.insert ((*i)->note());
2844 had_selected = true;
2848 if (allow_all_if_none_selected && !had_selected) {
2849 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2850 selected.insert ((*i)->note());
2856 MidiRegionView::update_ghost_note (double x, double y)
2862 nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2863 trackview.editor().snap_to (f);
2864 f -= _region->position ();
2867 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2872 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2874 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2875 _ghost_note->note()->set_length (length);
2876 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2878 update_note (_ghost_note);
2880 show_verbose_canvas_cursor (_ghost_note->note ());
2884 MidiRegionView::create_ghost_note (double x, double y)
2889 boost::shared_ptr<NoteType> g (new NoteType);
2890 _ghost_note = new NoEventCanvasNote (*this, *group, g);
2891 update_ghost_note (x, y);
2892 _ghost_note->show ();
2897 show_verbose_canvas_cursor (_ghost_note->note ());
2901 MidiRegionView::snap_changed ()
2907 create_ghost_note (_last_ghost_x, _last_ghost_y);
2911 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2914 snprintf (buf, sizeof (buf), "%s (%d)\nVel %d",
2915 Evoral::midi_note_name (n->note()).c_str(),
2917 (int) n->velocity());
2918 trackview.editor().show_verbose_canvas_cursor_with (buf);
2922 MidiRegionView::drop_down_keys ()
2924 _mouse_state = None;
2928 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double x, double y)
2930 double note = midi_stream_view()->y_to_note(y);
2932 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2934 cerr << "Selecting by position\n";
2936 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
2938 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
2939 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
2940 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
2941 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
2946 bool add_mrv_selection = false;
2948 if (_selection.empty()) {
2949 add_mrv_selection = true;
2952 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
2953 if (_selection.insert (*i).second) {
2954 (*i)->set_selected (true);
2958 if (add_mrv_selection) {
2959 PublicEditor& editor (trackview.editor());
2960 editor.get_selection().add (this);
2965 MidiRegionView::color_handler ()
2967 RegionView::color_handler ();
2969 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2970 (*i)->set_selected ((*i)->selected()); // will change color
2973 /* XXX probably more to do here */
2977 MidiRegionView::enable_display (bool yn)
2979 RegionView::enable_display (yn);
2986 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
2988 if (_step_edit_cursor == 0) {
2989 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
2991 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
2992 _step_edit_cursor->property_y1() = 0;
2993 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
2994 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,45,45,90);
2995 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (255,255,255,90);
2998 move_step_edit_cursor (pos);
2999 _step_edit_cursor->show ();
3003 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3005 if (_step_edit_cursor) {
3006 double pixel = trackview.editor().frame_to_pixel (beats_to_frames (pos));
3007 _step_edit_cursor->property_x1() = pixel;
3008 _step_edit_cursor->property_x2() = pixel + _step_edit_cursor_width;
3013 MidiRegionView::hide_step_edit_cursor ()
3015 if (_step_edit_cursor) {
3016 _step_edit_cursor->hide ();
3021 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3023 _step_edit_cursor_width = trackview.editor().frame_to_pixel (beats_to_frames (beats));
3024 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + _step_edit_cursor_width;