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"
73 using namespace ARDOUR;
75 using namespace Editing;
76 using namespace ArdourCanvas;
77 using Gtkmm2ext::Keyboard;
79 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
80 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
81 : RegionView (parent, tv, r, spu, basic_color)
83 , _last_channel_selection(0xFFFF)
84 , _current_range_min(0)
85 , _current_range_max(0)
86 , _model_name(string())
87 , _custom_device_mode(string())
89 , _note_group(new ArdourCanvas::Group(*parent))
93 , _step_edit_cursor (0)
94 , _step_edit_cursor_width (1.0)
95 , _step_edit_cursor_position (0.0)
99 , _optimization_iterator (_events.end())
101 , no_sound_notes (false)
102 , pre_enter_cursor (0)
104 _note_group->raise_to_top();
105 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
107 connect_to_diskstream ();
110 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
111 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
112 TimeAxisViewItem::Visibility visibility)
113 : RegionView (parent, tv, r, spu, basic_color, false, visibility)
115 , _last_channel_selection(0xFFFF)
116 , _model_name(string())
117 , _custom_device_mode(string())
119 , _note_group(new ArdourCanvas::Group(*parent))
125 , _sort_needed (true)
126 , _optimization_iterator (_events.end())
128 , no_sound_notes (false)
130 _note_group->raise_to_top();
131 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
133 connect_to_diskstream ();
136 MidiRegionView::MidiRegionView (const MidiRegionView& other)
137 : sigc::trackable(other)
140 , _last_channel_selection(0xFFFF)
141 , _model_name(string())
142 , _custom_device_mode(string())
144 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
150 , _sort_needed (true)
151 , _optimization_iterator (_events.end())
153 , no_sound_notes (false)
158 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
159 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
164 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
165 : RegionView (other, boost::shared_ptr<Region> (region))
167 , _last_channel_selection(0xFFFF)
168 , _model_name(string())
169 , _custom_device_mode(string())
171 , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
177 , _sort_needed (true)
178 , _optimization_iterator (_events.end())
180 , no_sound_notes (false)
185 UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
186 c.set_rgb_p (r/255.0, g/255.0, b/255.0);
192 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
194 PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
196 CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
197 ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
201 midi_region()->midi_source(0)->load_model();
204 _model = midi_region()->midi_source(0)->model();
205 _enable_display = false;
207 RegionView::init (basic_color, false);
209 compute_colors (basic_color);
211 set_height (trackview.current_height());
214 region_sync_changed ();
215 region_resized (ARDOUR::bounds_change);
218 reset_width_dependent_items (_pixel_width);
222 _enable_display = true;
225 display_model (_model);
229 group->raise_to_top();
230 group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
232 midi_view()->signal_channel_mode_changed().connect(
233 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
235 midi_view()->signal_midi_patch_settings_changed().connect(
236 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
238 trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
240 connect_to_diskstream ();
244 MidiRegionView::connect_to_diskstream ()
246 midi_view()->midi_track()->DataRecorded.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::data_recorded, this, _1, _2), gui_context ());
250 MidiRegionView::canvas_event(GdkEvent* ev)
252 if (!trackview.editor().internal_editing()) {
256 /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
257 to its items, which means that ev->type == GDK_SCROLL will never be seen
262 return scroll (&ev->scroll);
265 return key_press (&ev->key);
267 case GDK_KEY_RELEASE:
268 return key_release (&ev->key);
270 case GDK_BUTTON_PRESS:
271 return button_press (&ev->button);
273 case GDK_2BUTTON_PRESS:
276 case GDK_BUTTON_RELEASE:
277 return button_release (&ev->button);
279 case GDK_ENTER_NOTIFY:
280 return enter_notify (&ev->crossing);
282 case GDK_LEAVE_NOTIFY:
283 return leave_notify (&ev->crossing);
285 case GDK_MOTION_NOTIFY:
286 return motion (&ev->motion);
296 MidiRegionView::enter_notify (GdkEventCrossing* ev)
298 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
300 Keyboard::magic_widget_grab_focus();
303 if (trackview.editor().current_mouse_mode() == MouseRange) {
304 create_ghost_note (ev->x, ev->y);
311 MidiRegionView::leave_notify (GdkEventCrossing* ev)
313 trackview.editor().hide_verbose_canvas_cursor ();
320 MidiRegionView::button_press (GdkEventButton* ev)
324 group->w2i (_last_x, _last_y);
326 if (_mouse_state != SelectTouchDragging && ev->button == 1) {
327 _pressed_button = ev->button;
328 _mouse_state = Pressed;
332 _pressed_button = ev->button;
338 MidiRegionView::button_release (GdkEventButton* ev)
340 double event_x, event_y;
341 framepos_t event_frame = 0;
345 group->w2i(event_x, event_y);
346 group->ungrab(ev->time);
347 event_frame = trackview.editor().pixel_to_frame(event_x);
349 if (ev->button == 3) {
351 } else if (_pressed_button != 1) {
355 switch (_mouse_state) {
356 case Pressed: // Clicked
357 switch (trackview.editor().current_mouse_mode()) {
361 maybe_select_by_position (ev, event_x, event_y);
367 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
371 create_note_at (event_x, event_y, beats, true);
379 case SelectRectDragging: // Select drag done
385 case AddDragging: // Add drag done
387 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
388 const double x = _drag_rect->property_x1();
389 const double length = trackview.editor().pixel_to_frame
390 (_drag_rect->property_x2() - _drag_rect->property_x1());
392 create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), false);
398 create_ghost_note (ev->x, ev->y);
408 MidiRegionView::motion (GdkEventMotion* ev)
410 double event_x, event_y;
411 framepos_t event_frame = 0;
415 group->w2i(event_x, event_y);
417 // convert event_x to global frame
418 event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
419 trackview.editor().snap_to(event_frame);
420 // convert event_frame back to local coordinates relative to position
421 event_frame -= _region->position();
424 update_ghost_note (ev->x, ev->y);
427 /* any motion immediately hides velocity text that may have been visible */
429 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
430 (*i)->hide_velocity ();
433 switch (_mouse_state) {
434 case Pressed: // Maybe start a drag, if we've moved a bit
436 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
437 /* no appreciable movement since the button was pressed */
442 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject) {
443 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
444 Gdk::Cursor(Gdk::FLEUR), ev->time);
447 _drag_start_x = event_x;
448 _drag_start_y = event_y;
450 _drag_rect = new ArdourCanvas::SimpleRect(*group);
451 _drag_rect->property_x1() = event_x;
452 _drag_rect->property_y1() = event_y;
453 _drag_rect->property_x2() = event_x;
454 _drag_rect->property_y2() = event_y;
455 _drag_rect->property_outline_what() = 0xFF;
456 _drag_rect->property_outline_color_rgba()
457 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
458 _drag_rect->property_fill_color_rgba()
459 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
461 _mouse_state = SelectRectDragging;
464 // Add note drag start
465 } else if (trackview.editor().internal_editing()) {
470 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
471 Gdk::Cursor(Gdk::FLEUR), ev->time);
474 _drag_start_x = event_x;
475 _drag_start_y = event_y;
477 _drag_rect = new ArdourCanvas::SimpleRect(*group);
478 _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
480 _drag_rect->property_y1() = midi_stream_view()->note_to_y(
481 midi_stream_view()->y_to_note(event_y));
482 _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
483 _drag_rect->property_y2() = _drag_rect->property_y1()
484 + floor(midi_stream_view()->note_height());
485 _drag_rect->property_outline_what() = 0xFF;
486 _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
487 _drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
489 _mouse_state = AddDragging;
495 case SelectRectDragging: // Select drag motion
496 case AddDragging: // Add note drag motion
500 GdkModifierType state;
501 gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
506 if (_mouse_state == AddDragging)
507 event_x = trackview.editor().frame_to_pixel(event_frame);
510 if (event_x > _drag_start_x)
511 _drag_rect->property_x2() = event_x;
513 _drag_rect->property_x1() = event_x;
516 if (_drag_rect && _mouse_state == SelectRectDragging) {
517 if (event_y > _drag_start_y)
518 _drag_rect->property_y2() = event_y;
520 _drag_rect->property_y1() = event_y;
522 update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
528 case SelectTouchDragging:
540 MidiRegionView::scroll (GdkEventScroll* ev)
542 if (_selection.empty()) {
546 trackview.editor().hide_verbose_canvas_cursor ();
548 bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
550 if (ev->direction == GDK_SCROLL_UP) {
551 change_velocities (true, fine, false);
552 } else if (ev->direction == GDK_SCROLL_DOWN) {
553 change_velocities (false, fine, false);
559 MidiRegionView::key_press (GdkEventKey* ev)
561 /* since GTK bindings are generally activated on press, and since
562 detectable auto-repeat is the name of the game and only sends
563 repeated presses, carry out key actions at key press, not release.
566 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
567 _mouse_state = SelectTouchDragging;
570 } else if (ev->keyval == GDK_Escape) {
574 } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
576 bool start = (ev->keyval == GDK_comma);
577 bool end = (ev->keyval == GDK_period);
578 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
579 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
581 change_note_lengths (fine, shorter, 0.0, start, end);
585 } else if (ev->keyval == GDK_Delete) {
590 } else if (ev->keyval == GDK_Tab) {
592 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
593 goto_previous_note ();
599 } else if (ev->keyval == GDK_Up) {
601 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
602 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
604 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
605 change_velocities (true, fine, allow_smush);
607 transpose (true, fine, allow_smush);
611 } else if (ev->keyval == GDK_Down) {
613 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
614 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
616 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
617 change_velocities (false, fine, allow_smush);
619 transpose (false, fine, allow_smush);
623 } else if (ev->keyval == GDK_Left) {
628 } else if (ev->keyval == GDK_Right) {
633 } else if (ev->keyval == GDK_Control_L) {
642 MidiRegionView::key_release (GdkEventKey* ev)
644 if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
652 MidiRegionView::show_list_editor ()
655 _list_editor = new MidiListEditor (trackview.session(), midi_region());
657 _list_editor->present ();
660 /** Add a note to the model, and the view, at a canvas (click) coordinate.
661 * \param x horizontal position in pixels
662 * \param y vertical position in pixels
663 * \param length duration of the note in beats, which will be snapped to the grid
664 * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
667 MidiRegionView::create_note_at(double x, double y, double length, bool sh)
669 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
670 MidiStreamView* const view = mtv->midi_view();
672 double note = midi_stream_view()->y_to_note(y);
675 assert(note <= 127.0);
677 // Start of note in frames relative to region start
678 framepos_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
679 assert(start_frames >= 0);
682 length = frames_to_beats(
683 snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
685 assert (length != 0);
688 length = frames_to_beats (beats_to_frames (length) - 1);
691 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
695 /* pick the highest selected channel, unless all channels are selected,
696 which is interpreted to mean channel 1 (zero)
699 for (uint16_t i = 0; i < 16; ++i) {
700 if (chn_mask & (1<<i)) {
710 const boost::shared_ptr<NoteType> new_note (new NoteType (channel,
711 frames_to_beats(start_frames + _region->start()), length,
712 (uint8_t)note, 0x40));
714 if (_model->contains (new_note)) {
718 view->update_note_range(new_note->note());
720 MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
722 _model->apply_command(*trackview.session(), cmd);
724 play_midi_note (new_note);
728 MidiRegionView::clear_events()
733 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
734 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
739 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
744 _pgm_changes.clear();
746 _optimization_iterator = _events.end();
750 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
754 content_connection.disconnect ();
755 _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
759 if (_enable_display) {
765 MidiRegionView::start_diff_command(string name)
767 if (!_diff_command) {
768 _diff_command = _model->new_diff_command(name);
773 MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
776 _diff_command->add(note);
779 _marked_for_selection.insert(note);
782 _marked_for_velocity.insert(note);
787 MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
789 if (_diff_command && ev->note()) {
790 _diff_command->remove(ev->note());
795 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
796 MidiModel::DiffCommand::Property property,
800 _diff_command->change (ev->note(), property, val);
805 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
806 MidiModel::DiffCommand::Property property,
807 Evoral::MusicalTime val)
810 _diff_command->change (ev->note(), property, val);
815 MidiRegionView::apply_diff ()
819 if (!_diff_command) {
823 if ((add_or_remove = _diff_command->adds_or_removes())) {
824 // Mark all selected notes for selection when model reloads
825 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
826 _marked_for_selection.insert((*i)->note());
830 _model->apply_command(*trackview.session(), _diff_command);
832 midi_view()->midi_track()->playlist_modified();
835 _marked_for_selection.clear();
838 _marked_for_velocity.clear();
842 MidiRegionView::apply_diff_as_subcommand()
846 if (!_diff_command) {
850 if ((add_or_remove = _diff_command->adds_or_removes())) {
851 // Mark all selected notes for selection when model reloads
852 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
853 _marked_for_selection.insert((*i)->note());
857 _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
859 midi_view()->midi_track()->playlist_modified();
862 _marked_for_selection.clear();
864 _marked_for_velocity.clear();
869 MidiRegionView::abort_command()
871 delete _diff_command;
877 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
879 if (_optimization_iterator != _events.end()) {
880 ++_optimization_iterator;
883 if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
884 return *_optimization_iterator;
887 for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
888 if ((*_optimization_iterator)->note() == note) {
889 return *_optimization_iterator;
897 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
899 MidiModel::Notes notes;
900 _model->get_notes (notes, op, val, chan_mask);
902 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
903 CanvasNoteEvent* cne = find_canvas_note (*n);
911 MidiRegionView::redisplay_model()
913 // Don't redisplay the model if we're currently recording and displaying that
919 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
923 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
927 MidiModel::ReadLock lock(_model->read_lock());
929 MidiModel::Notes& notes (_model->notes());
930 _optimization_iterator = _events.begin();
932 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
934 boost::shared_ptr<NoteType> note (*n);
935 CanvasNoteEvent* cne;
938 if (note_in_region_range (note, visible)) {
940 if ((cne = find_canvas_note (note)) != 0) {
947 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
949 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
961 add_note (note, visible);
966 if ((cne = find_canvas_note (note)) != 0) {
974 /* remove note items that are no longer valid */
976 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
977 if (!(*i)->valid ()) {
979 i = _events.erase (i);
985 _pgm_changes.clear();
989 display_program_changes();
991 _marked_for_selection.clear ();
992 _marked_for_velocity.clear ();
994 /* we may have caused _events to contain things out of order (e.g. if a note
995 moved earlier or later). we don't generally need them in time order, but
996 make a note that a sort is required for those cases that require it.
1003 MidiRegionView::display_program_changes()
1005 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1006 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1008 for (uint8_t i = 0; i < 16; ++i) {
1009 if (chn_mask & (1<<i)) {
1010 display_program_changes_on_channel (i);
1016 MidiRegionView::display_program_changes_on_channel(uint8_t channel)
1018 boost::shared_ptr<Evoral::Control> control =
1019 _model->control(Evoral::MIDI::ProgramChange (MidiPgmChangeAutomation, channel));
1025 Glib::Mutex::Lock lock (control->list()->lock());
1027 for (AutomationList::const_iterator event = control->list()->begin();
1028 event != control->list()->end(); ++event) {
1029 double event_time = (*event)->when;
1030 double program_number = floor((*event)->value + 0.5);
1032 // Get current value of bank select MSB at time of the program change
1033 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1034 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1036 if (msb_control != 0) {
1037 msb = uint8_t(floor(msb_control->get_double(true, event_time) + 0.5));
1040 // Get current value of bank select LSB at time of the program change
1041 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1042 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1044 if (lsb_control != 0) {
1045 lsb = uint8_t(floor(lsb_control->get_double(true, event_time) + 0.5));
1048 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1050 boost::shared_ptr<MIDI::Name::Patch> patch =
1051 MIDI::Name::MidiPatchManager::instance().find_patch(
1052 _model_name, _custom_device_mode, channel, patch_key);
1054 PCEvent program_change(event_time, uint8_t(program_number), channel);
1057 add_pgm_change(program_change, patch->name());
1060 // program_number is zero-based: convert to one-based
1061 snprintf(buf, 4, "%d", int(program_number+1));
1062 add_pgm_change(program_change, buf);
1068 MidiRegionView::display_sysexes()
1070 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1071 Evoral::MusicalTime time = (*i)->time();
1076 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1077 str << int((*i)->buffer()[b]);
1078 if (b != (*i)->size() -1) {
1082 string text = str.str();
1084 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1086 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1088 double height = midi_stream_view()->contents_height();
1090 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1091 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1093 // Show unless program change is beyond the region bounds
1094 if (time - _region->start() >= _region->length() || time < _region->start()) {
1100 _sys_exes.push_back(sysex);
1105 MidiRegionView::~MidiRegionView ()
1107 in_destructor = true;
1109 trackview.editor().hide_verbose_canvas_cursor ();
1111 note_delete_connection.disconnect ();
1113 delete _list_editor;
1115 RegionViewGoingAway (this); /* EMIT_SIGNAL */
1117 if (_active_notes) {
1125 delete _diff_command;
1126 delete _step_edit_cursor;
1130 MidiRegionView::region_resized (const PropertyChange& what_changed)
1132 RegionView::region_resized(what_changed);
1134 if (what_changed.contains (ARDOUR::Properties::position)) {
1135 set_duration(_region->length(), 0);
1136 if (_enable_display) {
1143 MidiRegionView::reset_width_dependent_items (double pixel_width)
1145 RegionView::reset_width_dependent_items(pixel_width);
1146 assert(_pixel_width == pixel_width);
1148 if (_enable_display) {
1152 move_step_edit_cursor (_step_edit_cursor_position);
1153 set_step_edit_cursor_width (_step_edit_cursor_width);
1157 MidiRegionView::set_height (double height)
1159 static const double FUDGE = 2.0;
1160 const double old_height = _height;
1161 RegionView::set_height(height);
1162 _height = height - FUDGE;
1164 apply_note_range(midi_stream_view()->lowest_note(),
1165 midi_stream_view()->highest_note(),
1166 height != old_height + FUDGE);
1169 name_pixbuf->raise_to_top();
1172 for (PgmChanges::iterator x = _pgm_changes.begin(); x != _pgm_changes.end(); ++x) {
1173 (*x)->set_height (midi_stream_view()->contents_height());
1176 if (_step_edit_cursor) {
1177 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1182 /** Apply the current note range from the stream view
1183 * by repositioning/hiding notes as necessary
1186 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1188 if (!_enable_display) {
1192 if (!force && _current_range_min == min && _current_range_max == max) {
1196 _current_range_min = min;
1197 _current_range_max = max;
1199 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1200 CanvasNoteEvent* event = *i;
1201 boost::shared_ptr<NoteType> note (event->note());
1203 if (note->note() < _current_range_min ||
1204 note->note() > _current_range_max) {
1210 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1212 const double y1 = midi_stream_view()->note_to_y(note->note());
1213 const double y2 = y1 + floor(midi_stream_view()->note_height());
1215 cnote->property_y1() = y1;
1216 cnote->property_y2() = y2;
1218 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1220 double x = trackview.editor().frame_to_pixel(
1221 beats_to_frames(note->time()) - _region->start());
1222 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1223 double y = midi_stream_view()->note_to_y(event->note()->note())
1224 + ((diamond_size-2.0) / 4.0);
1226 chit->set_height (diamond_size);
1227 chit->move (x - chit->x1(), y - chit->y1());
1234 MidiRegionView::add_ghost (TimeAxisView& tv)
1238 double unit_position = _region->position () / samples_per_unit;
1239 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1240 MidiGhostRegion* ghost;
1242 if (mtv && mtv->midi_view()) {
1243 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1244 to allow having midi notes on top of note lines and waveforms.
1246 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1248 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1251 ghost->set_height ();
1252 ghost->set_duration (_region->length() / samples_per_unit);
1253 ghosts.push_back (ghost);
1255 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1256 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1257 ghost->add_note(note);
1261 GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1267 /** Begin tracking note state for successive calls to add_event
1270 MidiRegionView::begin_write()
1272 assert(!_active_notes);
1273 _active_notes = new CanvasNote*[128];
1274 for (unsigned i=0; i < 128; ++i) {
1275 _active_notes[i] = 0;
1280 /** Destroy note state for add_event
1283 MidiRegionView::end_write()
1285 delete[] _active_notes;
1287 _marked_for_selection.clear();
1288 _marked_for_velocity.clear();
1292 /** Resolve an active MIDI note (while recording).
1295 MidiRegionView::resolve_note(uint8_t note, double end_time)
1297 if (midi_view()->note_mode() != Sustained) {
1301 if (_active_notes && _active_notes[note]) {
1302 const framepos_t end_time_frames = beats_to_frames(end_time) - _region->start();
1303 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1304 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1305 _active_notes[note] = 0;
1310 /** Extend active notes to rightmost edge of region (if length is changed)
1313 MidiRegionView::extend_active_notes()
1315 if (!_active_notes) {
1319 for (unsigned i=0; i < 128; ++i) {
1320 if (_active_notes[i]) {
1321 _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1328 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1330 if (no_sound_notes || !trackview.editor().sound_notes()) {
1334 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1336 if (!route_ui || !route_ui->midi_track()) {
1340 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1346 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1348 if (no_sound_notes || !trackview.editor().sound_notes()) {
1352 RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1354 if (!route_ui || !route_ui->midi_track()) {
1358 NotePlayer* np = new NotePlayer (route_ui->midi_track());
1360 for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1369 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1371 const framepos_t note_start_frames = beats_to_frames(note->time());
1373 bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1374 (note_start_frames < _region->start());
1376 visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1377 (note->note() <= midi_stream_view()->highest_note());
1383 MidiRegionView::update_note (CanvasNote* ev)
1385 boost::shared_ptr<NoteType> note = ev->note();
1387 const framepos_t note_start_frames = beats_to_frames(note->time());
1389 /* trim note display to not overlap the end of its region */
1390 const framepos_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length());
1392 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1393 const double y1 = midi_stream_view()->note_to_y(note->note());
1394 const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1396 ev->property_x1() = x;
1397 ev->property_y1() = y1;
1398 if (note->length() > 0) {
1399 ev->property_x2() = note_endpixel;
1401 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1403 ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1405 if (note->length() == 0) {
1406 if (_active_notes) {
1407 assert(note->note() < 128);
1408 // If this note is already active there's a stuck note,
1409 // finish the old note rectangle
1410 if (_active_notes[note->note()]) {
1411 CanvasNote* const old_rect = _active_notes[note->note()];
1412 boost::shared_ptr<NoteType> old_note = old_rect->note();
1413 old_rect->property_x2() = x;
1414 old_rect->property_outline_what() = (guint32) 0xF;
1416 _active_notes[note->note()] = ev;
1418 /* outline all but right edge */
1419 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1421 /* outline all edges */
1422 ev->property_outline_what() = (guint32) 0xF;
1427 MidiRegionView::update_hit (CanvasHit* ev)
1429 boost::shared_ptr<NoteType> note = ev->note();
1431 const framepos_t note_start_frames = beats_to_frames(note->time());
1432 const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1433 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1434 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1439 /** Add a MIDI note to the view (with length).
1441 * If in sustained mode, notes with length 0 will be considered active
1442 * notes, and resolve_note should be called when the corresponding note off
1443 * event arrives, to properly display the note.
1446 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1448 CanvasNoteEvent* event = 0;
1450 assert(note->time() >= 0);
1451 assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1453 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1455 if (midi_view()->note_mode() == Sustained) {
1457 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1459 update_note (ev_rect);
1463 MidiGhostRegion* gr;
1465 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1466 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1467 gr->add_note(ev_rect);
1471 } else if (midi_view()->note_mode() == Percussive) {
1473 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1475 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1477 update_hit (ev_diamond);
1486 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1487 note_selected(event, true);
1490 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1491 event->show_velocity();
1493 event->on_channel_selection_change(_last_channel_selection);
1494 _events.push_back(event);
1505 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1506 Evoral::MusicalTime pos, Evoral::MusicalTime len)
1508 boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1510 /* potentially extend region to hold new note */
1512 framepos_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1513 framepos_t region_end = _region->position() + _region->length() - 1;
1515 if (end_frame > region_end) {
1516 _region->set_length (end_frame - _region->position(), this);
1519 _marked_for_selection.clear ();
1522 start_diff_command (_("step add"));
1523 diff_add_note (new_note, true, false);
1526 // last_step_edit_note = new_note;
1530 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1532 change_note_lengths (false, false, beats, false, true);
1536 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1538 assert(program.time >= 0);
1540 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1541 const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1543 double height = midi_stream_view()->contents_height();
1545 boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1546 new CanvasProgramChange(*this, *group,
1551 _custom_device_mode,
1552 program.time, program.channel, program.value));
1554 // Show unless program change is beyond the region bounds
1555 if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1561 _pgm_changes.push_back(pgm_change);
1565 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1567 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1568 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1570 if (msb_control != 0) {
1571 msb = int(msb_control->get_double(true, time));
1574 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1575 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1577 if (lsb_control != 0) {
1578 lsb = lsb_control->get_double(true, time);
1581 Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1582 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1583 double program_number = -1.0;
1584 if (program_control != 0) {
1585 program_number = program_control->get_double(true, time);
1588 key.msb = (int) floor(msb + 0.5);
1589 key.lsb = (int) floor(lsb + 0.5);
1590 key.program_number = (int) floor(program_number + 0.5);
1591 assert(key.is_sane());
1596 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1598 // TODO: Get the real event here and alter them at the original times
1599 Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1600 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1601 if (msb_control != 0) {
1602 msb_control->set_double(double(new_patch.msb), true, old_program.time);
1605 // TODO: Get the real event here and alter them at the original times
1606 Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1607 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1608 if (lsb_control != 0) {
1609 lsb_control->set_double(double(new_patch.lsb), true, old_program.time);
1612 Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1613 boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1615 assert(program_control != 0);
1616 program_control->set_double(float(new_patch.program_number), true, old_program.time);
1618 _pgm_changes.clear ();
1619 display_program_changes (); // XXX would be nice to limit to just old_program.channel
1623 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1625 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1626 alter_program_change(program_change_event, new_patch);
1630 MidiRegionView::previous_program(CanvasProgramChange& program)
1632 if (program.program() < 127) {
1633 MIDI::Name::PatchPrimaryKey key;
1634 get_patch_key_at(program.event_time(), program.channel(), key);
1635 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1637 key.program_number++;
1638 alter_program_change(program_change_event, key);
1643 MidiRegionView::next_program(CanvasProgramChange& program)
1645 if (program.program() > 0) {
1646 MIDI::Name::PatchPrimaryKey key;
1647 get_patch_key_at(program.event_time(), program.channel(), key);
1648 PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1650 key.program_number--;
1651 alter_program_change(program_change_event, key);
1656 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1658 if (_selection.empty()) {
1662 if (_selection.erase (cne) > 0) {
1663 cerr << "Erased a CNE from selection\n";
1668 MidiRegionView::delete_selection()
1670 if (_selection.empty()) {
1674 start_diff_command (_("delete selection"));
1676 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1677 if ((*i)->selected()) {
1678 _diff_command->remove((*i)->note());
1688 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1690 start_diff_command (_("delete note"));
1691 _diff_command->remove (n);
1694 trackview.editor().hide_verbose_canvas_cursor ();
1698 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1700 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1701 if ((*i)->selected() && (*i) != ev) {
1702 (*i)->set_selected(false);
1703 (*i)->hide_velocity();
1711 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1713 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1716 Selection::iterator tmp = i;
1719 (*i)->set_selected (false);
1720 _selection.erase (i);
1729 /* don't bother with removing this regionview from the editor selection,
1730 since we're about to add another note, and thus put/keep this
1731 regionview in the editor selection.
1734 if (!ev->selected()) {
1735 add_to_selection (ev);
1740 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1742 uint8_t low_note = 127;
1743 uint8_t high_note = 0;
1744 MidiModel::Notes& notes (_model->notes());
1745 _optimization_iterator = _events.begin();
1751 if (extend && _selection.empty()) {
1757 /* scan existing selection to get note range */
1759 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1760 if ((*i)->note()->note() < low_note) {
1761 low_note = (*i)->note()->note();
1763 if ((*i)->note()->note() > high_note) {
1764 high_note = (*i)->note()->note();
1768 low_note = min (low_note, notenum);
1769 high_note = max (high_note, notenum);
1772 no_sound_notes = true;
1774 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1776 boost::shared_ptr<NoteType> note (*n);
1777 CanvasNoteEvent* cne;
1778 bool select = false;
1780 if (((1 << note->channel()) & channel_mask) != 0) {
1782 if ((note->note() >= low_note && note->note() <= high_note)) {
1785 } else if (note->note() == notenum) {
1791 if ((cne = find_canvas_note (note)) != 0) {
1792 // extend is false because we've taken care of it,
1793 // since it extends by time range, not pitch.
1794 note_selected (cne, add, false);
1798 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1802 no_sound_notes = false;
1806 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1808 MidiModel::Notes& notes (_model->notes());
1809 _optimization_iterator = _events.begin();
1811 for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1813 boost::shared_ptr<NoteType> note (*n);
1814 CanvasNoteEvent* cne;
1816 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1817 if ((cne = find_canvas_note (note)) != 0) {
1818 if (cne->selected()) {
1819 note_deselected (cne);
1821 note_selected (cne, true, false);
1829 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1832 clear_selection_except(ev);
1837 if (!ev->selected()) {
1838 add_to_selection (ev);
1842 /* find end of latest note selected, select all between that and the start of "ev" */
1844 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
1845 Evoral::MusicalTime latest = 0;
1847 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1848 if ((*i)->note()->end_time() > latest) {
1849 latest = (*i)->note()->end_time();
1851 if ((*i)->note()->time() < earliest) {
1852 earliest = (*i)->note()->time();
1856 if (ev->note()->end_time() > latest) {
1857 latest = ev->note()->end_time();
1860 if (ev->note()->time() < earliest) {
1861 earliest = ev->note()->time();
1864 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1866 /* find notes entirely within OR spanning the earliest..latest range */
1868 if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1869 ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1870 add_to_selection (*i);
1874 /* if events were guaranteed to be time sorted, we could do this.
1875 but as of sept 10th 2009, they no longer are.
1878 if ((*i)->note()->time() > latest) {
1887 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1889 remove_from_selection (ev);
1893 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1903 // TODO: Make this faster by storing the last updated selection rect, and only
1904 // adjusting things that are in the area that appears/disappeared.
1905 // We probably need a tree to be able to find events in O(log(n)) time.
1907 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1909 /* check if any corner of the note is inside the rect
1912 1) this is computing "touched by", not "contained by" the rect.
1913 2) this does not require that events be sorted in time.
1916 const double ix1 = (*i)->x1();
1917 const double ix2 = (*i)->x2();
1918 const double iy1 = (*i)->y1();
1919 const double iy2 = (*i)->y2();
1921 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1922 (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1923 (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1924 (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1927 if (!(*i)->selected()) {
1928 add_to_selection (*i);
1930 } else if ((*i)->selected()) {
1931 // Not inside rectangle
1932 remove_from_selection (*i);
1938 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1940 Selection::iterator i = _selection.find (ev);
1942 if (i != _selection.end()) {
1943 _selection.erase (i);
1946 ev->set_selected (false);
1947 ev->hide_velocity ();
1949 if (_selection.empty()) {
1950 PublicEditor& editor (trackview.editor());
1951 editor.get_selection().remove (this);
1956 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1958 bool add_mrv_selection = false;
1960 if (_selection.empty()) {
1961 add_mrv_selection = true;
1964 if (_selection.insert (ev).second) {
1965 ev->set_selected (true);
1966 play_midi_note ((ev)->note());
1969 if (add_mrv_selection) {
1970 PublicEditor& editor (trackview.editor());
1971 editor.get_selection().add (this);
1976 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
1978 typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
1979 PossibleChord to_play;
1980 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
1982 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1983 if ((*i)->note()->time() < earliest) {
1984 earliest = (*i)->note()->time();
1988 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1989 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
1990 to_play.push_back ((*i)->note());
1992 (*i)->move_event(dx, dy);
1995 if (dy && !_selection.empty() && !no_sound_notes && trackview.editor().sound_notes()) {
1997 if (to_play.size() > 1) {
1999 PossibleChord shifted;
2001 for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2002 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2003 moved_note->set_note (moved_note->note() + cumulative_dy);
2004 shifted.push_back (moved_note);
2007 play_midi_chord (shifted);
2009 } else if (!to_play.empty()) {
2011 boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2012 moved_note->set_note (moved_note->note() + cumulative_dy);
2013 play_midi_note (moved_note);
2019 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2021 assert (!_selection.empty());
2023 uint8_t lowest_note_in_selection = 127;
2024 uint8_t highest_note_in_selection = 0;
2025 uint8_t highest_note_difference = 0;
2027 // find highest and lowest notes first
2029 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2030 uint8_t pitch = (*i)->note()->note();
2031 lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
2032 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2036 cerr << "dnote: " << (int) dnote << endl;
2037 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2038 << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2039 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2040 << int(highest_note_in_selection) << endl;
2041 cerr << "selection size: " << _selection.size() << endl;
2042 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2045 // Make sure the note pitch does not exceed the MIDI standard range
2046 if (highest_note_in_selection + dnote > 127) {
2047 highest_note_difference = highest_note_in_selection - 127;
2050 start_diff_command(_("move notes"));
2052 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2054 Evoral::MusicalTime new_time = frames_to_beats (beats_to_frames ((*i)->note()->time()) + dt);
2060 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
2062 uint8_t original_pitch = (*i)->note()->note();
2063 uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
2065 // keep notes in standard midi range
2066 clamp_to_0_127(new_pitch);
2068 // keep original pitch if note is dragged outside valid midi range
2069 if ((original_pitch != 0 && new_pitch == 0)
2070 || (original_pitch != 127 && new_pitch == 127)) {
2071 new_pitch = original_pitch;
2074 lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
2075 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2077 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
2082 // care about notes being moved beyond the upper/lower bounds on the canvas
2083 if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
2084 highest_note_in_selection > midi_stream_view()->highest_note()) {
2085 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2090 MidiRegionView::snap_pixel_to_frame(double x)
2092 PublicEditor& editor = trackview.editor();
2093 // x is region relative, convert it to global absolute frames
2094 framepos_t frame = editor.pixel_to_frame(x) + _region->position();
2095 editor.snap_to(frame);
2096 return frame - _region->position(); // convert back to region relative
2100 MidiRegionView::snap_frame_to_frame(framepos_t x)
2102 PublicEditor& editor = trackview.editor();
2103 // x is region relative, convert it to global absolute frames
2104 framepos_t frame = x + _region->position();
2105 editor.snap_to(frame);
2106 return frame - _region->position(); // convert back to region relative
2110 MidiRegionView::snap_to_pixel(double x)
2112 return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2116 MidiRegionView::get_position_pixels()
2118 framepos_t region_frame = get_position();
2119 return trackview.editor().frame_to_pixel(region_frame);
2123 MidiRegionView::get_end_position_pixels()
2125 framepos_t frame = get_position() + get_duration ();
2126 return trackview.editor().frame_to_pixel(frame);
2130 MidiRegionView::beats_to_frames(double beats) const
2132 return _time_converter.to(beats);
2136 MidiRegionView::frames_to_beats(framepos_t frames) const
2138 return _time_converter.from(frames);
2142 MidiRegionView::begin_resizing (bool /*at_front*/)
2144 _resize_data.clear();
2146 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2147 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2149 // only insert CanvasNotes into the map
2151 NoteResizeData *resize_data = new NoteResizeData();
2152 resize_data->canvas_note = note;
2154 // create a new SimpleRect from the note which will be the resize preview
2155 SimpleRect *resize_rect = new SimpleRect(
2156 *group, note->x1(), note->y1(), note->x2(), note->y2());
2158 // calculate the colors: get the color settings
2159 uint32_t fill_color = UINT_RGBA_CHANGE_A(
2160 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2163 // make the resize preview notes more transparent and bright
2164 fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2166 // calculate color based on note velocity
2167 resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2168 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2172 resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2173 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2175 resize_data->resize_rect = resize_rect;
2176 _resize_data.push_back(resize_data);
2181 /** Update resizing notes while user drags.
2182 * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2183 * @param at_front which end of the note (true == note on, false == note off)
2184 * @param delta_x change in mouse position since the start of the drag
2185 * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
2186 * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2187 * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
2188 * as the \a primary note.
2191 MidiRegionView::update_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2193 bool cursor_set = false;
2195 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2196 SimpleRect* resize_rect = (*i)->resize_rect;
2197 CanvasNote* canvas_note = (*i)->canvas_note;
2202 current_x = canvas_note->x1() + delta_x;
2204 current_x = primary->x1() + delta_x;
2208 current_x = canvas_note->x2() + delta_x;
2210 current_x = primary->x2() + delta_x;
2215 resize_rect->property_x1() = snap_to_pixel(current_x);
2216 resize_rect->property_x2() = canvas_note->x2();
2218 resize_rect->property_x2() = snap_to_pixel(current_x);
2219 resize_rect->property_x1() = canvas_note->x1();
2225 beats = snap_pixel_to_frame (current_x);
2226 beats = frames_to_beats (beats);
2231 if (beats < canvas_note->note()->end_time()) {
2232 len = canvas_note->note()->time() - beats;
2233 len += canvas_note->note()->length();
2238 if (beats >= canvas_note->note()->time()) {
2239 len = beats - canvas_note->note()->time();
2246 snprintf (buf, sizeof (buf), "%.3g beats", len);
2247 trackview.editor().show_verbose_canvas_cursor_with (buf);
2256 /** Finish resizing notes when the user releases the mouse button.
2257 * Parameters the same as for \a update_resizing().
2260 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNote* primary, bool at_front, double delta_x, bool relative)
2262 start_diff_command(_("resize notes"));
2264 for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2265 CanvasNote* canvas_note = (*i)->canvas_note;
2266 SimpleRect* resize_rect = (*i)->resize_rect;
2271 current_x = canvas_note->x1() + delta_x;
2273 current_x = primary->x1() + delta_x;
2277 current_x = canvas_note->x2() + delta_x;
2279 current_x = primary->x2() + delta_x;
2283 current_x = snap_pixel_to_frame (current_x);
2284 current_x = frames_to_beats (current_x);
2286 if (at_front && current_x < canvas_note->note()->end_time()) {
2287 diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2289 double len = canvas_note->note()->time() - current_x;
2290 len += canvas_note->note()->length();
2293 /* XXX convert to beats */
2294 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2299 double len = current_x - canvas_note->note()->time();
2302 /* XXX convert to beats */
2303 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2311 _resize_data.clear();
2316 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t channel)
2318 diff_add_change (event, MidiModel::DiffCommand::Channel, (uint8_t) channel);
2322 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2324 uint8_t new_velocity;
2327 new_velocity = event->note()->velocity() + velocity;
2328 clamp_to_0_127(new_velocity);
2330 new_velocity = velocity;
2333 event->set_selected (event->selected()); // change color
2335 diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2339 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2344 new_note = event->note()->note() + note;
2349 clamp_to_0_127 (new_note);
2350 diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2354 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2356 bool change_start = false;
2357 bool change_length = false;
2358 Evoral::MusicalTime new_start = 0;
2359 Evoral::MusicalTime new_length = 0;
2361 /* NOTE: the semantics of the two delta arguments are slightly subtle:
2363 front_delta: if positive - move the start of the note later in time (shortening it)
2364 if negative - move the start of the note earlier in time (lengthening it)
2366 end_delta: if positive - move the end of the note later in time (lengthening it)
2367 if negative - move the end of the note earlier in time (shortening it)
2371 if (front_delta < 0) {
2373 if (event->note()->time() < -front_delta) {
2376 new_start = event->note()->time() + front_delta; // moves earlier
2379 /* start moved toward zero, so move the end point out to where it used to be.
2380 Note that front_delta is negative, so this increases the length.
2383 new_length = event->note()->length() - front_delta;
2384 change_start = true;
2385 change_length = true;
2389 Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2391 if (new_pos < event->note()->end_time()) {
2392 new_start = event->note()->time() + front_delta;
2393 /* start moved toward the end, so move the end point back to where it used to be */
2394 new_length = event->note()->length() - front_delta;
2395 change_start = true;
2396 change_length = true;
2403 bool can_change = true;
2404 if (end_delta < 0) {
2405 if (event->note()->length() < -end_delta) {
2411 new_length = event->note()->length() + end_delta;
2412 change_length = true;
2417 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2420 if (change_length) {
2421 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2426 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2428 Evoral::MusicalTime new_time;
2432 if (event->note()->time() < -delta) {
2435 new_time = event->note()->time() + delta;
2438 new_time = event->note()->time() + delta;
2444 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2448 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2450 diff_add_change (event, MidiModel::DiffCommand::Length, t);
2454 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2458 if (_selection.empty()) {
2473 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2474 if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2480 start_diff_command(_("change velocities"));
2482 for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2483 Selection::iterator next = i;
2485 change_note_velocity (*i, delta, true);
2491 if (!_selection.empty()) {
2493 snprintf (buf, sizeof (buf), "Vel %d",
2494 (int) (*_selection.begin())->note()->velocity());
2495 trackview.editor().show_verbose_canvas_cursor_with (buf, 10, 10);
2501 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2503 if (_selection.empty()) {
2520 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2522 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2526 if ((int8_t) (*i)->note()->note() + delta > 127) {
2533 start_diff_command (_("transpose"));
2535 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2536 Selection::iterator next = i;
2538 change_note_note (*i, delta, true);
2546 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2552 /* grab the current grid distance */
2554 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2556 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2557 cerr << "Grid type not available as beats - TO BE FIXED\n";
2567 start_diff_command (_("change note lengths"));
2569 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2570 Selection::iterator next = i;
2573 /* note the negation of the delta for start */
2575 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2584 MidiRegionView::nudge_notes (bool forward)
2586 if (_selection.empty()) {
2590 /* pick a note as the point along the timeline to get the nudge distance.
2591 its not necessarily the earliest note, so we may want to pull the notes out
2592 into a vector and sort before using the first one.
2595 framepos_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2597 framepos_t distance;
2599 if (trackview.editor().snap_mode() == Editing::SnapOff) {
2601 /* grid is off - use nudge distance */
2603 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2609 framepos_t next_pos = ref_point;
2612 if (max_framepos - 1 < next_pos) {
2616 if (next_pos == 0) {
2622 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2623 distance = ref_point - next_pos;
2626 if (distance == 0) {
2630 Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2636 start_diff_command (_("nudge"));
2638 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2639 Selection::iterator next = i;
2641 change_note_time (*i, delta, true);
2649 MidiRegionView::change_channel(uint8_t channel)
2651 start_diff_command(_("change channel"));
2652 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2653 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2661 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2663 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2665 pre_enter_cursor = editor->get_canvas_cursor ();
2667 if (_mouse_state == SelectTouchDragging) {
2668 note_selected (ev, true);
2671 show_verbose_canvas_cursor (ev->note ());
2675 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2677 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2679 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2680 (*i)->hide_velocity ();
2683 editor->hide_verbose_canvas_cursor ();
2685 if (pre_enter_cursor) {
2686 editor->set_canvas_cursor (pre_enter_cursor);
2687 pre_enter_cursor = 0;
2692 MidiRegionView::note_mouse_position (float x_fraction, float y_fraction, bool can_set_cursor)
2694 Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2696 if (x_fraction > 0.0 && x_fraction < 0.25) {
2697 editor->set_canvas_cursor (editor->left_side_trim_cursor);
2698 } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
2699 editor->set_canvas_cursor (editor->right_side_trim_cursor);
2701 if (pre_enter_cursor && can_set_cursor) {
2702 editor->set_canvas_cursor (pre_enter_cursor);
2708 MidiRegionView::set_frame_color()
2711 if (_selected && should_show_selection) {
2712 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2714 frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2720 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2724 case FilterChannels:
2725 _force_channel = -1;
2728 _force_channel = mask;
2729 mask = 0xFFFF; // Show all notes as active (below)
2732 // Update notes for selection
2733 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2734 (*i)->on_channel_selection_change(mask);
2737 _last_channel_selection = mask;
2741 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2743 _model_name = model;
2744 _custom_device_mode = custom_device_mode;
2749 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2751 if (_selection.empty()) {
2755 PublicEditor& editor (trackview.editor());
2760 editor.get_cut_buffer().add (selection_as_cut_buffer());
2768 start_diff_command();
2770 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2776 diff_remove_note (*i);
2786 MidiRegionView::selection_as_cut_buffer () const
2790 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2791 NoteType* n = (*i)->note().get();
2792 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2795 MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2802 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
2808 start_diff_command (_("paste"));
2810 Evoral::MusicalTime beat_delta;
2811 Evoral::MusicalTime paste_pos_beats;
2812 Evoral::MusicalTime duration;
2813 Evoral::MusicalTime end_point = 0;
2815 duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2816 paste_pos_beats = frames_to_beats (pos - _region->position());
2817 beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2818 paste_pos_beats = 0;
2822 for (int n = 0; n < (int) times; ++n) {
2824 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2826 boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2827 copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2829 /* make all newly added notes selected */
2831 diff_add_note (copied_note, true);
2832 end_point = copied_note->end_time();
2835 paste_pos_beats += duration;
2838 /* if we pasted past the current end of the region, extend the region */
2840 framepos_t end_frame = _region->position() + beats_to_frames (end_point);
2841 framepos_t region_end = _region->position() + _region->length() - 1;
2843 if (end_frame > region_end) {
2845 trackview.session()->begin_reversible_command (_("paste"));
2847 _region->clear_changes ();
2848 _region->set_length (end_frame, this);
2849 trackview.session()->add_command (new StatefulDiffCommand (_region));
2855 struct EventNoteTimeEarlyFirstComparator {
2856 bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2857 return a->note()->time() < b->note()->time();
2862 MidiRegionView::time_sort_events ()
2864 if (!_sort_needed) {
2868 EventNoteTimeEarlyFirstComparator cmp;
2871 _sort_needed = false;
2875 MidiRegionView::goto_next_note ()
2877 // framepos_t pos = -1;
2878 bool use_next = false;
2880 if (_events.back()->selected()) {
2884 time_sort_events ();
2886 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2887 if ((*i)->selected()) {
2890 } else if (use_next) {
2892 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2897 /* use the first one */
2899 unique_select (_events.front());
2904 MidiRegionView::goto_previous_note ()
2906 // framepos_t pos = -1;
2907 bool use_next = false;
2909 if (_events.front()->selected()) {
2913 time_sort_events ();
2915 for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2916 if ((*i)->selected()) {
2919 } else if (use_next) {
2921 // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2926 /* use the last one */
2928 unique_select (*(_events.rbegin()));
2932 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2934 bool had_selected = false;
2936 time_sort_events ();
2938 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2939 if ((*i)->selected()) {
2940 selected.insert ((*i)->note());
2941 had_selected = true;
2945 if (allow_all_if_none_selected && !had_selected) {
2946 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2947 selected.insert ((*i)->note());
2953 MidiRegionView::update_ghost_note (double x, double y)
2959 framepos_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2960 trackview.editor().snap_to (f);
2961 f -= _region->position ();
2964 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2969 double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2971 _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2972 _ghost_note->note()->set_length (length);
2973 _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2975 update_note (_ghost_note);
2977 show_verbose_canvas_cursor (_ghost_note->note ());
2981 MidiRegionView::create_ghost_note (double x, double y)
2986 boost::shared_ptr<NoteType> g (new NoteType);
2987 _ghost_note = new NoEventCanvasNote (*this, *group, g);
2988 update_ghost_note (x, y);
2989 _ghost_note->show ();
2994 show_verbose_canvas_cursor (_ghost_note->note ());
2998 MidiRegionView::snap_changed ()
3004 create_ghost_note (_last_ghost_x, _last_ghost_y);
3008 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
3011 snprintf (buf, sizeof (buf), "%s (%d)\nVel %d",
3012 Evoral::midi_note_name (n->note()).c_str(),
3014 (int) n->velocity());
3015 trackview.editor().show_verbose_canvas_cursor_with (buf, 10, 20);
3019 MidiRegionView::drop_down_keys ()
3021 _mouse_state = None;
3025 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double x, double y)
3027 double note = midi_stream_view()->y_to_note(y);
3029 MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3031 cerr << "Selecting by position\n";
3033 uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3035 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3036 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3037 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3038 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3043 bool add_mrv_selection = false;
3045 if (_selection.empty()) {
3046 add_mrv_selection = true;
3049 for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3050 if (_selection.insert (*i).second) {
3051 (*i)->set_selected (true);
3055 if (add_mrv_selection) {
3056 PublicEditor& editor (trackview.editor());
3057 editor.get_selection().add (this);
3062 MidiRegionView::color_handler ()
3064 RegionView::color_handler ();
3066 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3067 (*i)->set_selected ((*i)->selected()); // will change color
3070 /* XXX probably more to do here */
3074 MidiRegionView::enable_display (bool yn)
3076 RegionView::enable_display (yn);
3083 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3085 if (_step_edit_cursor == 0) {
3086 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3088 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3089 _step_edit_cursor->property_y1() = 0;
3090 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3091 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3092 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3095 move_step_edit_cursor (pos);
3096 _step_edit_cursor->show ();
3100 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3102 _step_edit_cursor_position = pos;
3104 if (_step_edit_cursor) {
3105 double pixel = trackview.editor().frame_to_pixel (beats_to_frames (pos));
3106 _step_edit_cursor->property_x1() = pixel;
3107 set_step_edit_cursor_width (_step_edit_cursor_width);
3112 MidiRegionView::hide_step_edit_cursor ()
3114 if (_step_edit_cursor) {
3115 _step_edit_cursor->hide ();
3120 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3122 _step_edit_cursor_width = beats;
3124 if (_step_edit_cursor) {
3125 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (beats_to_frames (beats));
3129 /** Called when a diskstream on our track has received some data. Update the view, if applicable.
3130 * @param buf Data that has been recorded.
3131 * @param w Source that this data will end up in.
3134 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3136 if (!_active_notes) {
3137 /* we aren't actively being recorded to */
3141 boost::shared_ptr<MidiSource> src = w.lock ();
3142 if (!src || src != midi_region()->midi_source()) {
3143 /* recorded data was not destined for our source */
3147 MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3148 BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3150 for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3151 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3152 assert (ev.buffer ());
3154 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3156 if (ev.type() == MIDI_CMD_NOTE_ON) {
3158 boost::shared_ptr<Evoral::Note<Evoral::MusicalTime> > note (
3159 new Evoral::Note<Evoral::MusicalTime> (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3162 add_note (note, true);
3164 /* fix up our note range */
3165 if (ev.note() < _current_range_min) {
3166 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3167 } else if (ev.note() > _current_range_max) {
3168 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3171 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3172 resolve_note (ev.note (), time_beats);